@zevra/ui 0.8.0 → 0.10.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 (66) hide show
  1. package/DECISIONS.md +238 -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 +163 -13
  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 +28 -6
  39. package/dist/surfaces.d.ts.map +1 -1
  40. package/dist/surfaces.js +18 -6
  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 +348 -18
  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 +57 -7
  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/motion.css +23 -1
  63. package/src/navigation.css +207 -12
  64. package/src/surfaces.css +76 -7
  65. package/src/tokens.css +6 -0
  66. package/tokens/index.js +2 -0
@@ -1,6 +1,8 @@
1
1
  /* Zevra UI — navigation : le chrome de l'applicatif
2
2
  * Colonne latérale, barre supérieure, tiroir, feuille de source citée, menu
3
- * déroulant, fil d'Ariane, pagination, mécanique de défilement.
3
+ * déroulant, fil d'Ariane, pagination, mécanique de défilement — et, depuis
4
+ * le 25/08/2026 (hors relevé, voir DECISIONS.md) : l'onglet souligné, le
5
+ * popover et la barre d'onglets mobile.
4
6
  *
5
7
  * DÉPEND DE :
6
8
  * · tokens.css — neutres (--ink, --ink-2, --muted, --label, --line,
@@ -18,7 +20,9 @@
18
20
  * NE REDÉFINIT RIEN de ce que les autres modules possèdent déjà :
19
21
  * .zv-navlink et .zv-nav__* (nav de vitrine, layout.css) ne sont pas touchés —
20
22
  * l'entrée applicative s'appelle .zv-sidelink ; le déclencheur du menu est
21
- * .zv-select et l'onglet réemploie .zv-seg, tous deux dans forms.css.
23
+ * .zv-select et l'onglet en PUCES réemploie .zv-seg, tous deux dans
24
+ * forms.css. L'onglet souligné, lui, a sa classe ici depuis l'arbitrage du
25
+ * 25/08/2026.
22
26
  *
23
27
  * Ce module est un RELEVÉ de l'application Mémoire, en production, pas une
24
28
  * proposition de DA : chaque valeur vient de src/components/app-shell.tsx,
@@ -253,6 +257,23 @@
253
257
  margin-right: -8px;
254
258
  }
255
259
 
260
+ /* ─── Barre d'onglets mobile ────────────────────────────────────────────
261
+ * HORS RELEVÉ (25/08/2026, voir DECISIONS.md). Mémoire navigue au tiroir ;
262
+ * une application à trois ou quatre sections navigue plus vite par une
263
+ * barre basse. CINQ entrées au maximum — au-delà, c'est le tiroir.
264
+ *
265
+ * N'existe que SOUS le point de rupture : au-dessus, la colonne latérale
266
+ * est là. Tout le dessin vit donc dans le bloc mobile de fin de fichier ;
267
+ * ici, seulement l'effacement desktop.
268
+ *
269
+ * ⚠️ Elle réserve de la place : le gabarit qui la pose doit donner au
270
+ * contenu un padding-bottom à sa hauteur, sinon la dernière ligne de la
271
+ * page se cache dessous. */
272
+
273
+ .zv-tabbar {
274
+ display: none;
275
+ }
276
+
256
277
  /* ─── Bouton de chrome (croix bordée) ───────────────────────────────────
257
278
  * ⚠️ RENOMMÉ : ce bouton s'appelait .zv-icon-btn, nom que icons.css possède
258
279
  * déjà pour tout autre chose (bouton nu de 40px, coins arrondis). Deux
@@ -475,6 +496,31 @@
475
496
  * clic extérieur, fermeture à Échap. Le CSS ci-dessus est prêt à les recevoir
476
497
  * (aria-selected, :focus-visible) ; le composant doit les écrire. */
477
498
 
