From 2b76060ea3c1620daf04a44624aa3a2d186d3036 Mon Sep 17 00:00:00 2001 From: mrleo1nid <43583385+mrleo1nid@users.noreply.github.com> Date: Wed, 21 Oct 2026 09:06:33 +0300 Subject: [PATCH] fix(web): keep menu labels on one line and fold the admin section by default Co-authored-by: Cursor --- apps/web/src/App.test.tsx | 3 ++- apps/web/src/shared/ui/ui.module.css | 3 +++ apps/web/src/widgets/AppShell.module.css | 4 ++- apps/web/src/widgets/AppShell.tsx | 33 +++++++++++++++++++----- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx index d145d8f..f5bf6d4 100644 --- a/apps/web/src/App.test.tsx +++ b/apps/web/src/App.test.tsx @@ -117,7 +117,8 @@ describe('navigation by role', () => { it('shows users but not the audit log to moderators', async () => { const mod = makeUser('mod', 'moderator'); - await renderApp([mod], '/', { signedInAs: mod }); + const { user } = await renderApp([mod], '/', { signedInAs: mod }); + await user.click(await screen.findByRole('button', { name: 'Админка' })); expect(await screen.findByRole('link', { name: 'Пользователи' })).toBeInTheDocument(); expect(screen.queryByRole('link', { name: 'Журнал' })).not.toBeInTheDocument(); }); diff --git a/apps/web/src/shared/ui/ui.module.css b/apps/web/src/shared/ui/ui.module.css index 0f1a123..b7b41f9 100644 --- a/apps/web/src/shared/ui/ui.module.css +++ b/apps/web/src/shared/ui/ui.module.css @@ -268,7 +268,9 @@ right: 0; top: calc(100% + 6px); z-index: 40; + width: max-content; min-width: 220px; + max-width: min(360px, calc(100vw - 24px)); padding: 6px; border-radius: var(--radius-lg); background: var(--elev); @@ -292,6 +294,7 @@ border-radius: 8px; font-size: 14px; text-align: left; + white-space: nowrap; color: var(--text); outline: none; } diff --git a/apps/web/src/widgets/AppShell.module.css b/apps/web/src/widgets/AppShell.module.css index 40dfc29..5154108 100644 --- a/apps/web/src/widgets/AppShell.module.css +++ b/apps/web/src/widgets/AppShell.module.css @@ -84,7 +84,7 @@ .profileHead { display: grid; - grid-template-columns: auto 1fr; + grid-template-columns: auto max-content; gap: 4px 12px; align-items: center; padding: 10px 10px 12px; @@ -100,11 +100,13 @@ .profileName { font-weight: 600; + white-space: nowrap; } .profileMeta { font-size: 12px; color: var(--text3); + white-space: nowrap; } .side { diff --git a/apps/web/src/widgets/AppShell.tsx b/apps/web/src/widgets/AppShell.tsx index 9f632cf..16bb11b 100644 --- a/apps/web/src/widgets/AppShell.tsx +++ b/apps/web/src/widgets/AppShell.tsx @@ -79,25 +79,44 @@ function NavLink({ } const FOLDED_KEY = 'pipu.nav.folded'; +const TOUCHED_KEY = 'pipu.nav.touched'; -function readFolded(): string[] { +function readIds(key: string): string[] { try { - const v: unknown = JSON.parse(localStorage.getItem(FOLDED_KEY) ?? '[]'); + const v: unknown = JSON.parse(localStorage.getItem(key) ?? '[]'); return Array.isArray(v) ? v.filter((x): x is string => typeof x === 'string') : []; } catch { return []; } } -/** Раздел меню: заголовок сворачивает пункты; состояние помнит браузер. Свёрнутый раздел показывает сумму бейджей. */ -function NavSection({ id, title, badge, children }: { id: string; title: string; badge?: number; children: ReactNode }) { - const [folded, setFolded] = useState(() => readFolded().includes(id)); +/** Раздел меню: заголовок сворачивает пункты; явный выбор помнит браузер. Свёрнутый раздел показывает сумму бейджей. */ +function NavSection({ + id, + title, + badge, + children, + defaultFolded = false, +}: { + id: string; + title: string; + badge?: number; + children: ReactNode; + defaultFolded?: boolean; +}) { + const [folded, setFolded] = useState(() => { + if (readIds(FOLDED_KEY).includes(id)) return true; + if (readIds(TOUCHED_KEY).includes(id)) return false; + return defaultFolded; + }); const toggle = () => { const next = !folded; setFolded(next); try { - const rest = readFolded().filter((x) => x !== id); + const rest = readIds(FOLDED_KEY).filter((x) => x !== id); localStorage.setItem(FOLDED_KEY, JSON.stringify(next ? [...rest, id] : rest)); + const touched = readIds(TOUCHED_KEY); + if (!touched.includes(id)) localStorage.setItem(TOUCHED_KEY, JSON.stringify([...touched, id])); } catch { // Без хранилища раздел просто не запомнит состояние. } @@ -188,7 +207,7 @@ function Shell() { /> {staff && ( - + {user.role === 'admin' && (