- 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>
155 lines
6.2 KiB
Markdown
155 lines
6.2 KiB
Markdown
# 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 + SidebarOverlay
|
|
- `Sidebar.tsx` — modified: role-filtered nav, sidebar footer with UserMenu + LanguageSwitcher + ThemeToggle
|
|
- `MobileBar.tsx` — new: slim mobile bar with hamburger button
|
|
- `SidebarOverlay.tsx` — new: slide-over wrapper for Sidebar on mobile
|
|
- `ThemeToggle.tsx` — new: dark/light toggle button
|
|
- `useTheme.ts` — new: theme state + localStorage sync
|
|
- `Topbar.tsx` — deleted
|
|
- `index.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 to `prefers-color-scheme`
|
|
- [ ] Applies `light` or `dark` class 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 to `localStorage`, 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-0` transition, `duration-200`)
|
|
- [ ] Backdrop click calls `onClose`
|
|
- [ ] `data-testid="sidebar-overlay"`, `data-testid="sidebar-backdrop"`
|
|
|
|
### Step 6: Modify `Sidebar.tsx`
|
|
- [ ] Add `roles?: Role[]` to `NavItem` interface
|
|
- [ ] 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?: () => void` prop — render close button (`<X>`) at top of sidebar on mobile
|
|
- [ ] Add `SidebarFooter` section pinned to bottom: `UserMenu` + `LanguageSwitcher` + `ThemeToggle`
|
|
- [ ] Retain `data-testid="app-sidebar"`
|
|
|
|
### Step 7: Modify `AppLayout.tsx`
|
|
- [ ] Remove `Topbar` import and render
|
|
- [ ] Add `isMenuOpen` state (`useState(false)`)
|
|
- [ ] Add `MobileBar` (triggers `setIsMenuOpen(true)`)
|
|
- [ ] Add `SidebarOverlay` (rendered when `isMenuOpen`, passes `onClose`)
|
|
- [ ] Close overlay on route change via `useEffect` watching `useLocation()`
|
|
- [ ] Desktop layout: `Sidebar` fills 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 errors
|
|
- [ ] `pnpm lint` — clean
|
|
- [ ] `pnpm 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
|
|
|
|
[Answer]: A
|
|
|
|
---
|
|
|
|
## 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
|
|
|
|
[Answer]: A
|
|
|
|
---
|
|
|
|
## 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
|
|
|
|
[Answer]: A
|