expria-frontend/src/app/BottomNav.tsx
Hermann_Kitio b68f160bce feat(design-system): reskin Charcoal — tokens dark-default + sidebar navy permanent
- Remplacement intégral index.css par palette Charcoal (DESIGN_SYSTEM.md v2.0)
- Dark = thème par défaut, .light = override via @custom-variant light
- Sidebar navy #0C1528 permanent (identique dark+light)
- Script anti-FOUC inline dans index.html
- Layout : radial-gradient sur <main>, sidebar 230px, max-w-[1100px]
- Renommage tokens Boréal→Charcoal sur ~45 composants
- Inversion dark: → baseline + light: sur primitives shadcn
- Fix logo blanc forcé dans sidebar
- ADR 006 mis à jour

Typecheck: OK · Tests: 122/122 

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:09:15 +03:00

139 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Navigation mobile fixe — affichée uniquement en dessous de 1024px.
*
* 4 items : Accueil / Simuler / Progression / Compte.
* "Simuler" ouvre une bottom sheet (EE / EO / Examen blanc).
* Tap target 44×44px minimum (DESIGN_SYSTEM.md §7).
*
* Règle L : tokens du design system exclusivement.
* Règle H : aucune logique métier — navigation uniquement.
*/
import { useState } from 'react'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { Home, BookOpen, TrendingUp, User } from 'lucide-react'
import { cn } from '@/shared/lib/utils'
const SHEET_ITEMS = [
{ label: 'Expression Écrite', to: '/simulation/ee' },
{ label: 'Expression Orale', to: '/simulation/eo' },
{ label: 'Examen blanc', to: '/examen' },
] as const
export function BottomNav() {
const [isSheetOpen, setIsSheetOpen] = useState(false)
const location = useLocation()
const navigate = useNavigate()
const isActive = (prefix: string) => location.pathname.startsWith(prefix)
function handleSheetNavigate(to: string) {
setIsSheetOpen(false)
navigate(to)
}
const navItemClasses = (active: boolean) =>
cn(
'flex min-h-[44px] flex-1 flex-col items-center justify-center gap-0.5 text-[10px] font-medium transition-colors duration-150',
active ? 'text-brand-text' : 'text-ink-tertiary hover:text-ink-primary',
)
return (
<>
{/* Bottom sheet overlay */}
{isSheetOpen && (
<div
className="fixed inset-0 z-40 lg:hidden"
aria-hidden="true"
onClick={() => setIsSheetOpen(false)}
/>
)}
{/* Bottom sheet */}
{isSheetOpen && (
<div
role="dialog"
aria-label="Choisir une simulation"
className="fixed bottom-16 left-0 right-0 z-50 rounded-t-xl border-t border-border bg-surface px-2 py-2 shadow-raised lg:hidden"
>
<p className="px-3 pb-2 pt-1 text-[11px] font-semibold uppercase tracking-widest text-ink-tertiary">
Simuler
</p>
<ul role="list">
{SHEET_ITEMS.map((item) => (
<li key={item.to}>
<button
type="button"
onClick={() => handleSheetNavigate(item.to)}
className="flex min-h-[44px] w-full items-center rounded-md px-3 text-sm text-ink-primary transition-colors duration-150 hover:bg-surface-hover"
>
{item.label}
</button>
</li>
))}
</ul>
</div>
)}
{/* Bottom nav bar */}
<nav
aria-label="Navigation mobile"
className="fixed bottom-0 left-0 right-0 z-30 flex h-16 items-center border-t border-border bg-surface lg:hidden"
>
{/* Accueil */}
<Link
to="/dashboard"
aria-label="Accueil"
className={navItemClasses(isActive('/dashboard'))}
>
<Home
className={cn('size-5', isActive('/dashboard') && 'text-brand-text')}
aria-hidden="true"
/>
Accueil
</Link>
{/* Simuler */}
<button
type="button"
aria-label="Simuler"
aria-expanded={isSheetOpen}
onClick={() => setIsSheetOpen((v) => !v)}
className={navItemClasses(isActive('/simulation') || isSheetOpen)}
>
<BookOpen
className={cn('size-5', (isActive('/simulation') || isSheetOpen) && 'text-brand-text')}
aria-hidden="true"
/>
Simuler
</button>
{/* Progression */}
<Link
to="/progression"
aria-label="Progression"
className={navItemClasses(isActive('/progression'))}
>
<TrendingUp
className={cn('size-5', isActive('/progression') && 'text-brand-text')}
aria-hidden="true"
/>
Progression
</Link>
{/* Compte */}
<Link
to="/parametres"
aria-label="Compte"
className={navItemClasses(isActive('/parametres'))}
>
<User
className={cn('size-5', isActive('/parametres') && 'text-brand-text')}
aria-hidden="true"
/>
Compte
</Link>
</nav>
</>
)
}