11import { useState , useEffect , useRef , useCallback } from 'react' ;
22import { useShallow } from 'zustand/shallow' ;
33import { ToastContainer , toast } from 'react-toastify' ;
4+ import { Menu , X } from 'lucide-react' ;
45import 'react-toastify/dist/ReactToastify.css' ;
6+ import { Button } from '@/components/ui/button' ;
57import { EntityTreeSidebar } from '@/components/EntityTreeSidebar' ;
68import { EntityDetailPanel } from '@/components/EntityDetailPanel' ;
79import { ServerConnectionDialog } from '@/components/ServerConnectionDialog' ;
@@ -13,19 +15,21 @@ import { useAppStore } from '@/lib/store';
1315type ViewMode = 'entity' | 'faults-dashboard' ;
1416
1517function App ( ) {
16- const { isConnected, serverUrl, baseEndpoint, connect, clearSelection } = useAppStore (
18+ const { isConnected, serverUrl, baseEndpoint, connect, clearSelection, selectedPath } = useAppStore (
1719 useShallow ( ( state ) => ( {
1820 isConnected : state . isConnected ,
1921 serverUrl : state . serverUrl ,
2022 baseEndpoint : state . baseEndpoint ,
2123 connect : state . connect ,
2224 clearSelection : state . clearSelection ,
25+ selectedPath : state . selectedPath ,
2326 } ) )
2427 ) ;
2528
2629 const [ showConnectionDialog , setShowConnectionDialog ] = useState ( false ) ;
2730 const [ showSearch , setShowSearch ] = useState ( false ) ;
2831 const [ viewMode , setViewMode ] = useState < ViewMode > ( 'entity' ) ;
32+ const [ sidebarOpen , setSidebarOpen ] = useState ( true ) ;
2933 const autoConnectAttempted = useRef ( false ) ;
3034
3135 // Keyboard shortcut: Ctrl+K / Cmd+K to open search
@@ -36,13 +40,28 @@ function App() {
3640 const handleFaultsDashboardClick = useCallback ( ( ) => {
3741 clearSelection ( ) ;
3842 setViewMode ( 'faults-dashboard' ) ;
43+ // Close sidebar on mobile when navigating
44+ if ( window . innerWidth < 768 ) {
45+ setSidebarOpen ( false ) ;
46+ }
3947 } , [ clearSelection ] ) ;
4048
4149 // When entity is selected, switch back to entity view
4250 const handleEntitySelect = useCallback ( ( ) => {
4351 setViewMode ( 'entity' ) ;
52+ // Close sidebar on mobile when selecting entity
53+ if ( window . innerWidth < 768 ) {
54+ setSidebarOpen ( false ) ;
55+ }
4456 } , [ ] ) ;
4557
58+ // Close sidebar on mobile when entity is selected from search
59+ useEffect ( ( ) => {
60+ if ( selectedPath && window . innerWidth < 768 ) {
61+ setSidebarOpen ( false ) ;
62+ }
63+ } , [ selectedPath ] ) ;
64+
4665 // Auto-connect on mount if we have a stored URL
4766 useEffect ( ( ) => {
4867 if ( serverUrl && ! isConnected && ! autoConnectAttempted . current ) {
@@ -63,18 +82,52 @@ function App() {
6382 toast . error ( `Application error: ${ error . message } ` ) ;
6483 } }
6584 >
66- < div className = "flex h-screen bg-background" >
67- < EntityTreeSidebar
68- onSettingsClick = { ( ) => setShowConnectionDialog ( true ) }
69- onFaultsDashboardClick = { handleFaultsDashboardClick }
70- />
71- < ErrorBoundary >
72- < EntityDetailPanel
73- onConnectClick = { ( ) => setShowConnectionDialog ( true ) }
74- viewMode = { viewMode }
75- onEntitySelect = { handleEntitySelect }
85+ < div className = "flex h-screen bg-background relative" >
86+ { /* Mobile menu toggle */ }
87+ < Button
88+ variant = "ghost"
89+ size = "icon"
90+ className = "fixed top-3 left-3 z-50 md:hidden"
91+ onClick = { ( ) => setSidebarOpen ( ! sidebarOpen ) }
92+ aria-label = { sidebarOpen ? 'Close menu' : 'Open menu' }
93+ >
94+ { sidebarOpen ? < X className = "w-5 h-5" /> : < Menu className = "w-5 h-5" /> }
95+ </ Button >
96+
97+ { /* Sidebar with responsive behavior */ }
98+ < div
99+ className = { `
100+ fixed inset-y-0 left-0 z-40 w-80 transform transition-transform duration-200 ease-in-out
101+ md:relative md:translate-x-0
102+ ${ sidebarOpen ? 'translate-x-0' : '-translate-x-full' }
103+ ` }
104+ >
105+ < EntityTreeSidebar
106+ onSettingsClick = { ( ) => setShowConnectionDialog ( true ) }
107+ onFaultsDashboardClick = { handleFaultsDashboardClick }
108+ />
109+ </ div >
110+
111+ { /* Overlay for mobile when sidebar is open */ }
112+ { sidebarOpen && (
113+ < div
114+ className = "fixed inset-0 z-30 bg-black/50 md:hidden"
115+ onClick = { ( ) => setSidebarOpen ( false ) }
116+ aria-hidden = "true"
76117 />
77- </ ErrorBoundary >
118+ ) }
119+
120+ { /* Main content */ }
121+ < div className = "flex-1 md:ml-0" >
122+ < ErrorBoundary >
123+ < EntityDetailPanel
124+ onConnectClick = { ( ) => setShowConnectionDialog ( true ) }
125+ viewMode = { viewMode }
126+ onEntitySelect = { handleEntitySelect }
127+ />
128+ </ ErrorBoundary >
129+ </ div >
130+
78131 < ServerConnectionDialog open = { showConnectionDialog } onOpenChange = { setShowConnectionDialog } />
79132 < SearchCommand open = { showSearch } onOpenChange = { setShowSearch } />
80133 < ToastContainer
0 commit comments