499
+ /* ─── Popover ───────────────────────────────────────────────────────────
500
+ * HORS RELEVÉ (25/08/2026, voir DECISIONS.md). Le menu impose ses items ;
501
+ * il n'existait aucune surface ancrée LIBRE — aide contextuelle,
502
+ * mini-formulaire de filtre. Même ancre que le menu (.zv-select-anchor,
503
+ * forms.css), même surface (filet --line, ombre --shadow-menu), padding de
504
+ * panneau au lieu d'items. Fermeture par clic extérieur et Échap : au
505
+ * composant, comme pour le menu.
506
+ * ⚠️ Ce n'est PAS une infobulle : le popover s'ouvre au CLIC et peut porter
507
+ * de l'interactif ; l'infobulle (feedback.css) se montre au survol et n'en
508
+ * porte jamais. */
509
+
510
+ .zv-popover {
511
+ position: absolute;
512
+ top: calc(100% + 5px);
513
+ left: 0;
514
+ z-index: 60;
515
+ min-width: 230px;
516
+ max-width: 320px;
517
+ border: 1px solid var(--line);
518
+ border-radius: 0;
519
+ background: var(--white);
520
+ box-shadow: var(--shadow-menu);
521
+ padding: 14px 16px;
522
+ }
523
+
478
524
  /* ─── Fil d'Ariane ──────────────────────────────────────────────────────*/
479
525
 
