docs(unit-3): Functional design for Layout & Navigation

- Role-filtered sidebar (Owner/Admin/User visibility rules BR-U3-01–06)
- Topbar removed; UserMenu + LanguageSwitcher move to sidebar footer
- Mobile slide-over sidebar with hamburger button
- Theme toggle (light/dark) with localStorage persistence and no-flash init
- New components: MobileBar, SidebarOverlay, ThemeToggle, useTheme
- Unit 2 marked complete in aidlc-state

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 11:44:24 +02:00
co-authored by Claude Haiku 4.5
parent bbb8d5f423
commit 31cc45d79b
6 changed files with 349 additions and 2 deletions
@@ -0,0 +1,56 @@
# Business Logic Model — Unit 3: Layout & Navigation
## Component Hierarchy
```
AppLayout
├── Sidebar (desktop: always visible)
│ ├── Logo / AppName
│ ├── NavList (role-filtered NavItems)
│ │ └── NavItem (Link with activeProps)
│ └── SidebarFooter
│ ├── LanguageSwitcher
│ ├── ThemeToggle
│ └── UserMenu (name, email, logout)
├── MobileBar (mobile only — hamburger + app name)
│ └── opens → SidebarOverlay (Sidebar rendered in overlay)
└── <main>
└── <Outlet />
```
## Theme Initialisation (no flash)
Theme is applied in a blocking inline script in `index.html` — before React hydrates — to prevent a flash of the wrong theme:
```html
<script>
(function() {
var stored = localStorage.getItem('cms-theme');
var theme = stored || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.classList.add(theme);
})();
</script>
```
React's `useTheme` hook reads from `localStorage` on mount and keeps the toggle in sync.
## NavItem Filtering Logic
```ts
const visibleItems = NAV_ITEMS.filter(item =>
!item.roles || item.roles.includes(user.role)
);
```
`item.roles` being `undefined` means "visible to everyone". This is evaluated at render time whenever `user.role` changes.
## Mobile Sidebar State
Local `useState<boolean>` in `AppLayout` (or `MobileBar`). The sidebar overlay uses a `<dialog>` or a Tailwind-animated `translate-x` panel. Closes on:
- Backdrop click (`onBackdropClick`)
- Close button click
- TanStack Router navigation (via `useEffect` watching `location.pathname`)
## Logout Flow (unchanged from Unit 1/2)
`UserMenu` in sidebar footer calls `useAuth().logout()` then `navigate({ to: '/login' })`.