@zevra/ui 0.17.0 → 0.21.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.
package/react/forms.tsx CHANGED
@@ -21,6 +21,7 @@ import type {
21
21
  TextareaHTMLAttributes,
22
22
  } from 'react'
23
23
  import { cx } from './cx.js'
24
+ import { Icon } from './icon.js'
24
25
 
25
26
  /* ─── Groupe de champ ──────────────────────────────────────────────── */
26
27
 
@@ -471,3 +472,55 @@ export function Range({ className, style, min, max, value, defaultValue, ...rest
471
472
  />
472
473
  )
473
474
  }
475
+
476
+ export interface InputPasswordProps
477
+ extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className' | 'type'> {
478
+ /** Le mot de passe est-il en clair ? L'état est TENU PAR L'APPELANT. */
479
+ visible?: boolean
480
+ /** Bascule demandée. Sans lui, le bouton n'est pas rendu du tout. */
481
+ onBascule?: () => void
482
+ className?: string
483
+ }
484
+
485
+ /**
486
+ * Champ de saisie masquée, avec sa bascule d'affichage.
487
+ *
488
+ * C'est le seul endroit de l'interface où l'on tape sans voir ce qu'on écrit :
489
+ * sur un mot de passe long, saisi au clavier d'un téléphone, la bascule évite
490
+ * l'échec silencieux et la réinitialisation qui suit.
491
+ *
492
+ * ⚠️ Sans état, comme tout ce fichier : `visible` et `onBascule` viennent de
493
+ * l'appelant, qui est déjà un composant client puisqu'il tient la valeur du
494
+ * champ. Le paquet ne tire pas React côté navigateur pour un champ.
495
+ *
496
+ * Le libellé du bouton change avec l'état — une icône seule ne dit pas ce qui
497
+ * va se passer — et `aria-pressed` l'annonce aux lecteurs d'écran.
498
+ */
499
+ export function InputPassword({
500
+ visible,
501
+ onBascule,
502
+ className,
503
+ ...reste
504
+ }: InputPasswordProps) {
505
+ return (
506
+ <div className={cx('zv-affix', className)}>
507
+ <input
508
+ type={visible ? 'text' : 'password'}
509
+ className="zv-input zv-input--bare"
510
+ {...reste}
511
+ />
512
+ {onBascule && (
513
+ <button
514
+ type="button"
515
+ className="zv-affix__btn"
516
+ onClick={onBascule}
517
+ aria-pressed={visible ?? false}
518
+ aria-label={visible ? 'Masquer le mot de passe' : 'Afficher le mot de passe'}
519
+ title={visible ? 'Masquer le mot de passe' : 'Afficher le mot de passe'}
520
+ >
521
+ <Icon nom={visible ? 'oeil-barre' : 'oeil'} taille="sm" />
522
+ </button>
523
+ )}
524
+ </div>
525
+ )
526
+ }
@@ -8,6 +8,7 @@ export type IconId =
8
8
  | 'app-couches'
9
9
  | 'archiver'
10
10
  | 'attention'
11
+ | 'avancer'
11
12
  | 'balance'
12
13
  | 'base-de-donnees'
13
14
  | 'bouclier-verifie'
@@ -18,6 +19,7 @@ export type IconId =
18
19
  | 'chevron-droit'
19
20
  | 'chevron-gauche'
20
21
  | 'chevron-haut'
22
+ | 'citation'
21
23
  | 'coche'
22
24
  | 'copier'
23
25
  | 'corbeille'
@@ -38,25 +40,38 @@ export type IconId =
38
40
  | 'filtre'
39
41
  | 'globe'
40
42
  | 'graphique'
43
+ | 'gras'
41
44
  | 'horloge'
45
+ | 'image'
42
46
  | 'imprimante'
43
47
  | 'info'
48
+ | 'italique'
49
+ | 'lecture'
44
50
  | 'lien-externe'
