Skip to content

Commit c047aa6

Browse files
committed
Implement responsive sidebar with drawer functionality and navigation menu toggle
1 parent ab7d37b commit c047aa6

3 files changed

Lines changed: 98 additions & 9 deletions

File tree

src/components/admin/AdminLayout.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useState, useCallback } from 'react'
12
import { Outlet } from 'react-router-dom'
23
import { AdminNavbar } from './AdminNavbar'
34
import { AdminSidebar } from './AdminSidebar'
@@ -8,11 +9,16 @@ type AdminLayoutProps = {
89
}
910

1011
export function AdminLayout({ repoName, onLogout }: AdminLayoutProps) {
12+
const [drawerOpen, setDrawerOpen] = useState(false)
13+
14+
const openDrawer = useCallback(() => setDrawerOpen(true), [])
15+
const closeDrawer = useCallback(() => setDrawerOpen(false), [])
16+
1117
return (
1218
<div className="flex min-h-screen flex-col bg-slate-950 text-slate-50">
13-
<AdminNavbar repoName={repoName} onLogout={onLogout} />
19+
<AdminNavbar repoName={repoName} onLogout={onLogout} onMenuOpen={openDrawer} />
1420
<div className="flex flex-1">
15-
<AdminSidebar />
21+
<AdminSidebar drawerOpen={drawerOpen} onClose={closeDrawer} />
1622
<main className="flex-1 overflow-auto">
1723
<div className="mx-auto max-w-4xl p-6">
1824
<Outlet />

src/components/admin/AdminNavbar.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,23 @@ import { Link } from 'react-router-dom'
33
type AdminNavbarProps = {
44
repoName: string | null
55
onLogout: () => void
6+
onMenuOpen: () => void
67
}
78

8-
export function AdminNavbar({ repoName, onLogout }: AdminNavbarProps) {
9+
export function AdminNavbar({ repoName, onLogout, onMenuOpen }: AdminNavbarProps) {
910
return (
1011
<nav className="sticky top-0 z-30 flex h-14 items-center justify-between border-b border-slate-800 bg-slate-950/95 px-6 backdrop-blur">
11-
<div className="flex items-center gap-6">
12+
<div className="flex items-center gap-3">
13+
<button
14+
type="button"
15+
onClick={onMenuOpen}
16+
className="flex h-8 w-8 items-center justify-center rounded-md text-slate-400 transition hover:bg-slate-800 hover:text-slate-200 md:hidden"
17+
aria-label="Open navigation menu"
18+
>
19+
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
20+
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
21+
</svg>
22+
</button>
1223
<Link
1324
to="/admin"
1425
className="flex items-center gap-2 text-base font-semibold tracking-tight text-slate-50"
@@ -19,7 +30,7 @@ export function AdminNavbar({ repoName, onLogout }: AdminNavbarProps) {
1930
Portfolio Admin
2031
</Link>
2132
{repoName && (
22-
<span className="hidden text-xs text-slate-500 sm:inline">
33+
<span className="hidden text-xs text-slate-500 md:inline">
2334
{repoName}
2435
</span>
2536
)}

src/components/admin/AdminSidebar.tsx

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { NavLink } from 'react-router-dom'
1+
import { useEffect } from 'react'
2+
import { NavLink, useLocation } from 'react-router-dom'
23

34
const SIDEBAR_ITEMS = [
45
{
@@ -58,9 +59,9 @@ const SETTINGS_ITEMS = [
5859
},
5960
] as const
6061

61-
export function AdminSidebar() {
62+
function NavItems({ onNavigate }: { onNavigate?: () => void }) {
6263
return (
63-
<aside className="flex w-56 shrink-0 flex-col border-r border-slate-800 bg-slate-950/80">
64+
<>
6465
<div className="flex flex-col gap-0.5 p-3">
6566
<p className="mb-2 px-3 text-[10px] font-semibold uppercase tracking-wider text-slate-500">
6667
Content
@@ -70,6 +71,7 @@ export function AdminSidebar() {
7071
key={to}
7172
to={to}
7273
end={end}
74+
onClick={onNavigate}
7375
className={({ isActive }) =>
7476
`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition ${
7577
isActive
@@ -92,6 +94,7 @@ export function AdminSidebar() {
9294
key={to}
9395
to={to}
9496
end={end}
97+
onClick={onNavigate}
9598
className={({ isActive }) =>
9699
`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition ${
97100
isActive
@@ -105,6 +108,75 @@ export function AdminSidebar() {
105108
</NavLink>
106109
))}
107110
</div>
108-
</aside>
111+
</>
112+
)
113+
}
114+
115+
type AdminSidebarProps = {
116+
drawerOpen: boolean
117+
onClose: () => void
118+
}
119+
120+
export function AdminSidebar({ drawerOpen, onClose }: AdminSidebarProps) {
121+
const location = useLocation()
122+
123+
// Close drawer on route change
124+
useEffect(() => {
125+
onClose()
126+
}, [location.pathname, onClose])
127+
128+
// Lock body scroll when drawer is open
129+
useEffect(() => {
130+
if (drawerOpen) {
131+
document.body.style.overflow = 'hidden'
132+
} else {
133+
document.body.style.overflow = ''
134+
}
135+
return () => {
136+
document.body.style.overflow = ''
137+
}
138+
}, [drawerOpen])
139+
140+
return (
141+
<>
142+
{/* Desktop sidebar — always visible on md+ */}
143+
<aside className="hidden w-56 shrink-0 flex-col border-r border-slate-800 bg-slate-950/80 md:flex">
144+
<NavItems />
145+
</aside>
146+
147+
{/* Mobile drawer */}
148+
{/* Backdrop */}
149+
<div
150+
className={`fixed inset-0 z-40 bg-black/60 transition-opacity duration-300 md:hidden ${
151+
drawerOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'
152+
}`}
153+
onClick={onClose}
154+
aria-hidden="true"
155+
/>
156+
{/* Drawer panel */}
157+
<aside
158+
className={`fixed inset-y-0 left-0 z-50 flex w-64 flex-col border-r border-slate-800 bg-slate-950 transition-transform duration-300 ease-in-out md:hidden ${
159+
drawerOpen ? 'translate-x-0' : '-translate-x-full'
160+
}`}
161+
aria-label="Navigation drawer"
162+
>
163+
<div className="flex h-14 shrink-0 items-center justify-between border-b border-slate-800 px-4">
164+
<span className="text-sm font-semibold text-slate-200">Menu</span>
165+
<button
166+
type="button"
167+
onClick={onClose}
168+
className="flex h-8 w-8 items-center justify-center rounded-md text-slate-400 transition hover:bg-slate-800 hover:text-slate-200"
169+
aria-label="Close navigation menu"
170+
>
171+
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
172+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
173+
</svg>
174+
</button>
175+
</div>
176+
<div className="flex-1 overflow-y-auto">
177+
<NavItems />
178+
</div>
179+
</aside>
180+
</>
109181
)
110182
}

0 commit comments

Comments
 (0)