fix(ui): polish across routing, groups, inbounds, mobile sidebar

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.
This commit is contained in:
MHSanaei 2026-05-28 13:25:43 +02:00
parent 530e338c66
commit 2fea71387b
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
7 changed files with 77 additions and 10 deletions

View file

@ -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-nav .ant-menu-item-selected,
.sider-utility .ant-menu-item-selected, .sider-utility .ant-menu-item-selected,
.drawer-menu .ant-menu-item-selected { .drawer-menu .ant-menu-item-selected {

View file

@ -77,6 +77,19 @@
padding: 6px 0; 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 { .email-cell {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -332,6 +332,13 @@ export default function GroupsPage() {
label: t('pages.groups.addToGroup'), label: t('pages.groups.addToGroup'),
onClick: () => openAddClientsFor(row), onClick: () => openAddClientsFor(row),
}, },
{
key: 'rename',
icon: <EditOutlined />,
label: t('pages.groups.rename'),
onClick: () => openRename(row),
},
{ type: 'divider' },
{ {
key: 'removeClients', key: 'removeClients',
icon: <UsergroupDeleteOutlined />, icon: <UsergroupDeleteOutlined />,
@ -340,13 +347,6 @@ export default function GroupsPage() {
disabled: !row.clientCount, disabled: !row.clientCount,
onClick: () => openRemoveClientsFor(row), onClick: () => openRemoveClientsFor(row),
}, },
{ type: 'divider' },
{
key: 'rename',
icon: <EditOutlined />,
label: t('pages.groups.rename'),
onClick: () => openRename(row),
},
{ {
key: 'deleteClients', key: 'deleteClients',
icon: <DeleteOutlined />, icon: <DeleteOutlined />,

View file

@ -264,7 +264,10 @@ function buildRowActionsMenu({ record, subEnable, t, isMobile, hasClients }: { r
items.push({ key: 'attachClients', icon: <UsergroupAddOutlined />, label: t('pages.inbounds.attachClients') }); items.push({ key: 'attachClients', icon: <UsergroupAddOutlined />, label: t('pages.inbounds.attachClients') });
items.push({ key: 'detachClients', icon: <UsergroupDeleteOutlined />, label: t('pages.inbounds.detachClients') }); items.push({ key: 'detachClients', icon: <UsergroupDeleteOutlined />, label: t('pages.inbounds.detachClients') });
items.push({ key: 'addToGroup', icon: <TagsOutlined />, label: t('pages.inbounds.addClientsToGroup') }); items.push({ key: 'addToGroup', icon: <TagsOutlined />, label: t('pages.inbounds.addClientsToGroup') });
items.push({ type: 'divider' });
items.push({ key: 'delAllClients', icon: <UsergroupDeleteOutlined />, danger: true, label: t('pages.inbounds.delAllClients') }); items.push({ key: 'delAllClients', icon: <UsergroupDeleteOutlined />, danger: true, label: t('pages.inbounds.delAllClients') });
} else {
items.push({ type: 'divider' });
} }
items.push({ key: 'delete', icon: <DeleteOutlined />, danger: true, label: t('delete') }); items.push({ key: 'delete', icon: <DeleteOutlined />, danger: true, label: t('delete') });
return items; return items;

View file

@ -99,7 +99,7 @@
.rule-list { .rule-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 14px;
} }
.rule-card { .rule-card {
@ -109,11 +109,24 @@
gap: 8px; gap: 8px;
padding: 10px 12px; padding: 10px 12px;
background: var(--bg-card, #fff); background: var(--bg-card, #fff);
border: 1px solid var(--ant-color-border-secondary); border: 1px solid var(--ant-color-border);
border-radius: 8px; border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
transition: opacity 0.15s, box-shadow 0.15s; 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 { .rule-card.row-dragging {
opacity: 0.4; opacity: 0.4;
} }

View file

@ -88,6 +88,8 @@ export default function RoutingTab({
() => (templateSettings?.routing?.rules || []) as RoutingRule[], () => (templateSettings?.routing?.rules || []) as RoutingRule[],
[templateSettings?.routing?.rules], [templateSettings?.routing?.rules],
); );
const rulesRef = useRef(rules);
rulesRef.current = rules;
const rows: RuleRow[] = useMemo( const rows: RuleRow[] = useMemo(
() => () =>
@ -171,7 +173,7 @@ export default function RoutingTab({
setRuleModalOpen(true); setRuleModalOpen(true);
} }
function openEdit(idx: number) { function openEdit(idx: number) {
setEditingRule(rules[idx]); setEditingRule(rulesRef.current[idx]);
setEditingIndex(idx); setEditingIndex(idx);
setRuleModalOpen(true); setRuleModalOpen(true);
} }

View file

@ -89,6 +89,24 @@
min-height: calc(100vh - 120px); 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, .settings-page .header-row,
.xray-page .header-row { .xray-page .header-row {
display: flex; display: flex;