expria-frontend/docs/DESIGN_SYSTEM.md

13 KiB
Raw Blame History

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.

@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.

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é.