712 lines
35 KiB
Markdown
712 lines
35 KiB
Markdown
# 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 :
|
||
```bash
|
||
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**.
|