30-frontend.md : le mode fixtures est inactif dans les deux environnements (la doc affirmait l'inverse), services et pages manquants, widget alertes et états de chargement, strict activé. 32-design-systeme-frontend.md : tokens typographiques, ev-icon, app-alert-feed, classes de tableau.
7.5 KiB
Design système frontend
Ce que toute nouvelle page ou tout nouveau composant Angular doit réutiliser, plutôt que redéfinir ses propres couleurs, rayons ou espacements en dur. Contexte : issue #91, née d'une incohérence visuelle accumulée page après page (aucun jeton partagé n'existait avant ce chantier).
Tokens
Déclarés en CSS custom properties dans apps/frontend/src/styles/_tokens.scss, importés une
seule fois dans src/styles.scss. Disponibles partout sans import supplémentaire.
| Variable | Rôle |
|---|---|
--color-primary, --color-primary-hover, --color-primary-light |
Couleur de marque (vert, dérivé du logo), actions principales |
--color-text, --color-text-muted, --color-label |
Hiérarchie de texte (titres, texte secondaire, labels de formulaire) |
--color-border, --color-border-light |
Bordures d'inputs et de cartes |
--color-bg, --color-surface |
Fond de page vs fond des cartes/panneaux |
--color-disabled |
Éléments désactivés |
--color-success / -bg, --color-warning / -bg / -text, --color-danger / -hover / -bg / -border, --color-critical |
États sémantiques (alertes, badges) |
--color-text-inverse |
Texte sur fond coloré plein (boutons/badges) |
--font-family |
Police unique de l'application |
--font-size-xs à --font-size-2xl |
Échelle typographique (0.75rem à 2.25rem) : libellés, corps, titres, grands nombres |
--radius-sm, --radius-md, --radius-pill |
Rayons de bordure (input/bouton, carte, pastille) |
--shadow-card, --shadow-card-hover |
Ombre portée des cartes, au repos et au survol |
--space-1 à --space-5 |
Échelle d'espacement (0.35rem à 2.5rem) |
Les classes de formulaire partagées (.form-label, .form-input, .form-select, .form-hint)
sont dans apps/frontend/src/styles/_forms.scss, importées globalement de la même façon. Elles
s'appliquent directement à des <label>/<input>/<select> natifs, liés par formControlName ou
par un simple (change) : pas de composant ControlValueAccessor dédié, le gain n'en vaut pas la
complexité pour des formulaires aussi simples que ceux de ce projet. .form-select habille un
<select> natif avec la bordure et le focus de .form-input, plus un chevron. Les erreurs de
formulaire, elles, s'affichent via <ev-alert severity="danger">, pas une classe dédiée.
La classe .auth-page (apps/frontend/src/styles/_auth-page.scss, importée globalement) porte
le fond dégradé et le centrage commun aux pages d'authentification (login, change-password,
et à terme forgot-password/reset-password) : elle enveloppe la carte, pas de duplication du
fond par page.
Les classes de navigation partagées (.ev-link, .ev-breadcrumb, .ev-brand-link) sont dans
apps/frontend/src/styles/_links.scss, importées globalement. Convention pour toute page de la
zone authentifiée (derrière authGuard) : le logo (<ev-brand>) est enveloppé dans
<a routerLink="/dashboard" class="ev-brand-link"> pour ramener au tableau de bord en un clic,
et un <nav class="ev-breadcrumb"> liste le chemin de retour vers les pages parentes quand la
page n'est pas à la racine (voir site-list/site-detail-placeholder pour l'exemple).
Composants partagés
Dans apps/frontend/src/app/shared/components/ui/, chacun standalone, à importer directement
dans le tableau imports du composant qui l'utilise.
<ev-button>(button/) :variant(primary/secondary/danger, défautprimary),type(button/submit, défautbutton),disabled,fullWidth(défauttrue; passerfalsepour un bouton qui ne doit pas occuper toute la largeur de son conteneur, ex. une action isolée dans un en-tête).<ev-button type="submit" [disabled]="form.invalid">Valider</ev-button> <ev-button variant="secondary" [fullWidth]="false">Déconnexion</ev-button><ev-card>(card/) : conteneur à padding/rayon/ombre standard, sans input, tout est le contenu projeté (<ng-content>). Le style vit sur:host: une classe externe passée par le parent (<ev-card class="ma-classe">) se combine avec le style du composant sans le masquer.<ev-card><h1>Titre</h1></ev-card><ev-alert>(alert/) :severity(success/warning/danger, défautdanger),role="alert"posé automatiquement. Même principe de style sur:host.<ev-alert severity="danger">Erreur : {{ message }}</ev-alert><ev-badge>(badge/) :tone(success/warning/danger/critical/neutral, défautneutral), pastille à bord arrondi pour un statut court.dangeretcriticalsont deux rouges distincts (--color-dangervs--color-critical, plus sombre) : une sévéritécriticalne doit pas se confondre visuellement avec unehigh.<ev-badge tone="danger">critique</ev-badge><ev-brand>(brand/) : lockup icône + « EnerVision », sans input. La taille se pilote entièrement viafont-size(l'icône et le texte sont exprimés enem, donc ils grossissent ensemble en gardant le même écart proportionnel) : une page l'agrandit simplement avecev-brand { font-size: 2.1rem; }. Ne pas recomposer icône + texte en une seule image bitmap : un essai en ce sens (recadrage pixel de l'asset source) a produit un rendu bruité et un espacement figé, impossible à ajuster proprement.<ev-brand /><ev-icon>(icon/) :name(obligatoire :spike/threshold/anomaly/outage/sensor, les types d'alerte du contrat) etlabel(facultatif). SVG inline en trait surcurrentColor, dimensionné parfont-sizecommeev-brand. Sanslabell'icône est décorative (aria-hidden) ; avec, elle porterole="img"etaria-label. Pas de bibliothèque d'icônes : la CSP du reverse proxy (script-src 'self') interdit les scripts tiers, pas le SVG inline.<ev-icon name="spike" label="Pic de consommation" /><app-alert-feed>(shared/components/alert-feed/) : widget métier plutôt qu'atome du kit, mais réutilisable tel quel. InputsiteId(facultatif : fige le site et masque son filtre). Il porte ses filtres (.form-select), ses états et son rafraîchissement ; le parent ne fait que le poser dans une section.
Les classes de tableau partagées sont dans apps/frontend/src/styles/_tables.scss, importées
globalement : .ev-table-card sur la <ev-card> qui enveloppe un tableau (padding nul),
.ev-table sur le <table>, .ev-table__number pour une cellule numérique en chiffres
tabulaires, .ev-table__muted pour une cellule sans valeur.
Logo
L'icône seule (sans le mot-symbole), recadrée depuis l'asset source du projet, vit à deux
endroits qui doivent rester synchronisés si le logo change un jour :
apps/frontend/public/logo-icon.png (utilisée par <ev-brand>) et
apps/backend/app/static/logo-icon.png (référencée par /docs, favicon Swagger, et /redoc via
l'extension x-logo du schéma OpenAPI, voir app/main.py). Le mot-symbole « EnerVision » n'est
jamais une image : c'est le texte du composant <ev-brand>, en police système.
Le favicon apps/frontend/public/favicon.ico est généré depuis la même icône (multi-tailles
16 à 256px).
Règle pour toute nouvelle page
Utiliser les tokens et les composants ci-dessus plutôt que des valeurs en dur (couleurs hexadécimales, rayons, espacements). Étendre ce document si un nouveau composant partagé est créé.