@zevra/ui 0.8.0 → 0.9.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 (65) hide show
  1. package/DECISIONS.md +227 -0
  2. package/dist/actions.d.ts +13 -3
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +9 -2
  5. package/dist/actions.js.map +1 -1
  6. package/dist/catalogue.d.ts.map +1 -1
  7. package/dist/catalogue.js +160 -11
  8. package/dist/catalogue.js.map +1 -1
  9. package/dist/css-inline.d.ts.map +1 -1
  10. package/dist/css-inline.js +1 -1
  11. package/dist/css-inline.js.map +1 -1
  12. package/dist/data.d.ts +34 -1
  13. package/dist/data.d.ts.map +1 -1
  14. package/dist/data.js +27 -0
  15. package/dist/data.js.map +1 -1
  16. package/dist/feedback.d.ts +49 -0
  17. package/dist/feedback.d.ts.map +1 -1
  18. package/dist/feedback.js +24 -0
  19. package/dist/feedback.js.map +1 -1
  20. package/dist/forms.d.ts +36 -0
  21. package/dist/forms.d.ts.map +1 -1
  22. package/dist/forms.js +24 -0
  23. package/dist/forms.js.map +1 -1
  24. package/dist/icon-data.d.ts +1 -1
  25. package/dist/icon-data.d.ts.map +1 -1
  26. package/dist/icon-data.js +42 -0
  27. package/dist/icon-data.js.map +1 -1
  28. package/dist/icon.d.ts +1 -1
  29. package/dist/icon.js +1 -1
  30. package/dist/layout.d.ts +21 -0
  31. package/dist/layout.d.ts.map +1 -1
  32. package/dist/layout.js +18 -0
  33. package/dist/layout.js.map +1 -1
  34. package/dist/navigation.d.ts +45 -7
  35. package/dist/navigation.d.ts.map +1 -1
  36. package/dist/navigation.js +54 -7
  37. package/dist/navigation.js.map +1 -1
  38. package/dist/surfaces.d.ts +18 -0
  39. package/dist/surfaces.d.ts.map +1 -1
  40. package/dist/surfaces.js +6 -0
  41. package/dist/surfaces.js.map +1 -1
  42. package/icons/index.json +126 -0
  43. package/icons/sprite.svg +21 -0
  44. package/package.json +4 -3
  45. package/react/actions.tsx +28 -2
  46. package/react/catalogue.tsx +343 -17
  47. package/react/css-inline.ts +1 -1
  48. package/react/data.tsx +87 -1
  49. package/react/feedback.tsx +98 -0
  50. package/react/forms.tsx +90 -0
  51. package/react/icon-data.ts +63 -0
  52. package/react/icon.tsx +2 -2
  53. package/react/layout.tsx +39 -0
  54. package/react/navigation.tsx +108 -6
  55. package/react/surfaces.tsx +45 -0
  56. package/src/base.css +16 -0
  57. package/src/components.css +135 -3
  58. package/src/data.css +143 -1
  59. package/src/feedback.css +196 -37
  60. package/src/forms.css +137 -1
  61. package/src/layout.css +72 -0
  62. package/src/navigation.css +207 -12
  63. package/src/surfaces.css +76 -7
  64. package/src/tokens.css +6 -0
  65. package/tokens/index.js +2 -0
package/DECISIONS.md CHANGED
@@ -279,3 +279,230 @@ prototype `experience.html` compose toutes ses scènes produit en `span 5`
279
279
  (glyphe) + `span 7` (discours), et c'est cette répartition qui équilibre
280
280
  l'air entre le visuel et le texte. `zv-col-5` et `zv-col-7` complètent donc
281
281
  le relevé ; en 4+8, le texte nage et le glyphe s'exile au bord.
