@zevra/ui 0.44.1 → 0.45.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 (50) hide show
  1. package/DECISIONS.md +46 -0
  2. package/dist/actions.d.ts +9 -2
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +5 -5
  5. package/dist/actions.js.map +1 -1
  6. package/dist/catalogue.d.ts.map +1 -1
  7. package/dist/catalogue.js +18 -1
  8. package/dist/catalogue.js.map +1 -1
  9. package/dist/content.d.ts +17 -1
  10. package/dist/content.d.ts.map +1 -1
  11. package/dist/content.js +11 -3
  12. package/dist/content.js.map +1 -1
  13. package/dist/css-inline.d.ts.map +1 -1
  14. package/dist/css-inline.js +1 -1
  15. package/dist/css-inline.js.map +1 -1
  16. package/dist/cx.d.ts +9 -0
  17. package/dist/cx.d.ts.map +1 -1
  18. package/dist/cx.js +4 -0
  19. package/dist/cx.js.map +1 -1
  20. package/dist/feedback.d.ts +9 -2
  21. package/dist/feedback.d.ts.map +1 -1
  22. package/dist/feedback.js +5 -5
  23. package/dist/feedback.js.map +1 -1
  24. package/dist/forms.d.ts +13 -3
  25. package/dist/forms.d.ts.map +1 -1
  26. package/dist/forms.js +7 -7
  27. package/dist/forms.js.map +1 -1
  28. package/dist/icon.d.ts +5 -1
  29. package/dist/icon.d.ts.map +1 -1
  30. package/dist/icon.js +3 -3
  31. package/dist/icon.js.map +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/package.json +5 -5
  36. package/react/actions.tsx +11 -1
  37. package/react/catalogue.tsx +37 -1
  38. package/react/content.tsx +22 -1
  39. package/react/css-inline.ts +1 -1
  40. package/react/cx.ts +13 -0
  41. package/react/feedback.tsx +11 -2
  42. package/react/forms.tsx +16 -7
  43. package/react/icon.tsx +6 -2
  44. package/react/index.ts +1 -1
  45. package/src/components.css +2 -2
  46. package/src/data.css +10 -3
  47. package/src/feedback.css +3 -2
  48. package/src/forms.css +7 -4
  49. package/src/navigation.css +3 -3
  50. package/src/tokens.css +28 -0
