Collapsible sidebar for mobile, fix logout button visibility
On mobile: hamburger button in top bar opens sidebar as a slide-over with a backdrop overlay. Nav links and source selector close it on tap. On desktop: sidebar is static as before. Logout button now sits alongside the close button in the sidebar header with a gap so both are always visible. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2c573a5eeb
commit
71c4654361
@ -20,6 +20,7 @@ export default function App() {
|
|||||||
const [authed, setAuthed] = useState(false)
|
const [authed, setAuthed] = useState(false)
|
||||||
const [sources, setSources] = useState([])
|
const [sources, setSources] = useState([])
|
||||||
const [source, setSource] = useState(() => localStorage.getItem('selectedSource') || '')
|
const [source, setSource] = useState(() => localStorage.getItem('selectedSource') || '')
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||||
|
|
||||||
async function handleLogin(user, pass) {
|
async function handleLogin(user, pass) {
|
||||||
setCredentials(user, pass)
|
setCredentials(user, pass)
|
||||||
@ -53,25 +54,22 @@ export default function App() {
|
|||||||
|
|
||||||
if (!authed) return <Login onLogin={handleLogin} />
|
if (!authed) return <Login onLogin={handleLogin} />
|
||||||
|
|
||||||
return (
|
const sidebar = (
|
||||||
<BrowserRouter>
|
<div className="flex flex-col h-full">
|
||||||
<div className="flex h-screen bg-gray-50">
|
{/* Header */}
|
||||||
{/* Sidebar */}
|
|
||||||
<div className="w-44 bg-white border-r border-gray-200 flex flex-col">
|
|
||||||
<div className="px-4 py-4 border-b border-gray-200 flex items-center justify-between">
|
<div className="px-4 py-4 border-b border-gray-200 flex items-center justify-between">
|
||||||
<span className="text-sm font-semibold text-gray-800 tracking-wide uppercase">Dataflow</span>
|
<span className="text-sm font-semibold text-gray-800 tracking-wide uppercase">Dataflow</span>
|
||||||
<button onClick={handleLogout} className="text-xs text-gray-400 hover:text-gray-600" title="Sign out">⏻</button>
|
<div className="flex items-center gap-2">
|
||||||
|
<button onClick={handleLogout} className="text-xs text-gray-400 hover:text-gray-600 leading-none" title="Sign out">⏻</button>
|
||||||
|
<button onClick={() => setSidebarOpen(false)} className="md:hidden text-gray-400 hover:text-gray-600 leading-none" title="Close">✕</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Source selector */}
|
{/* Source selector */}
|
||||||
<div className="px-3 py-3 border-b border-gray-200">
|
<div className="px-3 py-3 border-b border-gray-200">
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<label className="text-xs text-gray-500">Source</label>
|
<label className="text-xs text-gray-500">Source</label>
|
||||||
<NavLink to="/sources"
|
<NavLink to="/sources" className="text-xs text-blue-400 hover:text-blue-600 leading-none" title="New source" onClick={() => setSidebarOpen(false)}>+</NavLink>
|
||||||
className="text-xs text-blue-400 hover:text-blue-600 leading-none"
|
|
||||||
title="New source"
|
|
||||||
onClick={() => {/* nav handled by link */}}
|
|
||||||
>+</NavLink>
|
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
className="w-full text-sm border border-gray-200 rounded px-2 py-1 bg-white focus:outline-none focus:border-blue-400"
|
className="w-full text-sm border border-gray-200 rounded px-2 py-1 bg-white focus:outline-none focus:border-blue-400"
|
||||||
@ -89,6 +87,7 @@ export default function App() {
|
|||||||
<NavLink
|
<NavLink
|
||||||
key={to}
|
key={to}
|
||||||
to={to}
|
to={to}
|
||||||
|
onClick={() => setSidebarOpen(false)}
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`block px-4 py-2 text-sm ${isActive
|
`block px-4 py-2 text-sm ${isActive
|
||||||
? 'bg-blue-50 text-blue-700 font-medium'
|
? 'bg-blue-50 text-blue-700 font-medium'
|
||||||
@ -100,8 +99,34 @@ export default function App() {
|
|||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<div className="flex h-screen bg-gray-50">
|
||||||
|
|
||||||
|
{/* Mobile overlay */}
|
||||||
|
{sidebarOpen && (
|
||||||
|
<div className="fixed inset-0 z-20 bg-black/30 md:hidden" onClick={() => setSidebarOpen(false)} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Sidebar — fixed on mobile, static on desktop */}
|
||||||
|
<div className={`
|
||||||
|
fixed inset-y-0 left-0 z-30 w-44 bg-white border-r border-gray-200 transform transition-transform duration-200
|
||||||
|
md:static md:translate-x-0 md:z-auto md:transition-none
|
||||||
|
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
|
||||||
|
`}>
|
||||||
|
{sidebar}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Main */}
|
{/* Main */}
|
||||||
|
<div className="flex-1 overflow-auto flex flex-col min-w-0">
|
||||||
|
{/* Mobile top bar */}
|
||||||
|
<div className="md:hidden flex items-center px-3 py-2 bg-white border-b border-gray-200">
|
||||||
|
<button onClick={() => setSidebarOpen(true)} className="text-gray-500 hover:text-gray-700 mr-3 text-lg leading-none">☰</button>
|
||||||
|
<span className="text-sm font-semibold text-gray-800 tracking-wide uppercase">Dataflow</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Navigate to="/sources" replace />} />
|
<Route path="/" element={<Navigate to="/sources" replace />} />
|
||||||
@ -113,6 +138,7 @@ export default function App() {
|
|||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user