@zevra/ui 0.35.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/DECISIONS.md +264 -208
  2. package/README.md +21 -21
  3. package/demo/app.html +6 -6
  4. package/demo/index.html +3 -3
  5. package/dist/actions.d.ts +8 -8
  6. package/dist/actions.d.ts.map +1 -1
  7. package/dist/actions.js +1 -1
  8. package/dist/actions.js.map +1 -1
  9. package/dist/catalogue.d.ts +2 -2
  10. package/dist/catalogue.js +67 -67
  11. package/dist/catalogue.js.map +1 -1
  12. package/dist/content.d.ts +23 -23
  13. package/dist/content.d.ts.map +1 -1
  14. package/dist/content.js +16 -16
  15. package/dist/content.js.map +1 -1
  16. package/dist/css-inline.d.ts +1 -1
  17. package/dist/css-inline.d.ts.map +1 -1
  18. package/dist/css-inline.js +4 -4
  19. package/dist/css-inline.js.map +1 -1
  20. package/dist/data-table.d.ts +1 -1
  21. package/dist/data.d.ts +7 -7
  22. package/dist/data.js +5 -5
  23. package/dist/email-tokens.js +1 -1
  24. package/dist/email.d.ts +6 -6
  25. package/dist/email.js +8 -8
  26. package/dist/email.js.map +1 -1
  27. package/dist/feedback.d.ts +10 -10
  28. package/dist/feedback.d.ts.map +1 -1
  29. package/dist/feedback.js +3 -3
  30. package/dist/forms.d.ts +12 -12
  31. package/dist/forms.js +5 -5
  32. package/dist/glyph-data.d.ts +2 -2
  33. package/dist/glyph-data.js +3 -3
  34. package/dist/glyph-urls.js +1 -1
  35. package/dist/glyphes.d.ts +2 -2
  36. package/dist/glyphes.js +4 -4
  37. package/dist/icon-data.d.ts +1 -1
  38. package/dist/icon-data.js +2 -2
  39. package/dist/icon.d.ts +5 -5
  40. package/dist/icon.js +1 -1
  41. package/dist/index.js +4 -4
  42. package/dist/layout.d.ts +21 -21
  43. package/dist/layout.d.ts.map +1 -1
  44. package/dist/layout.js +15 -15
  45. package/dist/layout.js.map +1 -1
  46. package/dist/navigation.d.ts +16 -16
  47. package/dist/navigation.d.ts.map +1 -1
  48. package/dist/navigation.js +9 -9
  49. package/dist/navigation.js.map +1 -1
  50. package/dist/overlays.d.ts +6 -6
  51. package/dist/overlays.d.ts.map +1 -1
  52. package/dist/overlays.js +5 -5
  53. package/dist/select-menu.d.ts +1 -1
  54. package/dist/select-menu.js +2 -2
  55. package/dist/surfaces.d.ts +8 -8
  56. package/dist/surfaces.d.ts.map +1 -1
  57. package/dist/surfaces.js +6 -6
  58. package/dist/surfaces.js.map +1 -1
  59. package/dist/typography.d.ts +4 -4
  60. package/dist/typography.d.ts.map +1 -1
  61. package/dist/typography.js +3 -3
  62. package/dist/typography.js.map +1 -1
  63. package/icons/index.json +1 -1
  64. package/package.json +2 -2
  65. package/react/actions.tsx +9 -9
  66. package/react/catalogue.tsx +68 -68
  67. package/react/content.tsx +28 -28
  68. package/react/css-inline.ts +4 -4
  69. package/react/data-table.tsx +3 -3
  70. package/react/data.tsx +9 -9
  71. package/react/email-tokens.ts +1 -1
  72. package/react/email.ts +10 -10
  73. package/react/feedback.tsx +12 -12
  74. package/react/forms.tsx +15 -15
  75. package/react/glyph-data.ts +3 -3
  76. package/react/glyph-urls.ts +1 -1
  77. package/react/glyphes.ts +4 -4
  78. package/react/icon-data.ts +2 -2
  79. package/react/icon.tsx +6 -6
  80. package/react/index.ts +4 -4
  81. package/react/layout.tsx +22 -22
  82. package/react/navigation.tsx +18 -18
  83. package/react/overlays.tsx +9 -9
  84. package/react/select-menu.tsx +3 -3
  85. package/react/surfaces.tsx +10 -10
  86. package/react/typography.tsx +6 -6
  87. package/src/base.css +10 -10
  88. package/src/components.css +49 -49
  89. package/src/data.css +41 -41
  90. package/src/feedback.css +55 -55
  91. package/src/fonts.css +2 -2
  92. package/src/forms.css +50 -50
  93. package/src/icons.css +10 -10
  94. package/src/index.css +1 -1
  95. package/src/layout.css +27 -27
  96. package/src/motion.css +16 -16
  97. package/src/motion.js +4 -4
  98. package/src/nav.js +5 -5
  99. package/src/navigation.css +62 -62
  100. package/src/overlays.css +14 -14
  101. package/src/rail.js +6 -6
  102. package/src/surfaces.css +11 -11
  103. package/src/tokens.css +14 -14
  104. package/tokens/index.js +8 -8
package/react/layout.tsx CHANGED
@@ -1,4 +1,4 @@
1
- // Mise en page vitrine — page, gouttières, grille 12, sections, hero, frise
1
+ // Mise en page vitrine : page, gouttières, grille 12, sections, hero, frise
2
2
  // d'étapes, statistiques, pieds de page, couches d'empilement.
3
3
  //
4
4
  // Couche VITRINE : une application n'a aucune raison de charger ce fichier.
