8.2 KiB
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", boutonsrole="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 : bordureborder+ textemuted. - Contenu piloté par le parent (
activeTabdansCvInputPanel) : un seulMarkdownTextareamonté pour l'onglet actif. Le texte de l'onglet actif est levaluecontrô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, varianteprimary) → 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 :
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/Tabssont 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
<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-bottomde séparation avec le corps de feuille.
4.3 CvLeftColumn (sections 2 à 5, 1/3)
Section générique CvSection :
<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 parlib/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.experiencesdans l'ordre du tableau = ordre de saisie (la 1ʳᵉ entrée en haut, cf. module 03). - Chaque
CvExperiencePreview: titre en gras + rendu Markdown ducontenu. - 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 →
overflowtolérable sur une « page 2 » de preview est hors scope v1. Comportement choisi : la feuille s'allonge (min-hseulement) 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-fullau-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,idliés. - Boutons icônes :
aria-labelexplicites (« Modifier l'expérience », « Supprimer l'expérience »). - Inputs/textarea :
<label htmlFor>avecidunique. - Lien focus : contour
focus-visibleaccent (Tailwindfocus-visible:outline-2+focus-visible:outline-accent).