169 lines
8.2 KiB
Markdown
169 lines
8.2 KiB
Markdown
# 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 à 6
|
||
|
||
- Basé sur la primitive `ui/Tabs` : `role="tablist"`, boutons `role="tab"`, `aria-selected`, `role="tabpanel"`.
|
||
- Affichage : barre de 6 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-6). 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 à 6, 1/3)
|
||
|
||
Section générique `CvSection` :
|
||
|
||
```tsx
|
||
<CvSection title="État civil" html={htmlEtatCivil} />
|
||
<CvSection title="Formations" html={htmlFormations} />
|
||
<CvSection title="Compétences" html={htmlCompetences} />
|
||
<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`). |