51
+ | 'liste-a-puces'
45
52
  | 'livre-ouvert'
46
53
  | 'menu'
47
54
  | 'moins'
55
+ | 'muet'
48
56
  | 'oeil'
57
+ | 'oeil-barre'
58
+ | 'pause'
59
+ | 'plein-ecran'
49
60
  | 'plus'
50
61
  | 'recherche'
51
62
  | 'registre'
52
63
  | 'reglages'
64
+ | 'rembobiner'
53
65
  | 'reseau'
66
+ | 'signet'
67
+ | 'son'
54
68
  | 'taches'
55
69
  | 'telecharger'
56
70
  | 'telephone'
57
71
  | 'tri'
58
72
  | 'trois-points'
59
73
  | 'utilisateur'
74
+ | 'video'
60
75
 
61
76
  export const CORPS: Record<IconId, string> = {
62
77
  'accueil': "<path d=\"M4 10.67 L12 4 L20 10.67 V20 H4 Z\" /><path d=\"M9.33 20 V13.33 H14.67 V20\" />",
@@ -65,6 +80,7 @@ export const CORPS: Record<IconId, string> = {
65
80
  'app-couches': "<path d=\"M12 2.67 L20 6.67 L12 10.67 L4 6.67 Z\" /><path d=\"M4 12 L12 16 L20 12\" /><path d=\"M4 17.33 L12 21.33 L20 17.33\" />",
66
81
  'archiver': "<rect x=\"3.33\" y=\"4\" width=\"17.33\" height=\"4.67\" /><path d=\"M5.33 8.67 V18.93 A1.07 1.07 0 0 0 6.4 20 H17.6 A1.07 1.07 0 0 0 18.67 18.93 V8.67\" /><path d=\"M9.33 12.67 H14.67\" />",
67
82
  'attention': "<path d=\"M10.67 3.87 L2.53 17.87 A1.55 1.55 0 0 0 3.87 20.19 H20.13 A1.55 1.55 0 0 0 21.47 17.87 L13.33 3.87 A1.54 1.54 0 0 0 10.67 3.87 Z\" /><path d=\"M12 9.33 V13.33\" /><rect x=\"11.2\" y=\"15.73\" width=\"1.6\" height=\"1.6\" fill=\"currentColor\" stroke=\"none\" />",
83
+ 'avancer': "<path d=\"M4 6 L11 12 L4 18 Z\" fill=\"currentColor\" stroke=\"none\" /><path d=\"M13 6 L20 12 L13 18 Z\" fill=\"currentColor\" stroke=\"none\" />",
68
84
  'balance': "<path d=\"M12 3.33 V20.67\" /><path d=\"M8.67 20.67 H15.33\" /><path d=\"M5.33 6 H18.67\" /><path d=\"M6.67 6 L3.33 13.33\" /><path d=\"M6.67 6 L10 13.33\" /><path d=\"M3.33 13.33 A3.33 3.33 0 0 0 10 13.33\" /><path d=\"M17.33 6 L14 13.33\" /><path d=\"M17.33 6 L20.67 13.33\" /><path d=\"M14 13.33 A3.33 3.33 0 0 0 20.67 13.33\" />",
69
85
  'base-de-donnees': "<ellipse cx=\"12\" cy=\"6\" rx=\"7.33\" ry=\"2.67\" /><path d=\"M4.67 6 V12 C4.67 13.47 7.87 14.67 12 14.67 C16.13 14.67 19.33 13.47 19.33 12 V6\" /><path d=\"M4.67 12 V18 C4.67 19.47 7.87 20.67 12 20.67 C16.13 20.67 19.33 19.47 19.33 18 V12\" />",
70
86
  'bouclier-verifie': "<path d=\"M12 2.67 L20 6 V12 C20 16.67 16.67 19.73 12 21.33 C7.33 19.73 4 16.67 4 12 V6 Z\" /><path d=\"M8.67 12 L11.07 14.4 L15.33 10\" />",
@@ -75,6 +91,7 @@ export const CORPS: Record<IconId, string> = {
75
91
  'chevron-droit': "<path d=\"M9.33 4 L17.33 12 L9.33 20\" />",
76
92
  'chevron-gauche': "<path d=\"M14.67 4 L6.67 12 L14.67 20\" />",
77
93
  'chevron-haut': "<path d=\"M6.67 14.67 L12 9.33 L17.33 14.67\" />",
94
+ 'citation': "<path d=\"M4 14 V10 C4 7.8 5.8 6 8 6 V9 C6.9 9 6 9.9 6 11 H9 V14 Z\" /><path d=\"M14 14 V10 C14 7.8 15.8 6 18 6 V9 C16.9 9 16 9.9 16 11 H19 V14 Z\" />",
78
95
  'coche': "<path d=\"M5 12.4 L9.4 16.8 L19 6.8\" />",
79
96
  'copier': "<rect x=\"4\" y=\"4\" width=\"12\" height=\"12\" /><path d=\"M8 16 V18.93 A1.07 1.07 0 0 0 9.07 20 H18.93 A1.07 1.07 0 0 0 20 18.93 V9.07 A1.07 1.07 0 0 0 18.93 8 H16\" />",
80
97
  'corbeille': "<path d=\"M4 6.67 H20\" /><path d=\"M9.33 6.67 V4.67 A1.33 1.33 0 0 1 10.67 3.33 H13.33 A1.33 1.33 0 0 1 14.67 4.67 V6.67\" /><path d=\"M6 6.67 L6.93 18.93 A1.33 1.33 0 0 0 8.27 20 H15.73 A1.33 1.33 0 0 0 17.07 18.93 L18 6.67\" /><path d=\"M10 10.67 V16.67\" /><path d=\"M14 10.67 V16.67\" />",
@@ -95,25 +112,38 @@ export const CORPS: Record<IconId, string> = {
95
112
  'filtre': "<path d=\"M2.67 4 H21.33 L14.67 12.27 V18.67 L9.33 21.33 V12.27 Z\" />",
96
113
  'globe': "<circle cx=\"12\" cy=\"12\" r=\"8.67\" /><path d=\"M3.33 12 H20.67\" /><path d=\"M12 3.33 C14.22 5.64 15.47 8.71 15.47 12 C15.47 15.29 14.22 18.36 12 20.67 C9.78 18.36 8.53 15.29 8.53 12 C8.53 8.71 9.78 5.64 12 3.33 Z\" />",
97
114
  'graphique': "<path d=\"M4 4 V20 H20\" /><path d=\"M8.67 20 V12.67\" /><path d=\"M12.67 20 V8\" /><path d=\"M16.67 20 V15.33\" />",
115
+ 'gras': "<path d=\"M7 4 H13 C15.2 4 16.5 5.3 16.5 7.5 C16.5 9.7 15.2 11 13 11 H7 Z\" /><path d=\"M7 11 H14 C16.2 11 17.5 12.5 17.5 15 C17.5 17.5 16.2 19 14 19 H7 Z\" />",
98
116
  'horloge': "<circle cx=\"12\" cy=\"12\" r=\"8\" /><path d=\"M12 6.67 V12 L15.33 14\" />",
117
+ 'image': "<path d=\"M3.5 5 H20.5 V19 H3.5 Z\" /><path d=\"M3.5 16 L9 10.5 L13.5 15 L16 12.5 L20.5 17\" /><path d=\"M15 8.5 H15.01\" />",
99
118
  'imprimante': "<path d=\"M6.67 8 V3.33 H17.33 V8\" /><path d=\"M6.67 17.33 H4.67 A1.33 1.33 0 0 1 3.33 16 V10.67 A1.33 1.33 0 0 1 4.67 9.33 H19.33 A1.33 1.33 0 0 1 20.67 10.67 V16 A1.33 1.33 0 0 1 19.33 17.33 H17.33\" /><rect x=\"6.67\" y=\"13.33\" width=\"10.67\" height=\"7.33\" />",
100
119
  'info': "<circle cx=\"12\" cy=\"12\" r=\"8.67\" /><path d=\"M12 11.33 V16\" /><rect x=\"11.2\" y=\"7.2\" width=\"1.6\" height=\"1.6\" fill=\"currentColor\" stroke=\"none\" />",
120
+ 'italique': "<path d=\"M10 5 H18\" /><path d=\"M6 19 H14\" /><path d=\"M14.5 5 L9.5 19\" />",
121
+ 'lecture': "<path d=\"M8 5 L19 12 L8 19 Z\" fill=\"currentColor\" stroke=\"none\" />",
101
122
  'lien-externe': "<path d=\"M10.67 4 H4 V20 H20 V13.33\" /><path d=\"M14.67 4 H20 V9.33\" /><path d=\"M20 4 L12 12\" />",
123
+ 'liste-a-puces': "<path d=\"M9 6.5 H20\" /><path d=\"M9 12 H20\" /><path d=\"M9 17.5 H20\" /><path d=\"M4 5.5 H6 V7.5 H4 Z\" fill=\"currentColor\" stroke=\"none\" /><path d=\"M4 11 H6 V13 H4 Z\" fill=\"currentColor\" stroke=\"none\" /><path d=\"M4 16.5 H6 V18.5 H4 Z\" fill=\"currentColor\" stroke=\"none\" />",
102
124
  'livre-ouvert': "<path d=\"M2.67 5.33 C5.33 4.67 7.78 4.89 12 6.67 C16.22 4.89 18.67 4.67 21.33 5.33 V18 C18.67 17.33 16.22 17.56 12 19.33 C7.78 17.56 5.33 17.33 2.67 18 Z\" /><path d=\"M12 6.67 V19.33\" />",
103
125
  'menu': "<path d=\"M0 5.33 H24\" /><path d=\"M0 12 H24\" /><path d=\"M0 18.67 H24\" />",
104
126
  'moins': "<path d=\"M4 12 H20\" />",
127
+ 'muet': "<path d=\"M4 9.5 H7.5 L12 5.5 V18.5 L7.5 14.5 H4 Z\" /><path d=\"M16 10 L21 14\" /><path d=\"M21 10 L16 14\" />",
105
128
  'oeil': "<path d=\"M2 12 C5.33 6.67 8.67 4.67 12 4.67 C15.33 4.67 18.67 6.67 22 12 C18.67 17.33 15.33 19.33 12 19.33 C8.67 19.33 5.33 17.33 2 12 Z\" /><circle cx=\"12\" cy=\"12\" r=\"2.67\" />",
129
+ 'oeil-barre': "<path d=\"M3 12 C5.5 7.5 8.6 5.5 12 5.5 C15.4 5.5 18.5 7.5 21 12 C18.5 16.5 15.4 18.5 12 18.5 C8.6 18.5 5.5 16.5 3 12 Z\" /><path d=\"M9.5 12 A2.5 2.5 0 0 1 14.5 12 A2.5 2.5 0 0 1 9.5 12 Z\" /><path d=\"M4 20 L20 4\" />",
130
+ 'pause': "<path d=\"M9 5 H11 V19 H9 Z\" fill=\"currentColor\" stroke=\"none\" /><path d=\"M13 5 H15 V19 H13 Z\" fill=\"currentColor\" stroke=\"none\" />",
131
+ 'plein-ecran': "<path d=\"M4 9 V4 H9\" /><path d=\"M20 9 V4 H15\" /><path d=\"M4 15 V20 H9\" /><path d=\"M20 15 V20 H15\" />",
106
132
  'plus': "<path d=\"M12 4 V20\" /><path d=\"M4 12 H20\" />",
107
133
  'recherche': "<circle cx=\"10.67\" cy=\"10.67\" r=\"6\" /><path d=\"M15.33 15.33 L20 20\" />",
108
134
  'registre': "<rect x=\"3.33\" y=\"5.33\" width=\"17.33\" height=\"5.33\" /><rect x=\"3.33\" y=\"13.33\" width=\"17.33\" height=\"5.33\" /><rect x=\"5.87\" y=\"7.2\" width=\"1.6\" height=\"1.6\" fill=\"currentColor\" stroke=\"none\" /><rect x=\"5.87\" y=\"15.2\" width=\"1.6\" height=\"1.6\" fill=\"currentColor\" stroke=\"none\" />",
109
135
  'reglages': "<circle cx=\"12\" cy=\"12\" r=\"2.93\" /><path d=\"M12 2 V4.67 M12 19.33 V22 M2 12 H4.67 M19.33 12 H22 M4.8 4.8 L6.67 6.67 M17.33 17.33 L19.2 19.2 M19.2 4.8 L17.33 6.67 M6.67 17.33 L4.8 19.2\" />",
136
+ 'rembobiner': "<path d=\"M11 6 L4 12 L11 18 Z\" fill=\"currentColor\" stroke=\"none\" /><path d=\"M20 6 L13 12 L20 18 Z\" fill=\"currentColor\" stroke=\"none\" />",
110
137
  'reseau': "<circle cx=\"12\" cy=\"12\" r=\"8\" /><path d=\"M4 12 H20\" /><path d=\"M12 4 C14.67 8 14.67 16 12 20\" /><path d=\"M12 4 C9.33 8 9.33 16 12 20\" />",
138
+ 'signet': "<path d=\"M6 4 H18 V20 L12 15.5 L6 20 Z\" />",
139
+ 'son': "<path d=\"M4 9.5 H7.5 L12 5.5 V18.5 L7.5 14.5 H4 Z\" /><path d=\"M16 9 C17.5 10.2 17.5 13.8 16 15\" /><path d=\"M18.8 6.6 C21.4 9 21.4 15 18.8 17.4\" />",
111
140
  'taches': "<path d=\"M4.67 6.67 L6.67 8.67 L10 5.33\" /><path d=\"M4.67 16 L6.67 18 L10 14.67\" /><path d=\"M13.33 7.33 H20\" /><path d=\"M13.33 16.67 H20\" />",
112
141
  'telecharger': "<path d=\"M12 3.33 V15.33\" /><polyline points=\"6.67,10 12,15.33 17.33,10\" /><path d=\"M4.67 20.67 H19.33\" />",
113
142
  'telephone': "<path d=\"M20.67 16.93 V19.33 A1.6 1.6 0 0 1 18.93 20.93 C16.13 20.67 13.47 19.67 11.2 18.13 C9.09 16.71 7.29 14.91 5.87 12.8 C4.33 10.52 3.33 7.85 3.07 5.05 A1.6 1.6 0 0 1 4.67 3.33 H7.07 A1.6 1.6 0 0 1 8.67 4.71 C8.79 5.83 9.04 6.93 9.4 7.97 A1.6 1.6 0 0 1 9.04 9.65 L8 10.69 C9.32 13.01 11.03 14.71 13.33 16 L14.37 14.96 A1.6 1.6 0 0 1 16.05 14.6 C17.09 14.96 18.19 15.21 19.31 15.33 A1.6 1.6 0 0 1 20.67 16.93 Z\" />",
114
143
  'tri': "<path d=\"M8 4 V20\" /><path d=\"M4.67 7.33 L8 4 L11.33 7.33\" /><path d=\"M16 20 V4\" /><path d=\"M12.67 16.67 L16 20 L19.33 16.67\" />",
115
144
  'trois-points': "<rect x=\"4\" y=\"10.67\" width=\"2.66\" height=\"2.66\" fill=\"currentColor\" stroke=\"none\" /><rect x=\"10.67\" y=\"10.67\" width=\"2.66\" height=\"2.66\" fill=\"currentColor\" stroke=\"none\" /><rect x=\"17.34\" y=\"10.67\" width=\"2.66\" height=\"2.66\" fill=\"currentColor\" stroke=\"none\" />",
116
145
  'utilisateur': "<circle cx=\"12\" cy=\"8\" r=\"3.33\" /><path d=\"M5.33 20 C5.33 15.33 8.33 13.33 12 13.33 C15.67 13.33 18.67 15.33 18.67 20\" />",
146
+ 'video': "<path d=\"M3.5 6 H15 V18 H3.5 Z\" /><path d=\"M15 10 L20.5 7 V17 L15 14 Z\" />",
117
147
  }
118
148
 
119
149
  /** Nom lisible de chaque icône — sert de titre accessible par défaut à
@@ -125,6 +155,7 @@ export const NOMS: Record<IconId, string> = {
125
155
  'app-couches': "Application (couches)",
126
156
  'archiver': "Archiver",
127
157
  'attention': "Attention (avertissement)",
158
+ 'avancer': "Avancer",
128
159
  'balance': "Balance (justice)",
129
160
  'base-de-donnees': "Base de données",
130
161
  'bouclier-verifie': "Bouclier vérifié (authentification)",
@@ -135,6 +166,7 @@ export const NOMS: Record<IconId, string> = {
135
166
  'chevron-droit': "Chevron droit",
136
167
  'chevron-gauche': "Chevron gauche",
137
168
  'chevron-haut': "Chevron haut",
169
+ 'citation': "Citation",
138
170
  'coche': "Coche",
139
171
  'copier': "Copier",
140
172
  'corbeille': "Corbeille (supprimer)",
@@ -155,23 +187,36 @@ export const NOMS: Record<IconId, string> = {
155
187
  'filtre': "Filtre",
156
188
  'globe': "Globe (international)",
157
189
  'graphique': "Graphique (barres)",
190
+ 'gras': "Gras",
158
191
  'horloge': "Horloge (intégration continue)",
192
+ 'image': "Image",
159
193
  'imprimante': "Imprimante",
160
194
  'info': "Information",
195
+ 'italique': "Italique",
196
+ 'lecture': "Lecture",
161
197
  'lien-externe': "Lien externe",
198
+ 'liste-a-puces': "Liste à puces",
162
199
  'livre-ouvert': "Livre ouvert (formation)",
163
200
  'menu': "Menu (hamburger)",
164
201
  'moins': "Moins",
202
+ 'muet': "Muet",
165
203
  'oeil': "Œil (afficher)",
204
+ 'oeil-barre': "Œil barré",
205
+ 'pause': "Pause",
206
+ 'plein-ecran': "Plein écran",
166
207
  'plus': "Plus",
167
208
  'recherche': "Recherche (loupe)",
168
209
  'registre': "Registre (serveurs empilés)",
169
210
  'reglages': "Réglages",
211
+ 'rembobiner': "Rembobiner",
170
212
  'reseau': "Réseau (entrée / globe)",
213
+ 'signet': "Signet",
214
+ 'son': "Son",
171
215
  'taches': "Tâches (liste à cocher)",
172
216
  'telecharger': "Télécharger",
173
217
  'telephone': "Téléphone",
174
218
  'tri': "Tri",
175
219
  'trois-points': "Trois points (menu contextuel)",
176
220
  'utilisateur': "Utilisateur",
221
+ 'video': "Vidéo",
177
222
  }
package/react/layout.tsx CHANGED
@@ -5,6 +5,7 @@
5
5
  // Tout y est sans état.
6
6
 
7
7
  import type {
8
+ AnchorHTMLAttributes,
8
9
  ButtonHTMLAttributes,
9
10
  CSSProperties,
10
11
  ElementType,
@@ -162,8 +163,14 @@ export function NavLinks({ className, ...reste }: Div & { className?: string })
162
163
  return <div className={cx('zv-nav__links', className)} {...reste} />
163
164
  }
164
165
 
166
+ /** Les composants qui rendent un `<a>` sont typés sur
167
+ * `AnchorHTMLAttributes` et non `HTMLAttributes` : sans quoi ils refusent
168
+ * `target`, `rel`, `download` et `hreflang`. Un lien sortant ne peut alors
169
+ * pas porter `rel="noopener"`, et un lien qui doit s'ouvrir à côté ne le
170
+ * peut pas non plus — une app consommatrice a dû écrire la classe à la main
171
+ * pour cette seule raison. (0.19.0) */
165
172
  export interface NavLinkProps
166
- extends Omit<HTMLAttributes<HTMLAnchorElement>, 'className'> {
173
+ extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className'> {
167
174
  /** Lien secondaire : connexion, mentions. */
168
175
  discret?: boolean
169
176
  href?: string
@@ -200,7 +207,7 @@ export function NavDrawerLink({
200
207
  courant,
201
208
  className,
202
209
  ...reste
203
- }: Omit<HTMLAttributes<HTMLAnchorElement>, 'className'> & {
210
+ }: Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className'> & {
204
211
  courant?: boolean
205
212
  href?: string
206
213
  className?: string
@@ -287,7 +294,7 @@ export function Langue({
287
294
  courante,
288
295
  className,
289
296
  ...reste
290
- }: Omit<HTMLAttributes<HTMLAnchorElement>, 'className'> & {
297
+ }: Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className'> & {
291
298
  courante?: boolean
292
299
  href?: string
293
300
  className?: string
@@ -389,7 +396,7 @@ export function RailItem({
389
396
  className,
390
397
  children,
391
398
  ...reste
392
- }: Omit<HTMLAttributes<HTMLAnchorElement>, 'className'> & {
399
+ }: Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className'> & {
393
400
  courant?: boolean
394
401
  href: string
395
402
  className?: string
@@ -6,7 +6,12 @@
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
 
9
- import type { ElementType, HTMLAttributes, ReactNode } from 'react'
9
+ import type {
10
+ AnchorHTMLAttributes,
11
+ ElementType,
12
+ HTMLAttributes,
13
+ ReactNode,
14
+ } from 'react'
10
15
  import { cx } from './cx.js'
11
16
 
12
17
  type Div = Omit<HTMLAttributes<HTMLElement>, 'className'>
@@ -122,12 +127,17 @@ export function Em({
122
127
  )
123
128
  }
124
129
 
130
+ /** Les composants qui rendent un `<a>` sont typés sur
131
+ * `AnchorHTMLAttributes` et non `HTMLAttributes` : sans quoi ils refusent
132
+ * `target`, `rel`, `download` et `hreflang`. Un lien sortant ne peut alors
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
135
+ * pour cette seule raison. (0.19.0) */
125
136
  export function Link({
126
137
  className,
127
138
  ...reste
128
- }: Omit<HTMLAttributes<HTMLAnchorElement>, 'className'> & {
139
+ }: Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className'> & {
129
140
  className?: string
130
- href?: string
131
141
  }) {
132
142
  return <a className={cx('zv-link', className)} {...reste} />
133
143
  }
package/src/base.css CHANGED
@@ -160,6 +160,13 @@ a:not([class]):hover {
160
160
  .zv-em {
161
161
  color: var(--accent-deep);
162
162
  font-weight: 400;
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
165
+ * fabrique donc une oblique de synthèse. Sur un titre de 76px, ça se voit.
166
+ * La charte ne demande à cette classe qu'un changement de couleur et de
167
+ * graisse ; on remet donc la droite. (`.zv-inset em` portait déjà cette
168
+ * règle : elle vit désormais là où le besoin est structurel.) */
169
+ font-style: normal;
163
170
  }
164
171
 
165
172
  /* Sur fond sombre l'accent ne change pas de couleur mais de graisse. */