- 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>
139 lines
4.2 KiB
TypeScript
139 lines
4.2 KiB
TypeScript
/**
|
||
* 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>
|
||
</>
|
||
)
|
||
}
|