feat(sprint-0.5-bis): AppLayout + primitives UI + refonte pages
- AppLayout (sidebar fixe, drawer mobile, BottomNav) - MobileHeader sticky + Sidebar avec verrouillage hasAccess() - Primitives src/shared/ui/ : Button, Card, Badge - SimulationPage + DashboardPage : suppression headers internes - TaskSelector : Card interactive + Badge EE/EO + eyebrow - router.tsx : layout routes + ComingSoon inline
This commit is contained in:
parent
997f39bd33
commit
8450265449
11 changed files with 752 additions and 161 deletions
148
src/app/BottomNav.tsx
Normal file
148
src/app/BottomNav.tsx
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* 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 Direction H 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)
|
||||
}
|
||||
|
||||
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-line bg-surface px-2 py-2 shadow-md lg:hidden"
|
||||
>
|
||||
<p className="px-3 pb-2 pt-1 text-[11px] font-semibold uppercase tracking-widest text-ink-5">
|
||||
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-2 transition-colors duration-150 hover:bg-canvas hover:text-ink-1"
|
||||
>
|
||||
{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-line bg-surface lg:hidden"
|
||||
>
|
||||
{/* Accueil */}
|
||||
<Link
|
||||
to="/dashboard"
|
||||
aria-label="Accueil"
|
||||
className={cn(
|
||||
'flex min-h-[44px] flex-1 flex-col items-center justify-center gap-0.5 text-[10px] font-medium transition-colors duration-150',
|
||||
isActive('/dashboard') ? 'text-expria' : 'text-ink-4 hover:text-ink-2',
|
||||
)}
|
||||
>
|
||||
<Home
|
||||
className={cn('size-5', isActive('/dashboard') && 'text-expria')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Accueil
|
||||
</Link>
|
||||
|
||||
{/* Simuler */}
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Simuler"
|
||||
aria-expanded={isSheetOpen}
|
||||
onClick={() => setIsSheetOpen((v) => !v)}
|
||||
className={cn(
|
||||
'flex min-h-[44px] flex-1 flex-col items-center justify-center gap-0.5 text-[10px] font-medium transition-colors duration-150',
|
||||
isActive('/simulation') || isSheetOpen ? 'text-expria' : 'text-ink-4 hover:text-ink-2',
|
||||
)}
|
||||
>
|
||||
<BookOpen
|
||||
className={cn(
|
||||
'size-5',
|
||||
(isActive('/simulation') || isSheetOpen) && 'text-expria',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Simuler
|
||||
</button>
|
||||
|
||||
{/* Progression */}
|
||||
<Link
|
||||
to="/progression"
|
||||
aria-label="Progression"
|
||||
className={cn(
|
||||
'flex min-h-[44px] flex-1 flex-col items-center justify-center gap-0.5 text-[10px] font-medium transition-colors duration-150',
|
||||
isActive('/progression') ? 'text-expria' : 'text-ink-4 hover:text-ink-2',
|
||||
)}
|
||||
>
|
||||
<TrendingUp
|
||||
className={cn('size-5', isActive('/progression') && 'text-expria')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Progression
|
||||
</Link>
|
||||
|
||||
{/* Compte */}
|
||||
<Link
|
||||
to="/parametres"
|
||||
aria-label="Compte"
|
||||
className={cn(
|
||||
'flex min-h-[44px] flex-1 flex-col items-center justify-center gap-0.5 text-[10px] font-medium transition-colors duration-150',
|
||||
isActive('/parametres') ? 'text-expria' : 'text-ink-4 hover:text-ink-2',
|
||||
)}
|
||||
>
|
||||
<User
|
||||
className={cn('size-5', isActive('/parametres') && 'text-expria')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Compte
|
||||
</Link>
|
||||
</nav>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue