11import { useState , useEffect } from 'react'
2- import { Outlet } from 'react-router-dom'
2+ import { Outlet , useLocation } from 'react-router-dom'
33import { Sidebar } from './Sidebar'
44import { TopNav } from './TopNav'
55import { CommandPalette } from './CommandPalette'
@@ -15,7 +15,9 @@ const SIDEBAR_STORAGE_KEY = 'codeintel-sidebar-collapsed'
1515
1616export function DashboardLayout ( { children } : DashboardLayoutProps ) {
1717 const { theme } = useTheme ( )
18+ const location = useLocation ( )
1819
20+ // Desktop: collapsed state (narrow sidebar)
1921 const [ sidebarCollapsed , setSidebarCollapsed ] = useState ( ( ) => {
2022 try {
2123 const stored = localStorage . getItem ( SIDEBAR_STORAGE_KEY )
@@ -24,8 +26,12 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
2426 return false
2527 }
2628 } )
29+
30+ // Mobile: open/closed state (overlay)
31+ const [ mobileMenuOpen , setMobileMenuOpen ] = useState ( false )
2732 const [ commandPaletteOpen , setCommandPaletteOpen ] = useState ( false )
2833
34+ // Persist desktop collapsed state
2935 useEffect ( ( ) => {
3036 try {
3137 localStorage . setItem ( SIDEBAR_STORAGE_KEY , JSON . stringify ( sidebarCollapsed ) )
@@ -34,6 +40,30 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
3440 }
3541 } , [ sidebarCollapsed ] )
3642
43+ // Close mobile menu on route change
44+ useEffect ( ( ) => {
45+ setMobileMenuOpen ( false )
46+ } , [ location . pathname ] )
47+
48+ // Close mobile menu on escape key
49+ useEffect ( ( ) => {
50+ const handleEscape = ( e : KeyboardEvent ) => {
51+ if ( e . key === 'Escape' ) setMobileMenuOpen ( false )
52+ }
53+ document . addEventListener ( 'keydown' , handleEscape )
54+ return ( ) => document . removeEventListener ( 'keydown' , handleEscape )
55+ } , [ ] )
56+
57+ // Prevent body scroll when mobile menu is open
58+ useEffect ( ( ) => {
59+ if ( mobileMenuOpen ) {
60+ document . body . style . overflow = 'hidden'
61+ } else {
62+ document . body . style . overflow = ''
63+ }
64+ return ( ) => { document . body . style . overflow = '' }
65+ } , [ mobileMenuOpen ] )
66+
3767 useKeyboardShortcut ( SHORTCUTS . COMMAND_PALETTE , ( ) => {
3868 setCommandPaletteOpen ( true )
3969 } )
@@ -42,26 +72,49 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
4272 setSidebarCollapsed ( ( prev : boolean ) => ! prev )
4373 } )
4474
75+ const handleToggleSidebar = ( ) => {
76+ // On mobile: toggle overlay menu
77+ // On desktop: toggle collapsed state
78+ if ( window . innerWidth < 1024 ) {
79+ setMobileMenuOpen ( ! mobileMenuOpen )
80+ } else {
81+ setSidebarCollapsed ( ! sidebarCollapsed )
82+ }
83+ }
84+
4585 return (
4686 < div className = "min-h-screen bg-background" >
4787 < TopNav
48- onToggleSidebar = { ( ) => setSidebarCollapsed ( ! sidebarCollapsed ) }
88+ onToggleSidebar = { handleToggleSidebar }
4989 sidebarCollapsed = { sidebarCollapsed }
5090 onOpenCommandPalette = { ( ) => setCommandPaletteOpen ( true ) }
5191 />
5292
5393 < div className = "flex" >
94+ { /* Mobile backdrop */ }
95+ { mobileMenuOpen && (
96+ < div
97+ className = "fixed inset-0 z-30 bg-black/50 lg:hidden"
98+ onClick = { ( ) => setMobileMenuOpen ( false ) }
99+ aria-hidden = "true"
100+ />
101+ ) }
102+
54103 < Sidebar
55104 collapsed = { sidebarCollapsed }
56- onToggle = { ( ) => setSidebarCollapsed ( ! sidebarCollapsed ) }
105+ onToggle = { handleToggleSidebar }
106+ mobileOpen = { mobileMenuOpen }
107+ onMobileClose = { ( ) => setMobileMenuOpen ( false ) }
57108 />
58109
110+ { /* Main content - no margin on mobile, dynamic margin on desktop */ }
59111 < main
60- className = { `flex-1 transition-all duration-300 ${
61- sidebarCollapsed ? 'ml-16' : 'ml-60'
62- } `}
112+ className = { `
113+ flex-1 transition-all duration-300 pt-[var(--navbar-height)]
114+ ml-0 ${ sidebarCollapsed ? 'lg:ml-[var(--sidebar-width-collapsed)]' : 'lg:ml-[var(--sidebar-width)]' }
115+ ` }
63116 >
64- < div className = "p-6" >
117+ < div className = "p-4 md:p- 6" >
65118 { children || < Outlet /> }
66119 </ div >
67120 </ main >
0 commit comments