Files
slp-modular-cms/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/frontend-components.md
T
SluijsensandClaude Haiku 4.5 6760257e49 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>
2026-06-22 11:51:24 +02:00

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[] to NavItem — filter visible items by user.role
  • Add SidebarFooter section at the bottom with LanguageSwitcher, ThemeToggle, UserMenu
  • Accept optional onClose?: () => void prop (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