expria-frontend/docs/DESIGN_SYSTEM.md

343 lines
13 KiB
Markdown
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.

# DESIGN_SYSTEM.md — Expria Frontend
> **Document de référence — Version 1.0 — Sprint 1**
> Source de vérité unique pour l'identité visuelle, les tokens de design et les primitives UI.
> Toute décision de DA doit être consignée ici avant d'être implémentée.
---
## 1. Direction artistique — verrouillée
**Nom :** Boréal
**Positionnement :** institutionnel chaleureux, premium sans flashy, sérieux sans austère.
**Référence mentale :** Stripe Dashboard, Linear, Notion Desktop — mais réchauffé d'un cran.
### Parti pris fondateurs
| Principe | Décision |
|---|---|
| Mode canonique Sprint 1 | **Clair uniquement** (light chaud) |
| Mode sombre | Prévu Sprint 2+ (tokens écrits dual-theme-ready dès J1) |
| Fond principal | `#F4F2EC` (off-white calibré, ni froid ni saturé) |
| Surfaces élevées | Blanc pur `#FFFFFF` pour contraste subtil avec le fond |
| Bleu de marque | `#1B4FD8` **sacro-saint** en mode clair — aucune variation |
| Bleu mode sombre | `#7C9BFF` **prévu** pour Sprint 2+ (pattern Apple system colors) |
| Accent chaleureux | Aucun en Sprint 1 — le bleu porte toute l'intentionnalité |
| Angles | Rayons généreux mais retenus : 8 / 12 / 16 px |
| Ombres | Minimales. 1 ombre-card unique, très subtile. Hairlines 1px privilégiées. |
| Animations | 150200 ms, `ease-out`, respect de `prefers-reduced-motion` |
| Icônes | SVG inline dans `shared/ui/icons/` — aucune dépendance externe |
| Typographie | Plus Jakarta Sans (via `font-family`, fallback système) |
### Ce qu'on refuse explicitement
- Gradients criards (le seul acceptable : aucun).
- Glassmorphism ou `backdrop-filter` généralisé — réservé à la bottom nav mobile si besoin.
- Emojis dans les éléments interactifs ou les labels fonctionnels.
- Ombres lourdes, "drop shadows" style Material Design 2.
- Plus de 2 niveaux d'élévation visuelle (fond → card → modal).
- Toute police de display fantaisiste, serif décorative ou condensée.
- Les motifs SaaS génériques : illustrations 3D, dégradés violet-rose, glass blobs.
---
## 2. Tokens — `src/index.css`
Remplacer intégralement le contenu actuel (`@import 'tailwindcss';`) par le bloc ci-dessous. Tailwind 4 lit automatiquement les tokens déclarés dans `@theme`.
```css
@import 'tailwindcss';
@theme {
/* ----- Brand ------------------------------------------------------- */
--color-brand: #1B4FD8;
--color-brand-hover: #1744B8;
--color-brand-active: #13379C;
--color-brand-soft: #E7EDFC;
--color-brand-ink: #FFFFFF;
/* ----- Surfaces (light — Sprint 1) --------------------------------- */
--color-bg: #F4F2EC;
--color-surface: #FBFAF6;
--color-surface-raised: #FFFFFF;
--color-surface-sunken: #EEECE4;
/* ----- Ink (texte) ------------------------------------------------- */
--color-ink-primary: #0F1220;
--color-ink-secondary: #4A4F5E;
--color-ink-tertiary: #8A8F9E;
--color-ink-inverse: #FBFAF6;
/* ----- Borders & dividers ------------------------------------------ */
--color-border: #E3E0D6;
--color-border-strong: #C9C5B7;
--color-border-focus: #1B4FD8;
/* ----- Feedback ---------------------------------------------------- */
--color-success: #1F7A4C;
--color-success-soft: #E3F2EA;
--color-warning: #B8741A;
--color-warning-soft: #F7EEDF;
--color-danger: #B8322D;
--color-danger-soft: #F7E1DF;
/* ----- Typographie ------------------------------------------------- */
--font-sans: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
--font-mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;
/* Échelle : mobile-first, les tailles desktop se gèrent via utilities Tailwind */
--text-xs: 11px;
--text-sm: 13px;
--text-base: 14px;
--text-md: 15px;
--text-lg: 17px;
--text-xl: 20px;
--text-2xl: 24px;
--text-3xl: 32px;
--text-display: 40px;
/* ----- Rayons ------------------------------------------------------ */
--radius-xs: 6px;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 20px;
--radius-pill: 999px;
/* ----- Ombres ------------------------------------------------------ */
--shadow-card: 0 1px 2px rgba(15, 18, 32, 0.04), 0 1px 8px rgba(15, 18, 32, 0.03);
--shadow-raised: 0 4px 16px rgba(15, 18, 32, 0.06), 0 1px 2px rgba(15, 18, 32, 0.04);
--shadow-focus: 0 0 0 3px rgba(27, 79, 216, 0.18);
}
/* --------------------------------------------------------------------- */
/* Globals — reset minimal, fond chaud par défaut */
/* --------------------------------------------------------------------- */
html, body {
background: var(--color-bg);
color: var(--color-ink-primary);
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-variant-numeric: tabular-nums;
}
*:focus-visible {
outline: none;
box-shadow: var(--shadow-focus);
border-radius: var(--radius-xs);
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0ms !important;
transition-duration: 0ms !important;
}
}
/* --------------------------------------------------------------------- */
/* TODO Sprint 2+ — Dark theme (Cadence recalibré) */
/* --------------------------------------------------------------------- */
/*
@theme {
--color-bg-dark: #0F1320;
--color-surface-dark: #171B2B;
--color-surface-raised-dark: #1E2338;
--color-ink-primary-dark: #E6E4DB;
--color-ink-secondary-dark: #9CA0AC;
--color-brand-dark: #7C9BFF;
--color-brand-hover-dark: #9AB3FF;
--color-border-dark: #2A3048;
}
*/
```
### Règles d'usage des tokens
1. **Aucune valeur hexadécimale en dur** dans les composants. Toute couleur passe par un token.
2. **Nommage sémantique obligatoire.** On écrit `bg-surface`, pas `bg-gray-50`.
3. Si un cas d'usage exige une teinte hors charte, **le documenter ici avant de l'ajouter**. Pas de token orphelin.
4. Les tokens marqués `*-dark` ne sont **pas utilisés en Sprint 1**. Leur présence en commentaire est intentionnelle pour faciliter la reprise Sprint 2+.
---
## 3. Typographie
| Usage | Taille | Poids | Tracking | Ligne | Token |
|---|---|---|---|---|---|
| Display (NCLC hero) | 40px | 700 | -0.02em | 1.0 | `text-display` |
| H1 page | 32px | 700 | -0.02em | 1.1 | `text-3xl` |
| H2 section | 24px | 700 | -0.015em | 1.2 | `text-2xl` |
| H3 card title | 20px | 700 | -0.01em | 1.3 | `text-xl` |
| Lead / intro | 17px | 500 | -0.005em | 1.5 | `text-lg` |
| Body | 14px | 400 | 0 | 1.6 | `text-base` |
| Body renforcé | 15px | 500 | 0 | 1.55 | `text-md` |
| Small / meta | 13px | 400 | 0 | 1.5 | `text-sm` |
| Eyebrow / label | 11px | 600 | 0.1em (uppercase) | 1.4 | `text-xs` |
**Règles :**
- Tout nombre métier (score 16/20, NCLC 7,5, compteur 3/5) est rendu en `font-variant-numeric: tabular-nums`. Hérité par le body, mais à re-spécifier explicitement sur les tables et listes.
- `Plus Jakarta Sans` est déclarée en `font-family` avec fallback système. **Aucune webfont chargée** tant qu'on n'a pas validé la stratégie self-hosting (décision reportée).
- Les chiffres français utilisent la **virgule** comme séparateur décimal (`7,5`, jamais `7.5`).
---
## 4. Primitives UI — Sprint 1
À créer dans `src/shared/ui/` en FSD, une primitive par dossier (`button/`, `card/`, etc.) avec `index.ts` pour l'export.
### Inventaire minimal
| Composant | Variants | Usage dashboard |
|---|---|---|
| `Button` | `primary` / `secondary` / `ghost` / `upgrade` | CTA "Nouvelle simulation", "Passer au plan Standard", actions tertiaires |
| `Card` | `default` / `raised` / `interactive` | Cadre métriques, item simulation, recommandation |
| `MetricCard` | `default` / `hero` (pour le NCLC) | Bloc NCLC, compteur simulations, dernier score |
| `ProgressBar` | `default` | Progression vers NCLC 9 |
| `Badge` | `plan` / `nclc` / `neutral` | Plan actuel dans header, niveau NCLC par simulation |
| `Sidebar` | — | Nav desktop (≥ 1024px) |
| `BottomNav` | — | Nav mobile (< 1024px), 4 items max |
| `PageHeader` | | Greeting + plan pill |
| `SectionHeader` | | Titre de section + action optionnelle |
### Règles d'implémentation
- Chaque primitive **accepte `className`** en plus de ses props typées, pour overrides ponctuels.
- Chaque primitive **expose ses props via un type exporté** (`ButtonProps`, `CardProps`, etc.).
- Aucune primitive ne contient de logique métier ou d'appel API. Elles reçoivent tout par props.
- Les icônes sont passées par une prop `icon` acceptant un `ReactNode`, jamais par nom de string.
---
## 5. Layout dashboard — spécification
Les primitives ci-dessus s'assemblent dans `src/features/dashboard/` et `src/pages/dashboard/`.
### Structure sémantique
```
<body>
<Sidebar /> (≥ 1024px)
<main>
<PageHeader /> (greeting + plan)
<section>
<MetricCard hero /> (NCLC estimé + progression)
<MetricCard /> (simulations restantes)
<MetricCard /> (dernier score)
</section>
<Button primary /> (Nouvelle simulation)
<Button upgrade /> (Passer au plan Standard)
<section>
<SectionHeader /> (3 dernières simulations)
<Card interactive /> × 3
</section>
<section>
<SectionHeader /> (Prochaine étape recommandée)
<Card raised />
</section>
</main>
<BottomNav /> (< 1024px)
</body>
```
### Breakpoints
| Breakpoint | Comportement |
|---|---|
| `< 1024px` | Mono-colonne, `BottomNav` fixe en bas, padding horizontal 20px |
| `≥ 1024px` | Sidebar 240px + contenu centré 860px max, padding horizontal 32px |
| `≥ 1440px` | Contenu centré 920px max (pas d'élargissement excessif) |
### Densité verticale
- Padding vertical section : 24px mobile, 32px desktop.
- Gap inter-cards : 12px mobile, 16px desktop.
- Marge sous `PageHeader` : 20px mobile, 28px desktop.
---
## 6. Données mock — Sprint 1
Avant branchement API, fournir les données via `src/shared/api/mock/dashboard.ts`. Données crédibles, françaises, alignées sur l'audience réelle.
```typescript
export const mockDashboard = {
user: {
firstName: 'Yacine',
plan: 'decouverte' as const,
planLabel: 'Plan Découverte',
},
metrics: {
nclcEstimated: 7.5,
nclcTarget: 9,
simulationsUsed: 2,
simulationsQuota: 5,
lastScore: { value: 16, max: 20, type: 'ecrit' as const },
},
recentSimulations: [
{ id: 's-001', type: 'ecrit', relativeDate: 'il y a 2 jours', score: 16, max: 20, nclc: 8 },
{ id: 's-002', type: 'oral', relativeDate: 'il y a 5 jours', score: 14, max: 20, nclc: 7 },
{ id: 's-003', type: 'ecrit', relativeDate: 'il y a 1 semaine', score: 15, max: 20, nclc: 7 },
],
nextStep: {
title: 'Cible une simulation orale cette semaine',
body: 'Ton écrit est solide (NCLC 8). L\'oral reste à consolider pour sécuriser ton NCLC 9.',
action: { label: 'Démarrer Expression Orale', to: '/simulation/orale' },
},
} as const;
```
**Règles contenu :**
- Aucun "Lorem ipsum", aucune date absolue relatif uniquement (`il y a X jours`).
- Les prénoms mocks reflètent l'audience : Yacine, Aminata, Kenza, Bilal, Fatou, Kévin (Canada).
- Les scores suivent une progression crédible (pas de 20/20 ni de 5/20).
---
## 7. Accessibilité — plancher Sprint 1
- Contraste minimum **WCAG AA** sur tous les couples texte/fond (vérifié pour la palette ci-dessus).
- Tous les éléments interactifs ont un `:focus-visible` avec `--shadow-focus` (halo bleu 3px).
- Les icônes purement décoratives portent `aria-hidden="true"`.
- Les icônes fonctionnelles (sans label visible) portent `aria-label`.
- Les landmarks sémantiques sont utilisés : `<header>`, `<nav>`, `<main>`, `<section>`.
- Le `BottomNav` mobile respecte la hauteur minimale tap target : **44×44 px** par item.
---
## 8. Dépendances externes — Sprint 1
**Aucune nouvelle dépendance UI n'est ajoutée en Sprint 1.** On n'installe pas `shadcn/ui`, pas `lucide-react`, pas `radix-ui`. Les primitives sont écrites à la main pour deux raisons :
1. On veut maîtriser **exactement** chaque composant (pas d'override Tailwind contre Radix).
2. Volume fonctionnel Sprint 1 minimal (~9 primitives) : pas d'économie à externaliser.
La porte reste ouverte Sprint 2+ pour `radix-ui` sur les primitives complexes (Dialog, Popover, Dropdown) si besoin justifié dans un ADR.
---
## 9. Journal des décisions DA
| Date | Décision | Contexte |
|---|---|---|
| 2026-04-17 | Direction A (Boréal) validée comme canonique | 5 directions explorées (A/B/E/F/G), A et B retenues, A choisie comme base |
| 2026-04-17 | Fond `#F4F2EC` (V1 calibré) retenu | Test côte-à-côte contre `#F5F3ED` et `#F2EFE6` |
| 2026-04-17 | Dark mode (Cadence) reporté Sprint 2+ | Tokens écrits dual-theme-ready dès J1 pour éviter réécriture |
| 2026-04-17 | Bleu `#1B4FD8` sacro-saint en light, `#7C9BFF` prévu pour dark | Pattern Apple system colors |
| 2026-04-17 | Pas de shadcn/ui en Sprint 1 | Volume faible, maîtrise totale souhaitée |
---
## 10. Hors périmètre Sprint 1
Éléments explicitement **reportés** :
- Dark mode applicatif.
- Thème haut-contraste (WCAG AAA).
- Internationalisation (i18n) Sprint 1 monolingue FR.
- Animations avancées (scroll-linked, shared element transitions).
- Illustrations personnalisées / iconographie signature.
- Self-hosting de la font Plus Jakarta Sans (on reste en fallback système Sprint 1).
Chacun de ces points mérite un ADR dédié quand il sera abordé.