# 04 — Spécifications UI et composants ## 1. Design tokens (Tailwind CSS 4) Fichier source : `app/globals.css` (via `@import "tailwindcss"` + `@theme`). | Token | Valeur | Usage | | --- | --- | --- | | `--color-surface` | blanc (`#ffffff`) | feuille A4 | | `--color-paper` | `#f1f5f9` gris très clair | fond de l'application hors feuille | | `--color-ink` | `#0f172a` | texte principal | | `--color-muted` | `#64748b` | texte secondaire | | `--color-accent` | `#2563eb` | actions, focus, onglet actif | | `--color-danger` | `#dc2626` | boutons « × » suppression | | `--color-border` | `#e2e8f0` | bordures et séparateurs | | `--radius-*` | `0.5rem` (md), `0.375rem` (sm) | cohérence des angles | | `--font-sans` | système + `Inter` en fallback | lisibilité écran et impression | Typographie du CV (feuille A4) : | Élément | Suggestion | | --- | --- | | Nom / accroche (header) | `text-base font-semibold` à `text-xl font-bold` | | Titres de section | `text-[0.75rem] uppercase tracking-wide text-muted font-bold` | | Corps | `text-[0.8rem] leading-snug text-ink` | ### Palette neutre recommandée Le CV imprimé doit rester sobre : **encre sombre** sur blanc, onglet actif en accent, **aucun fond de couleur sur la feuille A4** (économie d'encre et lisibilité). ## 2. Barre d'application (`CvToolbar`) | Élément | Contenu | | --- | --- | | Gauche | Logo (icône `FileText`) + « CV-Maker » | | Droite | Bouton **Imprimer / PDF**, bouton **Importer JSON**, bouton **Exporter JSON**, bouton **Réinitialiser**, lien **Documentation** | Rendre les liens actifs en `font-semibold` et inactifs en `text-muted` (page courante) — navigation classique App Router (`usePathname`). ## 2bis. Pied de page (`Footer`) - **Masqué à l'impression** (`print-hidden`), bordure supérieure. - Contenu : nom de l'application + lien **Mentions légales** vers `/mentions-legales` (remplace l'ancien lien RGPD de la barre). ## 3. Panneau latéral gauche ### 3.1 `CvTabs` — onglets 1 à 5 - Basé sur la primitive `ui/Tabs` : `role="tablist"`, boutons `role="tab"`, `aria-selected`, `role="tabpanel"`. - Affichage : barre de 5 boutons horizontaux, libellé = `numéro + label` (`1 · Accroche`, `2 · État civil`, …). - Onglet actif : fond `accent`, texte blanc ; inactifs : bordure `border` + texte `muted`. - **Contenu piloté par le parent** (`activeTab` dans `CvInputPanel`) : un seul `MarkdownTextarea` monté pour l'onglet actif. Le texte de l'onglet actif est le `value` contrôlé. ### 3.2 `MarkdownTextarea` | Élément | Spécification | | --- | --- | | Props | `label: string`, `value: string`, `onChange(text: string): void`, `placeholder?: string` | | Comportement | `textarea` contrôlé, `onChange` → mise à jour immédiate de l'état + persistance | | UX | `min-h` variable (accroche + courte), `resize-y`, monospace en saisie (`font-mono`) avec bascule visuelle explicite « le texte est en Markdown » | | Note Markdown | petite légende « Syntaxe Markdown supportée » avec lien vers `/documentation` | ### 3.3 `ExperienceManager` Composition : - **En-tête** : titre « Expériences » + bouton **+** (`ui/Button`, variante `primary`) → prépare une nouvelle expérience (zones titre/contenu vidées, focus du formulaire). - **Formulaire** : deux champs — `titre` (input texte) + `contenu` (textarea Markdown, même composant que les zones 1-5). Boutons : **Enregistrer** et **Annuler** (annulation quand modification). - **Liste numérotée** : sous le formulaire. États internes suivants : ```ts type ExperienceEditor = | { mode: "closed" } // rien en édition | { mode: "new" } // ajout en cours | { mode: "edit"; id: Experience["id"] }; // modification en cours ``` ### 3.4 Ligne d'expérience (`ExperienceItem`) Format visuel : `1. Titre de l'expérience [Modifier] [×]` | Action | Clé | Comportement | | --- | --- | --- | | Modifier | bouton icône `Pencil` + libellé | passe en mode `edit`, recharge titre/contenu dans le formulaire | | Supprimer | bouton `×` (`IconButton`, variante danger) | **confirmation** (petit état interne « Confirmer la suppression ? [Oui/Non] » ou `window.confirm`) puis `removeExperience(id)` | > Composants `ui/Button`, `ui/IconButton`, `ui/Textarea`, `ui/Tabs` sont les primitives réutilisables (cf. le volet « composants réutilisables » du cahier des charges). ## 4. Panneau latéral droit — feuille A4 (`CvPreview`) ### 4.1 Conteneur de feuille ```tsx
{/* wrapper lecture imprimée */}
CvHeader
{/* 1/3 */}
CvRightColumn
{/* 2/3 */}
``` Dimensions écran : | Propriété | Valeur | | --- | --- | | Largeur | `210mm` (with `max-w-full` pour rester contenu) | | Hauteur | `297mm` (`min-h`, la feuille peut être plus longue si contenu dépassant — dépassement scrollable) | | Ombre / bordure | `shadow-lg border border-border` (écran) / **supprimées** à l'impression | | Padding | `16mm` (marges type CV) | | Fond | blanc `surface` | Ratio 1/3–2/3 : `.cv-sheet-body { display:grid; grid-template-columns: 1fr 2fr; gap: 12mm; }`. ### 4.2 `CvHeader` (accroche — zone 1) - Toute la largeur de la feuille. - Rendu Markdown de `data.accroche` (contenu typiquement : nom + poste + accroche). - `border-bottom` de séparation avec le corps de feuille. ### 4.3 `CvLeftColumn` (sections 2 à 5, 1/3) Section générique `CvSection` : ```tsx ``` Règles : - Titre stylé selon le token « titres de section » (petit, majuscules, espacement). - Contenu : `dangerouslySetInnerHTML={{ __html: sanitizedHtml }}` **uniquement** si le HTML a été produit par `lib/markdown` (marked + DOMPurify) — jamais de HTML saisi brut. - Si une section est vide → masquée (pas de cadre vide) ; le GOAL.md ne demande pas d'emplacement fixe vide. ### 4.4 `CvRightColumn` (expériences, 2/3) - Titre fixe : **« Expériences professionnelles »** (selon token titres de section). - Liste des `data.experiences` **dans l'ordre du tableau** = ordre de saisie (la 1ʳᵉ entrée en haut, cf. module 03). - Chaque `CvExperiencePreview` : **titre** en gras + rendu Markdown du `contenu`. - Section vide → texte léger : instruction « Ajoutez une expérience dans le panneau gauche » (rendu écran uniquement, masqué à l'impression via `print:hidden`). ### 4.5 Dépassement de contenu - Sur le feuille : la hauteur est le **hapax A4** à l'impression (une page). En revanche, le contenu peut dépasser → `overflow` tolérable sur une « page 2 » de preview est hors scope v1. Comportement choisi : la feuille s'allonge (`min-h` seulement) pour montrer tout le contenu ; **l'impression** sera paginée par le navigateur (2 pages éventuelles). Documenté comme limitation assumée. ## 5. États UI transverses | État | Signal | | --- | --- | | Erreur de sauvegarde | `console.warn` de l'erreur (enregistrement silencieux, sans pastille) | | Onglet actif | fond accent (haut contraste) | | Formulaire expérience (nouveau / modification) | zone surlignée `border-accent` | | Suppression | état de confirmation inline (Oui / Non) | ## 6. Responsive minimal (nice to have) - **≥ 1024 px** : mise en page 2 colonnes (1/3 – 2/3) inchangée. - **< 1024 px** : le panneau gauche passe en `w-full` au-dessus de la preview (empilés verticalement) ; la preview se centre. Tablette viable ; mobile lisible mais non optimisé (desktop first). - L'impression ne dépend **pas** du viewport (elle est pilotée par `@media print`, voir module 02 §7). ## 7. Accessibilité (bonnes pratiques) - Onglets : `role="tablist"/"tab"/"tabpanel"`, navigation clavier (flèches ←/→), `aria-selected`, `aria-controls`, `id` liés. - Boutons icônes : `aria-label` explicites (« Modifier l'expérience », « Supprimer l'expérience »). - Inputs/textarea : `