xpeditis2.0/docs/design-system-audit.md
2026-08-13 12:26:46 +02:00

35 KiB
Raw Blame History

Design System Audit — Xpeditis

Statut : CHARTE VERROUILLÉE (IMMUTABLE). Ce document est la source de vérité de l'identité visuelle Xpeditis. Toute refonte UI doit produire un rendu dont chaque couleur, police, rayon et token est présent dans ce document. Aucune valeur de ce document ne peut être modifiée dans le cadre d'une refonte UI.

Date de l'audit : 2026-08-12 Périmètre : apps/frontend (126 fichiers .tsx, ~38 000 lignes, 70 routes) Branche : ui_update


0. Source de vérité actuelle

Il existe quatre sources concurrentes, ce qui est le problème structurel n°1 du projet :

# Source Fichier Autorité réelle Statut
1 Tailwind theme apps/frontend/tailwind.config.ts ✅ Source de vérité effective À conserver tel quel
2 Variables CSS shadcn apps/frontend/app/globals.css (:root, .dark) ⚠️ Partiellement contredite par (1) Conflit documenté §1.3
3 Classes @layer components apps/frontend/app/globals.css ⚠️ Quasi inutilisées dans l'app Legacy
4 Doc marketing apps/frontend/DESIGN_SYSTEM.md ❌ Descriptive, non appliquée Documentaire

Décision d'audit : la source de vérité retenue est tailwind.config.ts, complétée par les variables CSS de globals.css pour les tokens --border, --input, --muted, --radius, --destructive.

⚠️ Constat majeur : l'application n'applique pas sa propre charte. On compte 816 occurrences de blue-* Tailwind (bleu générique, hors charte) contre ~700 occurrences de tokens de marque, et 2 485 occurrences de gray-* au lieu de l'échelle neutral navy définie dans le thème. Voir §23.


1. Color palette

1.1 Couleurs de marque — IMMUTABLES

Définies dans tailwind.config.ts → theme.extend.colors.

Token Tailwind Valeur HEX Usage documenté Occurrences
brand-navy #10183A Couleur primaire, titres, surfaces sombres, sidebar admin 123 text-, 46 bg-
brand-turquoise #34CCCD Accent, CTA, focus, liens 137 bg-, 134 text-
brand-green #067224 Succès, validation 19 bg-, 11 text-
brand-gray #F2F2F2 Fond de section clair 8 bg-

1.2 Alias sémantiques de marque — IMMUTABLES

Token Valeur Note
primary.DEFAULT #10183A Override explicite du bleu shadcn
primary.foreground #FFFFFF
primary.navy #10183A Alias redondant
accent.DEFAULT #34CCCD Override explicite du gris shadcn
accent.foreground #FFFFFF
accent.turquoise #34CCCD Alias redondant
success.DEFAULT #067224
success.light #08A131
success.dark #044F19

1.3 ⚠️ Conflit tokens Tailwind ↔ variables CSS

globals.css définit encore la palette shadcn par défaut, contredite par tailwind.config.ts :

