Interface

Design system : jetons, thème, catalogue

Trois couches, et une règle : aucune couleur en dur dans un composant.

CoucheFichierCe qu'elle porte
Charte de l'instanceinstances/<id>/research.config.ts4 couleurs (primary, accent, ink, surface), 2 polices, logo
Thème Mantinesrc/theme/index.ts, colors.tsÉchelles de 10 nuances, typographie, rayons, variantes accessibles
Jetons CSSsrc/theme/tokens.cssFonds, texte, traits, anneau de mise au point, mesure de lecture

La charte pilote tout le reste

brand.colors ne contient que quatre valeurs. Les échelles Mantine en sont calculées (colorScale) : la nuance 6 est la couleur exacte, 0 à 5 sont éclaircies vers le blanc, 7 à 9 assombries. Les variantes light et outline prennent la nuance 9 pour tenir 4,5:1, quelle que soit la charte fournie (variantColorResolver).

Changer l'apparence d'une instance, c'est donc changer quatre hexadécimaux — pas retoucher des composants.

Les jetons CSS

src/theme/tokens.css est la source unique des valeurs qui ne passent pas par Mantine. Le catalogue les liste et les montre : story Fondations / Charte → Jetons.

--surface fait exception : la mise en page racine l'écrase sur <body> avec la couleur de l'instance. La valeur du fichier sert de repli, notamment dans Storybook.

Avant cette mise au propre, trois fonds blancs étaient écrits en dur (bg="white", rgba(255,255,255,0.96) pour l'en-tête) : une instance à la charte colorée obtenait un site partiellement blanc. C'est corrigé ; le linter ne l'empêche pas encore, c'est une discipline de relecture.

Le catalogue de composants

TerminalCode
pnpm storybook # http://localhost:6006 pnpm build-storybook # sortie statique dans storybook-static/

Storybook monte exactement l'enveloppe de l'application (AppProviders : thème Mantine + dictionnaire), si bien qu'une story rend ce que rend une page.

  • Fondations / Charte : échelles de couleur calculées, jetons, typographie, grilles.
  • Composants : cartes, en-tête de page, fil d'Ariane, état vide, liens et boutons.

Deux réglages utiles :

  • la bascule de langue dans la barre d'outils rend la story en français ou en anglais ;
  • NEXT_PUBLIC_RESEARCH_INSTANCE=paris1 pnpm storybook affiche l'autre charte — c'est le moyen le plus rapide de vérifier qu'un composant ne dépend pas des couleurs de Saclay.

L'extension d'accessibilité (@storybook/addon-a11y) est active : chaque story affiche le rapport axe de son propre rendu.

Visualisation et tableau de bord interne

packages/analytics-ui porte les composants de graphique (Mantine + Recharts). Il ne collecte rien — c'est packages/analytics qui collecte — et il ne connaît pas les données du projet : on lui passe des tableaux de valeurs.

Le tableau de bord qui s'en sert est /admin : couverture des fiches publiées champ par champ, état des sources, lacunes à arbitrer, couverture à la moisson par source, et conformité à la référence figée.

TerminalCode
NEXT_PUBLIC_ADMIN=true pnpm dev:saclay # puis http://localhost:3000/admin

Sans cette variable, la route répond 404 et ne contient aucune donnée : le build produit bien un fichier à cette adresse, mais c'est la page « introuvable », pas le tableau de bord — vérifié sur la sortie de build (7 Ko, rien du contenu). La page montre les écarts avec le RNSR et les liens morts — rien de personnel, mais rien qui doive se trouver sur le site public avant arbitrage. robots.txt l'exclut aussi, par ceinture et bretelles.

Trois règles tenues dans ces composants, et leur raison :

  • une seule teinte par graphique. La longueur encode déjà la grandeur ;
  • un statut n'est jamais une couleur seule. Le vert et le rouge employés ici sont à ΔE 4,1 en deutéranopie : chaque statut porte une icône et un mot ;
  • deux jeux de couleurs de statut : l'un pour les aplats (3:1 contre la surface), l'autre, plus sombre, pour le texte (4,5:1). Mesuré, pas estimé — la première version échouait à l'audit axe sur huit badges.

Chaque graphique a une bascule « Tableau » et un role="img" résumant ses valeurs : la page reste lisible sans voir les couleurs.

Un piège de composant serveur

Table.Thead, Table.Td et les autres sous-composants Mantine ne traversent pas la frontière serveur : depuis un composant serveur, ce sont des propriétés statiques d'une référence de module client, donc undefined, et le rendu échoue sur « Element type is invalid ». Tout usage de Table.* vit dans un fichier marqué 'use client' — c'est la raison d'être de src/app/admin/GapsTable.tsx.

Écrire une story

Les données d'exemple vivent dans src/components/data-display/fixtures.ts et imitent les fiches réelles — textes longs, acronymes, ordres de grandeur. Une story qui ne montre que le cas idéal ne sert à rien : cards.stories.tsx rend aussi une fiche sans acronyme, sans présentation et à compteurs nuls, et une grille où les écarts de hauteur se voient.