480
526
  .zv-crumbs {
@@ -577,17 +623,91 @@
577
623
 
578
624
  /* ─── Onglets ───────────────────────────────────────────────────────────
579
625
  * ⚠️ AUCUNE MATIÈRE PREMIÈRE : pas un seul jeu d'onglets dans les écrans
580
- * relevés. Ce module ne livre donc RIEN ici.
626
+ * relevés. La rangée de PUCES se construit toujours avec .zv-seg-group et
627
+ * .zv-seg (forms.css), qui portent déjà la grammaire d'un état actif —
628
+ * c'est le motif des FILTRES, qui restreignent une collection.
581
629
  *
582
- * Une rangée d'onglets se construit avec .zv-seg-group et .zv-seg
583
- * (forms.css), qui portent déjà la grammaire d'un état actif. Ce module a
584
- * d'abord défini une .zv-tabs — display flex, wrap, gouttière 6px — qui
585
- * était la copie exacte de .zv-seg-group : deux noms pour le MÊME nœud du
586
- * DOM, et c'est l'ordre de chargement qui aurait tranché. Elle est retirée.
587
- *
588
- * Un onglet SOULIGNÉ, lui, reste à faire : il suppose d'arbitrer la couleur
589
- * et l'épaisseur du trait actif, ce qu'aucune source ne donne.
590
- */
630
+ * L'onglet SOULIGNÉ, longtemps « à faire », est ARBITRÉ le 25/08/2026
631
+ * (DECISIONS.md) : trait actif de 2 px — l'épaisseur des filets dessinés du
632
+ * système (filet de spectre, rail d'étapes) — en --accent, la couleur de
633
+ * l'action, comme l'entrée de nav courante. PAS le spectre : un quatrième
634
+ * emploi n'existe pas. C'est le motif des VUES d'un même objet (Aperçu /
635
+ * Sources) ; pour filtrer, prendre les puces. */
636
+
637
+ .zv-tabs-line {
638
+ display: flex;
639
+ gap: 22px;
640
+ /* La rangée ne s'enroule pas — des onglets sur deux lignes ne se lisent
641
+ * plus comme des onglets : elle déborde dans son propre cadre, jamais
642
+ * dans la page. */
643
+ overflow-x: auto;
644
+ /* La ligne de la rangée est une ombre INTERNE, pas une bordure : les
645
+ * onglets (étirés à la hauteur de la rangée) peignent leur trait
646
+ * par-dessus. Le montage classique — bordure de rangée + marge basse de
647
+ * -1px sur l'onglet — fait déborder l'onglet d'un pixel dans un conteneur
648
+ * en overflow, et cet unique pixel suffit à faire naître un ascenseur
649
+ * vertical. */
650
+ box-shadow: inset 0 -1px 0 var(--line);
651
+ }
652
+
653
+ /* L'onglet se pose SUR la ligne : étiré à la hauteur de la rangée (le
654
+ * défaut flex), son trait de 2 px partage son bord bas avec elle. Inactif,
655
+ * le trait est transparent et laisse voir la ligne ; actif, il la couvre. */
656
+ .zv-tabs-line__tab {
657
+ display: inline-flex;
658
+ align-items: center;
659
+ gap: 7px;
660
+ flex: none;
661
+ border: 0;
662
+ border-bottom: 2px solid transparent;
663
+ background: none;
664
+ padding: 0 2px 9px;
665
+ font-family: var(--font-text);
666
+ font-size: var(--app);
667
+ font-weight: 500;
668
+ white-space: nowrap;
669
+ color: var(--ink-2);
670
+ cursor: pointer;
671
+ transition:
672
+ color 140ms cubic-bezier(0.2, 0.7, 0.2, 1),
673
+ border-color 140ms cubic-bezier(0.2, 0.7, 0.2, 1);
674
+ }
675
+
676
+ .zv-tabs-line__tab:hover {
677
+ color: var(--ink);
678
+ }
679
+
680
+ /* Le contour de base.css serait rogné par l'overflow de la rangée : il
681
+ * rentre, comme dans la colonne latérale. */
682
+ .zv-tabs-line__tab:focus-visible {
683
+ outline-offset: -2px;
684
+ }
685
+
686
+ /* La grammaire d'état de .zv-seg : 500 → 600, la graisse distingue l'actif
687
+ * avant la couleur. Le sélecteur suit l'attribut autant que la classe pour
688
+ * que le balisage tablist correct ne coûte rien. */
689
+ .zv-tabs-line__tab--on,
690
+ .zv-tabs-line__tab[aria-selected='true'] {
691
+ border-bottom-color: var(--accent);
692
+ color: var(--accent);
693
+ font-weight: 600;
694
+ }
695
+
696
+ /* Au survol de l'onglet actif, le trait reste : la vue où l'on se trouve ne
697
+ * s'éteint pas sous le pointeur — même règle que l'entrée de nav courante. */
698
+ .zv-tabs-line__tab--on:hover,
699
+ .zv-tabs-line__tab[aria-selected='true']:hover {
700
+ color: var(--accent);
701
+ }
702
+
703
+ /* Le compte de l'onglet — mêmes mesures que celui de la puce (.zv-seg__count,
704
+ * forms.css) mais SANS sa marge : ici c'est la gouttière de l'onglet qui
705
+ * espace, et réemployer la classe de la puce doublait l'écart (6 + 7 px). */
706
+ .zv-tabs-line__count {
707
+ font-family: var(--font-mono);
708
+ font-size: var(--mono);
709
+ color: var(--label);
710
+ }
591
711
 
592
712
  /* ─── Défilement ────────────────────────────────────────────────────────
593
713
  * La mécanique, elle, est relevée et livrée : `min-height: 0` ET
@@ -686,6 +806,81 @@
686
806
  max-width: calc(100vw - 40px);
687
807
  }
688
808
 
809
+ /* Même garde-fou pour le popover, qui partage l'ancre du menu. */
810
+ .zv-popover {
811
+ max-width: calc(100vw - 40px);
812
+ }
813
+
814
+ /* L'onglet souligné devient une cible tactile ; le trait descend avec le
815
+ * padding, la géométrie ne change pas. */
816
+ .zv-tabs-line__tab {
817
+ min-height: var(--tap);
818
+ padding-bottom: 12px;
819
+ }
820
+
821
+ /* ─── Barre d'onglets mobile — le dessin ──────────────────────────────
822
+ * z-index 50 : le SOMMET des couches de page (l'étage de .zv-layer-top,
823
+ * layout.css), et SOUS tous les recouvrements applicatifs (60 et plus) —
824
+ * le voile doit voiler la barre, le tiroir passer devant. C'est du chrome
825
+ * permanent, pas un enrobage : il ne monte pas dans leur échelle. */
826
+ .zv-tabbar {
827
+ position: fixed;
828
+ inset-inline: 0;
829
+ bottom: 0;
830
+ z-index: 50;
831
+ display: flex;
832
+ border-top: 1px solid var(--line);
833
+ background: var(--panel);
834
+ /* L'encoche basse d'iOS : sans cette réserve, la rangée tombe sous la
835
+ * barre système du téléphone. Mot-clé d'environnement, pas une mesure
836
+ * inventée. */
837
+ padding-bottom: env(safe-area-inset-bottom);
838
+ }
839
+
840
+ .zv-tabbar__item {
841
+ flex: 1 1 0%;
842
+ display: flex;
843
+ flex-direction: column;
844
+ align-items: center;
845
+ justify-content: center;
846
+ gap: 3px;
847
+ min-height: calc(var(--tap) + 8px);
848
+ padding: 6px 4px;
849
+ border: 0;
850
+ background: none;
851
+ font-family: var(--font-mono);
852
+ font-size: var(--mono-sm);
853
+ font-weight: 600;
854
+ letter-spacing: 0.08em;
855
+ text-transform: uppercase;
856
+ color: var(--muted);
857
+ text-decoration: none;
858
+ cursor: pointer;
859
+ }
860
+
861
+ /* Même contre-ordre que .zv-sidelink : sans lui, l'entrée vire au bleu
862
+ * souligné dans une page qui applique le style de lien éditorial. */
863
+ .zv-tabbar__item:hover {
864
+ color: var(--muted);
865
+ text-decoration: none;
866
+ }
867
+
868
+ .zv-tabbar__item:focus-visible {
869
+ outline-offset: -2px;
870
+ }
871
+
872
+ /* aria-current="page" est le marquage attendu ; la classe existe pour que
873
+ * l'état se pose aussi sans routeur. */
874
+ .zv-tabbar__item--on,
875
+ .zv-tabbar__item[aria-current='page'] {
876
+ color: var(--accent);
877
+ }
878
+
879
+ .zv-tabbar__item--on:hover,
880
+ .zv-tabbar__item[aria-current='page']:hover {
881
+ color: var(--accent);
882
+ }
883
+
689
884
  .zv-menu__item {
690
885
  display: flex;
691
886
  align-items: center;
package/src/surfaces.css CHANGED
@@ -1,12 +1,14 @@
1
- /* Zevra UI — surfaces : halos, sols quadrillés, voiles, bandeau manifeste
2
- * Dépend de tokens.css. Indépendant des autres modules.
1
+ /* Zevra UI — surfaces : halos, sols quadrillés, voiles, bandeau manifeste —
2
+ * et, depuis le 25/08/2026 (hors relevé, voir DECISIONS.md), les formats
3
+ * sociaux. Dépend de tokens.css. Indépendant des autres modules.
3
4
  *
4
5
  * C'est ce qui distingue une page Zevra d'une page blanche bien composée.
5
- * Toutes ces surfaces sont DÉCORATIVES : elles se posent en absolu derrière
6
- * le contenu, ne captent jamais le pointeur, et n'ont pas à être lues par un
7
- * lecteur d'écran (les poser en aria-hidden).
8
- *
9
- * Elles supposent toutes un parent `position: relative; overflow: hidden`.
6
+ * Les surfaces décoratives (halo, sol, voile, filigrane, lueur) se posent
7
+ * en absolu derrière le contenu, ne captent jamais le pointeur, et n'ont
8
+ * pas à être lues par un lecteur d'écran (les poser en aria-hidden) ; elles
9
+ * supposent un parent `position: relative; overflow: hidden`.
10
+ * ⚠️ Les formats sociaux, eux, sont du CONTENU — une planche d'export qui
11
+ * porte un texte : jamais d'aria-hidden sur eux.
10
12
  */
11
13
 
12
14
  /* ─── Halo ambiant ──────────────────────────────────────────────────────
@@ -202,6 +204,73 @@
202
204
  pointer-events: none;
203
205
  }
204
206
 
207
+ /* ─── Formats sociaux ───────────────────────────────────────────────────
208
+ * HORS RELEVÉ (25/08/2026, voir DECISIONS.md). Trois planches d'EXPORT aux
209
+ * dimensions des réseaux — paysage 1200×630 (OpenGraph, lien LinkedIn),
210
+ * carré 1080×1080 (post), story 1080×1920. FIGÉES en pixels, hors du point
211
+ * de rupture : ce sont des planches qu'on CAPTURE, pas des pages qu'on
212
+ * publie en HTML — la seule dérogation du paquet à l'interdit des largeurs
213
+ * fixes, et elle est là pour ça.
214
+ *
215
+ * Elles composent les pièces existantes — bande par --manifeste, halo, sol,
216
+ * kicker, display — et portent un pied wordmark + adresse. La règle
217
+ * « jamais de faux contenu » s'y applique au premier chef : un visuel
218
+ * social EST une publication. */
219
+
220
+ .zv-social {
221
+ position: relative;
222
+ overflow: hidden;
223
+ display: flex;
224
+ flex-direction: column;
225
+ justify-content: space-between;
226
+ gap: 40px;
227
+ flex: none;
228
+ width: 1200px;
229
+ height: 630px;
230
+ /* La gouttière standard de la page (layout.css), reprise telle quelle. */
231
+ padding: 72px;
232
+ background: var(--paper);
233
+ color: var(--ink);
234
+ }
235
+
236
+ .zv-social--carre {
237
+ width: 1080px;
238
+ height: 1080px;
239
+ }
240
+
241
+ .zv-social--story {
242
+ width: 1080px;
243
+ height: 1920px;
244
+ padding: 96px 72px;
245
+ }
246
+
247
+ /* La planche sombre : le manifeste en fond, les règles du fond sombre
248
+ * s'appliquent (--accent-on-dark pour les labels, jamais les neutres
249
+ * inversés). */
250
+ .zv-social--manifeste {
251
+ background: var(--manifeste);
252
+ color: var(--white);
253
+ }
254
+
255
+ /* Contenu et pied passent AU-DESSUS des surfaces décoratives (halo, sol),
256
+ * comme dans une page. */
257
+ .zv-social__contenu {
258
+ position: relative;
259
+ z-index: 1;
260
+ display: flex;
261
+ flex-direction: column;
262
+ gap: 24px;
263
+ }
264
+
265
+ .zv-social__pied {
266
+ position: relative;
267
+ z-index: 1;
268
+ display: flex;
269
+ align-items: center;
270
+ justify-content: space-between;
271
+ gap: 24px;
272
+ }
273
+
205
274
  /* ─── Mouvement ────────────────────────────────────────────────────────
206
275
  * Les animations vivent dans motion.css, qui porte les neuf mouvements
207
276
  * autorisés par la passation. Les surfaces d'ici en sont les cibles
package/src/tokens.css CHANGED
@@ -65,6 +65,12 @@
65
65
  --font-display: "Outfit", "Helvetica Neue", Arial, sans-serif; /* titres 200-400, wordmarks 600 */
66
66
  --font-text: "Public Sans", "Helvetica Neue", Arial, sans-serif; /* 400/600 */
67
67
  --font-mono: "Space Grotesk", "Menlo", monospace; /* labels caps, 0.18-0.2em */
68
+ /* Chasse fixe RÉELLE, pour le code seulement. --font-mono (Space Grotesk)
69
+ * est proportionnelle : elle porte le REGISTRE des labels, pas
70
+ * l'alignement — du code exige la chasse fixe entière, que tabular-nums
71
+ * ne donne qu'aux chiffres. Pile système, aucune fonte chargée.
72
+ * HORS RELEVÉ (25/08/2026), voir DECISIONS.md « Bloc de code ». */
73
+ --font-code: ui-monospace, "SF Mono", "Menlo", "Consolas", monospace;
68
74
 
69
75
  /* Échelle display (letter-spacing -0.02em, graisse 300 + chute en 400 --accent-deep) */
70
76
  --h1: 76px;
package/tokens/index.js CHANGED
@@ -72,6 +72,8 @@ export const polices = {
72
72
  display: '"Outfit", "Helvetica Neue", Arial, sans-serif',
73
73
  texte: '"Public Sans", "Helvetica Neue", Arial, sans-serif',
74
74
  mono: '"Space Grotesk", "Menlo", monospace',
75
+ // Chasse fixe réelle, pour le code seulement — voir tokens.css.
76
+ code: 'ui-monospace, "SF Mono", "Menlo", "Consolas", monospace',
75
77
  }
76
78
 
77
79
  export const tailles = {