Token Valeur CSS :root (globals.css) Valeur Tailwind (config) Qui gagne
--primary 221.2 83.2% 53.3% (bleu #3B82F6) #10183A Tailwind (bg-primary = navy)
--accent 210 40% 96.1% (gris quasi-blanc) #34CCCD Tailwind (bg-accent = turquoise)
--ring 221.2 83.2% 53.3% (bleu) (non overridé) CSS → ❗ anneaux de focus bleus, hors charte

Conséquence : focus-visible:ring-ring de Button et Input produit un halo bleu #3B82F6 sur toute l'application. Statut : défaut d'implémentation, pas un choix de charte. --ring doit pointer vers le turquoise de marque — c'est une restauration de la charte, pas une modification.

1.4 Échelle neutre (dérivée du navy) — IMMUTABLE

tailwind.config.ts → colors.neutral :

Token HEX
neutral-50 #F8F9FC
neutral-100 #EDEEF5
neutral-200 #DADBEB
neutral-300 #B0B6DA
neutral-400 #8590C9
neutral-500 #5A6BB8
neutral-600 #3A4A97
neutral-700 #2C3978
neutral-800 #1E2859
neutral-900 #10183A

Échelle froide, bleutée, dérivée du navy. Actuellement quasi inutilisée (92 occurrences) au profit de gray-* Tailwind (2 485 occurrences), qui est une échelle neutre-froide différente (#F9FAFB → #111827).

1.5 Variables CSS shadcn conservées (mode clair)

globals.css :root :

Variable HSL Équivalent
--background 0 0% 100% #FFFFFF
--foreground 222.2 84% 4.9% #020817
--card / --popover 0 0% 100% #FFFFFF
--card-foreground / --popover-foreground 222.2 84% 4.9% #020817
--secondary 210 40% 96.1% #F1F5F9
--secondary-foreground 222.2 47.4% 11.2% #0F172A
--muted 210 40% 96.1% #F1F5F9
--muted-foreground 215.4 16.3% 46.9% #64748B
--destructive 0 84.2% 60.2% #EF4444
--destructive-foreground 210 40% 98% #F8FAFC
--border 214.3 31.8% 91.4% #E2E8F0
--input 214.3 31.8% 91.4% #E2E8F0

1.6 Couleurs hardcodées relevées

Alignées charte (à conserver, idéalement tokenisées) :

HEX Occurrences Emplacements
#34CCCD 50 globals.css (prose, tiptap), maps, charts, SVG
#10183A 23 globals.css (prose), charts, SVG
#067224 3 badges, charts
#F2F2F2 1 section landing

Hors charte (dérives) :

HEX Occ. Emplacement Nature
#1A2550, #1A2A5E 7 dégradés navy Nuances navy inventées
#0E9999, #2BB8B9, #2AB8B9 4 hovers turquoise Nuances turquoise inventées
#3B82F6, #2563EB, #1E3A8A 4 charts Recharts, Bar fill Bleu générique
#10B981, #EF4444, #F59E0B 3 dashboard/page.tsx — Pie chart status Palette Tailwind brute
#8884D8 1 dashboard/page.tsx — Pie fill par défaut Violet par défaut Recharts (oubli)
#0D1117, #161B22 2 CodeBlock.tsx Thème GitHub Dark
#FFD700, #FF6600, #FF00FF, #E30613, #006633, #003A70, #002B5C, #00243D, #E65100 9 couleurs de compagnies maritimes / partenaires Légitime (identités tierces)
#F3F4F6, #E5E7EB, #ADB5BD, #F0F0F0 4 prose, tiptap, grilles charts Gris utilitaires

1.7 Familles Tailwind utilisées hors charte

Famille Occurrences Verdict
gray-* 2 485 Neutre de fait de l'app — doublon de neutral-*
blue-* 816 ❗ Accent parallèle hors charte (voir §23)
red-* 368 Destructif — acceptable, à tokeniser
green-* 246 Succès — doublon de brand-green/success
amber-* / yellow-* 189 Avertissement — acceptable, à tokeniser
orange-* 66 Doublon d'amber
purple-* / violet-* 43 ❗ Sans justification (KPI volume, badge Platinium)
emerald-* 15 Doublon de green
slate-* / neutral-* / zinc-* 104 Doublon de gray
indigo-*, cyan-*, sky-*, teal-*, rose-* 24 Bruit

2. Typography

Chargement : next/font/google dans apps/frontend/src/lib/fonts.ts, display: 'swap', preload: true, subset latin. Variables CSS injectées sur <html> dans app/[locale]/layout.tsx.

Rôle Police Variable CSS Alias Tailwind Poids chargés
Titres Manrope --font-manrope font-manrope, font-heading 200–800
Corps / UI Montserrat --font-montserrat font-montserrat, font-body, font-sans (défaut) 100–900
  • body porte font-body (Montserrat) — app/[locale]/layout.tsx + globals.css.
  • h1–h6 portent font-heading (Manrope) via @layer base dans globals.css.
  • .prose h1..h4 forcent Manrope (globals.css).

IMMUTABLE : aucune autre famille ne doit être introduite. Aucune police système, aucune substitution.


3. Font weights

Contexte Poids Défini dans
display-lg 800 tailwind.config.ts
display-md, display-sm, h1 700 tailwind.config.ts
h2, h3, h4 600 tailwind.config.ts
h5, h6 500 tailwind.config.ts
body-* 400 tailwind.config.ts
label-* 600 tailwind.config.ts
Boutons (Button) 500 (font-medium) src/components/ui/button.tsx
Boutons (.btn-* CSS) 600 (font-semibold) globals.css — ⚠️ incohérent avec ci-dessus
Badge 600 (font-semibold) src/components/ui/badge.tsx
CardTitle 600 src/components/ui/card.tsx
TableHead 500 src/components/ui/table.tsx

4. Font sizes

Échelle complète définie dans tailwind.config.ts → theme.extend.fontSize. IMMUTABLE.

Display

Token Taille Line-height Letter-spacing Weight
text-display-lg 4.5rem / 72px 1.1 −0.02em 800
text-display-md 3.75rem / 60px 1.15 −0.02em 700
text-display-sm 3rem / 48px 1.2 −0.01em 700

Headings

Token Taille Line-height Weight
text-h1 2.5rem / 40px 1.25 700
text-h2 2rem / 32px 1.3 600
text-h3 1.5rem / 24px 1.35 600
text-h4 1.25rem / 20px 1.4 600
text-h5 1.125rem / 18px 1.45 500
text-h6 1rem / 16px 1.5 500

Body

Token Taille Line-height Weight
text-body-lg 1.125rem / 18px 1.6 400
text-body 1rem / 16px 1.6 400
text-body-sm 0.875rem / 14px 1.55 400
text-body-xs 0.75rem / 12px 1.5 400

Labels

Token Taille Line-height Weight Letter-spacing
text-label-lg 0.875rem / 14px 1.4 600 0.05em
text-label 0.75rem / 12px 1.4 600 0.05em
text-label-sm 0.6875rem / 11px 1.4 600 0.05em

⚠️ Constat : cette échelle est quasi inutilisée dans l'app produit. Les pages utilisent l'échelle Tailwind par défaut (text-xs, text-sm, text-base, text-lg, text-2xl, text-3xl). Les deux échelles divergent (ex. text-h1 = 40px vs text-3xl = 30px).

Échelle Tailwind par défaut réellement utilisée

text-[10px], text-xs (12), text-sm (14), text-base (16), text-lg (18), text-xl (20), text-2xl (24), text-3xl (30), text-4xl (36), text-5xl (48), text-6xl (60).


5. Line heights

Source Valeurs
Tokens fontSize (config) 1.1 / 1.15 / 1.2 / 1.25 / 1.3 / 1.35 / 1.4 / 1.45 / 1.5 / 1.55 / 1.6
Utilitaires employés dans l'app leading-none, leading-tight, leading-relaxed, leading-tight sur [10px]

Aucune échelle lineHeight custom n'est déclarée dans tailwind.config.ts — seules les valeurs couplées aux fontSize existent.


6. Spacing system

Échelle Tailwind par défaut (base 4px) — aucune extension spacing dans la config.

Valeurs réellement employées, par fréquence :

Rang Classe Occ.
1 py-2 315
2 px-6 308
3 px-4 295
4 mb-4 213
5 py-3 202
6 mt-1 / mb-2 174 / 168
7 px-3 168
8 p-4 149
9 mb-6 147
10 py-4 125
11 gap-4 / gap-2 / gap-3 118 / 115 / 68
12 p-6 / p-3 / p-8 90 / 77 / 59
13 space-y-2 / space-y-6 / space-y-4 71 / 68 / 52

Container Tailwind (theme.container) : center: true, padding: 2rem, screens.2xl: 1400px.

Rythme de padding constaté : p-4 (mobile/compact) et p-6 (desktop/card) dominants — c'est le rythme de fait.


7. Border radius

Token racine : --radius: 0.5rem (8px) — globals.css. IMMUTABLE.

Token Tailwind Calcul Valeur
rounded-sm calc(var(--radius) - 4px) 4px
rounded-md calc(var(--radius) - 2px) 6px
rounded-lg var(--radius) 8px

Les autres rayons proviennent de l'échelle Tailwind par défaut (rounded 4px, rounded-xl 12px, rounded-2xl 16px, rounded-3xl 24px, rounded-full 9999px).

Usage réel :

Classe Occ. Rayon
rounded-lg 452 8px — rayon standard de l'app
rounded-full 241 pilules, avatars, badges
rounded-md 188 6px — contrôles (Button, Input)
rounded-xl 95 12px
rounded 83 4px
rounded-2xl 62 16px
rounded-3xl 10 24px

Rayons canoniques retenus (IMMUTABLES) : rounded-md (contrôles) · rounded-lg (cartes, conteneurs) · rounded-full (pilules/avatars). rounded-xl, 2xl, 3xl sont des dérives ponctuelles (landing, modales) — non canoniques.


8. Shadows

Aucune extension boxShadow dans tailwind.config.ts → échelle Tailwind par défaut.

Classe Occ. Valeur CSS
shadow-sm 123 0 1px 2px 0 rgb(0 0 0 / 0.05)
shadow-lg 76 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px …
shadow 39 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px …
shadow-xl 36 0 20px 25px -5px rgb(0 0 0 / 0.1), …
shadow-md 28 0 4px 6px -1px rgb(0 0 0 / 0.1), …
shadow-2xl 23 0 25px 50px -12px rgb(0 0 0 / 0.25)
shadow-brand 2 ❗ classe inexistante — aucun token shadow-brand déclaré. Rend un no-op.

Élévation canonique constatée : shadow-sm pour les cartes (+ hover:shadow-md), shadow-lg pour sidebar/dropdowns, shadow-xl/2xl pour modales.


9. Borders

Élément Valeur Source
Couleur par défaut globale hsl(var(--border)) = #E2E8F0 globals.css — règle * { @apply border-border }
Bordure des inputs hsl(var(--input)) = #E2E8F0 globals.css + ui/input.tsx
Épaisseur standard 1px (border) —
Bouton outline (CSS legacy) 2px (border-2 border-primary) globals.css .btn-outline
Input legacy CSS border-neutral-300 = #B0B6DA globals.css .input
Bordures de cartes en pages border-gray-200 = #E5E7EB ⚠️ hardcodé, doublon de --border
Séparateurs internes border-gray-100 = #F3F4F6 / divide-gray-100 ⚠️ hardcodé
Sidebar admin (sur navy) border-white/10 admin/layout.tsx

10. Icons

Aspect Valeur
Bibliothèque lucide-react v0.294.0 — unique et exclusive
Tailles employées w-3 h-3 (12), w-4 h-4 (16, dominante), w-5 h-5 (20, nav), w-6 h-6 (24), h-3 w-3
Épaisseur strokeWidth par défaut (2), sauf SVG inline hamburger/close (strokeWidth={2} explicite)
Couleur Hérite de currentColor
Exceptions SVG inline manuscrits pour le menu burger et la fermeture de sidebar (dashboard/layout.tsx, admin/layout.tsx) — doublons de Menu/X de lucide

Icônes de marque / logos :

Asset Chemin Usage
Logo noir public/assets/logos/logo-black.svg Sidebar dashboard, favicon, OG image
Logo blanc (SVG) public/assets/logos/logo-white.svg Sidebar admin, footer navy
Logo blanc (PNG) public/assets/logos/logo-white.png Fallback
Logos partenaires public/assets/logos/partner/*.png (7 fichiers, noms avec espaces) Landing — bandeau partenaires
Images background-login.png, background-section-1-landingpage.png Auth, landing
Vidéos demo-xpeditis.mp4 / .webm Modale démo

IMMUTABLE : logos, fichiers de marque, et le choix de lucide-react.


11. Buttons

Trois systèmes coexistent — c'est une incohérence, pas une charte :

11.1 src/components/ui/button.tsx (composant React)

Adoption : 17 usages dans 8 fichiers sur 126.

Base : inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none

Variant Classes
default bg-primary text-primary-foreground hover:bg-primary/90 → navy
destructive bg-destructive text-destructive-foreground hover:bg-destructive/90 → rouge
outline border border-input bg-background hover:bg-accent hover:text-accent-foreground → hover turquoise plein
secondary bg-secondary text-secondary-foreground hover:bg-secondary/80
ghost hover:bg-accent hover:text-accent-foreground → hover turquoise plein
link text-primary underline-offset-4 hover:underline
Size Classes Hauteur
default h-10 px-4 py-2 40px
sm h-9 rounded-md px-3 36px
lg h-11 rounded-md px-8 44px
icon h-10 w-10 40×40

⚠️ hover:bg-accent sur ghost/outline produit un aplat turquoise #34CCCD au survol (car accent est overridé) — visuellement agressif et non intentionnel. ⚠️ Pas de cn() / tailwind-merge : la concaténation de string empêche l'override propre des classes. ⚠️ Pas de asChild : <Link><Button> génère un <a> contenant un <button> (nesting invalide, utilisé sur le dashboard).

11.2 Classes @layer components (globals.css)

Adoption : réelle mais minoritaire — .btn-primary dans 8 fichiers, .btn-secondary 3, .btn-success 2, .btn-outline 2. Les classes .label et .link sont largement employées dans /register et /admin/login. ⚠️ Ces classes ne peuvent donc pas être supprimées avant refonte des pages concernées (lots L8/L9).

Classe Style
.btn-primary bg-accent text-white font-heading font-semibold px-6 py-3 rounded-lg hover:bg-accent/90 shadow-sm hover:shadow
.btn-secondary bg-primary text-white font-heading font-semibold px-6 py-3 rounded-lg hover:bg-neutral-800
.btn-success bg-success text-white font-heading font-semibold px-6 py-3 rounded-lg hover:bg-success-dark
.btn-outline border-2 border-primary text-primary font-heading font-semibold px-6 py-3 rounded-lg hover:bg-primary hover:text-white

Noter l'inversion sémantique : .btn-primary est turquoise alors que Button variant="default" est navy.

11.3 <button> bruts

299 occurrences de <button contre 17 de <Button. La très grande majorité des boutons de l'application sont stylés à la main, majoritairement en bg-blue-600 hover:bg-blue-700.


12. Inputs

12.1 src/components/ui/input.tsx

Adoption : 16 usages dans 5 fichiers.

flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50

  • Hauteur : 40px · Rayon : 6px · Texte : 14px · Focus : anneau 2px bleu (--ring) ❗

12.2 .input (globals.css)

border border-neutral-300 rounded-lg px-4 py-2.5 font-body text-body focus:ring-2 focus:ring-accent focus:border-transparent → rayon 8px, focus turquoise, texte 16px. Incohérent avec 12.1.

12.3 .label (globals.css)

text-label text-neutral-600 font-body font-semibold mb-1.5 block uppercase tracking-wider

12.4 <input> bruts

175 occurrences contre 16 <Input>. Pattern dominant en page : w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ❗ (focus bleu hors charte)

12.5 Select / Popover / Command — ❗ NON FONCTIONNELS

package.json déclare @radix-ui/react-dialog, react-dropdown-menu, react-label, react-select, react-slot, react-tabs. Aucun import @radix-ui/* n'existe dans le code source. Les fichiers select.tsx, popover.tsx, command.tsx, dialog.tsx sont des ré-implémentations manuelles incomplètes :

  • SelectValue retourne <span>{placeholder}</span> — n'affiche jamais la valeur sélectionnée.
  • Popover n'est pas portalisé (position: relative local) → clipping par overflow: hidden.
  • Aucun focus trap, aucune gestion clavier, aucun aria-* complet.

13. Cards

src/components/ui/card.tsx

Adoption : 19 fichiers — le primitif le mieux adopté.

Sous-composant Classes
Card rounded-lg border bg-card text-card-foreground shadow-sm
CardHeader flex flex-col space-y-1.5 p-6
CardTitle text-2xl font-semibold leading-none tracking-tight (rendu en <h3>)
CardDescription text-sm text-muted-foreground
CardContent p-6 pt-0
CardFooter flex items-center p-6 pt-0

⚠️ CardTitle à text-2xl (24px) est trop gros : toutes les pages l'écrasent en text-base font-semibold text-gray-900. Le défaut est donc systématiquement contourné. ⚠️ CardHeader p-6 également écrasé en p-4 / pb-3 / pb-4 partout.

.card (globals.css)

bg-white rounded-lg shadow-md p-6 + .card-header font-heading font-semibold text-h4 text-brand-navy mb-4 — non utilisé.

Pattern de carte réel en pages

border border-gray-200 shadow-sm hover:shadow-md transition-shadow bg-white + CardContent className="p-4".


14. Tables

src/components/ui/table.tsx

Adoption : 2 fichiers seulement. 26 <table> bruts ailleurs.

Élément Classes
Wrapper w-full overflow-auto
table w-full caption-bottom text-sm
thead [&_tr]:border-b
tbody [&_tr:last-child]:border-0
tr border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted
th h-12 px-4 text-left align-middle font-medium text-muted-foreground
td p-4 align-middle
caption mt-4 text-sm text-muted-foreground
  • Hauteur de ligne d'en-tête : 48px · Padding cellule : 16px · Texte : 14px
  • @tanstack/react-table v8 et @tanstack/react-virtual v3 sont installés — utilisés uniquement dans rate-search/RateResultsTable.tsx.
  • 21 fichiers gèrent le débordement horizontal via overflow-x-auto à la main.
  • Aucun composant de table responsive : pas de bascule carte-sur-mobile mutualisée.

15. Navigation

15.1 Chrome public (landing)

src/components/layout/LandingHeader.tsx (388 l.) + LandingFooter.tsx. Utilise les tokens de marque (navy/turquoise).

15.2 Chrome application (app/[locale]/dashboard/layout.tsx)

  • Topbar : sticky top-0 z-10, hauteur h-14 mobile / h-16 desktop, bg-white border-b. Contenu : burger (mobile) · titre dérivé de l'item de nav actif · LanguageSwitcher · NotificationDropdown · avatar bg-blue-600 ❗
  • Titre de topbar : text-base lg:text-xl font-semibold text-gray-900 — doublonne le <h1> de chaque page (double titre à l'écran).

15.3 Bottom nav mobile

fixed bottom-0 z-30, grid-cols-5 h-16, items : Home / Bookings / Documents / Tracking / Profile. Actif : text-blue-600 ❗ · Labels text-[10px].

⚠️ Ne couvre que 5 des 8 entrées de la sidebar ; Organisation, Wiki, Clés API sont inaccessibles en navigation mobile primaire (uniquement via le burger).

15.4 Breadcrumbs

Aucun composant fil d'Ariane n'existe dans le projet.


16. Sidebar

Dashboard (dashboard/layout.tsx)

Propriété Valeur
Largeur w-64 (256px), fixe, non repliable
Fond bg-white shadow-lg
Comportement fixed inset-y-0 left-0 z-50, lg:translate-x-0, drawer -translate-x-full sous lg
Transition transform transition-transform duration-300 ease-in-out
Overlay mobile fixed inset-0 z-40 bg-gray-600 bg-opacity-75 lg:hidden
En-tête h-16 px-6 border-b, logo logo-black.svg 50×60
Item nav flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-colors
Item actif bg-blue-50 text-blue-700 ❗
Item inactif text-gray-700 hover:bg-gray-100
Item verrouillé text-gray-400 hover:bg-gray-50 + icône Lock w-4 h-4 text-gray-300, redirige vers /pricing
Icône nav mr-3 h-5 w-5
Espacement px-4 py-6 space-y-2
Lien Admin bg-brand-navy text-white hover:bg-brand-navy/90 ✅ (seul élément de chrome à la charte)
Pied avatar w-10 h-10 bg-blue-600 rounded-full ❗ + nom + email + StatusBadge + bouton logout text-red-700 bg-red-50
Décalage contenu lg:pl-64

Admin (admin/layout.tsx)

Même géométrie, mais fond bg-brand-navy text-white, logo logo-white.svg 44×52, séparateurs border-white/10. ✅ Conforme charte.

Navigation dashboard (8 entrées) : Dashboard · Réservations · Documents · Tracking · Wiki · Organisation · Clés API · Utilisateurs (ADMIN/MANAGER). Chaque entrée peut porter un requiredFeature (gating par plan via useSubscription).

Navigation admin (7 entrées) : Utilisateurs · Organisations · Réservations · Documents · Grilles CSV · Blog · Logs.


17. Modals

src/components/ui/dialog.tsx — implémentation manuelle, adoption : 1 fichier.

Élément Classes
Overlay fixed inset-0 z-50 bg-background/80 backdrop-blur-sm
Panneau fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg sm:rounded-lg
Header flex flex-col space-y-1.5 text-center sm:text-left
Title text-lg font-semibold leading-none tracking-tight (<h3>)
Description text-sm text-muted-foreground
Footer flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2

Défauts : pas de portal · pas de focus trap · pas de fermeture par Escape · pas de scroll-lock du body · pas de role="dialog" / aria-modal · pas d'animation d'entrée/sortie · React.cloneElement sur tous les enfants (casse dès qu'un enfant est un texte ou un fragment).

Conséquence : la quasi-totalité des modales de l'app sont réimplémentées inline dans les pages (documents/page.tsx en contient 3 : ajout, remplacement, suppression).


18. Toasts

❌ Aucun système de toast / notification transitoire n'existe dans le projet.

Le retour utilisateur est assuré par :

  • 44 appels à alert() natif
  • 14 appels à confirm() natif
  • des bandeaux d'erreur inline ad hoc (bg-red-50 border-red-200 text-red-700)

Aucune dépendance sonner / react-hot-toast / @radix-ui/react-toast n'est installée.

Note pour la refonte : introduire un système de toast est un ajout de composant, pas une modification de charte — les couleurs employées devront provenir strictement de §1.


19. States

État Traitement actuel Source
Hover (bouton) hover:bg-{color}/90 ou /80 button.tsx
Hover (nav) hover:bg-gray-100 layouts
Hover (ligne de table) hover:bg-muted/50 ou hover:bg-gray-50 table.tsx / pages
Hover (carte) hover:shadow-md transition-shadow pages
Focus focus:ring-2 focus:ring-blue-500 (454 occ. focus:) ❗ pages
Focus-visible focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 (13 occ. seulement) primitives
Active ❌ Aucun style active: systématique —
Disabled disabled:opacity-50 disabled:pointer-events-none (Button) / disabled:cursor-not-allowed disabled:opacity-50 (Input) primitives
Loading animate-spin sur bordure ou Loader2 — 40 fichiers, aucune mutualisation pages
Skeleton 1 seul fichier ; ailleurs bg-gray-100 animate-pulse rounded inline dashboard/page.tsx
Empty 27 fichiers avec length === 0, chaque empty state réécrit à la main pages
Error 29 fichiers, bandeaux inline ad hoc pages
Selected data-[state=selected]:bg-muted (table uniquement) table.tsx
Locked (feature gating) text-gray-400 + icône Lock + redirection /pricing dashboard/layout.tsx

Transitions employées : transition-colors (dominante), transition-shadow, transition-transform, transition-all. Durée par défaut Tailwind (150ms), sauf sidebar duration-300 ease-in-out.


20. Responsive behavior

Breakpoints = valeurs Tailwind par défaut, non modifiées :

Nom Min-width Occurrences dans le code
sm 640px 287
md 768px 120
lg 1024px 259
xl 1280px 14
2xl 1536px 0 ❗

container.screens.2xl = 1400px (config).

Conteneurs de page : max-w-7xl (1280px) — 63 occurrences, dominante. Puis max-w-2xl (37), max-w-md (36, formulaires auth), max-w-4xl (27), max-w-3xl (25).

Bascule principale de l'app shell : lg (1024px) — sidebar fixe ≥ lg, drawer + bottom-nav < lg.

Constats :

  • Aucune adaptation au-delà de 1280px. Sur un écran 1920/2560px, le contenu reste en colonne de 1280px centrée avec de larges marges vides.
  • Le palier md (tablette 768–1023px) est sous-traité : la sidebar est encore un drawer, mais le layout de contenu est déjà en grille desktop → zone la plus fragile.
  • 21 tables reposent sur overflow-x-auto : sur mobile, la lecture se fait par défilement horizontal, sans alternative en cartes.

21. Dark / light mode

Aspect État
Stratégie Tailwind darkMode: ['class'] — configurée
Variables .dark Définies intégralement dans globals.css (14 tokens)
Classes dark: dans le code 5 occurrences au total (dont 1 dans ui/alert.tsx)
Toggle de thème ❌ Inexistant
Attribut class="dark" posé ❌ Jamais — <html> ne reçoit que les variables de police
prefers-color-scheme Non géré

Verdict : l'application est en mode clair exclusif. Le dark mode est un vestige de scaffolding shadcn, non fonctionnel.

Palette .dark définie (dormante) : --background: 222.2 84% 4.9% · --foreground: 210 40% 98% · --card: 222.2 84% 4.9% · --primary: 217.2 91.2% 59.8% · --secondary/--muted/--accent: 217.2 32.6% 17.5% · --destructive: 0 62.8% 30.6% · --border/--input: 217.2 32.6% 17.5% · --ring: 224.3 76.3% 48%.


22. Animation principles

Déclarées dans tailwind.config.ts

Animation Keyframes Durée / easing
accordion-down height: 0 → var(--radix-accordion-content-height) 0.2s ease-out
accordion-up inverse 0.2s ease-out

Plugin tailwindcss-animate installé → animate-in, animate-out, fade-in, zoom-in, slide-in-from-*, duration-*, ease-* disponibles. Quasi inutilisé.

Bibliothèque

framer-motion v12 — présent dans 16 fichiers (principalement landing / marketing).

Animations réellement en production

Effet Usage
animate-spin 40 fichiers — loaders
animate-pulse skeletons inline
transition-colors dominante, 150ms
transition-transform duration-300 ease-in-out ouverture/fermeture sidebar
transition-shadow hover des cartes

Principes constatés (à formaliser) : durée courte (150–300ms), easing ease-out/ease-in-out, animation limitée à color, background-color, box-shadow, transform, opacity. Aucune animation de layout coûteuse. Aucun respect de prefers-reduced-motion nulle part.


23. ❗ Constat central : deux identités visuelles parallèles

C'est le résultat le plus important de cet audit.

Zone Identité appliquée
Landing, pages marketing, footer, sidebar admin ✅ Charte Xpeditis — navy #10183A + turquoise #34CCCD
Toute l'application produit (dashboard, réservations, documents, recherche, paramètres, wiki) ❌ Bleu Tailwind générique blue-600 #2563EB / blue-50 / blue-700

Mesure : blue-* apparaît 816 fois dans 64 des 126 fichiers .tsx, contre ~700 occurrences cumulées de l'ensemble des tokens de marque. Les foyers principaux :

Fichier Occ. blue-*
dashboard/bookings/new/page.tsx 31
admin/blog/page.tsx 30
dashboard/search-advanced/page.tsx 28
dashboard/track-trace/page.tsx 27
dashboard/documents/page.tsx 27
dashboard/booking/[id]/pay/page.tsx 20
(+ 58 autres fichiers)

Éléments de chrome concernés : item de sidebar actif, avatars utilisateur (×2), item de bottom-nav actif, CTA principal du dashboard, anneaux de focus de tous les formulaires, barre de progression du tunnel de réservation, Bar fill des graphiques.

Position d'audit : ce bleu n'appartient pas à la charte. Il n'est déclaré dans aucune des quatre sources du §0. Le remplacer par brand-navy / brand-turquoise selon le rôle constitue une restauration de la charte, pas une modification — mais comme il s'agit du changement visuel le plus visible de la refonte, il est soumis à validation explicite avant toute exécution (voir §24).


24. Règles de verrouillage pour la refonte

❌ Interdit — modification de charte

  1. Modifier brand-navy, brand-turquoise, brand-green, brand-gray.
  2. Modifier primary.*, accent.*, success.*.
  3. Modifier l'échelle neutral-50…900.
  4. Changer Manrope / Montserrat, ou leurs affectations (titres / corps).
  5. Modifier l'échelle fontSize de tailwind.config.ts.
  6. Modifier --radius: 0.5rem ou les dérivés sm/md/lg.
  7. Remplacer lucide-react.
  8. Modifier ou substituer les logos et assets de marque.
  9. Introduire une couleur d'accent nouvelle, un dégradé décoratif, une teinte inventée.

✅ Autorisé — travail de design

  1. Remplacer une couleur hors charte par le token de charte correspondant (blue-600 → brand-turquoise/brand-navy, gray-* → neutral-*).
  2. Corriger --ring pour pointer vers le turquoise de marque (restauration).
  3. Ajouter des tokens purement structurels absents (échelle d'élévation nommée, durées d'animation, largeurs de sidebar) — sans introduire de couleur nouvelle.
  4. Réviser composition, hiérarchie, densité, espacement, responsive, états, accessibilité.
  5. Mutualiser, refactorer et compléter les composants.
  6. Ajouter les composants manquants (Toast, Skeleton, EmptyState, Breadcrumb, Drawer, Pagination, CommandMenu).

Procédure de contrôle

Avant chaque commit de refonte :

cd apps/frontend
git diff --stat tailwind.config.ts app/globals.css   # doit rester vide, sauf --ring validé
grep -rE "\b(bg|text|border|ring)-blue-" app src --include="*.tsx" | wc -l   # doit décroître, jamais croître
npm run type-check && npm run lint && npm run build

Toute modification non validée d'un token existant → retour à la valeur d'origine.