- Remove Topbar; move UserMenu + LanguageSwitcher to sidebar footer - Add ThemeToggle (dark/light) with localStorage persistence - Add useTheme hook; no-flash inline script in index.html - Add MobileBar (hamburger + app name, mobile-only) - Add SidebarOverlay (slide-in from left, backdrop closes it) - Sidebar: role-filtered nav (BR-U3-01–06), onClose prop for mobile - AppLayout: desktop sidebar-only layout, mobile bar + overlay - i18n: theme.* and nav.openMenu/closeMenu keys (NL + EN) - Tests: 43/43 passing (14 new — useTheme, Sidebar roles, AppLayout) Stories: US-08, US-18, US-19 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
6.2 KiB
Code Generation Plan — Unit 3: Layout & Navigation
Status: 🚧 In Progress
Unit Context
Unit: Unit 3 — Layout & Navigation Type: Frontend (React/TypeScript with TanStack Router) Depends on: Unit 2 (AuthContext, useAuth, RoleGuard) Stories Covered: US-08, US-18, US-19
Key Deliverables:
AppLayout.tsx— modified: remove Topbar, add MobileBar + SidebarOverlaySidebar.tsx— modified: role-filtered nav, sidebar footer with UserMenu + LanguageSwitcher + ThemeToggleMobileBar.tsx— new: slim mobile bar with hamburger buttonSidebarOverlay.tsx— new: slide-over wrapper for Sidebar on mobileThemeToggle.tsx— new: dark/light toggle buttonuseTheme.ts— new: theme state + localStorage syncTopbar.tsx— deletedindex.html— modified: no-flash inline script- i18n — additions:
theme.*,nav.openMenu - Unit tests for all new/modified components
Answers
| Q | Answer | Decision |
|---|---|---|
| Q1 Mobile animation | A | Slide in from left with CSS transition |
| Q2 Sidebar footer | A | UserMenu dropdown + LanguageSwitcher + ThemeToggle as icon buttons |
| Q3 Tests scope | A | All new and modified components |
Code Generation Steps
Step 1: Add no-flash theme script to index.html
- Add inline
<script>in<head>before stylesheets - Reads
localStorage['cms-theme'], falls back toprefers-color-scheme - Applies
lightordarkclass to<html>synchronously
Step 2: Create useTheme.ts hook
- Create
src/hooks/useTheme.ts - Returns
{ theme, isDark, toggleTheme } - On mount: reads from
localStorage(already set by inline script) toggleTheme: flips value, writes tolocalStorage, updates<html>class
Step 3: Create ThemeToggle.tsx
- Create
src/components/layout/ThemeToggle.tsx - Uses
useTheme()— renders<Sun>(dark mode) or<Moon>(light mode) data-testid="theme-toggle"- i18n aria-label via
theme.switchToLight/theme.switchToDark
Step 4: Create MobileBar.tsx
- Create
src/components/layout/MobileBar.tsx - Props:
onMenuOpen: () => void - Visible only
< md(md:hidden) - Contains
<Menu>icon button + app name data-testid="app-mobile-bar",data-testid="mobile-menu-button"
Step 5: Create SidebarOverlay.tsx
- Create
src/components/layout/SidebarOverlay.tsx - Props:
onClose: () => void - Full-screen fixed overlay with semi-transparent backdrop
- Sidebar panel slides in from left (
translate-x-0transition,duration-200) - Backdrop click calls
onClose data-testid="sidebar-overlay",data-testid="sidebar-backdrop"
Step 6: Modify Sidebar.tsx
- Add
roles?: Role[]toNavIteminterface - Add nav items: Settings (Owner only), Profile (all) — Dashboard, Users, CMS already exist
- Filter visible items:
NAV_ITEMS.filter(item => !item.roles || item.roles.includes(user.role)) - Add
onClose?: () => voidprop — render close button (<X>) at top of sidebar on mobile - Add
SidebarFootersection pinned to bottom:UserMenu+LanguageSwitcher+ThemeToggle - Retain
data-testid="app-sidebar"
Step 7: Modify AppLayout.tsx
- Remove
Topbarimport and render - Add
isMenuOpenstate (useState(false)) - Add
MobileBar(triggerssetIsMenuOpen(true)) - Add
SidebarOverlay(rendered whenisMenuOpen, passesonClose) - Close overlay on route change via
useEffectwatchinguseLocation() - Desktop layout:
Sidebarfills left,<main>fills right (no top bar)
Step 8: Delete Topbar.tsx
- Remove
src/components/layout/Topbar.tsx
Step 9: Extend i18n translations
- Add to
nl/translation.json:theme.switchToLight,theme.switchToDark,nav.openMenu - Add to
en/translation.json: same keys in English
Step 10: Unit tests — useTheme
- Create
src/hooks/useTheme.test.ts - Initial value from localStorage / OS preference
- Toggle updates localStorage and
<html>class
Step 11: Unit tests — Sidebar role filtering
- Create
src/components/layout/Sidebar.test.tsx - Owner: all 5 items visible
- Administrator: Dashboard, Users, Profile visible; Settings, CMS hidden
- User: Dashboard, Profile visible; Users, Settings, CMS hidden
Step 12: Unit tests — AppLayout + MobileBar + SidebarOverlay
- Create
src/components/layout/AppLayout.test.tsx - Mobile bar renders on small screens
- Hamburger opens overlay; backdrop click closes it
- Overlay closes on navigation
Step 13: Final verification
pnpm build— no errorspnpm lint— cleanpnpm test— all tests pass (existing 29 + new)- Dev server: verify sidebar on desktop, mobile overlay, theme toggle, role filtering
Step 14: Commit
- Commit with message referencing US-08, US-18, US-19
Total Steps: 14
Please answer the following questions by filling in the letter after each [Answer]: tag.
Question 1: Mobile sidebar animation
When the slide-over sidebar opens on mobile, should it animate?
A) Slide in from the left with a CSS transition (recommended — feels native, Tailwind transition-transform)
B) Fade in (opacity transition only)
C) No animation — appear/disappear instantly
Question 2: Sidebar footer layout
The sidebar footer contains UserMenu, LanguageSwitcher and ThemeToggle. How should these be presented?
A) UserMenu shows avatar/name button + dropdown (as it does now in the Topbar) — LanguageSwitcher and ThemeToggle as icon buttons next to it (recommended — compact, consistent with current UserMenu) B) Flat list: name + email displayed as text, separate logout button, LanguageSwitcher and ThemeToggle as icon buttons below C) Collapsed by default — only icons visible, expands on click to show labels
Question 3: Unit tests scope
Which components should have unit tests?
A) All new and modified components: AppLayout, Sidebar (role filtering), MobileBar, SidebarOverlay, ThemeToggle, useTheme (recommended) B) Only the role-filtering logic in Sidebar and the useTheme hook — skip layout wiring tests C) No new tests for this unit — layout is covered by existing RouteGuard tests