Skip to content

Commit 2bdaef9

Browse files
committed
feat(responsive): add mobile-first responsive layout
- Add collapsible sidebar with hamburger menu toggle on mobile (<768px) - Add overlay backdrop when sidebar is open on mobile - Auto-close sidebar when selecting entity or navigating on mobile - Add responsive padding to sidebar header for menu button - Use Tailwind md: breakpoint for tablet/desktop layout
1 parent aa04841 commit 2bdaef9

2 files changed

Lines changed: 67 additions & 14 deletions

File tree

src/App.tsx

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { useState, useEffect, useRef, useCallback } from 'react';
22
import { useShallow } from 'zustand/shallow';
33
import { ToastContainer, toast } from 'react-toastify';
4+
import { Menu, X } from 'lucide-react';
45
import 'react-toastify/dist/ReactToastify.css';
6+
import { Button } from '@/components/ui/button';
57
import { EntityTreeSidebar } from '@/components/EntityTreeSidebar';
68
import { EntityDetailPanel } from '@/components/EntityDetailPanel';
79
import { ServerConnectionDialog } from '@/components/ServerConnectionDialog';
@@ -13,19 +15,21 @@ import { useAppStore } from '@/lib/store';
1315
type ViewMode = 'entity' | 'faults-dashboard';
1416

1517
function 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

src/components/EntityTreeSidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ export function EntityTreeSidebar({ onSettingsClick, onFaultsDashboardClick }: E
7676

7777
return (
7878
<aside className="w-80 border-r bg-card flex flex-col h-full">
79-
{/* Header */}
80-
<div className="p-4 border-b flex items-center justify-between">
79+
{/* Header - with top padding on mobile for menu button */}
80+
<div className="p-4 pt-14 md:pt-4 border-b flex items-center justify-between">
8181
<div className="flex items-center gap-2">
8282
<Server className="w-5 h-5 text-primary" />
8383
<h2 className="font-semibold">Entity Tree</h2>

0 commit comments

Comments
 (0)