package/react/cx.ts CHANGED
@@ -18,3 +18,16 @@ export function cx(...parts: (string | false | null | undefined)[]): string {
18
18
  export function mod(base: string, variante?: string | null): string {
19
19
  return variante ? `${base}--${variante}` : ''
20
20
  }
21
+
22
+ /**
23
+ * Les angles d'un composant (DECISIONS.md, « Les angles arrondis, en
24
+ * option ») : `vifs` est le défaut de la charte, `arrondis` l'option. Sans
25
+ * valeur, le composant suit son conteneur : c'est ce qui laisse une app
26
+ * entière s'arrondir d'une seule enveloppe `Angles`.
27
+ */
28
+ export type ChoixAngles = 'vifs' | 'arrondis'
29
+
30
+ /** La classe qui redéfinit les tokens de rayon, ou rien pour suivre le conteneur. */
31
+ export function classeAngles(angles?: ChoixAngles | null): string {
32
+ return angles === 'arrondis' ? 'zv-arrondi' : angles === 'vifs' ? 'zv-vif' : ''
33
+ }
@@ -9,7 +9,7 @@
9
9
  // roue dit « attendez », cette phrase dit sur quoi.
10
10
 
11
11
  import type { HTMLAttributes, ReactNode } from 'react'
12
- import { cx } from './cx.js'
12
+ import { classeAngles, cx, type ChoixAngles } from './cx.js'
13
13
 
14
14
  type Div = Omit<HTMLAttributes<HTMLDivElement>, 'className'>
15
15
  type Span = Omit<HTMLAttributes<HTMLSpanElement>, 'className'>
@@ -25,10 +25,13 @@ export interface AlertProps extends Div {
25
25
  /** `sm` resserre, `lg` amplifie : ce dernier pour le message qui REMPLACE
26
26
  * un contenu attendu (incident, réponse impossible). */
27
27
  taille?: 'sm' | 'normal' | 'lg'
28
+ /** `arrondis` (option) ou `vifs` (défaut de la charte). Sans valeur, le
29
+ * composant suit son conteneur : voir `Angles`. */
30
+ angles?: ChoixAngles
28
31
  className?: string
29
32
  }
30
33
 
31
- export function Alert({ ton, taille = 'normal', className, ...reste }: AlertProps) {
34
+ export function Alert({ ton, taille = 'normal', angles, className, ...reste }: AlertProps) {
32
35
  return (
33
36
  <div
34
37
  role="alert"
@@ -37,6 +40,7 @@ export function Alert({ ton, taille = 'normal', className, ...reste }: AlertProp
37
40
  `zv-alert--${ton}`,
38
41
  taille === 'sm' && 'zv-alert--sm',
39
42
  taille === 'lg' && 'zv-alert--lg',
43
+ classeAngles(angles),
40
44
  className,
41
45
  )}
42
46
  {...reste}
@@ -55,6 +59,9 @@ export interface BadgeProps extends Span {
55
59
  gabarit?: 'micro' | 'state'
56
60
  /** Pastille en tête, de la couleur du texte. */
57
61
  pastille?: boolean
62
+ /** `arrondis` (option) ou `vifs` (défaut de la charte). Sans valeur, le
63
+ * composant suit son conteneur : voir `Angles`. */
64
+ angles?: ChoixAngles
58
65
  className?: string
59
66
  children?: ReactNode
60
67
  }
@@ -65,6 +72,7 @@ export function Badge({
65
72
  ton = 'neutre',
66
73
  gabarit,
67
74
  pastille,
75
+ angles,
68
76
  className,
69
77
  children,
70
78
  ...reste
@@ -77,6 +85,7 @@ export function Badge({
77
85
  gabarit === 'micro' && 'zv-badge--micro',
78
86
  gabarit === 'state' && 'zv-badge--state',
79
87
  pastille && 'zv-badge--dot',
88
+ classeAngles(angles),
80
89
  className,
81
90
  )}
82
91
  {...reste}
package/react/forms.tsx CHANGED
@@ -20,7 +20,7 @@ import type {
20
20
  SelectHTMLAttributes,
21
21
  TextareaHTMLAttributes,
22
22
  } from 'react'
23
- import { cx } from './cx.js'
23
+ import { classeAngles, cx, type ChoixAngles } from './cx.js'
24
24
  import { Icon } from './icon.js'
25
25
 
26
26
  /* ─── Groupe de champ ──────────────────────────────────────────────── */
@@ -92,13 +92,16 @@ export interface InputProps
92
92
  /** Pose `aria-invalid` : c'est lui qui rend l'erreur audible, et le CSS le
93
93
  * suit autant que la classe. */
94
94
  erreur?: boolean
95
+ /** `arrondis` (option) ou `vifs` (défaut de la charte). Sans valeur, le
96
+ * composant suit son conteneur : voir `Angles`. */
97
+ angles?: ChoixAngles
95
98
  className?: string
96
99
  }
97
100
 
98
- export function Input({ mono, nu, erreur, className, ...reste }: InputProps) {
101
+ export function Input({ mono, nu, erreur, angles, className, ...reste }: InputProps) {
99
102
  return (
100
103
  <input
101
- className={cx('zv-input', mono && 'zv-input--mono', nu && 'zv-input--bare', className)}
104
+ className={cx('zv-input', mono && 'zv-input--mono', nu && 'zv-input--bare', classeAngles(angles), className)}
102
105
  aria-invalid={erreur || undefined}
103
106
  {...reste}
104
107
  />
@@ -108,13 +111,16 @@ export function Input({ mono, nu, erreur, className, ...reste }: InputProps) {
108
111
  export interface TextareaProps
109
112
  extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'className'> {
110
113
  erreur?: boolean
114
+ /** `arrondis` (option) ou `vifs` (défaut de la charte). Sans valeur, le
115
+ * composant suit son conteneur : voir `Angles`. */
116
+ angles?: ChoixAngles
111
117
  className?: string
112
118
  }
113
119
 
114
- export function Textarea({ erreur, className, ...reste }: TextareaProps) {
120
+ export function Textarea({ erreur, angles, className, ...reste }: TextareaProps) {
115
121
  return (
116
122
  <textarea
117
- className={cx('zv-textarea', className)}
123
+ className={cx('zv-textarea', classeAngles(angles), className)}
118
124
  aria-invalid={erreur || undefined}
119
125
  {...reste}
120
126
  />
@@ -135,15 +141,18 @@ export function Select({
135
141
 
136
142
  export interface SearchProps
137
143
  extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className'> {
144
+ /** `arrondis` (option) ou `vifs` (défaut de la charte). Sans valeur, le
145
+ * composant suit son conteneur : voir `Angles`. */
146
+ angles?: ChoixAngles
138
147
  /** Enveloppe : c'est elle qui porte le cadre et la largeur. */
139
148
  className?: string
140
149
  }
141
150
 
142
151
  /** Cadre + loupe + champ nu. La loupe est dans le balisage (et non un fond
143
152
  * CSS) pour suivre `currentColor` et rester nette en zoom. */
144
- export function Search({ className, ...reste }: SearchProps) {
153
+ export function Search({ angles, className, ...reste }: SearchProps) {
145
154
  return (
146
- <div className={cx('zv-search', className)}>
155
+ <div className={cx('zv-search', classeAngles(angles), className)}>
147
156
  <svg
148
157
  className="zv-search__icon"
149
158
  viewBox="0 0 13 13"
package/react/icon.tsx CHANGED
@@ -6,7 +6,7 @@
6
6
  // sur papier comme sur la bande manifeste sans déclinaison.
7
7
 
8
8
  import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode, SVGAttributes } from 'react'
9
- import { cx } from './cx.js'
9
+ import { classeAngles, cx, type ChoixAngles } from './cx.js'
10
10
  import { CORPS, NOMS, type IconId } from './icon-data.js'
11
11
 
12
12
  export type { IconId }
@@ -58,6 +58,9 @@ export interface IconButtonProps
58
58
  * (« Croix ») : c'est ce que le lecteur d'écran annonce. */
59
59
  label: string
60
60
  taille?: IconProps['taille']
61
+ /** `arrondis` (option) ou `vifs` (défaut de la charte). Sans valeur, le
62
+ * composant suit son conteneur : voir `Angles`. */
63
+ angles?: ChoixAngles
61
64
  className?: string
62
65
  children?: ReactNode
63
66
  }
@@ -66,6 +69,7 @@ export function IconButton({
66
69
  icone,
67
70
  label,
68
71
  taille = 'lg',
72
+ angles,
69
73
  className,
70
74
  children,
71
75
  type = 'button',
@@ -75,7 +79,7 @@ export function IconButton({
75
79
  <button
76
80
  type={type}
77
81
  aria-label={label}
78
- className={cx('zv-icon-btn', className)}
82
+ className={cx('zv-icon-btn', classeAngles(angles), className)}
79
83
  {...reste}
80
84
  >
81
85
  {icone ? <Icon nom={icone} taille={taille} /> : children}
package/react/index.ts CHANGED
@@ -27,7 +27,7 @@
27
27
  // lit : les avertissements ⚠️ signalent ce qui casse le système si on le
28
28
  // contourne, pas de simples préférences.
29
29
 
30
- export { cx, mod } from './cx.js'
30
+ export { cx, mod, type ChoixAngles } from './cx.js'
31
31
 
32
32
  export * from './actions.js'
33
33
  export * from './content.js'
@@ -796,7 +796,7 @@
796
796
  .zv-progress {
797
797
  height: 3px;
798
798
  background: var(--hairline);
799
- border-radius: 0;
799
+ border-radius: var(--r-pill);
800
800
  overflow: hidden;
801
801
  }
802
802
 
@@ -1129,7 +1129,7 @@
1129
1129
  overflow: hidden;
1130
1130
  background: var(--hairline);
1131
1131
  /* Angle vif comme le reste ; aucune ombre, l'image est dans le flux. */
1132
- border-radius: 0;
1132
+ border-radius: var(--r-card);
1133
1133
  }
1134
1134
 
1135
1135
  /* Les quatre rapports du système. Un cinquième se demande, il ne s'invente
package/src/data.css CHANGED
@@ -47,9 +47,10 @@
47
47
  * Le tableau scrolle DANS son cadre : sans ce confinement, une colonne trop
48
48
  * large fait scroller la page entière et le rail de navigation part avec.
49
49
  * Angle vif et sans ombre : un tableau est une surface de travail, pas une
50
- * carte éditoriale. Aucun rayon n'est posé, la valeur par défaut est déjà 0,
51
- * il n'y a rien à annuler tant qu'on ne compose pas .zv-table avec .zv-card
52
- * (ce que ce module déconseille : ce sont deux intentions différentes). */
50
+ * carte éditoriale. Son rayon est celui d'un panneau (--r-panel, 0 par
51
+ * défaut) : il ne s'arrondit que sous .zv-arrondi, et le défilement interne
52
+ * se rogne au rayon. Ne pas le composer avec .zv-card : ce sont deux
53
+ * intentions différentes. */
53
54
 
54
55
  .zv-table {
55
56
  overflow-x: auto;
@@ -73,6 +74,7 @@
73
74
  * Les colonnes souples sont en minmax(0|150px, …) : sans borne basse à 0 ou
74
75
  * sans min-width sur la cellule, la troncature ne se déclenche jamais. */
75
76
  --zv-cols: minmax(0, 1fr);
77
+ border-radius: var(--r-panel); /* 0 par défaut, arrondi sous .zv-arrondi */
76
78
  }
77
79
 
78
80
  .zv-table__head {
@@ -631,6 +633,7 @@
631
633
  font-size: var(--mono);
632
634
  font-weight: 600;
633
635
  color: var(--ink-2);
636
+ border-radius: var(--r-pill); /* 0 par défaut, arrondi sous .zv-arrondi */
634
637
  }
635
638
 
636
639
  /* Valeur technique dans une étiquette (date normalisée, référence) : la mono
@@ -756,6 +759,7 @@
756
759
  .zv-fold {
757
760
  border: 1px solid var(--line);
758
761
  background: var(--white);
762
+ border-radius: var(--r-panel); /* 0 par défaut, arrondi sous .zv-arrondi */
759
763
  }
760
764
 
761
765
  .zv-fold + .zv-fold {
@@ -830,6 +834,7 @@
830
834
  font-weight: 600;
831
835
  white-space: nowrap;
832
836
  color: var(--ink-2);
837
+ border-radius: var(--r-pill); /* 0 par défaut, arrondi sous .zv-arrondi */
833
838
  }
834
839
 
835
840
  /* ─── Code ─────────────────────────────────────────────────────────────
@@ -851,6 +856,7 @@
851
856
  font-size: 0.85em;
852
857
  overflow-wrap: break-word;
853
858
  color: var(--ink-2);
859
+ border-radius: var(--r-pill); /* 0 par défaut, arrondi sous .zv-arrondi */
854
860
  }
855
861
 
856
862
  /* En bloc. Le cadre défile en son sein, comme le tableau : une ligne longue
@@ -866,6 +872,7 @@
866
872
  line-height: 1.7;
867
873
  tab-size: 2;
868
874
  color: var(--ink);
875
+ border-radius: var(--r-panel); /* 0 par défaut, arrondi sous .zv-arrondi */
869
876
  }
870
877
 
871
878
  /* Le <code> intérieur n'apporte que la sémantique : sans ce rappel, il
package/src/feedback.css CHANGED
@@ -75,7 +75,7 @@
75
75
  * les quatre tokens de rayon à 0. La contradiction longtemps signalée ici
76
76
  * (un bouton arrondi au milieu de blocs vifs dans l'écran vide) est
77
77
  * éteinte par cet arbitrage, pas par ce module. */
78
- border-radius: 0;
78
+ border-radius: var(--r-panel);
79
79
  }
80
80
 
81
81
  /* Information : le seul ton relevé en bandeau de bas de page (garanties,
@@ -206,7 +206,7 @@
206
206
  /* Un état ne se coupe jamais en deux lignes : « EN CHARGE » sur deux
207
207
  * lignes se lit comme deux états. */
208
208
  white-space: nowrap;
209
- border-radius: 0; /* angle vif : relevé, et arbitré depuis (25/08/2026) */
209
+ border-radius: var(--r-pill); /* angle vif : relevé, et arbitré depuis (25/08/2026) */
210
210
  }
211
211
 
212
212
  /* Gabarit micro : niveau de log en tête de ligne, badge de la coque. À
@@ -641,6 +641,7 @@
641
641
  transition:
642
642
  opacity 140ms cubic-bezier(0.2, 0.7, 0.2, 1),
643
643
  visibility 0s linear 140ms;
644
+ border-radius: var(--r-panel); /* 0 par défaut, arrondi sous .zv-arrondi */
644
645
  }
645
646
 
646
647
  .zv-tooltip-hit:hover > .zv-tooltip,
package/src/forms.css CHANGED
@@ -113,7 +113,7 @@
113
113
  border: 1px solid var(--line);
114
114
  /* Angle vif : aucun rounded-* sur un seul champ des huit écrans. Posé
115
115
  * explicitement, sinon Safari arrondit les champs de lui-même. */
116
- border-radius: 0;
116
+ border-radius: var(--r-btn);
117
117
  background: var(--white);
118
118
  padding: 9px 12px;
119
119
  /* Un <input> n'hérite pas de la police du document : sans cette ligne, le
@@ -190,6 +190,7 @@
190
190
  transition:
191
191
  border-color 140ms cubic-bezier(0.2, 0.7, 0.2, 1),
192
192
  box-shadow 140ms cubic-bezier(0.2, 0.7, 0.2, 1);
193
+ border-radius: var(--r-btn); /* 0 par défaut, arrondi sous .zv-arrondi */
193
194
  }
194
195
 
195
196
  .zv-search:hover {
@@ -331,7 +332,7 @@
331
332
  min-width: 0;
332
333
  min-height: 96px; /* sans source : voir ci-dessus */
333
334
  border: 1px solid var(--line);
334
- border-radius: 0;
335
+ border-radius: var(--r-btn);
335
336
  background: var(--white);
336
337
  padding: 9px 12px;
337
338
  font-family: var(--font-text);
@@ -369,6 +370,7 @@
369
370
  position: relative;
370
371
  display: inline-block;
371
372
  max-width: 100%;
373
+ border-radius: var(--r-btn); /* 0 par défaut, arrondi sous .zv-arrondi */
372
374
  }
373
375
 
374
376
  /* Ancre pleine largeur : le cas d'un CHAMP de formulaire, où la liste doit
@@ -761,7 +763,7 @@
761
763
 
762
764
  .zv-seg {
763
765
  border: 1px solid var(--line);
764
- border-radius: 0;
766
+ border-radius: var(--r-btn);
765
767
  background: var(--white);
766
768
  padding: 4px 9px;
767
769
  font-family: var(--font-text);
@@ -849,6 +851,7 @@
849
851
  background: var(--white);
850
852
  padding: 38px 24px;
851
853
  cursor: pointer;
854
+ border-radius: var(--r-panel); /* 0 par défaut, arrondi sous .zv-arrondi */
852
855
  }
853
856
 
854
857
  /* Survol et survol de dépôt rendus à l'identique : la zone répond pareil que
@@ -1125,7 +1128,7 @@
1125
1128
  margin-right: -6px;
1126
1129
  padding: 0;
1127
1130
  border: 0;
1128
- border-radius: 0;
1131
+ border-radius: var(--r-btn);
1129
1132
  background: none;
1130
1133
  color: var(--muted);
1131
1134
  cursor: pointer;
@@ -290,7 +290,7 @@
290
290
  align-items: center;
291
291
  justify-content: center;
292
292
  border: 1px solid var(--line);
293
- border-radius: 0;
293
+ border-radius: var(--r-btn);
294
294
  background: var(--white);
295
295
  /* base.css ne reset pas les boutons : sans police ni fond explicites, un
296
296
  * <button> retombe sur ceux du navigateur et détonne. Même précaution que
@@ -419,7 +419,7 @@
419
419
  flex-direction: column;
420
420
  min-width: 230px;
421
421
  border: 1px solid var(--line);
422
- border-radius: 0;
422
+ border-radius: var(--r-panel);
423
423
  background: var(--white);
424
424
  padding: 5px;
425
425
  /* Seule ombre du chrome applicatif, et elle est justifiée : un menu flotte
@@ -614,7 +614,7 @@ a.zv-menu__item:hover {
614
614
  min-width: 230px;
615
615
  max-width: 320px;
616
616
  border: 1px solid var(--line);
617
- border-radius: 0;
617
+ border-radius: var(--r-panel);
618
618
  background: var(--white);
619
619
  box-shadow: var(--shadow-menu);
620
620
  padding: 14px 16px;
package/src/tokens.css CHANGED
@@ -158,3 +158,31 @@
158
158
  /* Signature */
159
159
  --spec-filet: 56px; /* filet de spectre 56×2px devant chaque kicker */
160
160
  }
161
+
162
+ /* ─── Les angles arrondis, en option (27/10/2026) ───────────────────
163
+ *
164
+ * HORS RELEVÉ (DECISIONS.md, « Les angles arrondis, en option ») : le
165
+ * défaut reste l'angle vif du 25/08. `zv-arrondi` redéfinit les quatre
166
+ * tokens, et une propriété personnalisée s'hérite : posée sur <html>, la
167
+ * classe arrondit l'app ; sur un conteneur, la zone ; sur un composant,
168
+ * lui seul. `zv-vif` remet un élément au carré dans une zone arrondie.
169
+ *
170
+ * Légèrement, et c'est voulu : à peu près la moitié du relevé d'avant le
171
+ * 25/08. Le rond de forme (pastille, radio, interrupteur) n'en dépend pas. */
172
+ .zv-arrondi {
173
+ --r-btn: 4px;
174
+ --r-pill: 3px;
175
+ --r-panel: 6px;
176
+ --r-card: 8px;
177
+ }
178
+ .zv-vif {
179
+ --r-btn: 0;
180
+ --r-pill: 0;
181
+ --r-panel: 0;
182
+ --r-card: 0;
183
+ }
184
+ /* L'enveloppe `Angles` : elle ne porte que les tokens, sans ajouter de
185
+ * boîte à la mise en page (l'héritage passe par l'arbre, pas par la boîte). */
186
+ .zv-angles {
187
+ display: contents;
188
+ }