282
+
283
+ ---
284
+
285
+ ## L'angle vif (25/08/2026)
286
+
287
+ « Très carré, ça fait pro et inspire confiance » — arbitrage DA rendu sur
288
+ pièces. Les quatre tokens de rayon (`--r-btn`, `--r-pill`, `--r-panel`,
289
+ `--r-card`) sont ramenés à `0` et RESTENT nommés : rendre de la rondeur à
290
+ l'une des quatre intentions doit rester une ligne à changer, pas une chasse
291
+ dans douze fichiers. Seul survit le rond de FORME — pastille d'état, radio,
292
+ halo, bouton d'interrupteur exclu (passé au carré), roue incluse — parce
293
+ qu'il porte du sens là où le rayon ne portait que du style.
294
+
295
+ Cet arbitrage SUCCÈDE au « rayon des cartes — clos » ci-dessus : la
296
+ question tranchée là était la fidélité au relevé (16px contre 14 et 12),
297
+ celle-ci est une décision de DA qui lui passe devant.
298
+
299
+ ---
300
+
301
+ ## Compléments du 25/08/2026 (0.9.0) — les manques comblés, hors relevé
302
+
303
+ La demande : compléter le système au niveau d'une bibliothèque de référence
304
+ (états d'interaction, contrôles absents, formats), en restant dans la charte.
305
+ Tout ce qui suit est **HORS RELEVÉ** — dessiné dans les tokens existants,
306
+ chaque écart dit — et se rejuge sur pièces avec la DA. Un seul token entre
307
+ (`--font-code`, voir « Bloc de code ») ; aucune couleur nouvelle.
308
+
309
+ ### Les états de survol et d'appui des boutons
310
+
311
+ Aucune maquette ne donne d'état `:hover` ni `:active` aux trois boutons —
312
+ elles sont figées. Un bouton sans réaction se lit comme un décor (l'argument
313
+ qui a déjà tranché pour `.zv-chrome-btn`). Arbitrages :
314
+
315
+ - **le survol assombrit, l'appui assombrit davantage et rassoit** : les
316
+ teintes sont dérivées par `color-mix` (l'idiome que le voile emploie
317
+ déjà) des couleurs que le bouton porte déjà — le sommet de dégradé relevé
318
+ (`--zv-btn-haut`, #2b2bb4, sans token) et `--accent-deep`. Aucune teinte
319
+ nouvelle n'entre ;
320
+ - à l'appui, l'ombre TOMBE et le liseré s'éteint : le bouton se rassoit sur
321
+ la page au lieu de flotter — aucun déplacement, c'est la lumière qui dit
322
+ l'enfoncement ;
323
+ - le fantôme prend le filet `--accent` et l'appui pose `--accent-soft` en
324
+ fond ; l'encre s'éclaircit d'un cran (un aplat sombre ne peut pas
325
+ s'assombrir visiblement) ;
326
+ - une seule transition, la courbe maison, 160 ms — coupée par
327
+ `prefers-reduced-motion` (base.css) ;
328
+ - **aucun déplacement** : pas de translateY. Les entrées du système montent
329
+ au défilement ; un bouton qui saute au survol raconterait autre chose.
330
+
331
+ ### Le bouton destructif
332
+
333
+ La modale destructive porte son filet `--err`, mais son action de
334
+ confirmation restait un plein indigo : « Supprimer » s'y lisait comme
335
+ n'importe quelle action. `zv-btn--danger` pose l'aplat `--err`, sans
336
+ dégradé ni ombre indigo. **Il compte comme LE bouton plein de l'écran** — la
337
+ règle « un seul plein par page » ne distingue pas les couleurs, elle compte
338
+ les aplats.
339
+
340
+ ### L'attente d'un bouton
341
+
342
+ Relevé de Mémoire, cette fois (le motif de balisage documenté dans
343
+ feedback.css) : pendant l'action, le libellé change (« Connexion… ») et le
344
+ bouton passe en `cursor-wait; opacity: .7` — valeurs reprises telles
345
+ quelles. Le paquet le code sur `[aria-busy="true"]` — l'attribut qui rend
346
+ l'attente audible ne coûte alors rien — et la façade React l'expose en prop
347
+ `occupe`, qui pose `aria-disabled` et AVALE le clic plutôt que de poser
348
+ `disabled` : un `disabled` sortirait le bouton du parcours clavier au
349
+ moment précis où l'attente doit s'annoncer. Mémoire, elle, désactive ; cet
350
+ écart-là est assumé.
351
+
352
+ ### L'onglet souligné — l'arbitrage attendu
353
+
354
+ La question ouverte depuis le relevé Mémoire est tranchée ainsi :
355
+
356
+ - **trait actif : 2 px, `--accent`**. 2 px est l'épaisseur des filets
357
+ dessinés du système (filet de spectre, rail d'étapes) ; l'accent est la
358
+ couleur de l'action, comme l'entrée de nav courante. PAS le spectre : un
359
+ quatrième emploi n'existe pas.
360
+ - l'onglet inactif est `--ink-2` graisse 500, l'actif `--accent` graisse
361
+ 600 — la grammaire exacte de `.zv-seg` ;
362
+ - la rangée porte un filet bas `--line` d'un pixel que le trait actif
363
+ recouvre : l'onglet se lit comme posé SUR une ligne, pas souligné en l'air.
364
+
365
+ `.zv-tabs-line` vit dans navigation.css, à l'emplacement que la note « ce
366
+ module ne livre rien ici » réservait. La rangée de puces (`Tabs` en variante
367
+ par défaut) reste le motif des filtres ; l'onglet souligné est celui des
368
+ VUES d'un même objet (Aperçu / Sources).
369
+
370
+ ### Roue, squelette, infobulle — les trois manques assumés, livrés
371
+
372
+ feedback.css les refusait faute de matière première, et posait les
373
+ contraintes à respecter « le jour où la DA les tranche ». Ce jour : les
374
+ contraintes sont reprises telles quelles, les valeurs restantes sont
375
+ arbitrées ici.
376
+
377
+ - **La roue** (`zv-spinner`) : anneau de 16 px — la taille de `.zv-icon` —
378
+ trait de 2 (l'épaisseur du jeu d'icônes), piste `--accent-line`, secteur
379
+ `--accent`, rotation 0,8 s linéaire. La variante `--lg` fait 24 px, la
380
+ grille du jeu d'icônes. Elle ne REMPLACE pas `.zv-loading` : la phrase
381
+ qui dit le travail reste le motif par défaut, la roue est pour l'attente
382
+ courte et locale (un bouton, une cellule). `prefers-reduced-motion` la
383
+ fige — l'anneau interrompu reste lisible immobile. Son libellé d'annonce
384
+ est un texte masqué (`.zv-sr-only`, base.css), pas un `aria-label` sur un
385
+ span vide, que tous les lecteurs n'annoncent pas.
386
+ - **Le squelette** (`zv-skeleton`) : `--hairline`, comme exigé — pas de
387
+ balayage spectral. Pulsation d'OPACITÉ (1 → 0,55, 1,8 s) : animer la
388
+ couleur ferait un dégradé de gris que la charte n'a pas. La géométrie
389
+ vient du consommateur (largeur/hauteur en style) ; seule la variante
390
+ `--text` fixe une hauteur d'un cadratin de texte courant.
391
+ - **L'infobulle** (`zv-tooltip`) : la combinaison imposée — panneau blanc,
392
+ filet `--line`, ombre `--shadow-menu`, texte `--app-sm`/`--ink-2` —
393
+ posée AU-DESSUS de la cible, centrée, à 6 px (le pointeur vient d'en bas
394
+ et la masquerait dessous ; `--bas` existe pour le rare cas inverse),
395
+ plafonnée à 240 px et, sous le seuil, à l'écran. L'apparition attend
396
+ 300 ms — sans quoi traverser une rangée d'icônes fait clignoter une bulle
397
+ par icône — la disparition est immédiate. Trois exigences de WCAG 1.4.13
398
+ sont tenues : elle reste tant que le pointeur ou le focus y sont, elle
399
+ est survolable (le masquage par `visibility` évite les zones fantômes),
400
+ et Échap la ferme — la seule ligne de script du composant, posée sans
401
+ état. L'annonce passe par `aria-describedby`, câblée en identifiants
402
+ EXPLICITES comme le couple `htmlFor`/`id` de `Field` — pas de `useId`,
403
+ qui interdirait le rendu en composant serveur. Réservée à un COMPLÉMENT
404
+ dispensable : une commande n'a pas le droit de cacher son nom dans une
405
+ infobulle.
406
+
407
+ ### Le curseur (`zv-range`)
408
+
409
+ Aucune source. Dérivé de l'interrupteur redessiné : bouton CARRÉ de 14 px
410
+ (la taille exacte du bouton du switch), `--accent`, qui passe
411
+ `--accent-deep` au survol ; piste de 3 px (la hauteur de `.zv-progress`),
412
+ `--line`, remplie en `--accent` jusqu'au curseur. **Pas le spectre** : la
413
+ barre de progression est un des trois emplois, le curseur n'en est pas un.
414
+ Le remplissage se calcule dans la façade (pourcentage de position — une
415
+ géométrie, comme la largeur de `Progress`, pas une valeur de charte).
416
+
417
+ ### L'affixe de champ (`zv-affix`)
418
+
419
+ « 30 », seul dans un champ, ne dit pas s'il s'agit de jours ou d'euros. La
420
+ grammaire est celle de `.zv-search`, le seul cadre-à-champ-nu du relevé :
421
+ cadre `--line`, champ nu dedans, allumage `:focus-within` identique.
422
+ L'affixe est en mono `--label` — une unité est un libellé, pas une donnée.
423
+
424
+ ### Le dépliant (`zv-fold`)
425
+
426
+ La FAQ est le pliage de la vitrine ; l'applicatif n'en avait aucun (options
427
+ avancées, détail d'une ligne de journal). Construit sur `<details>` natif :
428
+ clavier et annonce viennent du navigateur, aucun JavaScript. Rangé dans
429
+ data.css — c'est du contenu qui se déplie, pas de la navigation. Le chevron
430
+ tourne, seule rotation du module, coupée par `prefers-reduced-motion`.
431
+
432
+ ### La touche (`zv-kbd`) et le bloc de code (`zv-codeblock`)
433
+
434
+ Une documentation d'interface cite des raccourcis et des commandes ; le
435
+ paquet n'avait ni l'un ni l'autre.
436
+
437
+ - La touche : `--panel`, filet `--line` avec un bas de 2 px qui fait le
438
+ relief de la touche — géométrie, pas ombre.
439
+ - **Le bloc de code introduit le seul token nouveau, `--font-code`**
440
+ (`ui-monospace / SF Mono / Menlo`). La raison est déjà écrite deux fois
441
+ dans le paquet : `--font-mono` (Space Grotesk) n'est PAS à chasse fixe, et
442
+ `tabular-nums` n'aligne que les chiffres — du code exige la chasse fixe
443
+ entière. Détourner `--font-mono` aurait menti ; un hexadécimal de pile de
444
+ polices en dur dans data.css aurait été la première valeur de charte hors
445
+ tokens. Le token est reflété dans le miroir JS, comme les autres.
446
+
447
+ ### Le popover (`zv-popover`)
448
+
449
+ Le menu déroulant impose ses items ; il n'existait aucune surface ancrée
450
+ LIBRE (aide contextuelle, mini-formulaire de filtre). Même ancre
451
+ (`.zv-select-anchor`), même surface que `.zv-menu` (filet `--line`, ombre
452
+ `--shadow-menu`), padding de panneau au lieu d'items. Fermeture par clic
453
+ extérieur et Échap : au composant, comme pour le menu.
454
+
455
+ ### Le carrousel (`zv-carousel`)
456
+
457
+ La bande de logos de la landing défile déjà — sans composant, chaque page
458
+ réinvente le sien. Défilement natif `scroll-snap`, AUCUN JavaScript : pas de
459
+ flèches (elles exigeraient un script et un arbitrage de dessin), pas de
460
+ points. La barre de défilement est masquée, le clavier passe par le focus du
461
+ conteneur. Un carrousel reste un aveu d'espace : la règle de variété lui
462
+ préfère une grille tant que le contenu tient.
463
+
464
+ ### Le bandeau d'annonce (`zv-banner`)
465
+
466
+ La bande au-dessus de la barre de navigation (annonce de version,
467
+ information réglementaire). `--accent-soft` sur filet `--accent-line` — le
468
+ ton information de l'alerte, en pleine largeur. Un seul par page, et
469
+ JAMAIS pour du marketing permanent : un bandeau qui ne part pas devient un
470
+ bourrelet de chrome.
471
+
472
+ ### La barre d'onglets mobile (`zv-tabbar`)
473
+
474
+ Mémoire navigue au tiroir ; une application à trois ou quatre sections
475
+ navigue plus vite par une barre basse. N'existe que SOUS le point de
476
+ rupture (au-dessus, la colonne latérale est là), cibles `--tap`, icône +
477
+ micro-label, état courant `--accent` sur `aria-current="page"`. Cinq
478
+ entrées au maximum — au-delà, c'est le tiroir.
479
+
480
+ ### Les formats sociaux (`zv-social`)
481
+
482
+ Les visuels LinkedIn/OpenGraph se refaisaient à la main, hors charte. Trois
483
+ gabarits FIGÉS en pixels — paysage 1200×630 (OpenGraph et lien LinkedIn),
484
+ carré 1080×1080 (post), story 1080×1920 — qui ne répondent à aucun point de
485
+ rupture : ce sont des planches d'export, pas des pages. Ils composent les
486
+ pièces existantes (bande manifeste, kicker, halo) et portent un pied
487
+ wordmark + adresse. La règle « jamais de faux contenu » s'y applique au
488
+ premier chef : un visuel social EST une publication.
489
+
490
+ ### Vingt et une icônes de plus
491
+
492
+ Le jeu passe de 34 à 55. Les règles du jeu ne bougent pas : trait 2, bouts
493
+ et jonctions arrondis, grille 24, `currentColor`. Les ajouts comblent les
494
+ trous les plus fréquents — `chevron-haut` manquait au chevron près, et le
495
+ catalogue citait `telecharger` en exemple sans qu'elle existe — plus le
496
+ vocabulaire du métier : balance, livre-ouvert, diplome. Aucune icône
497
+ pleine, aucune couleur.
498
+
499
+ ### Écarté, et pourquoi
500
+
501
+ - **Le code de vérification (OTP)** : aucun écran du groupe n'en pose. Le
502
+ livrer supposerait d'inventer la gestion de focus inter-cases sans aucune
503
+ source — le jour où un écran d'authentification à code existe, c'est lui
504
+ qui tranche.
505
+ - **Le sélecteur de date** : le natif (`.zv-input` sur `type="date"`) reste
506
+ le motif ; un calendrier maison est un projet, pas un complément.
507
+ - **La barre de commande (⌘K)** : composable avec Modal + Search + Menu ;
508
+ un composant dédié attendra un usage réel.
package/dist/actions.d.ts CHANGED
@@ -1,19 +1,29 @@
1
1
  import type { AnchorHTMLAttributes, ButtonHTMLAttributes, HTMLAttributes, ReactNode } from 'react';
2
- /** Variantes de bouton. Une seule pleine par écran — c'est la règle de charte. */
3
- export type BoutonVariante = 'plein' | 'fantome' | 'encre';
2
+ /** Variantes de bouton. Une seule pleine par écran — c'est la règle de
3
+ * charte, et « danger » COMPTE comme la pleine : la règle compte les
4
+ * aplats, pas les couleurs. « danger » est réservée à l'action destructive
5
+ * (la confirmation d'une modale de suppression), jamais à une action
6
+ * simplement importante. */
7
+ export type BoutonVariante = 'plein' | 'fantome' | 'encre' | 'danger';
4
8
  export interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {
5
9
  variante?: BoutonVariante;
6
10
  /** Hauteur réduite, pour une barre de navigation. */
7
11
  compact?: boolean;
8
12
  /** Pleine largeur — l'appel à l'action d'un écran mobile. */
9
13
  bloc?: boolean;
14
+ /** Attente pendant l'action : pose aria-busy et aria-disabled, et AVALE
15
+ * le clic — sans poser `disabled`, qui sortirait le bouton du parcours
16
+ * clavier au moment précis où l'attente doit s'annoncer.
17
+ * ⚠️ Le LIBELLÉ doit changer avec elle (« Connexion… ») — c'est le motif
18
+ * relevé de Mémoire : l'estompement seul ne dit pas ce qu'on attend. */
19
+ occupe?: boolean;
10
20
  /** Échappatoire assumée. À employer pour un ajustement de position, jamais
11
21
  * pour redéfinir une couleur ou une taille : c'est par là que les apps
12
22
  * recommencent à écrire leur propre charte. */
13
23
  className?: string;
14
24
  children?: ReactNode;
15
25
  }
16
- export declare function Button({ variante, compact, bloc, className, type, ...reste }: ButtonProps): import("react").JSX.Element;
26
+ export declare function Button({ variante, compact, bloc, occupe, className, type, disabled, onClick, ...reste }: ButtonProps): import("react").JSX.Element;
17
27
  export interface ButtonLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className'> {
18
28
  variante?: BoutonVariante;
19
29
  compact?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../react/actions.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,SAAS,EACV,MAAM,OAAO,CAAA;AAGd,kFAAkF;AAClF,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAA;AAQ1D,MAAM,WAAW,WACf,SAAQ,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAClE,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;oDAEgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED,wBAAgB,MAAM,CAAC,EACrB,QAAkB,EAClB,OAAO,EACP,IAAI,EACJ,SAAS,EACT,IAAe,EACf,GAAG,KAAK,EACT,EAAE,WAAW,+BAab;AAED,MAAM,WAAW,eACf,SAAQ,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAClE,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;oEACoE;AACpE,wBAAgB,UAAU,CAAC,EACzB,QAAkB,EAClB,OAAO,EACP,IAAI,EACJ,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,+BAYjB;AAED,MAAM,WAAW,cACf,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC;IACzD,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,+BAOxE;AAED,MAAM,WAAW,cACf,SAAQ,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAClE,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED;yEACyE;AACzE,wBAAgB,SAAS,CAAC,EACxB,SAAS,EACT,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,cAAc,+BAiBhB"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../react/actions.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,SAAS,EACV,MAAM,OAAO,CAAA;AAGd;;;;6BAI6B;AAC7B,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAA;AASrE,MAAM,WAAW,WACf,SAAQ,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAClE,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;;;6EAIyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;oDAEgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED,wBAAgB,MAAM,CAAC,EACrB,QAAkB,EAClB,OAAO,EACP,IAAI,EACJ,MAAM,EACN,SAAS,EACT,IAAe,EACf,QAAQ,EACR,OAAO,EACP,GAAG,KAAK,EACT,EAAE,WAAW,+BAyBb;AAED,MAAM,WAAW,eACf,SAAQ,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAClE,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;oEACoE;AACpE,wBAAgB,UAAU,CAAC,EACzB,QAAkB,EAClB,OAAO,EACP,IAAI,EACJ,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,+BAYjB;AAED,MAAM,WAAW,cACf,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC;IACzD,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,+BAOxE;AAED,MAAM,WAAW,cACf,SAAQ,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAClE,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED;yEACyE;AACzE,wBAAgB,SAAS,CAAC,EACxB,SAAS,EACT,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,cAAc,+BAiBhB"}
package/dist/actions.js CHANGED
@@ -4,9 +4,16 @@ const VARIANTE_BOUTON = {
4
4
  plein: 'zv-btn',
5
5
  fantome: 'zv-btn--ghost',
6
6
  encre: 'zv-btn--ink',
7
+ danger: 'zv-btn--danger',
7
8
  };
8
- export function Button({ variante = 'plein', compact, bloc, className, type = 'button', ...reste }) {
9
- return (_jsx("button", { type: type, className: cx(VARIANTE_BOUTON[variante], compact && 'zv-btn--sm', bloc && 'zv-btn--block', className), ...reste }));
9
+ export function Button({ variante = 'plein', compact, bloc, occupe, className, type = 'button', disabled, onClick, ...reste }) {
10
+ return (_jsx("button", { type: type, "aria-busy": occupe || undefined, "aria-disabled": occupe || undefined, disabled: disabled, onClick: occupe
11
+ ? (e) => {
12
+ // Le clic est avalé, pas interdit : un `disabled` sortirait le
13
+ // bouton du parcours clavier pendant l'annonce de l'attente.
14
+ e.preventDefault();
15
+ }
16
+ : onClick, className: cx(VARIANTE_BOUTON[variante], compact && 'zv-btn--sm', bloc && 'zv-btn--block', className), ...reste }));
10
17
  }
11
18
  /** Le même bouton, mais qui navigue. Un <a> et un <button> ne s'échangent pas :
12
19
  * l'un ouvre dans un onglet, se copie et s'indexe, l'autre non. */
@@ -1 +1 @@
1
- {"version":3,"file":"actions.js","sourceRoot":"","sources":["../react/actions.tsx"],"names":[],"mappings":";AAYA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAK5B,MAAM,eAAe,GAAmC;IACtD,KAAK,EAAE,QAAQ;IACf,OAAO,EAAE,eAAe;IACxB,KAAK,EAAE,aAAa;CACrB,CAAA;AAgBD,MAAM,UAAU,MAAM,CAAC,EACrB,QAAQ,GAAG,OAAO,EAClB,OAAO,EACP,IAAI,EACJ,SAAS,EACT,IAAI,GAAG,QAAQ,EACf,GAAG,KAAK,EACI;IACZ,OAAO,CACL,iBACE,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,EAAE,CACX,eAAe,CAAC,QAAQ,CAAC,EACzB,OAAO,IAAI,YAAY,EACvB,IAAI,IAAI,eAAe,EACvB,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAUD;oEACoE;AACpE,MAAM,UAAU,UAAU,CAAC,EACzB,QAAQ,GAAG,OAAO,EAClB,OAAO,EACP,IAAI,EACJ,SAAS,EACT,GAAG,KAAK,EACQ;IAChB,OAAO,CACL,YACE,SAAS,EAAE,EAAE,CACX,eAAe,CAAC,QAAQ,CAAC,EACzB,OAAO,IAAI,YAAY,EACvB,IAAI,IAAI,eAAe,EACvB,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAA;AACH,CAAC;AASD,MAAM,UAAU,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,EAAkB;IACvE,OAAO,CACL,cACE,SAAS,EAAE,EAAE,CAAC,YAAY,EAAE,MAAM,IAAI,mBAAmB,EAAE,SAAS,CAAC,KACjE,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAUD;yEACyE;AACzE,MAAM,UAAU,SAAS,CAAC,EACxB,SAAS,EACT,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACO;IACf,OAAO,CACL,aACE,SAAS,EAAE,EAAE,CACX,eAAe,EACf,SAAS,IAAI,wBAAwB,EACrC,SAAS,CACV,KACG,KAAK,aAER,QAAQ,EACT,eAAK,OAAO,EAAC,WAAW,iBAAa,MAAM,aACzC,eAAM,CAAC,EAAC,UAAU,GAAG,EACrB,eAAM,CAAC,EAAC,eAAe,GAAG,IACtB,IACJ,CACL,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../react/actions.tsx"],"names":[],"mappings":";AAYA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAS5B,MAAM,eAAe,GAAmC;IACtD,KAAK,EAAE,QAAQ;IACf,OAAO,EAAE,eAAe;IACxB,KAAK,EAAE,aAAa;IACpB,MAAM,EAAE,gBAAgB;CACzB,CAAA;AAsBD,MAAM,UAAU,MAAM,CAAC,EACrB,QAAQ,GAAG,OAAO,EAClB,OAAO,EACP,IAAI,EACJ,MAAM,EACN,SAAS,EACT,IAAI,GAAG,QAAQ,EACf,QAAQ,EACR,OAAO,EACP,GAAG,KAAK,EACI;IACZ,OAAO,CACL,iBACE,IAAI,EAAE,IAAI,eACC,MAAM,IAAI,SAAS,mBACf,MAAM,IAAI,SAAS,EAClC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EACL,MAAM;YACJ,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE;gBACJ,+DAA+D;gBAC/D,6DAA6D;gBAC7D,CAAC,CAAC,cAAc,EAAE,CAAA;YACpB,CAAC;YACH,CAAC,CAAC,OAAO,EAEb,SAAS,EAAE,EAAE,CACX,eAAe,CAAC,QAAQ,CAAC,EACzB,OAAO,IAAI,YAAY,EACvB,IAAI,IAAI,eAAe,EACvB,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAUD;oEACoE;AACpE,MAAM,UAAU,UAAU,CAAC,EACzB,QAAQ,GAAG,OAAO,EAClB,OAAO,EACP,IAAI,EACJ,SAAS,EACT,GAAG,KAAK,EACQ;IAChB,OAAO,CACL,YACE,SAAS,EAAE,EAAE,CACX,eAAe,CAAC,QAAQ,CAAC,EACzB,OAAO,IAAI,YAAY,EACvB,IAAI,IAAI,eAAe,EACvB,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAA;AACH,CAAC;AASD,MAAM,UAAU,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,EAAkB;IACvE,OAAO,CACL,cACE,SAAS,EAAE,EAAE,CAAC,YAAY,EAAE,MAAM,IAAI,mBAAmB,EAAE,SAAS,CAAC,KACjE,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAUD;yEACyE;AACzE,MAAM,UAAU,SAAS,CAAC,EACxB,SAAS,EACT,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACO;IACf,OAAO,CACL,aACE,SAAS,EAAE,EAAE,CACX,eAAe,EACf,SAAS,IAAI,wBAAwB,EACrC,SAAS,CACV,KACG,KAAK,aAER,QAAQ,EACT,eAAK,OAAO,EAAC,WAAW,iBAAa,MAAM,aACzC,eAAM,CAAC,EAAC,UAAU,GAAG,EACrB,eAAM,CAAC,EAAC,eAAe,GAAG,IACtB,IACJ,CACL,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"catalogue.d.ts","sourceRoot":"","sources":["../react/catalogue.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAsChD,MAAM,WAAW,OAAO;IACtB,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAA;IACd,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,SAAS,CAAA;IAChB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd;+BAC2B;IAC3B,MAAM,EAAE,QAAQ,GAAG,YAAY,GAAG,SAAS,CAAA;IAC3C,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,OAAO,EAAE,CAAA;CACpB;AA+KD,eAAO,MAAM,SAAS,EAAE,eAAe,EAs/CtC,CAAA;AAMD,yEAAyE;AACzE,eAAO,MAAM,QAAQ,EAAE,CAAC,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EAEpD,CAAA"}
1
+ {"version":3,"file":"catalogue.d.ts","sourceRoot":"","sources":["../react/catalogue.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAyChD,MAAM,WAAW,OAAO;IACtB,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAA;IACd,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,SAAS,CAAA;IAChB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd;+BAC2B;IAC3B,MAAM,EAAE,QAAQ,GAAG,YAAY,GAAG,SAAS,CAAA;IAC3C,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,OAAO,EAAE,CAAA;CACpB;AA2OD,eAAO,MAAM,SAAS,EAAE,eAAe,EA6vDtC,CAAA;AAMD,yEAAyE;AACzE,eAAO,MAAM,QAAQ,EAAE,CAAC,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EAEpD,CAAA"}
package/dist/catalogue.js CHANGED
@@ -16,15 +16,15 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
16
16
  import { useState } from 'react';
17
17
  import { Button, ButtonLink, ButtonRow, LinkArrow } from './actions.js';
18
18
  import { Callout, Card, Check, Divider, Ent, EntText, Faq, FaqItem, Flow, FlowStep, Glass, IndexItem, IndexList, Inset, MiniStat, MiniStatItem, Pill, Price, Quote, Rows, RowsItem, Src, SrcRow, StatCard, Tag, UiPreview, } from './content.js';
19
- import { Avatar, Break, Cell, Chip, ChipRow, Cite, CiteRow, FileType, KV, KVRow, List, ListMain, ListName, ListRow, Meta, MetaRow, Row, Table, TableHead, Trunc, Unwrap, } from './data.js';
20
- import { Alert, Badge, Caret, Dot, Empty, Loading, Progress, Status, } from './feedback.js';
21
- import { Checkbox, Drop, Field, Input, Radio, Search, Seg, SegGroup, Select, SelectAnchor, SelectTrigger, Switch, Textarea, } from './forms.js';
19
+ import { Avatar, Break, Cell, Chip, ChipRow, Cite, CiteRow, Code, CodeBlock, FileType, Fold, KV, KVRow, Kbd, List, ListMain, ListName, ListRow, Meta, MetaRow, Row, Table, TableHead, Trunc, Unwrap, } from './data.js';
20
+ import { Alert, Badge, Caret, Dot, Empty, Loading, Progress, Skeleton, Spinner, Status, Tooltip, } from './feedback.js';
21
+ import { Checkbox, Drop, Field, Input, InputAffix, Radio, Range, Search, Seg, SegGroup, Select, SelectAnchor, SelectTrigger, Switch, Textarea, } from './forms.js';
22
22
  import { ICONES, Icon, IconButton, NOMS_ICONES } from './icon.js';
23
- import { CTA, Col, Footer, FooterMini, Grid, Grid12, Gutter, Hero, Nav, NavLink, NavLinks, NavLogo, ProofBar, SectionHead, SectionRule, SheetSection, Stat, Stats, Step, Steps, } from './layout.js';
24
- import { ChromeButton, CitePanel, Crumbs, Menu, MenuItem, Pager, PagerArrow, PagerRank, PagerStep, Scroll, SideGroup, SideLink, Sidebar, SidebarFoot, SidebarHead, Tabs, Topbar, TopbarTitle, } from './navigation.js';
23
+ import { Banner, CTA, Carousel, CarouselItem, Col, Footer, FooterMini, Grid, Grid12, Gutter, Hero, Nav, NavLink, NavLinks, NavLogo, ProofBar, SectionHead, SectionRule, SheetSection, Stat, Stats, Step, Steps, } from './layout.js';
24
+ import { ChromeButton, CitePanel, Crumbs, Menu, MenuItem, Pager, PagerArrow, PagerRank, PagerStep, Popover, Scroll, SideGroup, SideLink, Sidebar, SidebarFoot, SidebarHead, Tabs, Topbar, TopbarTitle, } from './navigation.js';
25
25
  import { Drawer, Modal, Toast, Toasts } from './overlays.js';
26
26
  import { SelectMenu } from './select-menu.js';
27
- import { Band, Fade, Glyph, GridFloor, Halo, PanelNavy, TextOnDark, anim } from './surfaces.js';
27
+ import { Band, Fade, Glyph, GridFloor, Halo, PanelNavy, SocialFrame, TextOnDark, anim, } from './surfaces.js';
28
28
  import { Body, CardTitle, Display, Em, H1, H2, H3, Heading, Kicker, Lead, Link, Mono, Note, Rule, Spec, Tagline, } from './typography.js';
29
29
  /* ── Démonstrations qui ont besoin d'état ─────────────────────────── */
30
30
  function DemoSwitch() {
@@ -81,6 +81,22 @@ function DemoDrop() {
81
81
  setSurvol(false);
82
82
  } }));
83
83
  }
84
+ function DemoRange() {
85
+ const [sources, setSources] = useState(12);
86
+ return (_jsx("div", { style: { maxWidth: 320 }, children: _jsx(Field, { label: "SOURCES INTERROG\u00C9ES", htmlFor: "ex-range", help: `${sources} sources sur 17`, children: _jsx(Range, { id: "ex-range", min: 1, max: 17, value: sources, onChange: (e) => setSources(Number(e.currentTarget.value)) }) }) }));
87
+ }
88
+ function DemoTabsLine() {
89
+ const [actif, setActif] = useState('apercu');
90
+ return (_jsx(Tabs, { variante: "soulignes", actif: actif, onChange: setActif, onglets: [
91
+ { id: 'apercu', label: 'Aperçu' },
92
+ { id: 'sources', label: 'Sources', compte: 12 },
93
+ { id: 'journal', label: 'Journal' },
94
+ ] }));
95
+ }
96
+ function DemoPopover() {
97
+ const [ouvert, setOuvert] = useState(false);
98
+ return (_jsxs(SelectAnchor, { children: [_jsx(Button, { variante: "fantome", compact: true, "aria-expanded": ouvert, onClick: () => setOuvert(!ouvert), children: "Colonnes affich\u00E9es" }), ouvert && (_jsx(Popover, { children: _jsx("div", { style: { display: 'grid', gap: 8 }, children: ['Nom', 'Taille', 'Statut'].map((c) => (_jsxs("label", { style: { display: 'flex', gap: 8, alignItems: 'center' }, children: [_jsx(Checkbox, { defaultChecked: c !== 'Taille' }), _jsx("span", { className: "zv-body zv-body--dense", children: c })] }, c))) }) }))] }));
99
+ }
84
100
  /* ── Le catalogue ─────────────────────────────────────────────────── */
85
101
  export const CATALOGUE = [
86
102
  {
@@ -152,7 +168,7 @@ export const CATALOGUE = [
152
168
  nom: 'Actions',
153
169
  couche: 'commun',
154
170
  css: 'components.css',
155
- resume: "Trois variantes, et UNE SEULE pleine par page : c'est ce qui fait que l'action principale se voit. Un écran à deux boutons pleins n'en a plus aucun.",
171
+ resume: "Quatre variantes, et UNE SEULE pleine par page — « danger » comprise, la règle compte les aplats, pas les couleurs : c'est ce qui fait que l'action principale se voit. Un écran à deux boutons pleins n'en a plus aucun.",
156
172
  exemples: [
157
173
  {
158
174
  id: 'boutons',
@@ -174,6 +190,15 @@ export const CATALOGUE = [
174
190
  <ButtonLink href="/tarifs" variante="fantome">Voir les tarifs</ButtonLink>`,
175
191
  rendu: (_jsxs("div", { style: { display: 'grid', gap: 10, maxWidth: 280 }, children: [_jsxs(ButtonRow, { children: [_jsx(Button, { compact: true, children: "Enregistrer" }), _jsx(ButtonLink, { href: "#", variante: "fantome", compact: true, children: "Voir les tarifs" })] }), _jsx(Button, { bloc: true, children: "Se connecter" })] })),
176
192
  },
193
+ {
194
+ id: 'etats',
195
+ nom: 'États',
196
+ resume: '« danger » COMPTE comme le bouton plein de la page — la règle compte les aplats, pas les couleurs ; à réserver à la confirmation destructive. « occupe » pose aria-busy et aria-disabled et AVALE le clic sans sortir le bouton du parcours clavier : le LIBELLÉ doit changer avec lui, l’estompement seul ne dit pas ce qu’on attend. Survol et appui sont dans le CSS, rien à câbler.',
197
+ code: `<Button variante="danger">Supprimer définitivement</Button>
198
+ <Button occupe>Connexion…</Button>
199
+ <Button variante="fantome" disabled>Envoyer</Button>`,
200
+ rendu: (_jsxs(ButtonRow, { children: [_jsx(Button, { variante: "danger", children: "Supprimer d\u00E9finitivement" }), _jsx(Button, { occupe: true, children: "Connexion\u2026" }), _jsx(Button, { variante: "fantome", disabled: true, children: "Envoyer" })] })),
201
+ },
177
202
  {
178
203
  id: 'lien-fleche',
179
204
  nom: 'Lien fléché',
@@ -261,6 +286,27 @@ export const CATALOGUE = [
261
286
  <Switch on={on} texte="ACTIVÉ" onClick={…} />`,
262
287
  rendu: (_jsxs("div", { style: { display: 'flex', gap: 20, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsxs("label", { style: { display: 'flex', gap: 8, alignItems: 'center' }, children: [_jsx(Checkbox, { defaultChecked: true }), " ", _jsx("span", { className: "zv-body zv-body--dense", children: "Anonymiser" })] }), _jsxs("label", { style: { display: 'flex', gap: 8, alignItems: 'center' }, children: [_jsx(Radio, { name: "ex-r", defaultChecked: true }), " ", _jsx("span", { className: "zv-body zv-body--dense", children: "Tout" })] }), _jsx(DemoSwitch, {})] })),
263
288
  },
289
+ {
290
+ id: 'affixe',
291
+ nom: 'Champ à affixe',
292
+ resume: '« 30 », seul dans un champ, ne dit pas s’il s’agit de jours ou d’euros. Le champ intérieur est un <Input nu> : c’est le CADRE qui fait le champ. Les identifiants d’affixe (idAvant/idApres) se citent en aria-describedby sur le champ, sinon l’unité reste muette au lecteur d’écran. À ne pas employer quand l’unité est déjà dans l’intitulé.',
293
+ code: `<Field label="HONORAIRES" htmlFor="hono">
294
+ <InputAffix avant="€" apres="HT" idAvant="hono-avant" idApres="hono-apres">
295
+ <Input nu id="hono" type="number" defaultValue={1200}
296
+ aria-describedby="hono-avant hono-apres" />
297
+ </InputAffix>
298
+ </Field>`,
299
+ rendu: (_jsxs("div", { style: { display: 'grid', gap: 16, maxWidth: 320 }, children: [_jsx(Field, { label: "D\u00C9LAI DE R\u00C9PONSE", htmlFor: "ex-delai", children: _jsx(InputAffix, { apres: "JOURS", idApres: "ex-delai-apres", children: _jsx(Input, { nu: true, id: "ex-delai", type: "number", defaultValue: 30, "aria-describedby": "ex-delai-apres" }) }) }), _jsx(Field, { label: "HONORAIRES", htmlFor: "ex-hono", children: _jsx(InputAffix, { avant: "\u20AC", apres: "HT", idAvant: "ex-hono-avant", idApres: "ex-hono-apres", children: _jsx(Input, { nu: true, id: "ex-hono", type: "number", defaultValue: 1200, "aria-describedby": "ex-hono-avant ex-hono-apres" }) }) })] })),
300
+ },
301
+ {
302
+ id: 'curseur',
303
+ nom: 'Curseur',
304
+ resume: 'Natif habillé : clavier, bornes et annonce viennent du navigateur ; le remplissage suit la valeur en contrôlé. Toujours un intitulé ET la valeur affichée en clair — un curseur seul ne dit ni ce qu’il règle, ni sur combien. Pas de spectre sur la piste : ce n’est pas une barre de progression.',
305
+ code: `<Field label="SOURCES INTERROGÉES" htmlFor="prof" help={\`\${sources} sources sur 17\`}>
306
+ <Range id="prof" min={1} max={17} value={sources} onChange={…} />
307
+ </Field>`,
308
+ rendu: _jsx(DemoRange, {}),
309
+ },
264
310
  {
265
311
  id: 'puces',
266
312
  nom: 'Puces de filtre',
@@ -286,7 +332,7 @@ export const CATALOGUE = [
286
332
  nom: 'Retours',
287
333
  couche: 'applicatif',
288
334
  css: 'feedback.css',
289
- resume: "Rien n'est interactif ici. ⚠️ Ni roue, ni squelette, ni infobulle : le CSS n'en a aucun, parce que le relevé de production n'en contenait aucun. Le travail en cours se dit par « Loading », qui nomme ce qu'on attend.",
335
+ resume: "Rien n'y crie : le bandeau porte le ton, la pastille l'état, la phrase le travail. La roue, le squelette et l'infobulle — longtemps refusés faute de relevé — sont arbitrés depuis le 25/08/2026 (DECISIONS.md), chacun avec son périmètre écrit ; « Loading », qui nomme ce qu'on attend, reste le motif d'attente par défaut.",
290
336
  exemples: [
291
337
  {
292
338
  id: 'alertes',
@@ -334,6 +380,34 @@ export const CATALOGUE = [
334
380
  <span>…réponse en cours<Caret /></span>`,
335
381
  rendu: (_jsxs("div", { style: { display: 'grid', gap: 12 }, children: [_jsx(Loading, { children: "Recherche dans 4 812 documents anonymis\u00E9s\u2026" }), _jsx(Progress, { valeur: 0.62, label: "Indexation" }), _jsxs("span", { className: "zv-body zv-body--dense", children: ["La clause de non-concurrence est limit\u00E9e \u00E0 vingt-quatre mois", _jsx(Caret, {})] })] })),
336
382
  },
383
+ {
384
+ id: 'roue',
385
+ nom: 'Roue',
386
+ resume: 'Attente COURTE et LOCALE : un bouton, une cellule, un panneau sans la place d’une phrase. Pour l’attente d’un écran, « Loading » reste le motif — la phrase dit sur quoi et sur combien. Sans label, la roue est décorative et masquée aux lecteurs d’écran.',
387
+ code: `<Spinner label="Vérification du dépôt…" />
388
+ <Spinner grand />`,
389
+ rendu: (_jsxs("div", { style: { display: 'flex', gap: 16, alignItems: 'center' }, children: [_jsx(Spinner, { label: "V\u00E9rification du d\u00E9p\u00F4t\u2026" }), _jsx(Spinner, { grand: true })] })),
390
+ },
391
+ {
392
+ id: 'squelette',
393
+ nom: 'Squelette',
394
+ resume: 'Il ANNONCE une structure connue — la carte, la ligne qui va venir ; pour un contenu de forme inconnue, c’est « Loading ». La géométrie vient de l’appelant : un squelette qui invente ses dimensions ne ressemble jamais au contenu. C’est la zone qui charge qui porte aria-busy, pas chaque os.',
395
+ code: `<div aria-busy="true">
396
+ <Skeleton texte largeur="60%" />
397
+ <Skeleton texte />
398
+ <Skeleton hauteur={96} />
399
+ </div>`,
400
+ rendu: (_jsxs("div", { "aria-busy": "true", style: { display: 'grid', gap: 10, maxWidth: 380 }, children: [_jsx(Skeleton, { texte: true, largeur: "60%" }), _jsx(Skeleton, { texte: true }), _jsx(Skeleton, { hauteur: 96 })] })),
401
+ },
402
+ {
403
+ id: 'infobulle',
404
+ nom: 'Infobulle',
405
+ resume: 'Un COMPLÉMENT dispensable — un raccourci, une date exacte, une unité : une commande n’a pas le droit d’y cacher son nom, et le tactile ne survole pas. Liage aria-describedby EXPLICITE (id), comme le htmlFor de Field — pas de useId, le rendu serveur reste possible. Apparition retardée de 300 ms, disparition immédiate, Échap la ferme.',
406
+ code: `<Tooltip id="th-date" contenu="Déposé le 12/08/2026 à 14 h 02">
407
+ <Button variante="fantome" compact aria-describedby="th-date">12 août</Button>
408
+ </Tooltip>`,
409
+ rendu: (_jsx("div", { style: { paddingTop: 44 }, children: _jsx(Tooltip, { id: "ex-th-date", contenu: "D\u00E9pos\u00E9 le 12/08/2026 \u00E0 14 h 02", children: _jsx(Button, { variante: "fantome", compact: true, "aria-describedby": "ex-th-date", children: "12 ao\u00FBt" }) }) })),
410
+ },
337
411
  ],
338
412
  },
339
413
  {
@@ -521,6 +595,22 @@ export const CATALOGUE = [
521
595
  code: `<FileType format="pdf" /> <FileType format="docx" />`,
522
596
  rendu: (_jsxs("div", { style: { display: 'flex', gap: 8 }, children: [_jsx(FileType, { format: "pdf" }), _jsx(FileType, { format: "docx" })] })),
523
597
  },
598
+ {
599
+ id: 'depliant',
600
+ nom: 'Rangée dépliante',
601
+ resume: '<details> NATIF : clavier, annonce et état viennent du navigateur, aucun JavaScript. Le pliage APPLICATIF — options avancées, détail d’une ligne de journal ; la FAQ (contenu) reste celui de la vitrine. Plusieurs rangées se posent bord à bord, le filet mitoyen tombe seul.',
602
+ code: `<Fold titre="Options avancées">…</Fold>
603
+ <Fold titre="Journal d'indexation" ouvert>…</Fold>`,
604
+ rendu: (_jsxs("div", { style: { maxWidth: 420 }, children: [_jsx(Fold, { titre: "Options avanc\u00E9es", children: "P\u00E9rim\u00E8tre, profondeur et sources exclues se r\u00E8glent par dossier, jamais par requ\u00EAte." }), _jsx(Fold, { titre: "Journal d\u2019indexation", ouvert: true, children: "Re\u00E7u, OCR, indexation : chaque \u00E9tape est dat\u00E9e, et l\u2019\u00E9chec d\u2019une page n\u2019interrompt pas les suivantes." })] })),
605
+ },
606
+ {
607
+ id: 'code',
608
+ nom: 'Code et raccourcis',
609
+ resume: 'La chasse fixe RÉELLE (--font-code) : --font-mono est proportionnelle et n’aligne pas du code, tabular-nums n’aligne que les chiffres. Aucune coloration syntaxique — elle imposerait une palette que la charte n’a pas. « ⌘K » fait DEUX touches, donc deux Kbd.',
610
+ code: `<Body>Lancer <Code>pnpm build</Code>, puis <Kbd>⌘</Kbd> <Kbd>K</Kbd>.</Body>
611
+ <CodeBlock>claude mcp add --transport http zevra-design …</CodeBlock>`,
612
+ rendu: (_jsxs("div", { style: { display: 'grid', gap: 12, maxWidth: 520 }, children: [_jsxs(Body, { taille: "dense", children: ["Lancer ", _jsx(Code, { children: "pnpm build" }), ", puis ouvrir la recherche avec ", _jsx(Kbd, { children: "\u2318" }), ' ', _jsx(Kbd, { children: "K" }), "."] }), _jsx(CodeBlock, { children: 'claude mcp add --transport http zevra-design \\\n https://mcp.design.zevra.tech/mcp --scope user' })] })),
613
+ },
524
614
  ],
525
615
  },
526
616
  {
@@ -563,14 +653,34 @@ export const CATALOGUE = [
563
653
  },
564
654
  {
565
655
  id: 'onglets',
566
- nom: 'Onglets',
567
- resume: '⚠️ Il n’existe AUCUNE classe d’onglet dans le CSS : une rangée d’onglets EST une rangée de puces. Ce composant n’ajoute que l’ARIA, pas un style de plus. L’onglet souligné reste à arbitrer avec la DA.',
656
+ nom: 'Onglets en puces',
657
+ resume: 'La rangée de puces, grammaire d’état de Seg — le motif des FILTRES, qui restreignent une collection. Ce composant n’ajoute que l’ARIA. Pour les VUES d’un même objet, prendre la variante soulignée.',
568
658
  code: `<Tabs actif={actif} onChange={setActif} onglets={[
569
659
  { id: 'apercu', label: 'Aperçu' },
570
660
  { id: 'sources', label: 'Sources', compte: 12 },
571
661
  ]} />`,
572
662
  rendu: _jsx(DemoTabs, {}),
573
663
  },
664
+ {
665
+ id: 'onglets-soulignes',
666
+ nom: 'Onglets soulignés',
667
+ resume: 'L’arbitrage longtemps en attente, tranché le 25/08/2026 : trait actif de 2 px — l’épaisseur des filets dessinés du système — en accent, PAS en spectre (un quatrième emploi n’existe pas). Pour les VUES d’un même objet (Aperçu / Sources) ; pour filtrer, les puces.',
668
+ code: `<Tabs variante="soulignes" actif={actif} onChange={setActif} onglets={[
669
+ { id: 'apercu', label: 'Aperçu' },
670
+ { id: 'sources', label: 'Sources', compte: 12 },
671
+ ]} />`,
672
+ rendu: _jsx(DemoTabsLine, {}),
673
+ },
674
+ {
675
+ id: 'popover',
676
+ nom: 'Popover',
677
+ resume: 'Surface ancrée LIBRE — aide contextuelle, mini-formulaire de filtre — dans la même ancre que le menu (SelectAnchor). Ce n’est PAS une infobulle : le popover s’ouvre au CLIC et peut porter de l’interactif. Fermeture par clic extérieur et Échap : à l’appelant, comme pour le menu.',
678
+ code: `<SelectAnchor>
679
+ <Button variante="fantome" compact aria-expanded={ouvert} onClick={…}>Colonnes affichées</Button>
680
+ {ouvert && <Popover>…</Popover>}
681
+ </SelectAnchor>`,
682
+ rendu: _jsx(DemoPopover, {}),
683
+ },
574
684
  {
575
685
  id: 'pagination',
576
686
  nom: 'Pagination',
@@ -650,7 +760,7 @@ export const CATALOGUE = [
650
760
  nom: 'Icônes',
651
761
  couche: 'commun',
652
762
  css: 'icons.css',
653
- resume: "34 icônes TRACÉES, jamais pleines : trait 2, bouts arrondis, grille de 24. Elles héritent de la couleur du texte — c'est ce qui leur permet de fonctionner sur papier comme sur la bande manifeste sans déclinaison. Le tracé est inliné : rien à résoudre côté bundler, rien à autoriser côté CSP.",
763
+ resume: "55 icônes TRACÉES, jamais pleines : trait 2, bouts arrondis, grille de 24. Elles héritent de la couleur du texte — c'est ce qui leur permet de fonctionner sur papier comme sur la bande manifeste sans déclinaison. Le tracé est inliné : rien à résoudre côté bundler, rien à autoriser côté CSP.",
654
764
  exemples: [
655
765
  {
656
766
  id: 'jeu',
@@ -831,6 +941,33 @@ export const CATALOGUE = [
831
941
  <OnlyMobile><NavBurger aria-label="Menu">…</NavBurger></OnlyMobile>`,
832
942
  rendu: (_jsxs("div", { style: { display: 'flex', gap: 10 }, children: [_jsx("span", { className: "zv-only-desktop", children: _jsx(Badge, { ton: "accent", children: "VISIBLE \u2265 768" }) }), _jsx("span", { className: "zv-only-mobile", children: _jsx(Badge, { ton: "accent", children: "VISIBLE < 768" }) })] })),
833
943
  },
944
+ {
945
+ id: 'bandeau',
946
+ nom: 'Bandeau d’annonce',
947
+ resume: 'La bande AU-DESSUS de la barre de navigation : annonce de version, information réglementaire. UN SEUL par page, et jamais pour du marketing permanent — un bandeau qui ne part pas devient un bourrelet de chrome. Le lien s’y souligne : sur un fond déjà indigo, la couleur seule ne le distingue plus.',
948
+ code: `<Banner>
949
+ La charte 0.8 est en ligne — angles vifs sur tout le système.{' '}
950
+ <a href="/journal">Ce qui change</a>
951
+ </Banner>`,
952
+ rendu: (_jsxs(Banner, { children: ["La charte 0.8 est en ligne \u2014 angles vifs sur tout le syst\u00E8me.", ' ', _jsx("a", { href: "#", children: "Ce qui change" })] })),
953
+ },
954
+ {
955
+ id: 'carrousel',
956
+ nom: 'Carrousel',
957
+ resume: 'Défilement natif scroll-snap, AUCUN JavaScript : pas de flèches, pas de points ; le conteneur prend le focus, et son label est OBLIGATOIRE — une région qui défile sans nom est une énigme au lecteur d’écran. Un carrousel reste un aveu d’espace : la règle de variété lui préfère une grille tant que le contenu tient.',
958
+ code: `<Carousel label="La suite Zevra">
959
+ <CarouselItem style={{ width: 260 }}>
960
+ <Card>…</Card>
961
+ </CarouselItem>
962
+ …
963
+ </Carousel>`,
964
+ rendu: (_jsx("div", { style: { maxWidth: 560 }, children: _jsx(Carousel, { label: "La suite Zevra", children: [
965
+ ['Mémoire', 'Votre savoir, interrogeable en une phrase.'],
966
+ ['Lexform', 'Vos actes, anonymisés à la volée.'],
967
+ ['Campus', 'La profession qui se rééquipe se forme.'],
968
+ ['MCP juridique', 'Vos sources, branchées sur vos agents.'],
969
+ ].map(([nom, phrase]) => (_jsx(CarouselItem, { style: { width: 240 }, children: _jsx(Card, { children: _jsxs("div", { style: { display: 'grid', gap: 8 }, children: [_jsx(CardTitle, { children: nom }), _jsx(Body, { taille: "dense", children: phrase })] }) }) }, nom))) }) })),
970
+ },
834
971
  ],
835
972
  },
836
973
  {
@@ -893,6 +1030,18 @@ export const CATALOGUE = [
893
1030
  <div className={anim('converge', 'tr')}>…</div>`,
894
1031
  rendu: (_jsx("div", { style: { display: 'flex', gap: 8, flexWrap: 'wrap' }, children: ['rise', 'pop', 'filet', 'glyph-in', 'converge', 'breathe', 'drift', 'dot-active', 'progress'].map((nom) => (_jsx("span", { className: anim(nom), children: _jsx(Chip, { variante: "mono", children: nom }) }, nom))) })),
895
1032
  },
1033
+ {
1034
+ id: 'format-social',
1035
+ nom: 'Formats sociaux',
1036
+ resume: 'Planches d’EXPORT — paysage 1200×630 (OpenGraph, lien LinkedIn), carré 1080 (post), story 1080×1920 — FIGÉES en pixels : on les capture, on ne les publie pas en HTML, et elles ignorent le point de rupture. Un visuel social EST une publication : jamais de faux chiffre ni de fausse citation. Rendue ici à l’échelle 0,35.',
1037
+ code: `<SocialFrame manifeste pied={<><Mono ton="on-dark">ZEVRA</Mono><Mono ton="on-dark">zevra.tech</Mono></>}>
1038
+ <Kicker surSombre><Mono ton="on-dark">Manifeste · Article 1ᵉʳ</Mono></Kicker>
1039
+ <Heading taille="h2" as="p">
1040
+ Émanciper les avocats et les juristes des outils qu'on leur <Em surSombre>impose</Em>.
1041
+ </Heading>
1042
+ </SocialFrame>`,
1043
+ rendu: (_jsx("div", { style: { width: 421, height: 221, overflow: 'hidden' }, children: _jsx("div", { style: { transform: 'scale(0.35)', transformOrigin: 'top left' }, children: _jsxs(SocialFrame, { manifeste: true, pied: _jsxs(_Fragment, { children: [_jsx(Mono, { ton: "on-dark", children: "ZEVRA" }), _jsx(Mono, { ton: "on-dark", children: "zevra.tech" })] }), children: [_jsx(Kicker, { surSombre: true, children: _jsx(Mono, { ton: "on-dark", children: "Manifeste \u00B7 Article 1\u1D49\u02B3" }) }), _jsxs(Heading, { taille: "h2", as: "p", children: ["\u00C9manciper les avocats et les juristes des outils qu\u2019on leur", ' ', _jsx(Em, { surSombre: true, children: "impose" }), "."] })] }) }) })),
1044
+ },
896
1045
  ],
897
1046
  },
898
1047
  ];