@@ -34,7 +34,7 @@ export function Page({
34
34
 
35
35
  export interface GutterProps extends Div {
36
36
  /** `wide` pour une bande pleine largeur, `chrome` pour la barre de
37
- * navigation — qui ne s'aligne pas sur la colonne de texte. */
37
+ * navigation : qui ne s'aligne pas sur la colonne de texte. */
38
38
  variante?: 'wide' | 'chrome'
39
39
  as?: ElementType
40
40
  className?: string
@@ -67,9 +67,9 @@ export function Grid12({ className, ...reste }: Div & { className?: string }) {
67
67
  export type Span12 = 3 | 4 | 5 | 6 | 7 | 8 | 9
68
68
 
69
69
  /** ⚠️ Seules ces sept portées existent : ce sont les découpes employées par
70
- * la vitrine (5+7 vient du prototype experience.html — le glyphe en 5, le
70
+ * la vitrine (5+7 vient du prototype experience.html, le glyphe en 5, le
71
71
  * discours en 7). En ajouter une revient à ajouter une mise en page, pas
72
- * une utilité — c'est une décision de DA. */
72
+ * une utilité : c'est une décision de DA. */
73
73
  export function Col({
74
74
  span,
75
75
  className,
@@ -170,7 +170,7 @@ export function NavLinks({ className, ...reste }: Div & { className?: string })
170
170
  * `AnchorHTMLAttributes` et non `HTMLAttributes` : sans quoi ils refusent
171
171
  * `target`, `rel`, `download` et `hreflang`. Un lien sortant ne peut alors
172
172
  * pas porter `rel="noopener"`, et un lien qui doit s'ouvrir à côté ne le
173
- * peut pas non plus — une app consommatrice a dû écrire la classe à la main
173
+ * peut pas non plus : une app consommatrice a dû écrire la classe à la main
174
174
  * pour cette seule raison. (0.19.0) */
175
175
  export interface NavLinkProps
176
176
  extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className'> {
@@ -181,7 +181,7 @@ export interface NavLinkProps
181
181
  }
182
182
 
183
183
  /** ⚠️ `.zv-navlink` est la navigation de VITRINE. L'entrée applicative
184
- * s'appelle `SideLink` — les deux ne doivent jamais se recouvrir. */
184
+ * s'appelle `SideLink` : les deux ne doivent jamais se recouvrir. */
185
185
  export function NavLink({ discret, className, ...reste }: NavLinkProps) {
186
186
  return (
187
187
  <a className={cx('zv-navlink', discret && 'zv-navlink--quiet', className)} {...reste} />
@@ -201,7 +201,7 @@ export function NavBurger({
201
201
  return <button type={type} className={cx('zv-nav__burger', className)} {...reste} />
202
202
  }
203
203
 
204
- /** Le contenu du tiroir de navigation vitrine — à poser dans `Drawer`. */
204
+ /** Le contenu du tiroir de navigation vitrine : à poser dans `Drawer`. */
205
205
  export function NavDrawer({ className, ...reste }: Div & { className?: string }) {
206
206
  return <div className={cx('zv-nav-drawer', className)} {...reste} />
207
207
  }
@@ -233,7 +233,7 @@ export function NavDrawerGroup({ className, ...reste }: Span & { className?: str
233
233
  /* ─── Entrées à menu déroulant ─────────────────────────────────────── */
234
234
 
235
235
  /** Une famille qui se déplie. Ouverture au survol ET au focus, en CSS seul :
236
- * pas d'état à tenir, donc rien à hydrater — une barre de vitrine reste
236
+ * pas d'état à tenir, donc rien à hydrater, une barre de vitrine reste
237
237
  * rendue au serveur. */
238
238
  export function NavDropdown({ className, ...reste }: Span & { className?: string }) {
239
239
  return <span className={cx('zv-navdd', className)} {...reste} />
@@ -367,7 +367,7 @@ export function LockupWord({
367
367
  /* ─── Rail de lecture ──────────────────────────────────────────────── */
368
368
 
369
369
  /** La table des matières d'une page longue, posée sur le côté. Elle navigue
370
- * (chaque entrée est une ancre) et elle suit — poser `data-zv-rail` et
370
+ * (chaque entrée est une ancre) et elle suit : poser `data-zv-rail` et
371
371
  * importer `@zevra/ui/rail.js` pour que l'entrée courante s'allume. Sans le
372
372
  * module, le rail navigue toujours : rien ne casse, le suivi manque.
373
373
  *
@@ -392,7 +392,7 @@ export function Rail({
392
392
  }
393
393
 
394
394
  /** ⚠️ `href` doit désigner une ancre de la page (`#section`) : c'est elle
395
- * que le module surveille, et c'est le balisage qui fait foi — il n'y a
395
+ * que le module surveille, et c'est le balisage qui fait foi, il n'y a
396
396
  * pas de liste de sections à tenir à jour ailleurs. */
397
397
  export function RailItem({
398
398
  courant,
@@ -420,7 +420,7 @@ export function RailItem({
420
420
  /* ─── Frise d'étapes ───────────────────────────────────────────────── */
421
421
 
422
422
  export interface StepsProps extends Div {
423
- /** Avancement du rail, entre 0 et 1 — piloté au défilement. */
423
+ /** Avancement du rail, entre 0 et 1 : piloté au défilement. */
424
424
  avancement?: number
425
425
  className?: string
426
426
  children?: ReactNode
@@ -562,7 +562,7 @@ export function OnlyMobile({ className, ...reste }: Div & { className?: string }
562
562
 
563
563
  /** La bande AU-DESSUS de la barre de navigation : annonce de version,
564
564
  * information réglementaire. UN SEUL par page, et jamais pour du marketing
565
- * permanent — un bandeau qui ne part pas devient un bourrelet de chrome. */
565
+ * permanent : un bandeau qui ne part pas devient un bourrelet de chrome. */
566
566
  export function Banner({ className, ...reste }: Div & { className?: string }) {
567
567
  return <div className={cx('zv-banner', className)} {...reste} />
568
568
  }
@@ -577,7 +577,7 @@ export interface CarouselProps extends Div {
577
577
  }
578
578
 
579
579
  /** Défilement natif scroll-snap, AUCUN JavaScript : pas de flèches, pas de
580
- * points. Le conteneur est focalisable — c'est lui que les flèches du
580
+ * points. Le conteneur est focalisable : c'est lui que les flèches du
581
581
  * clavier font défiler. La largeur des éléments vient du consommateur.
582
582
  * ⚠️ Un carrousel reste un aveu d'espace : la règle de variété lui préfère
583
583
  * une grille tant que le contenu tient. */
@@ -602,7 +602,7 @@ export function CarouselItem({ className, ...reste }: Div & { className?: string
602
602
  * ⚠️ À poser À CÔTÉ de `NavLinks`, jamais DEDANS. `NavLinks` disparaît sous
603
603
  * le point de rupture (et sous 1150px en `nav--riche`), remplacée par le
604
604
  * burger : un compte placé dedans s'évanouit sur téléphone, et l'on ne peut
605
- * plus se connecter — c'est pourtant la première chose qu'on y fait. */
605
+ * plus se connecter, c'est pourtant la première chose qu'on y fait. */
606
606
  export function NavActions({ className, ...reste }: Div & { className?: string }) {
607
607
  return <div className={cx('zv-nav__actions', className)} {...reste} />
608
608
  }
@@ -610,12 +610,12 @@ export function NavActions({ className, ...reste }: Div & { className?: string }
610
610
  /* ─── Lanceur d'applications ───────────────────────────────────────── */
611
611
 
612
612
  /** Le lanceur : passer d'un produit Zevra à l'autre, depuis n'importe lequel.
613
- * Il se pose À DROITE de la barre, à côté du compte — c'est la convention
613
+ * Il se pose À DROITE de la barre, à côté du compte : c'est la convention
614
614
  * que tout le monde connaît, et ce n'est pas le moment d'en inventer une.
615
615
  *
616
616
  * Ouvert au survol et au focus en CSS seul : la barre reste rendue au
617
617
  * serveur, sans rien à hydrater. Une app qui préfère l'ouverture au CLIC
618
- * (la convention des lanceurs) tient l'état elle-même et passe `ouvert` —
618
+ * (la convention des lanceurs) tient l'état elle-même et passe `ouvert` :
619
619
  * un état se dit en prop, il ne se peint pas depuis l'app. */
620
620
  export function Lanceur({
621
621
  label = 'Applications Zevra',
@@ -627,7 +627,7 @@ export function Lanceur({
627
627
  }: Div & {
628
628
  label?: string
629
629
  /** L'intitulé du panneau, avec le filet de spectre. Le passer à `null`
630
- * pour un panneau nu — mais c'est ce filet qui fait qu'un lanceur
630
+ * pour un panneau nu : mais c'est ce filet qui fait qu'un lanceur
631
631
  * ressemble à Zevra plutôt qu'à n'importe quel autre. */
632
632
  titre?: ReactNode
633
633
  ouvert?: boolean
@@ -668,7 +668,7 @@ export function LanceurGroupe({ className, ...reste }: Span & { className?: stri
668
668
  return <span className={cx('zv-lanceur__groupe', className)} {...reste} />
669
669
  }
670
670
 
671
- /** La grille de tuiles. Elle enveloppe les `LanceurApp` d'une même famille —
671
+ /** La grille de tuiles. Elle enveloppe les `LanceurApp` d'une même famille :
672
672
  * c'est ce qui permet à un panneau d'en porter plusieurs. */
673
673
  export function LanceurApps({ className, ...reste }: Span & { className?: string }) {
674
674
  return <span className={cx('zv-lanceur__apps', className)} {...reste} />
@@ -682,7 +682,7 @@ export interface LanceurAppProps
682
682
  * qui ne connaît pas encore la suite : un nom de marque seul ne dit rien
683
683
  * à un client qui découvre. */
684
684
  accroche?: string
685
- /** Le glyphe du produit — `GLYPHES.memoire` de `@zevra/ui/glyphes`. Sans
685
+ /** Le glyphe du produit : `GLYPHES.memoire` de `@zevra/ui/glyphes`. Sans
686
686
  * lui, la tuile porte l'initiale : une image qui ne charge pas ne doit
687
687
  * pas laisser un trou. */
688
688
  glyphe?: string
@@ -732,7 +732,7 @@ export interface CompteProps extends Div {
732
732
  ouvert?: boolean
733
733
  label?: string
734
734
  className?: string
735
- /** Les entrées du panneau — des `CompteLien`. */
735
+ /** Les entrées du panneau : des `CompteLien`. */
736
736
  children?: ReactNode
737
737
  }
738
738
 
@@ -743,7 +743,7 @@ export interface CompteProps extends Div {
743
743
  *
744
744
  * ⚠️ Le panneau n'est pas un `role="menu"` : ce rôle promet une navigation
745
745
  * aux flèches que le CSS seul ne peut pas tenir. Ce sont des liens dans un
746
- * panneau, atteignables par la tabulation — une promesse tenue vaut mieux
746
+ * panneau, atteignables par la tabulation : une promesse tenue vaut mieux
747
747
  * qu'un rôle exact et menteur. */
748
748
  export function Compte({
749
749
  utilisateur,
@@ -799,7 +799,7 @@ export function Compte({
799
799
  }
800
800
 
801
801
  /** Une entrée du panneau de compte. Rendue en `<a>` avec `href`, en
802
- * `<button>` sans — se déconnecter est une ACTION, pas une navigation, et
802
+ * `<button>` sans : se déconnecter est une ACTION, pas une navigation, et
803
803
  * un lien qui n'en est pas un se copie, s'ouvre dans un onglet et casse. */
804
804
  export function CompteLien({
805
805
  sortie,
@@ -1,8 +1,8 @@
1
- // Navigation applicative — colonne latérale, barre supérieure, fil d'Ariane,
1
+ // Navigation applicative : colonne latérale, barre supérieure, fil d'Ariane,
2
2
  // menu, pagination, feuille de source, zones défilantes.
3
3
  //
4
4
  // Rien ici n'a d'état propre : l'entrée courante, la page courante, l'item
5
- // sélectionné viennent de l'appelant. Aucun « use client », donc — c'est ce
5
+ // sélectionné viennent de l'appelant. Aucun « use client », donc : c'est ce
6
6
  // qui permet à une colonne de navigation Next de rester rendue au serveur.
7
7
  //
8
8
  // ⚠️ Les OVERLAYS (tiroir, modale, notification) sont dans overlays.tsx : eux
@@ -26,7 +26,7 @@ type Btn = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'>
26
26
  /* ─── Colonne latérale ─────────────────────────────────────────────── */
27
27
 
28
28
  /** Une colonne, un filet, rien d'autre : ni ombre, ni rayon. Une ombre ferait
29
- * flotter une pièce qui ne bouge jamais. Elle défile pour son propre compte —
29
+ * flotter une pièce qui ne bouge jamais. Elle défile pour son propre compte :
30
30
  * une nav longue n'entraîne jamais le contenu. */
31
31
  export function Sidebar({ className, ...reste }: Div & { className?: string }) {
32
32
  return <aside className={cx('zv-sidebar', className)} {...reste} />
@@ -61,7 +61,7 @@ export interface SideLinkProps extends A {
61
61
  /** Pose `aria-current="page"` en plus de la classe : sans lui, l'entrée
62
62
  * courante est marquée pour l'œil et muette au lecteur d'écran. */
63
63
  courant?: boolean
64
- /** Rang mono ou glyphe, sur une colonne de largeur fixe — sinon les
64
+ /** Rang mono ou glyphe, sur une colonne de largeur fixe : sinon les
65
65
  * libellés se décalent d'un groupe à l'autre. */
66
66
  num?: ReactNode
67
67
  className?: string
@@ -85,7 +85,7 @@ export function SideLink({ courant, num, className, children, ...reste }: SideLi
85
85
 
86
86
  /** Elle REMPLACE la colonne sous le point de rupture : masquée au-dessus,
87
87
  * elle n'apparaît que dans le bloc mobile du CSS. Ne pas la conditionner en
88
- * JavaScript — le rendu serveur ne connaît pas la largeur de l'écran. */
88
+ * JavaScript : le rendu serveur ne connaît pas la largeur de l'écran. */
89
89
  export function Topbar({ className, ...reste }: Div & { className?: string }) {
90
90
  return <header className={cx('zv-topbar', className)} {...reste} />
91
91
  }
@@ -104,7 +104,7 @@ export function TopbarBurger({ className, type = 'button', ...reste }: Btn & { c
104
104
  return <button type={type} className={cx('zv-topbar__burger', className)} {...reste} />
105
105
  }
106
106
 
107
- /** Croix bordée du chrome — fermeture d'un tiroir, d'une feuille de source.
107
+ /** Croix bordée du chrome : fermeture d'un tiroir, d'une feuille de source.
108
108
  * Angles vifs : le rayon signale l'action, le chrome n'en est pas une. */
109
109
  export function ChromeButton({
110
110
  className,
@@ -125,7 +125,7 @@ export interface CrumbsProps extends Omit<A, 'children'> {
125
125
  className?: string
126
126
  }
127
127
 
128
- /** ⚠️ Le fil ENTIER est cliquable — c'est un seul nœud, pas une liste de
128
+ /** ⚠️ Le fil ENTIER est cliquable : c'est un seul nœud, pas une liste de
129
129
  * liens. Sans `href`, il se rend en `<span>` : un `<a>` sans cible reste
130
130
  * annoncé comme un lien et déçoit. */
131
131
  export function Crumbs({
@@ -159,7 +159,7 @@ export function Crumbs({
159
159
  /* ─── Menu déroulant ───────────────────────────────────────────────── */
160
160
 
161
161
  /** À poser dans un `SelectAnchor` (forms) : c'est l'enveloppe qui ancre le
162
- * menu, pas le déclencheur — l'ancrer sur le déclencheur casse sa largeur
162
+ * menu, pas le déclencheur, l'ancrer sur le déclencheur casse sa largeur
163
163
  * maximale calculée sur la fenêtre. */
164
164
  export function Menu({ className, ...reste }: Div & { className?: string; role?: string }) {
165
165
  return <div role="listbox" className={cx('zv-menu', className)} {...reste} />
@@ -185,13 +185,13 @@ export function MenuItem({ actif, className, type = 'button', ...reste }: MenuIt
185
185
 
186
186
  /* ─── Popover ──────────────────────────────────────────────────────── */
187
187
 
188
- /** Surface ancrée LIBRE — aide contextuelle, mini-formulaire de filtre.
188
+ /** Surface ancrée LIBRE : aide contextuelle, mini-formulaire de filtre.
189
189
  * Même ancre que le menu : à poser dans un `SelectAnchor`, rendu à
190
190
  * l'ouverture seulement (`{ouvert && <Popover>…}`). Ce n'est PAS une
191
191
  * infobulle : le popover s'ouvre au CLIC et peut porter de l'interactif ;
192
192
  * l'infobulle (retours) se montre au survol et n'en porte jamais.
193
193
  * ⚠️ Fermeture par clic extérieur et Échap : au composant appelant, comme
194
- * pour le menu — et le déclencheur porte aria-expanded. */
194
+ * pour le menu, et le déclencheur porte aria-expanded. */
195
195
  export function Popover({ className, ...reste }: Div & { className?: string }) {
196
196
  return <div className={cx('zv-popover', className)} {...reste} />
197
197
  }
@@ -202,9 +202,9 @@ export interface TabsProps<T extends string> {
202
202
  onglets: readonly { id: T; label: ReactNode; compte?: ReactNode }[]
203
203
  actif: T
204
204
  onChange: (id: T) => void
205
- /** « puces » (défaut) : la rangée de Seg — le motif des FILTRES, qui
206
- * restreignent une collection. « soulignes » : l'onglet au trait —
207
- * arbitré le 25/08/2026 (2 px, accent) — pour les VUES d'un même objet
205
+ /** « puces » (défaut) : la rangée de Seg, le motif des FILTRES, qui
206
+ * restreignent une collection. « soulignes » : l'onglet au trait (
207
+ * arbitré le 25/08/2026 (2 px, accent)) pour les VUES d'un même objet
208
208
  * (Aperçu / Sources). */
209
209
  variante?: 'puces' | 'soulignes'
210
210
  /** Rangée qui défile au lieu de passer à la ligne (variante puces ; la
@@ -216,7 +216,7 @@ export interface TabsProps<T extends string> {
216
216
  }
217
217
 
218
218
  /** Deux dessins, une seule grammaire d'état. En puces, ce composant n'ajoute
219
- * que l'ARIA au-dessus de `Seg` ; en soulignés, il pose `.zv-tabs-line` —
219
+ * que l'ARIA au-dessus de `Seg` ; en soulignés, il pose `.zv-tabs-line` :
220
220
  * l'arbitrage longtemps en attente, tranché le 25/08/2026 (DECISIONS.md).
221
221
  * Les deux portent le contrat clavier du rôle : la rangée est UNE étape de
222
222
  * tabulation (roving tabindex), les flèches passent d'un onglet à l'autre
@@ -371,7 +371,7 @@ export function CitePanel({
371
371
 
372
372
  export interface ScrollProps extends Div {
373
373
  /** Barre fine, aux couleurs du système. OPT-IN : par défaut la barre native
374
- * est conservée — sur macOS et iOS, l'habiller la rend permanente et elle
374
+ * est conservée, sur macOS et iOS, l'habiller la rend permanente et elle
375
375
  * mange une colonne. */
376
376
  habillee?: boolean
377
377
  className?: string
@@ -394,14 +394,14 @@ export function Scroll({ habillee, className, ...reste }: ScrollProps) {
394
394
 
395
395
  export interface TabbarProps extends Div {
396
396
  /** Le nom de la barre (« Sections ») : un <nav> anonyme de plus est une
397
- * énigme au lecteur d'écran — même règle que le label de `Carousel`. */
397
+ * énigme au lecteur d'écran, même règle que le label de `Carousel`. */
398
398
  label: string
399
399
  className?: string
400
400
  }
401
401
 
402
402
  /** Barre basse de navigation, pour une application à trois ou quatre
403
403
  * sections. N'existe que SOUS le point de rupture : au-dessus, la colonne
404
- * latérale est là. CINQ entrées au maximum — au-delà, c'est le tiroir.
404
+ * latérale est là. CINQ entrées au maximum : au-delà, c'est le tiroir.
405
405
  * ⚠️ Elle est fixe : le gabarit qui la pose doit donner au contenu un
406
406
  * padding-bottom à sa hauteur, sinon la dernière ligne se cache dessous. */
407
407
  export function Tabbar({ label, className, ...reste }: TabbarProps) {
@@ -411,7 +411,7 @@ export function Tabbar({ label, className, ...reste }: TabbarProps) {
411
411
  export interface TabbarItemProps extends A {
412
412
  /** Section courante : pose `aria-current="page"`, que le CSS suit. */
413
413
  courant?: boolean
414
- /** L'icône au-dessus du libellé — un `<Icon>` du paquet. */
414
+ /** L'icône au-dessus du libellé : un `<Icon>` du paquet. */
415
415
  icone?: ReactNode
416
416
  className?: string
417
417
  children?: ReactNode
@@ -1,13 +1,13 @@
1
1
  'use client'
2
2
 
3
- // Enrobages — voile, tiroir, modale, notification flottante.
3
+ // Enrobages : voile, tiroir, modale, notification flottante.
4
4
  //
5
5
  // SEUL fichier du paquet marqué « use client », avec un hook. Tout ce qui vit
6
6
  // ici vole le regard à la page : il faut une touche Échap, un piège de focus
7
7
  // et un verrou de défilement, et rien de cela ne s'écrit en CSS.
8
8
  //
9
9
  // ⚠️ Ne PAS écrire `data-open={ouvert}` en React : cela rend `data-open="false"`,
10
- // que le sélecteur d'attribut du CSS considère comme PRÉSENT — le panneau
10
+ // que le sélecteur d'attribut du CSS considère comme PRÉSENT : le panneau
11
11
  // resterait ouvert. Ces composants emploient la classe `--open`, prévue pour ça.
12
12
 
13
13
  import { useCallback, useEffect, useRef } from 'react'
@@ -22,7 +22,7 @@ const FOCUSABLES =
22
22
  export interface UseOverlayOptions {
23
23
  ouvert: boolean
24
24
  onClose: () => void
25
- /** Le nœud à piéger. Sans lui, pas de piège — mais Échap fonctionne quand
25
+ /** Le nœud à piéger. Sans lui, pas de piège : mais Échap fonctionne quand
26
26
  * même. */
27
27
  panneau?: RefObject<HTMLElement | null>
28
28
  /** Verrouille le défilement du document. Vrai pour une modale, vrai pour un
@@ -46,7 +46,7 @@ export function useOverlay({ ouvert, onClose, panneau, verrouille = true }: UseO
46
46
  // ⚠️ Le document du PANNEAU, pas `document` : un enrobage peut vivre dans
47
47
  // un autre document que celui du script (planche de composants rendue dans
48
48
  // un iframe, fenêtre détachée). Écouter `document` y poserait la touche
49
- // Échap sur la page hôte — elle fermerait la modale d'à côté et pas
49
+ // Échap sur la page hôte : elle fermerait la modale d'à côté et pas
50
50
  // celle-ci. Sans panneau, on retombe sur le document courant.
51
51
  const docu = cible?.ownerDocument ?? document
52
52
  origine.current = docu.activeElement as HTMLElement | null
@@ -105,7 +105,7 @@ export function Scrim({ className, ...reste }: Div & { className?: string }) {
105
105
  export interface DrawerProps extends Omit<Div, 'title'> {
106
106
  ouvert: boolean
107
107
  onClose: () => void
108
- /** Nom du dialogue — ce que le lecteur d'écran annonce à l'ouverture. */
108
+ /** Nom du dialogue : ce que le lecteur d'écran annonce à l'ouverture. */
109
109
  label?: string
110
110
  /** Croix de fermeture posée en absolu par-dessus la tête du panneau. */
111
111
  fermeture?: ReactNode
@@ -114,7 +114,7 @@ export interface DrawerProps extends Omit<Div, 'title'> {
114
114
  }
115
115
 
116
116
  /** ⚠️ Le tiroir n'existe QUE sous le point de rupture : au-dessus, la
117
- * navigation est une colonne et il n'y a rien à ouvrir. Le CSS s'en charge —
117
+ * navigation est une colonne et il n'y a rien à ouvrir. Le CSS s'en charge :
118
118
  * ne pas conditionner le rendu sur la largeur en JavaScript, le serveur ne
119
119
  * la connaît pas. */
120
120
  export function Drawer({
@@ -156,11 +156,11 @@ export interface ModalProps extends Omit<Div, 'title'> {
156
156
  titre?: ReactNode
157
157
  /** Actions de tête, à droite du titre (la croix, en général). */
158
158
  actions?: ReactNode
159
- /** Pied d'actions. Il reste visible quand le corps défile — c'est tout
159
+ /** Pied d'actions. Il reste visible quand le corps défile : c'est tout
160
160
  * l'intérêt du plafond de hauteur. */
161
161
  pied?: ReactNode
162
162
  /** Modale destructive : le filet HAUT porte l'avertissement, pas un fond
163
- * rouge — une surface d'alerte pleine rendrait le texte pénible à lire. */
163
+ * rouge, une surface d'alerte pleine rendrait le texte pénible à lire. */
164
164
  danger?: boolean
165
165
  className?: string
166
166
  children?: ReactNode
@@ -231,7 +231,7 @@ export interface ToastProps extends Div {
231
231
 
232
232
  /** ⚠️ Ne remplace PAS `Alert` : une erreur de formulaire se dit à côté du
233
233
  * champ, jamais dans un coin qui disparaît. Réserver ce composant à ce qui
234
- * est vrai APRÈS COUP — « copié », « enregistré », « envoyé ». */
234
+ * est vrai APRÈS COUP : « copié », « enregistré », « envoyé ». */
235
235
  export function Toast({ ton, className, ...reste }: ToastProps) {
236
236
  return <div className={cx('zv-toast', ton && `zv-toast--${ton}`, className)} {...reste} />
237
237
  }
@@ -1,12 +1,12 @@
1
1
  'use client'
2
2
 
3
- // Sélection DESSINÉE — le déclencheur a l'apparence d'un champ, et la liste
3
+ // Sélection DESSINÉE : le déclencheur a l'apparence d'un champ, et la liste
4
4
  // est peinte par le paquet.
5
5
  //
6
6
  // ⚠️ POURQUOI CE COMPOSANT EXISTE, ALORS QUE `Select` (natif) EST LÀ.
7
7
  // Un <select> natif ne peut pas être habillé : sa liste déroulée est peinte
8
8
  // par le SYSTÈME, pas par la page. Sur un macOS en mode sombre elle sort en
9
- // menu noir au milieu d'une interface claire — aucun CSS ne corrige cela.
9
+ // menu noir au milieu d'une interface claire : aucun CSS ne corrige cela.
10
10
  //
11
11
  // ⚠️ CE QUE ÇA COÛTE, ET QUI EST PAYÉ ICI. En remplaçant le contrôle natif,
12
12
  // on perd le clavier, l'annonce au lecteur d'écran et la liste système. Tout
@@ -39,7 +39,7 @@ export interface SelectMenuProps<T extends string = string> {
39
39
  onChange: (valeur: T) => void
40
40
  /** Affiché quand rien n'est choisi. */
41
41
  placeholder?: string
42
- /** Identifiant du déclencheur — à faire correspondre au `htmlFor` du champ. */
42
+ /** Identifiant du déclencheur : à faire correspondre au `htmlFor` du champ. */
43
43
  id?: string
44
44
  /** Nom du contrôle, quand aucun intitulé visible ne le désigne. */
45
45
  label?: string
@@ -1,10 +1,10 @@
1
- // Surfaces — halos, sols quadrillés, voiles de fondu, bandeau manifeste,
1
+ // Surfaces : halos, sols quadrillés, voiles de fondu, bandeau manifeste,
2
2
  // glyphes. C'est ce qui distingue une page Zevra d'une page blanche bien
3
3
  // composée.
4
4
  //
5
5
  // ⚠️ TOUT est DÉCORATIF : posé en absolu derrière le contenu, sans capter le
6
6
  // pointeur, et masqué aux lecteurs d'écran. Ces composants posent
7
- // `aria-hidden` eux-mêmes — ne pas y mettre de texte à lire.
7
+ // `aria-hidden` eux-mêmes : ne pas y mettre de texte à lire.
8
8
  //
9
9
  // ⚠️ Tous supposent un parent `position: relative; overflow: hidden`. Sans le
10
10
  // second, un halo de 1400px de large fait déborder la page.
@@ -17,7 +17,7 @@ type Div = Omit<HTMLAttributes<HTMLDivElement>, 'className'>
17
17
  /* ─── Halo ambiant ─────────────────────────────────────────────────── */
18
18
 
19
19
  /** Le violet et le cyan du spectre, très dilués. Ce n'est PAS un emploi du
20
- * spectre en aplat : c'est une lumière — elle ne compte pas dans les trois
20
+ * spectre en aplat : c'est une lumière, elle ne compte pas dans les trois
21
21
  * emplois autorisés. */
22
22
  export function Halo({
23
23
  place,
@@ -101,7 +101,7 @@ export function PanelNavy({ className, ...reste }: Div & { className?: string })
101
101
  }
102
102
 
103
103
  /** Repeint un texte pour une surface sombre. `Band` le fait déjà pour son
104
- * contenu courant — cette classe sert aux cas isolés. */
104
+ * contenu courant : cette classe sert aux cas isolés. */
105
105
  export function TextOnDark({ className, ...reste }: Div & { className?: string }) {
106
106
  return <div className={cx('zv-text-on-dark', className)} {...reste} />
107
107
  }
@@ -143,15 +143,15 @@ export type Animation =
143
143
  | 'dot-active'
144
144
  | 'progress'
145
145
 
146
- /** Les neuf animations du paquet, nommées — plus un dosage. ⚠️ Elles sont
146
+ /** Les neuf animations du paquet, nommées : plus un dosage. ⚠️ Elles sont
147
147
  * pilotées AU DÉFILEMENT et réservées à la vitrine : le chrome applicatif
148
148
  * n'anime rien. `prefers-reduced-motion` les coupe toutes, globalement,
149
149
  * depuis base.css.
150
150
  *
151
151
  * `option` échelonne les entrées d'une même rangée (`pop`, rangs 2 à 6),
152
- * donne le coin d'arrivée (`converge` : tr, bl, br — le défaut vient du
152
+ * donne le coin d'arrivée (`converge` : tr, bl, br (le défaut vient du
153
153
  * haut-gauche), ou DOSE l'entrée (`rise` + « doux » : douze pixels au
154
- * lieu de trente-deux, pour les blocs des pages intérieures — la home
154
+ * lieu de trente-deux, pour les blocs des pages intérieures) la home
155
155
  * garde l'entrée pleine). Passée à une autre animation, elle est
156
156
  * ignorée. */
157
157
  export function anim(
@@ -170,10 +170,10 @@ export function anim(
170
170
  export interface SocialFrameProps extends Div {
171
171
  /** paysage 1200×630 (OpenGraph, lien LinkedIn) · carre 1080×1080 (post) ·
172
172
  * story 1080×1920. FIGÉS en pixels : ce sont des planches qu'on CAPTURE,
173
- * pas des pages — elles ignorent le point de rupture, et c'est voulu. */
173
+ * pas des pages, elles ignorent le point de rupture, et c'est voulu. */
174
174
  format?: 'paysage' | 'carre' | 'story'
175
175
  /** La planche sombre : le manifeste en fond. Les règles du fond sombre
176
- * s'appliquent — labels en `Mono ton="on-dark"`, jamais les neutres
176
+ * s'appliquent : labels en `Mono ton="on-dark"`, jamais les neutres
177
177
  * inversés. */
178
178
  manifeste?: boolean
179
179
  /** Le pied : wordmark à gauche, adresse en mono à droite. */
@@ -184,7 +184,7 @@ export interface SocialFrameProps extends Div {
184
184
 
185
185
  /** Planche d'export pour les réseaux. ⚠️ La règle « jamais de faux
186
186
  * contenu » s'y applique au premier chef : un visuel social EST une
187
- * publication — pas de chiffre inventé, pas de citation fabriquée. */
187
+ * publication, pas de chiffre inventé, pas de citation fabriquée. */
188
188
  export function SocialFrame({
189
189
  format = 'paysage',
190
190
  manifeste,
@@ -1,8 +1,8 @@
1
- // Typographie — titres, corps, kickers, labels mono, filets.
1
+ // Typographie : titres, corps, kickers, labels mono, filets.
2
2
  //
3
3
  // La HIÉRARCHIE VISUELLE et le NIVEAU SÉMANTIQUE sont deux choses : une page
4
4
  // n'a qu'un <h1>, mais elle peut avoir plusieurs blocs à la taille du h1.
5
- // D'où `as` sur les titres — la taille est un choix de mise en page, la
5
+ // D'où `as` sur les titres : la taille est un choix de mise en page, la
6
6
  // balise un choix de structure. Les confondre casse le plan du document pour
7
7
  // un lecteur d'écran sans que rien ne se voie à l'écran.
8
8
 
@@ -59,7 +59,7 @@ export const CardTitle = (p: Omit<HeadingProps, 'taille'>) => (
59
59
  <Heading taille="carte" {...p} />
60
60
  )
61
61
 
62
- /** Chiffre ou mot posé pour sa PRÉSENCE, hors de la hiérarchie des titres —
62
+ /** Chiffre ou mot posé pour sa PRÉSENCE, hors de la hiérarchie des titres,
63
63
  * d'où le `<div>` : un chiffre géant annoncé comme titre de section pollue
64
64
  * le plan du document. */
65
65
  export function Display({ className, ...reste }: Div & { className?: string }) {
@@ -131,7 +131,7 @@ export function Em({
131
131
  * `AnchorHTMLAttributes` et non `HTMLAttributes` : sans quoi ils refusent
132
132
  * `target`, `rel`, `download` et `hreflang`. Un lien sortant ne peut alors
133
133
  * pas porter `rel="noopener"`, et un lien qui doit s'ouvrir à côté ne le
134
- * peut pas non plus — une app consommatrice a dû écrire la classe à la main
134
+ * peut pas non plus : une app consommatrice a dû écrire la classe à la main
135
135
  * pour cette seule raison. (0.19.0) */
136
136
  export function Link({
137
137
  className,
@@ -207,14 +207,14 @@ export interface MonoProps extends Span {
207
207
  /** Le ton, pas la taille : `label` est le gris d'intitulé, `ink` l'encre
208
208
  * pleine, `muted` le gris secondaire, `on-dark` la bande manifeste. */
209
209
  ton?: 'label' | 'ink' | 'muted' | 'on-dark'
210
- /** Le plus petit palier — badge dense, compteur de coque. */
210
+ /** Le plus petit palier : badge dense, compteur de coque. */
211
211
  micro?: boolean
212
212
  className?: string
213
213
  }
214
214
 
215
215
  /** Capitales espacées. ⚠️ Réservé aux LIBELLÉS : une date ou un nom de
216
216
  * fichier passés en capitales espacées deviennent illisibles et s'alignent
217
- * mal — pour ceux-là, c'est `Meta` (bas de casse, chasse tabulaire). */
217
+ * mal, pour ceux-là, c'est `Meta` (bas de casse, chasse tabulaire). */
218
218
  export function Mono({ ton, micro, className, ...reste }: MonoProps) {
219
219
  return (
220
220
  <span
package/src/base.css CHANGED
@@ -1,9 +1,9 @@
1
- /* Zevra UI — socle : reset, document, typographie, signature
1
+ /* Zevra UI, socle : reset, document, typographie, signature
2
2
  * Dépend de tokens.css. À charger en premier.
3
3
  *
4
4
  * Chaque valeur ci-dessous est relevée dans les maquettes de la passation du
5
5
  * 23/08/2026. Quand deux planches divergent, l'arbitrage est écrit dans
6
- * DECISIONS.md — jamais tranché en silence.
6
+ * DECISIONS.md : jamais tranché en silence.
7
7
  *
8
8
  * POINT DE RUPTURE : 768px. Il ne vient PAS du relevé (les cinq maquettes sont
9
9
  * à largeur fixe, sans une seule media query). C'est la convention `md` du
@@ -32,7 +32,7 @@ body {
32
32
  /* ─── Liens ─────────────────────────────────────────────────────────────
33
33
  * Restreint aux liens SANS classe. Les maquettes stylent `a` nu, ce qui
34
34
  * repeint le libellé des boutons au survol (a:hover, 0-1-1, l'emporte sur
35
- * .zv-btn, 0-1-0) — bug visible dans Main.html et signalé au relevé. Le
35
+ * .zv-btn, 0-1-0) : bug visible dans Main.html et signalé au relevé. Le
36
36
  * périmètre est réduit plutôt que neutralisé par un :hover inventé. */
37
37
 
38
38
  a:not([class]) {
@@ -55,7 +55,7 @@ a:not([class]):hover {
55
55
 
56
56
  /* ─── Accessibilité ─────────────────────────────────────────────────────
57
57
  * HORS RELEVÉ, et assumé : aucune des cinq maquettes ne déclare d'état de
58
- * focus. Un système livré sans focus clavier est inutilisable au clavier —
58
+ * focus. Un système livré sans focus clavier est inutilisable au clavier :
59
59
  * on ne réplique pas cette lacune. Seules des valeurs existantes sont
60
60
  * employées. */
61
61
 
@@ -64,7 +64,7 @@ a:not([class]):hover {
64
64
  outline-offset: 2px;
65
65
  }
66
66
 
67
- /* Texte pour lecteurs d'écran seulement — la géométrie standard du motif,
67
+ /* Texte pour lecteurs d'écran seulement : la géométrie standard du motif,
68
68
  * aucune valeur de charte. HORS RELEVÉ (25/08/2026) : ajouté avec la roue,
69
69
  * dont le libellé d'annonce doit être un TEXTE dans le flux (un aria-label
70
70
  * sur un span vide n'est pas annoncé par tous les lecteurs). */
@@ -161,7 +161,7 @@ a:not([class]):hover {
161
161
  color: var(--accent-deep);
162
162
  font-weight: 400;
163
163
  /* Le composant `Em` rend un vrai <em>, que le navigateur penche par
164
- * défaut — et AUCUNE italique n'est chargée : Outfit n'en a pas, il en
164
+ * défaut, et AUCUNE italique n'est chargée : Outfit n'en a pas, il en
165
165
  * fabrique donc une oblique de synthèse. Sur un titre de 76px, ça se voit.
166
166
  * La charte ne demande à cette classe qu'un changement de couleur et de
167
167
  * graisse ; on remet donc la droite. (`.zv-inset em` portait déjà cette
@@ -220,7 +220,7 @@ a:not([class]):hover {
220
220
  }
221
221
 
222
222
  /* 12,5px : la note d'accompagnement d'un bloc ou d'une carte de
223
- * documentation. Le plus petit palier de texte courant du système — en
223
+ * documentation. Le plus petit palier de texte courant du système : en
224
224
  * dessous, on passe en label capitales. */
225
225
  .zv-note {
226
226
  margin: 0;
@@ -268,7 +268,7 @@ a:not([class]):hover {
268
268
 
269
269
  /* Accroche produit, posée sous le nom d'un glyphe : « Votre savoir,
270
270
  * interrogeable en une phrase ». Moins interlettrée que le label qu'elle
271
- * suit (0.14em contre 0.2em) — sinon la phrase se disloque. */
271
+ * suit (0.14em contre 0.2em) : sinon la phrase se disloque. */
272
272
  .zv-tagline {
273
273
  font-family: var(--font-mono);
274
274
  text-transform: uppercase;
@@ -296,7 +296,7 @@ a:not([class]):hover {
296
296
  width: var(--spec-filet);
297
297
  height: 2px;
298
298
  background: var(--spectre);
299
- /* Angle vif comme tout le reste depuis le 25/08 — sur 2px de haut, le
299
+ /* Angle vif comme tout le reste depuis le 25/08 : sur 2px de haut, le
300
300
  * rayon d'1px du relevé ne se voyait de toute façon pas. */
301
301
  border-radius: 0;
302
302
  }
@@ -361,7 +361,7 @@ a:not([class]):hover {
361
361
  }
362
362
 
363
363
  /* .zv-body garde 15px : c'est déjà la taille du chapô mobile. Seule
364
- * l'interligne se resserre — et --dense reste distinct, à 13px/1.45. */
364
+ * l'interligne se resserre : et --dense reste distinct, à 13px/1.45. */
365
365
  .zv-body {
366
366
  line-height: 1.55;
367
367
  }