Files
cv-maker/docs/conception/04-specifications-ui.md
T
2026-09-16 21:34:09 +02:00

168 lines
8.2 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.
# 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
<div className="cv-sheet"> {/* wrapper lecture imprimée */}
<header>CvHeader</header>
<div className="cv-sheet-body">
<aside>CvLeftColumn</aside> {/* 1/3 */}
<main>CvRightColumn</main> {/* 2/3 */}
</div>
</div>
```
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
<CvSection title="État civil" html={htmlEtatCivil} />
<CvSection title="Formations" html={htmlFormations} />
<CvSection title="Soft skills" html={htmlSoftSkills} />
<CvSection title="Centres d'intérêt" html={htmlCentresInteret} />
```
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 : `<label htmlFor>` avec `id` unique.
- Lien focus : contour `focus-visible` accent (Tailwind `focus-visible:outline-2` + `focus-visible:outline-accent`).