diff --git a/apps/client/src/components/layouts/global/app-header.tsx b/apps/client/src/components/layouts/global/app-header.tsx index 96b0a75d..1cc4b5b6 100644 --- a/apps/client/src/components/layouts/global/app-header.tsx +++ b/apps/client/src/components/layouts/global/app-header.tsx @@ -12,6 +12,7 @@ import TopMenu from "@/components/layouts/global/top-menu.tsx"; import { Link } from "react-router-dom"; import { useAtom } from "jotai"; import { + NAVBAR_COLLAPSE_BREAKPOINT, desktopSidebarAtom, mobileSidebarAtom, } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; @@ -53,7 +54,13 @@ export function AppHeader() { aria-label={t("Sidebar toggle")} opened={mobileOpened} onClick={toggleMobile} - hiddenFrom="sm" + // Must match the AppShell navbar breakpoint (md). The navbar + // collapses to the MOBILE drawer below md, so the mobile toggle + // (which flips mobileOpened) must be the one visible across the + // whole md navbar change). + hiddenFrom={NAVBAR_COLLAPSE_BREAKPOINT} size="sm" /> @@ -63,7 +70,7 @@ export function AppHeader() { aria-label={t("Sidebar toggle")} opened={desktopOpened} onClick={toggleDesktop} - visibleFrom="sm" + visibleFrom={NAVBAR_COLLAPSE_BREAKPOINT} size="sm" /> diff --git a/apps/client/src/components/layouts/global/global-app-shell.tsx b/apps/client/src/components/layouts/global/global-app-shell.tsx index 41d3886f..1b41011e 100644 --- a/apps/client/src/components/layouts/global/global-app-shell.tsx +++ b/apps/client/src/components/layouts/global/global-app-shell.tsx @@ -6,6 +6,7 @@ import SettingsSidebar from "@/components/settings/settings-sidebar.tsx"; import { useAtom } from "jotai"; import { APP_NAVBAR_ID, + NAVBAR_COLLAPSE_BREAKPOINT, asideStateAtom, desktopSidebarAtom, mobileSidebarAtom, @@ -88,7 +89,13 @@ export default function GlobalAppShell({ header={{ height: 45 }} navbar={{ width: isSpaceRoute ? sidebarWidth : 300, - breakpoint: "sm", + // `md` (not `sm`): below 992px the fixed ~300px sidebar leaves too little + // room for content — the settings tables (Members/…) overflow the offset + // content area on tablet (~768px) and clip the Role/actions columns + // off-screen with no horizontal scroll. Collapsing the navbar to a toggle + // drawer across the whole tablet band frees the full width for content + // (the mobile drawer is closed by default, so nothing overlaps on load). + breakpoint: NAVBAR_COLLAPSE_BREAKPOINT, collapsed: { mobile: !mobileOpened, desktop: !desktopOpened, @@ -97,7 +104,7 @@ export default function GlobalAppShell({ aside={ isPageRoute && { width: 420, - breakpoint: "sm", + breakpoint: "md", collapsed: { mobile: !isAsideOpen, desktop: !isAsideOpen }, } } diff --git a/apps/client/src/components/layouts/global/hooks/atoms/sidebar-atom.ts b/apps/client/src/components/layouts/global/hooks/atoms/sidebar-atom.ts index f85f0fbc..c87c698c 100644 --- a/apps/client/src/components/layouts/global/hooks/atoms/sidebar-atom.ts +++ b/apps/client/src/components/layouts/global/hooks/atoms/sidebar-atom.ts @@ -7,6 +7,13 @@ import { atom } from "jotai"; // would create a shell -> chat-window -> shell import cycle). export const APP_NAVBAR_ID = "app-shell-navbar"; +// Single source of truth for the navbar collapse breakpoint. The AppShell navbar +// `breakpoint` and BOTH burger toggles' `hiddenFrom`/`visibleFrom` MUST use this +// exact value: if they drift, the sidebar becomes unreachable on tablet widths +// (the round-1 regression of #292). Kept here so the shell and the header share +// one constant the compiler enforces, instead of three hand-synced string literals. +export const NAVBAR_COLLAPSE_BREAKPOINT = "md"; + export const mobileSidebarAtom = atom(false); export const desktopSidebarAtom = atomWithWebStorage(