docs(unit-3): Complete functional design based on user answers
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>
This commit is contained in:
+74
@@ -0,0 +1,74 @@
|
||||
# Business Logic Model — Unit 3: Layout & Navigation
|
||||
|
||||
## Component Hierarchy
|
||||
|
||||
```
|
||||
AppLayout
|
||||
├── Sidebar desktop: always visible (md+)
|
||||
│ ├── SidebarHeader (logo + app name)
|
||||
│ ├── NavList
|
||||
│ │ └── NavItem × N filtered by user.role
|
||||
│ └── SidebarFooter
|
||||
│ ├── LanguageSwitcher moved from Topbar
|
||||
│ ├── ThemeToggle new
|
||||
│ └── UserMenu moved from Topbar
|
||||
├── MobileBar mobile only (< md)
|
||||
│ └── hamburger button → opens SidebarOverlay
|
||||
├── SidebarOverlay rendered only when mobile menu is open
|
||||
│ ├── backdrop (closes on click)
|
||||
│ └── Sidebar (with onClose prop)
|
||||
└── <main>
|
||||
└── <Outlet />
|
||||
```
|
||||
|
||||
## NavItem Filtering
|
||||
|
||||
```ts
|
||||
const visibleItems = NAV_ITEMS.filter(item =>
|
||||
!item.roles || item.roles.includes(user.role)
|
||||
);
|
||||
```
|
||||
|
||||
`item.roles === undefined` means visible to all authenticated users. Evaluated at render time.
|
||||
|
||||
## Mobile Overlay State
|
||||
|
||||
`isMenuOpen: boolean` state lives in `AppLayout`. Passed as:
|
||||
- `onMenuOpen` to `MobileBar`
|
||||
- `onClose` to `SidebarOverlay` → forwarded as `onClose` to `Sidebar`
|
||||
|
||||
Closes automatically on route change via `useEffect` watching the current pathname.
|
||||
|
||||
## Theme Initialisation (no flash)
|
||||
|
||||
A blocking inline `<script>` in `index.html` runs synchronously before React loads:
|
||||
|
||||
```html
|
||||
<script>
|
||||
(function() {
|
||||
var stored = localStorage.getItem('cms-theme');
|
||||
var theme = stored
|
||||
? stored
|
||||
: (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||
document.documentElement.classList.add(theme);
|
||||
})();
|
||||
</script>
|
||||
```
|
||||
|
||||
The `useTheme` hook reads from `localStorage` on mount and keeps toggle in sync with the DOM class.
|
||||
|
||||
## Theme Toggle Logic (`useTheme`)
|
||||
|
||||
```ts
|
||||
function toggleTheme() {
|
||||
const next = theme === 'dark' ? 'light' : 'dark';
|
||||
setTheme(next);
|
||||
localStorage.setItem('cms-theme', next);
|
||||
document.documentElement.classList.remove('light', 'dark');
|
||||
document.documentElement.classList.add(next);
|
||||
}
|
||||
```
|
||||
|
||||
## Logout Flow (unchanged)
|
||||
|
||||
`UserMenu` in sidebar footer calls `useAuth().logout()` then `navigate({ to: '/login' })`.
|
||||
Reference in New Issue
Block a user