From 29cd7167369eec557120412611a3cd3beaae309a Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 5 Nov 2026 18:38:35 +0000 Subject: [PATCH 1/4] fix(app): open the full nav over the page on compact viewports Below 900px the sidebar is forced into the 50px icon rail, so the toggle button changed a saved preference that the compact layout ignores. It now opens the full nav as an overlay. The rail width stays reserved so the page does not shift, and the saved desktop collapse preference is left alone. Co-authored-by: Cursor --- .../src/components/AppNav/AppNav.module.scss | 21 +++++++ packages/app/src/components/AppNav/AppNav.tsx | 39 ++++++++++--- .../__tests__/useMobileNavOverlay.test.tsx | 55 +++++++++++++++++++ .../components/AppNav/useMobileNavOverlay.ts | 39 +++++++++++++ 4 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 packages/app/src/components/AppNav/__tests__/useMobileNavOverlay.test.tsx create mode 100644 packages/app/src/components/AppNav/useMobileNavOverlay.ts diff --git a/packages/app/src/components/AppNav/AppNav.module.scss b/packages/app/src/components/AppNav/AppNav.module.scss index 2d4407490d..784d19be92 100644 --- a/packages/app/src/components/AppNav/AppNav.module.scss +++ b/packages/app/src/components/AppNav/AppNav.module.scss @@ -42,6 +42,19 @@ $transition-slow: 0.2s ease; .navGhost { transition: width 0.2s ease; will-change: width; + flex-shrink: 0; +} + +// Covers the page while the rail's full nav is open on a compact viewport. +// Dimming sits one layer below the nav, in the same layer as top-level drawers. +.navScrim { + position: fixed; + inset: 0; + z-index: 9; + border: 0; + padding: 0; + background: rgb(0 0 0 / 40%); + cursor: pointer; } .nav { @@ -60,6 +73,14 @@ $transition-slow: 0.2s ease; position: fixed; } + // Top-level drawers use z-index 10 (see PageHeader.module.scss). + &Overlay { + top: 0; + left: 0; + z-index: 10; + box-shadow: 4px 0 16px rgb(0 0 0 / 25%); + } + &Collapsed { .navLinks { padding-inline: 0; diff --git a/packages/app/src/components/AppNav/AppNav.tsx b/packages/app/src/components/AppNav/AppNav.tsx index 04937d6fba..8a6b093c7e 100644 --- a/packages/app/src/components/AppNav/AppNav.tsx +++ b/packages/app/src/components/AppNav/AppNav.tsx @@ -23,6 +23,7 @@ import api from '@/api'; import { useLogomark, useWordmark } from '@/brand'; import { AlertStatusIcon } from '@/components/AlertStatusIcon'; import { InstanceLabel } from '@/components/AppNav/InstanceLabel'; +import { useMobileNavOverlay } from '@/components/AppNav/useMobileNavOverlay'; import { useCompactViewport } from '@/components/CompactViewport/useCompactViewport'; import { APP_VERSION, IS_LOCAL_MODE } from '@/config'; import { Dashboard, useDashboards } from '@/dashboard'; @@ -182,9 +183,19 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) { defaultValue: false, }); - const isCollapsed = useCompactViewport() || isPreferCollapsed; - + const isCompact = useCompactViewport(); + const { + isOpen: isMobileNavOpen, + toggle: toggleMobileNav, + close: closeMobileNav, + } = useMobileNavOverlay(isCompact); + + // The page layout reserves the rail width even while the overlay is open, + // so opening the nav does not shift the content. + const isRail = isCompact || isPreferCollapsed; + const isCollapsed = isRail && !isMobileNavOpen; const navWidth = isCollapsed ? 50 : 250; + const reservedWidth = isRail ? 50 : 250; useEffect(() => { HyperDX.addAction('user navigated', { @@ -321,15 +332,23 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) { return ( - {fixed && ( + {(fixed || isMobileNavOpen) && (
)} + {isMobileNavOpen && ( +