From 2fea71387b3cc047acd754f23743a9941d590251 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Thu, 28 May 2026 13:25:43 +0200 Subject: [PATCH] fix(ui): polish across routing, groups, inbounds, mobile sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bundle of small UI fixes that surfaced together while reviewing the panel. Routing rules — stale Edit after drag: - Dragging a rule and then clicking its Edit button used to open the modal with the *previous* rule's content. Root cause: desktopColumns was memoized with [t, isMobile, rows.length] (rows.length doesn't change on reorder), so the cached render function kept handing AntD the openEdit closure that captured the pre-drag rules array. Fix is a rulesRef updated each render and read inside openEdit, so even the cached closure sees the live array. - Mobile rule cards on the same page were hard to tell apart: bumped the inter-card gap, slightly stronger border, soft shadow, and a small centered divider line between adjacent cards. Mobile drawer (dark / ultra): - The AntD Menu inside the mobile drawer was rendering with its own darkItemBg (#15161a / #050507) while the drawer body used the lighter colorBgElevated, producing visible two-tone seams. Force the drawer-content / drawer-body to the same dark color that the desktop sider uses, and make the menus transparent so they inherit. Row menus — visual grouping: - Groups page row menu: moved Rename above the divider so the ordering reads safe → divider → destructive (Remove from group, Delete clients, Delete group only) instead of mixing the two groups. - Inbounds page row menu: inserted a divider before delAllClients / delete so the destructive items sit visually separated from the earlier safe actions. Dropdown affordances: - Non-danger dropdown items had no perceivable hover state (default colorBgTextHover is too subtle, especially under the light theme). Apply the same primary-tint pattern the sider/drawer menu uses: 14% primary background and primary color on label + icon. - ant-dropdown-menu-item-divider now uses var(--ant-color-border) (and an explicit rgba in dark) so the separator is actually visible in the light theme. Clients toolbar — narrow-desktop wrap: - Between 769px and 920px, the bulk-action bar (Attach / Detach / Add to group / Ungroup / more + Delete) wrapped to two rows with Delete stranded alone on the right. In that range, switch the toolbar buttons to icon-only, tighten gap to 6px and inline padding to 8px so everything stays on one line. --- frontend/src/components/AppSidebar.css | 18 ++++++++++++++++++ frontend/src/pages/clients/ClientsPage.css | 13 +++++++++++++ frontend/src/pages/groups/GroupsPage.tsx | 14 +++++++------- frontend/src/pages/inbounds/InboundList.tsx | 3 +++ frontend/src/pages/xray/RoutingTab.css | 17 +++++++++++++++-- frontend/src/pages/xray/RoutingTab.tsx | 4 +++- frontend/src/styles/page-shell.css | 18 ++++++++++++++++++ 7 files changed, 77 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/AppSidebar.css b/frontend/src/components/AppSidebar.css index ac1de456..4784572f 100644 --- a/frontend/src/components/AppSidebar.css +++ b/frontend/src/components/AppSidebar.css @@ -247,6 +247,24 @@ } } +body.dark .ant-drawer-content, +body.dark .ant-drawer-body { + background-color: #15161a; +} + +html[data-theme="ultra-dark"] body.dark .ant-drawer-content, +html[data-theme="ultra-dark"] body.dark .ant-drawer-body { + background-color: #050507; +} + +body.dark .ant-drawer-body .drawer-menu, +body.dark .ant-drawer-body .drawer-menu.ant-menu-dark, +body.dark .ant-drawer-body .drawer-menu .ant-menu-item, +body.dark .ant-drawer-body .drawer-menu .ant-menu-sub, +body.dark .ant-drawer-body .drawer-menu .ant-menu-item-group-list { + background-color: transparent; +} + .sider-nav .ant-menu-item-selected, .sider-utility .ant-menu-item-selected, .drawer-menu .ant-menu-item-selected { diff --git a/frontend/src/pages/clients/ClientsPage.css b/frontend/src/pages/clients/ClientsPage.css index 8eaba3fa..085ce490 100644 --- a/frontend/src/pages/clients/ClientsPage.css +++ b/frontend/src/pages/clients/ClientsPage.css @@ -77,6 +77,19 @@ padding: 6px 0; } +@media (min-width: 769px) and (max-width: 920px) { + .card-toolbar { + gap: 6px; + } + .card-toolbar .ant-btn .ant-btn-icon + span, + .card-toolbar .ant-btn > span:not(.ant-btn-icon):not(.ant-tag):not(.anticon) { + display: none; + } + .card-toolbar .ant-btn { + padding-inline: 8px; + } +} + .email-cell { display: flex; flex-direction: column; diff --git a/frontend/src/pages/groups/GroupsPage.tsx b/frontend/src/pages/groups/GroupsPage.tsx index 1e1cc896..6d16e4d3 100644 --- a/frontend/src/pages/groups/GroupsPage.tsx +++ b/frontend/src/pages/groups/GroupsPage.tsx @@ -332,6 +332,13 @@ export default function GroupsPage() { label: t('pages.groups.addToGroup'), onClick: () => openAddClientsFor(row), }, + { + key: 'rename', + icon: , + label: t('pages.groups.rename'), + onClick: () => openRename(row), + }, + { type: 'divider' }, { key: 'removeClients', icon: , @@ -340,13 +347,6 @@ export default function GroupsPage() { disabled: !row.clientCount, onClick: () => openRemoveClientsFor(row), }, - { type: 'divider' }, - { - key: 'rename', - icon: , - label: t('pages.groups.rename'), - onClick: () => openRename(row), - }, { key: 'deleteClients', icon: , diff --git a/frontend/src/pages/inbounds/InboundList.tsx b/frontend/src/pages/inbounds/InboundList.tsx index c0168190..7bf8f9e0 100644 --- a/frontend/src/pages/inbounds/InboundList.tsx +++ b/frontend/src/pages/inbounds/InboundList.tsx @@ -264,7 +264,10 @@ function buildRowActionsMenu({ record, subEnable, t, isMobile, hasClients }: { r items.push({ key: 'attachClients', icon: , label: t('pages.inbounds.attachClients') }); items.push({ key: 'detachClients', icon: , label: t('pages.inbounds.detachClients') }); items.push({ key: 'addToGroup', icon: , label: t('pages.inbounds.addClientsToGroup') }); + items.push({ type: 'divider' }); items.push({ key: 'delAllClients', icon: , danger: true, label: t('pages.inbounds.delAllClients') }); + } else { + items.push({ type: 'divider' }); } items.push({ key: 'delete', icon: , danger: true, label: t('delete') }); return items; diff --git a/frontend/src/pages/xray/RoutingTab.css b/frontend/src/pages/xray/RoutingTab.css index 6ad04cec..d79b2f17 100644 --- a/frontend/src/pages/xray/RoutingTab.css +++ b/frontend/src/pages/xray/RoutingTab.css @@ -99,7 +99,7 @@ .rule-list { display: flex; flex-direction: column; - gap: 8px; + gap: 14px; } .rule-card { @@ -109,11 +109,24 @@ gap: 8px; padding: 10px 12px; background: var(--bg-card, #fff); - border: 1px solid var(--ant-color-border-secondary); + border: 1px solid var(--ant-color-border); border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); transition: opacity 0.15s, box-shadow 0.15s; } +.rule-list > .rule-card:not(:last-child)::after { + content: ''; + position: absolute; + left: 50%; + bottom: -8px; + width: 32px; + height: 1px; + background: var(--ant-color-border); + transform: translateX(-50%); + opacity: 0.7; +} + .rule-card.row-dragging { opacity: 0.4; } diff --git a/frontend/src/pages/xray/RoutingTab.tsx b/frontend/src/pages/xray/RoutingTab.tsx index d9d89b67..8943ad6e 100644 --- a/frontend/src/pages/xray/RoutingTab.tsx +++ b/frontend/src/pages/xray/RoutingTab.tsx @@ -88,6 +88,8 @@ export default function RoutingTab({ () => (templateSettings?.routing?.rules || []) as RoutingRule[], [templateSettings?.routing?.rules], ); + const rulesRef = useRef(rules); + rulesRef.current = rules; const rows: RuleRow[] = useMemo( () => @@ -171,7 +173,7 @@ export default function RoutingTab({ setRuleModalOpen(true); } function openEdit(idx: number) { - setEditingRule(rules[idx]); + setEditingRule(rulesRef.current[idx]); setEditingIndex(idx); setRuleModalOpen(true); } diff --git a/frontend/src/styles/page-shell.css b/frontend/src/styles/page-shell.css index ce504c05..d8c64e93 100644 --- a/frontend/src/styles/page-shell.css +++ b/frontend/src/styles/page-shell.css @@ -89,6 +89,24 @@ min-height: calc(100vh - 120px); } +.ant-dropdown-menu-item:not(.ant-dropdown-menu-item-disabled):not(.ant-dropdown-menu-item-danger):hover, +.ant-dropdown-menu-item:not(.ant-dropdown-menu-item-disabled):not(.ant-dropdown-menu-item-danger):hover .ant-dropdown-menu-title-content, +.ant-dropdown-menu-item:not(.ant-dropdown-menu-item-disabled):not(.ant-dropdown-menu-item-danger):hover > .anticon { + color: var(--ant-color-primary) !important; +} + +.ant-dropdown-menu-item:not(.ant-dropdown-menu-item-disabled):not(.ant-dropdown-menu-item-danger):hover { + background-color: color-mix(in srgb, var(--ant-color-primary) 14%, transparent) !important; +} + +.ant-dropdown-menu-item-divider { + background-color: var(--ant-color-border) !important; +} + +body.dark .ant-dropdown-menu-item-divider { + background-color: rgba(255, 255, 255, 0.12) !important; +} + .settings-page .header-row, .xray-page .header-row { display: flex;