All Q1-Q5 answered A: - Topbar removed; LanguageSwitcher + UserMenu move to sidebar footer - Mobile: slide-over overlay with hamburger button - Theme toggle in sidebar footer - No-flash init via inline script in index.html - Role-filtered sidebar items client-side Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
4.5 KiB
Frontend Components — Unit 3: Layout & Navigation
Modified Components
AppLayout.tsx (modify)
Path: frontend/src/components/layout/AppLayout.tsx
Remove Topbar. Add MobileBar and SidebarOverlay. Manage isMenuOpen state.
<div className="flex min-h-svh">
<Sidebar className="hidden md:flex" />
{isMenuOpen && <SidebarOverlay onClose={() => setIsMenuOpen(false)} />}
<div className="flex flex-1 flex-col">
<MobileBar onMenuOpen={() => setIsMenuOpen(true)} />
<main className="flex-1 p-6" data-testid="app-main">
<Outlet />
</main>
</div>
</div>
Sidebar.tsx (modify)
Path: frontend/src/components/layout/Sidebar.tsx
Changes:
- Add
roles?: Role[]toNavItem— filter visible items byuser.role - Add
SidebarFootersection at the bottom withLanguageSwitcher,ThemeToggle,UserMenu - Accept optional
onClose?: () => voidprop (used by mobile overlay close button) - Retain
data-testid="app-sidebar"
Nav items and role visibility:
| Label key | Route | Roles | Icon |
|---|---|---|---|
nav.dashboard |
/dashboard |
all | LayoutDashboard |
nav.users |
/users |
Owner, Administrator | Users |
nav.settings |
/settings |
Owner | Settings |
nav.cms |
/cms |
Owner | FileText |
nav.profile |
/profile |
all | User |
New Components
MobileBar.tsx
Path: frontend/src/components/layout/MobileBar.tsx
Visible only on < md. Contains hamburger button and app name.
Props: onMenuOpen: () => void
<header
className="flex h-14 items-center gap-3 border-b border-border bg-card px-4 md:hidden"
data-testid="app-mobile-bar"
>
<button onClick={onMenuOpen} aria-label={t('nav.openMenu')} data-testid="mobile-menu-button">
<Menu className="size-5" />
</button>
<span className="font-semibold">{t('common.appName')}</span>
</header>
SidebarOverlay.tsx
Path: frontend/src/components/layout/SidebarOverlay.tsx
Full-screen overlay wrapping Sidebar for mobile. Backdrop closes it on click.
<div className="fixed inset-0 z-40 md:hidden" data-testid="sidebar-overlay">
<div
className="absolute inset-0 bg-black/50"
onClick={onClose}
data-testid="sidebar-backdrop"
/>
<div className="absolute left-0 top-0 h-full w-64 shadow-xl">
<Sidebar onClose={onClose} />
</div>
</div>
Props: onClose: () => void
ThemeToggle.tsx
Path: frontend/src/components/layout/ThemeToggle.tsx
Toggles dark/light mode. Uses useTheme() hook.
<button
onClick={toggleTheme}
aria-label={isDark ? t('theme.switchToLight') : t('theme.switchToDark')}
data-testid="theme-toggle"
>
{isDark ? <Sun className="size-4" /> : <Moon className="size-4" />}
</button>
useTheme.ts
Path: frontend/src/hooks/useTheme.ts
function useTheme(): { theme: Theme; isDark: boolean; toggleTheme: () => void }
Reads initial value from localStorage['cms-theme'] or prefers-color-scheme. Syncs DOM class on <html>.
Removed Components
Topbar.tsx
Deleted. data-testid="app-topbar" removed from the DOM.
index.html Change
Add no-flash inline script in <head> before any stylesheet:
<script>
(function(){
var s=localStorage.getItem('cms-theme');
var t=s?s:(window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');
document.documentElement.classList.add(t);
})();
</script>
i18n Additions
nl/translation.json:
{
"nav": {
"settings": "Instellingen",
"profile": "Profiel",
"openMenu": "Navigatie openen"
},
"theme": {
"switchToLight": "Overschakelen naar licht thema",
"switchToDark": "Overschakelen naar donker thema"
}
}
en/translation.json:
{
"nav": {
"settings": "Settings",
"profile": "Profile",
"openMenu": "Open navigation"
},
"theme": {
"switchToLight": "Switch to light theme",
"switchToDark": "Switch to dark theme"
}
}
Test IDs Summary
| Element | data-testid |
|---|---|
| Sidebar | app-sidebar |
| Mobile bar | app-mobile-bar |
| Hamburger button | mobile-menu-button |
| Sidebar overlay container | sidebar-overlay |
| Sidebar backdrop | sidebar-backdrop |
| Theme toggle | theme-toggle |
| Nav: dashboard | nav-dashboard |
| Nav: users | nav-users |
| Nav: settings | nav-settings |
| Nav: cms | nav-cms |
| Nav: profile | nav-profile |
| Main content area | app-main |