@zevra/ui 0.7.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.
- package/DECISIONS.md +227 -0
- package/dist/actions.d.ts +13 -3
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +9 -2
- package/dist/actions.js.map +1 -1
- package/dist/catalogue.d.ts.map +1 -1
- package/dist/catalogue.js +160 -11
- package/dist/catalogue.js.map +1 -1
- package/dist/css-inline.d.ts.map +1 -1
- package/dist/css-inline.js +1 -1
- package/dist/css-inline.js.map +1 -1
- package/dist/data-table.d.ts +30 -0
- package/dist/data-table.d.ts.map +1 -0
- package/dist/data-table.js +35 -0
- package/dist/data-table.js.map +1 -0
- package/dist/data.d.ts +34 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +27 -0
- package/dist/data.js.map +1 -1
- package/dist/feedback.d.ts +49 -0
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +24 -0
- package/dist/feedback.js.map +1 -1
- package/dist/forms.d.ts +36 -0
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +24 -0
- package/dist/forms.js.map +1 -1
- package/dist/icon-data.d.ts +1 -1
- package/dist/icon-data.d.ts.map +1 -1
- package/dist/icon-data.js +42 -0
- package/dist/icon-data.js.map +1 -1
- package/dist/icon.d.ts +1 -1
- package/dist/icon.js +1 -1
- package/dist/layout.d.ts +21 -0
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +18 -0
- package/dist/layout.js.map +1 -1
- package/dist/navigation.d.ts +45 -7
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +54 -7
- package/dist/navigation.js.map +1 -1
- package/dist/surfaces.d.ts +18 -0
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +6 -0
- package/dist/surfaces.js.map +1 -1
- package/icons/index.json +126 -0
- package/icons/sprite.svg +21 -0
- package/package.json +18 -2
- package/react/actions.tsx +28 -2
- package/react/catalogue.tsx +343 -17
- package/react/css-inline.ts +1 -1
- package/react/data-table.tsx +153 -0
- package/react/data.tsx +87 -1
- package/react/feedback.tsx +98 -0
- package/react/forms.tsx +90 -0
- package/react/icon-data.ts +63 -0
- package/react/icon.tsx +2 -2
- package/react/layout.tsx +39 -0
- package/react/navigation.tsx +108 -6
- package/react/surfaces.tsx +45 -0
- package/src/base.css +16 -0
- package/src/components.css +135 -3
- package/src/data.css +188 -1
- package/src/feedback.css +196 -37
- package/src/forms.css +137 -1
- package/src/layout.css +72 -0
- package/src/navigation.css +207 -12
- package/src/surfaces.css +76 -7
- package/src/tokens.css +6 -0
- package/tokens/index.js +2 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Tableau piloté par TanStack Table.
|
|
2
|
+
//
|
|
3
|
+
// ⚠️ CE FICHIER N'EST PAS DANS LE BARREL, et c'est délibéré :
|
|
4
|
+
// `@tanstack/react-table` est une dépendance OPTIONNELLE. L'importer depuis
|
|
5
|
+
// `@zevra/ui/react` casserait toutes les apps qui ne l'ont pas installée —
|
|
6
|
+
// une landing n'a aucun tableau. D'où le sous-chemin dédié :
|
|
7
|
+
//
|
|
8
|
+
// import { DataTable } from '@zevra/ui/react/table'
|
|
9
|
+
//
|
|
10
|
+
// POURQUOI TANSTACK plutôt que notre propre tri/pagination : tri multi-colonnes,
|
|
11
|
+
// filtres, groupement, virtualisation, sélection de lignes — c'est un travail
|
|
12
|
+
// de plusieurs mois que personne ici n'a envie de refaire, et mal. TanStack est
|
|
13
|
+
// HEADLESS : il ne rend rien, il calcule des modèles de lignes. Ce fichier lui
|
|
14
|
+
// donne l'apparence du paquet, exactement comme shadcn le fait avec ses
|
|
15
|
+
// variables. Aucune classe nouvelle n'entre par cette porte : ce sont les
|
|
16
|
+
// mêmes .zv-table / .zv-row / .zv-cell que le tableau écrit à la main.
|
|
17
|
+
|
|
18
|
+
import type { ReactNode } from 'react'
|
|
19
|
+
import {
|
|
20
|
+
flexRender,
|
|
21
|
+
type RowData,
|
|
22
|
+
type Table as TableTanstack,
|
|
23
|
+
} from '@tanstack/react-table'
|
|
24
|
+
import { cx } from './cx.js'
|
|
25
|
+
|
|
26
|
+
// Le pont entre les deux mondes : une colonne déclare ici son registre
|
|
27
|
+
// d'affichage, et le composant le traduit en classes du paquet. C'est ce qui
|
|
28
|
+
// évite d'avoir à écrire une cellule sur mesure pour aligner un nombre.
|
|
29
|
+
declare module '@tanstack/react-table' {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
31
|
+
interface ColumnMeta<TData extends RowData, TValue> {
|
|
32
|
+
/** `nom` : colonne d'identité, la seule en encre pleine.
|
|
33
|
+
* `num` : chiffres alignés à droite, en chasse tabulaire. */
|
|
34
|
+
registre?: 'nom' | 'num'
|
|
35
|
+
/** Intitulé qui REMPLACE l'en-tête sous le point de rupture, où les
|
|
36
|
+
* colonnes s'empilent et où l'en-tête disparaît. */
|
|
37
|
+
label?: string
|
|
38
|
+
/** Unité accolée au nombre, en plus petit. */
|
|
39
|
+
unite?: ReactNode
|
|
40
|
+
/** Largeur de la colonne dans la grille (syntaxe grid-template-columns).
|
|
41
|
+
* Par défaut : `minmax(0, 1fr)`. */
|
|
42
|
+
largeur?: string
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DataTableProps<T> {
|
|
47
|
+
table: TableTanstack<T>
|
|
48
|
+
/** Rendu à la place des lignes quand le modèle est vide. ⚠️ C'est la LIGNE
|
|
49
|
+
* d'absence qu'on attend ici (filtre trop étroit), pas l'écran d'absence :
|
|
50
|
+
* l'en-tête doit rester visible pour qu'on puisse élargir le filtre. */
|
|
51
|
+
vide?: ReactNode
|
|
52
|
+
/** Appelé au clic sur une ligne. Sans lui, la ligne n'est pas cliquable —
|
|
53
|
+
* et elle ne doit pas en avoir l'air. */
|
|
54
|
+
onRowClick?: (ligne: T) => void
|
|
55
|
+
className?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function DataTable<T>({ table, vide, onRowClick, className }: DataTableProps<T>) {
|
|
59
|
+
const colonnes = table.getVisibleLeafColumns()
|
|
60
|
+
const gabarit = colonnes
|
|
61
|
+
.map((c) => c.columnDef.meta?.largeur ?? 'minmax(0, 1fr)')
|
|
62
|
+
.join(' ')
|
|
63
|
+
const lignes = table.getRowModel().rows
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
className={cx('zv-table', className)}
|
|
68
|
+
style={{ ['--zv-cols']: gabarit } as React.CSSProperties}
|
|
69
|
+
>
|
|
70
|
+
{table.getHeaderGroups().map((groupe) => (
|
|
71
|
+
<div className="zv-table__head" key={groupe.id}>
|
|
72
|
+
{groupe.headers.map((entete) => {
|
|
73
|
+
const triable = entete.column.getCanSort()
|
|
74
|
+
const sens = entete.column.getIsSorted()
|
|
75
|
+
const registre = entete.column.columnDef.meta?.registre
|
|
76
|
+
const contenu = entete.isPlaceholder
|
|
77
|
+
? null
|
|
78
|
+
: flexRender(entete.column.columnDef.header, entete.getContext())
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
key={entete.id}
|
|
82
|
+
className={cx('zv-cell', registre === 'num' && 'zv-cell--num')}
|
|
83
|
+
// La colonne triée est annoncée : sans aria-sort, le tri est
|
|
84
|
+
// une information purement visuelle.
|
|
85
|
+
aria-sort={
|
|
86
|
+
sens === 'asc' ? 'ascending' : sens === 'desc' ? 'descending' : undefined
|
|
87
|
+
}
|
|
88
|
+
>
|
|
89
|
+
{triable ? (
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
className="zv-sort"
|
|
93
|
+
onClick={entete.column.getToggleSortingHandler()}
|
|
94
|
+
>
|
|
95
|
+
{contenu}
|
|
96
|
+
<span className="zv-sort__mark" aria-hidden="true">
|
|
97
|
+
{sens === 'asc' ? '↑' : sens === 'desc' ? '↓' : '↕'}
|
|
98
|
+
</span>
|
|
99
|
+
</button>
|
|
100
|
+
) : (
|
|
101
|
+
contenu
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
)
|
|
105
|
+
})}
|
|
106
|
+
</div>
|
|
107
|
+
))}
|
|
108
|
+
|
|
109
|
+
{lignes.length === 0
|
|
110
|
+
? vide
|
|
111
|
+
: lignes.map((ligne) => (
|
|
112
|
+
<div
|
|
113
|
+
key={ligne.id}
|
|
114
|
+
className="zv-row"
|
|
115
|
+
// Le clavier suit le clic : une ligne cliquable qui n'est pas
|
|
116
|
+
// atteignable au clavier est une action inaccessible.
|
|
117
|
+
role={onRowClick ? 'button' : undefined}
|
|
118
|
+
tabIndex={onRowClick ? 0 : undefined}
|
|
119
|
+
style={onRowClick ? undefined : { cursor: 'default' }}
|
|
120
|
+
onClick={onRowClick ? () => onRowClick(ligne.original) : undefined}
|
|
121
|
+
onKeyDown={
|
|
122
|
+
onRowClick
|
|
123
|
+
? (e) => {
|
|
124
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
125
|
+
e.preventDefault()
|
|
126
|
+
onRowClick(ligne.original)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
: undefined
|
|
130
|
+
}
|
|
131
|
+
>
|
|
132
|
+
{ligne.getVisibleCells().map((cellule) => {
|
|
133
|
+
const meta = cellule.column.columnDef.meta
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
key={cellule.id}
|
|
137
|
+
className={cx(
|
|
138
|
+
'zv-cell',
|
|
139
|
+
meta?.registre === 'nom' && 'zv-cell--name',
|
|
140
|
+
meta?.registre === 'num' && 'zv-cell--num',
|
|
141
|
+
)}
|
|
142
|
+
>
|
|
143
|
+
{meta?.label && <span className="zv-cell__label">{meta.label}</span>}
|
|
144
|
+
{flexRender(cellule.column.columnDef.cell, cellule.getContext())}
|
|
145
|
+
{meta?.unite && <span className="zv-cell__unit">{meta.unite}</span>}
|
|
146
|
+
</div>
|
|
147
|
+
)
|
|
148
|
+
})}
|
|
149
|
+
</div>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
)
|
|
153
|
+
}
|
package/react/data.tsx
CHANGED
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
// et en carte empilée en dessous, sans dupliquer le balisage. `Unwrap` est la
|
|
6
6
|
// primitive qui rend cela possible.
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
AnchorHTMLAttributes,
|
|
10
|
+
CSSProperties,
|
|
11
|
+
DetailsHTMLAttributes,
|
|
12
|
+
HTMLAttributes,
|
|
13
|
+
ReactNode,
|
|
14
|
+
} from 'react'
|
|
9
15
|
import { cx } from './cx.js'
|
|
10
16
|
|
|
11
17
|
type Div = Omit<HTMLAttributes<HTMLDivElement>, 'className'>
|
|
@@ -298,3 +304,83 @@ export function FileType({ format, className, ...reste }: FileTypeProps) {
|
|
|
298
304
|
</span>
|
|
299
305
|
)
|
|
300
306
|
}
|
|
307
|
+
|
|
308
|
+
/* ─── Rangée dépliante ─────────────────────────────────────────────── */
|
|
309
|
+
|
|
310
|
+
export interface FoldProps
|
|
311
|
+
extends Omit<DetailsHTMLAttributes<HTMLDetailsElement>, 'className' | 'open'> {
|
|
312
|
+
titre: ReactNode
|
|
313
|
+
/** Ouverte au premier rendu. L'état vit ensuite dans le NAVIGATEUR
|
|
314
|
+
* (<details> natif) : pas de prop contrôlée, pas de JavaScript — le
|
|
315
|
+
* clavier et l'annonce viennent avec. `open` est retiré des attributs :
|
|
316
|
+
* deux canaux pour le même état, et c'est le silencieux qui gagnerait. */
|
|
317
|
+
ouvert?: boolean
|
|
318
|
+
className?: string
|
|
319
|
+
children?: ReactNode
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Le pliage de l'applicatif : options avancées, détail d'une ligne de
|
|
323
|
+
* journal. La FAQ (contenu) reste le pliage de la VITRINE — question en
|
|
324
|
+
* 14px, respiration éditoriale ; celui-ci est un chrome dense. Le chevron
|
|
325
|
+
* fait partie du composant, comme la flèche de LinkArrow. */
|
|
326
|
+
export function Fold({ titre, ouvert, className, children, ...reste }: FoldProps) {
|
|
327
|
+
return (
|
|
328
|
+
<details className={cx('zv-fold', className)} open={ouvert} {...reste}>
|
|
329
|
+
<summary className="zv-fold__titre">
|
|
330
|
+
{titre}
|
|
331
|
+
<svg
|
|
332
|
+
className="zv-fold__chevron"
|
|
333
|
+
viewBox="0 0 24 24"
|
|
334
|
+
fill="none"
|
|
335
|
+
stroke="currentColor"
|
|
336
|
+
strokeWidth="2"
|
|
337
|
+
strokeLinecap="round"
|
|
338
|
+
strokeLinejoin="round"
|
|
339
|
+
aria-hidden="true"
|
|
340
|
+
>
|
|
341
|
+
<path d="M6.67 9.33 L12 14.67 L17.33 9.33" />
|
|
342
|
+
</svg>
|
|
343
|
+
</summary>
|
|
344
|
+
<div className="zv-fold__corps">{children}</div>
|
|
345
|
+
</details>
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* ─── Touche de clavier ────────────────────────────────────────────── */
|
|
350
|
+
|
|
351
|
+
/** Une touche, une seule : « ⌘K » est DEUX touches, donc deux `Kbd` — les
|
|
352
|
+
* fondre dans une seule dessine une touche qui n'existe sur aucun
|
|
353
|
+
* clavier. */
|
|
354
|
+
export function Kbd({
|
|
355
|
+
className,
|
|
356
|
+
...reste
|
|
357
|
+
}: Omit<HTMLAttributes<HTMLElement>, 'className'> & { className?: string }) {
|
|
358
|
+
return <kbd className={cx('zv-kbd', className)} {...reste} />
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* ─── Code ─────────────────────────────────────────────────────────── */
|
|
362
|
+
|
|
363
|
+
/** Code EN LIGNE, dans une phrase : `pnpm build`. Taille relative au texte
|
|
364
|
+
* qui l'accueille. Pour un extrait de plusieurs lignes, `CodeBlock`. */
|
|
365
|
+
export function Code({
|
|
366
|
+
className,
|
|
367
|
+
...reste
|
|
368
|
+
}: Omit<HTMLAttributes<HTMLElement>, 'className'> & { className?: string }) {
|
|
369
|
+
return <code className={cx('zv-code', className)} {...reste} />
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Bloc de code. Chasse fixe RÉELLE (--font-code) : --font-mono est
|
|
373
|
+
* proportionnelle et n'aligne pas du code. Aucune coloration syntaxique —
|
|
374
|
+
* elle imposerait une palette entière que la charte n'a pas. Le bloc
|
|
375
|
+
* défile en son sein : une ligne longue ne pousse jamais la page. */
|
|
376
|
+
export function CodeBlock({
|
|
377
|
+
className,
|
|
378
|
+
children,
|
|
379
|
+
...reste
|
|
380
|
+
}: Omit<HTMLAttributes<HTMLPreElement>, 'className'> & { className?: string }) {
|
|
381
|
+
return (
|
|
382
|
+
<pre className={cx('zv-codeblock', className)} {...reste}>
|
|
383
|
+
<code>{children}</code>
|
|
384
|
+
</pre>
|
|
385
|
+
)
|
|
386
|
+
}
|
package/react/feedback.tsx
CHANGED
|
@@ -227,3 +227,101 @@ export function Progress({ valeur, label, className, ...reste }: ProgressProps)
|
|
|
227
227
|
</div>
|
|
228
228
|
)
|
|
229
229
|
}
|
|
230
|
+
|
|
231
|
+
/* ─── Roue ─────────────────────────────────────────────────────────── */
|
|
232
|
+
|
|
233
|
+
export interface SpinnerProps extends Span {
|
|
234
|
+
/** En tête d'un panneau d'attente : la roue seule, plus grande. */
|
|
235
|
+
grand?: boolean
|
|
236
|
+
/** Ce qu'on attend, annoncé au lecteur d'écran. Sans lui la roue est
|
|
237
|
+
* DÉCORATIVE et masquée — le bon défaut quand une phrase voisine dit
|
|
238
|
+
* déjà le travail (`Loading`, ou le libellé d'un bouton `occupe`). */
|
|
239
|
+
label?: string
|
|
240
|
+
className?: string
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Attente COURTE ET LOCALE : un bouton, une cellule, un panneau sans la
|
|
244
|
+
* place d'une phrase. Pour l'attente d'un écran, préférer `Loading` — la
|
|
245
|
+
* phrase qui dit le travail réel reste le motif par défaut du système.
|
|
246
|
+
* Le libellé est un TEXTE masqué visuellement, pas un aria-label : sur un
|
|
247
|
+
* span vide, l'aria-label n'est pas annoncé par tous les lecteurs. */
|
|
248
|
+
export function Spinner({ grand, label, className, ...reste }: SpinnerProps) {
|
|
249
|
+
return (
|
|
250
|
+
<span
|
|
251
|
+
role={label ? 'status' : undefined}
|
|
252
|
+
aria-hidden={label ? undefined : true}
|
|
253
|
+
className={cx('zv-spinner', grand && 'zv-spinner--lg', className)}
|
|
254
|
+
{...reste}
|
|
255
|
+
>
|
|
256
|
+
{label && <span className="zv-sr-only">{label}</span>}
|
|
257
|
+
</span>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* ─── Squelette ────────────────────────────────────────────────────── */
|
|
262
|
+
|
|
263
|
+
export interface SkeletonProps extends Div {
|
|
264
|
+
/** Une ligne de texte (hauteur d'un cadratin) plutôt qu'un bloc libre. */
|
|
265
|
+
texte?: boolean
|
|
266
|
+
/** La géométrie vient du CONSOMMATEUR : un squelette qui invente ses
|
|
267
|
+
* dimensions ne ressemble jamais au contenu qu'il annonce. */
|
|
268
|
+
largeur?: number | string
|
|
269
|
+
hauteur?: number | string
|
|
270
|
+
className?: string
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Il ANNONCE une structure connue (la carte, la ligne qui va venir) — pour
|
|
274
|
+
* un contenu dont la forme est inconnue, c'est `Loading` qui dit ce cas.
|
|
275
|
+
* Le squelette est masqué aux lecteurs d'écran : c'est la zone qui charge
|
|
276
|
+
* qui porte aria-busy, pas chaque os. */
|
|
277
|
+
export function Skeleton({ texte, largeur, hauteur, className, style, ...reste }: SkeletonProps) {
|
|
278
|
+
return (
|
|
279
|
+
<div
|
|
280
|
+
aria-hidden="true"
|
|
281
|
+
className={cx('zv-skeleton', texte && 'zv-skeleton--text', className)}
|
|
282
|
+
style={{ width: largeur, height: hauteur, ...style }}
|
|
283
|
+
{...reste}
|
|
284
|
+
/>
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* ─── Infobulle ────────────────────────────────────────────────────── */
|
|
289
|
+
|
|
290
|
+
export interface TooltipProps extends Span {
|
|
291
|
+
/** Le complément — un raccourci, une date exacte, une unité. JAMAIS le
|
|
292
|
+
* nom de la commande : lui vit sur la cible (libellé ou aria-label), une
|
|
293
|
+
* infobulle n'existe pas au doigt. */
|
|
294
|
+
contenu: ReactNode
|
|
295
|
+
/** Identifiant EXPLICITE, à répéter en `aria-describedby` sur la cible —
|
|
296
|
+
* le contrat du couple htmlFor/id de Field, pour la même raison : pas de
|
|
297
|
+
* useId, le composant reste rendu au serveur. */
|
|
298
|
+
id?: string
|
|
299
|
+
/** Sous la cible, pour les rares cas où le haut est occupé. */
|
|
300
|
+
bas?: boolean
|
|
301
|
+
className?: string
|
|
302
|
+
/** La cible. Elle doit être FOCALISABLE (un bouton, un lien) : c'est son
|
|
303
|
+
* focus qui montre l'infobulle au clavier. */
|
|
304
|
+
children?: ReactNode
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Échap ferme la bulle jusqu'au blur (WCAG 1.4.13) : la classe est posée
|
|
308
|
+
* sur le nœud sans état React — le composant reste rendu au serveur, et
|
|
309
|
+
* sans script la bulle reste simplement au comportement survol/focus. */
|
|
310
|
+
export function Tooltip({ contenu, id, bas, className, children, ...reste }: TooltipProps) {
|
|
311
|
+
return (
|
|
312
|
+
<span
|
|
313
|
+
className={cx('zv-tooltip-hit', className)}
|
|
314
|
+
onKeyDown={(e) => {
|
|
315
|
+
if (e.key === 'Escape') e.currentTarget.classList.add('zv-tooltip-hit--off')
|
|
316
|
+
}}
|
|
317
|
+
onBlur={(e) => e.currentTarget.classList.remove('zv-tooltip-hit--off')}
|
|
318
|
+
onMouseLeave={(e) => e.currentTarget.classList.remove('zv-tooltip-hit--off')}
|
|
319
|
+
{...reste}
|
|
320
|
+
>
|
|
321
|
+
{children}
|
|
322
|
+
<span role="tooltip" id={id} className={cx('zv-tooltip', bas && 'zv-tooltip--bas')}>
|
|
323
|
+
{contenu}
|
|
324
|
+
</span>
|
|
325
|
+
</span>
|
|
326
|
+
)
|
|
327
|
+
}
|
package/react/forms.tsx
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type {
|
|
15
15
|
ButtonHTMLAttributes,
|
|
16
|
+
CSSProperties,
|
|
16
17
|
HTMLAttributes,
|
|
17
18
|
InputHTMLAttributes,
|
|
18
19
|
ReactNode,
|
|
@@ -381,3 +382,92 @@ export function DropInput({
|
|
|
381
382
|
}) {
|
|
382
383
|
return <input type="file" className={cx('zv-drop__input', className)} {...reste} />
|
|
383
384
|
}
|
|
385
|
+
|
|
386
|
+
/* ─── Champ à affixe ───────────────────────────────────────────────── */
|
|
387
|
+
|
|
388
|
+
export interface InputAffixProps
|
|
389
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {
|
|
390
|
+
/** Préfixe, en mono label : « € », « HT ». Une unité est un libellé, pas
|
|
391
|
+
* une donnée — elle ne se saisit pas, elle qualifie ce qui est saisi. */
|
|
392
|
+
avant?: ReactNode
|
|
393
|
+
/** Suffixe : « JOURS », « / MOIS », « .zevra.tech ». */
|
|
394
|
+
apres?: ReactNode
|
|
395
|
+
/** Identifiants EXPLICITES des affixes, à citer dans `aria-describedby`
|
|
396
|
+
* sur le champ intérieur — le contrat htmlFor/id de `Field`, pour la
|
|
397
|
+
* même raison : pas de useId, le composant reste rendu au serveur.
|
|
398
|
+
* ⚠️ Sans ce câblage, un lecteur d'écran en mode formulaire entend
|
|
399
|
+
* « 1200 » sans jamais apprendre qu'il s'agit d'euros HT — exactement le
|
|
400
|
+
* défaut que le composant existe pour corriger. */
|
|
401
|
+
idAvant?: string
|
|
402
|
+
idApres?: string
|
|
403
|
+
className?: string
|
|
404
|
+
/** Le champ lui-même — un `<Input nu>` : c'est le CADRE qui fait le champ,
|
|
405
|
+
* un `<Input>` bordé dessinerait deux cadres imbriqués. */
|
|
406
|
+
children?: ReactNode
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Cadre à affixe : « 30 », seul dans un champ, ne dit pas s'il s'agit de
|
|
410
|
+
* jours ou d'euros. Même grammaire que `Search` (cadre + champ nu) ; à ne
|
|
411
|
+
* pas employer quand l'unité est déjà dans l'intitulé du champ — deux fois
|
|
412
|
+
* la même unité se lisent comme deux informations. */
|
|
413
|
+
export function InputAffix({
|
|
414
|
+
avant,
|
|
415
|
+
apres,
|
|
416
|
+
idAvant,
|
|
417
|
+
idApres,
|
|
418
|
+
className,
|
|
419
|
+
children,
|
|
420
|
+
...reste
|
|
421
|
+
}: InputAffixProps) {
|
|
422
|
+
return (
|
|
423
|
+
<div className={cx('zv-affix', className)} {...reste}>
|
|
424
|
+
{avant != null && (
|
|
425
|
+
<span className="zv-affix__add" id={idAvant}>
|
|
426
|
+
{avant}
|
|
427
|
+
</span>
|
|
428
|
+
)}
|
|
429
|
+
{children}
|
|
430
|
+
{apres != null && (
|
|
431
|
+
<span className="zv-affix__add" id={idApres}>
|
|
432
|
+
{apres}
|
|
433
|
+
</span>
|
|
434
|
+
)}
|
|
435
|
+
</div>
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* ─── Curseur ──────────────────────────────────────────────────────── */
|
|
440
|
+
|
|
441
|
+
export interface RangeProps
|
|
442
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className' | 'type'> {
|
|
443
|
+
className?: string
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** Curseur natif habillé (`<input type="range">`) : clavier, annonce et
|
|
447
|
+
* bornes viennent du navigateur. Le remplissage de la piste est calculé
|
|
448
|
+
* ici depuis value/min/max — une géométrie, comme la largeur de
|
|
449
|
+
* `Progress`, pas une valeur de charte. En NON contrôlé, il reflète la
|
|
450
|
+
* valeur initiale et ne suit plus le doigt : contrôler le champ dès que le
|
|
451
|
+
* remplissage doit suivre.
|
|
452
|
+
* ⚠️ Toujours accompagné d'un intitulé (`Field`) ET d'une valeur affichée
|
|
453
|
+
* en clair : un curseur seul ne dit ni ce qu'il règle, ni sur combien. */
|
|
454
|
+
export function Range({ className, style, min, max, value, defaultValue, ...reste }: RangeProps) {
|
|
455
|
+
const borneMin = Number(min ?? 0)
|
|
456
|
+
const borneMax = Number(max ?? 100)
|
|
457
|
+
const courant = Number(value ?? defaultValue ?? NaN)
|
|
458
|
+
const part = Number.isNaN(courant)
|
|
459
|
+
? 0.5
|
|
460
|
+
: Math.min(1, Math.max(0, (courant - borneMin) / (borneMax - borneMin || 1)))
|
|
461
|
+
return (
|
|
462
|
+
<input
|
|
463
|
+
type="range"
|
|
464
|
+
min={min}
|
|
465
|
+
max={max}
|
|
466
|
+
value={value}
|
|
467
|
+
defaultValue={defaultValue}
|
|
468
|
+
className={cx('zv-range', className)}
|
|
469
|
+
style={{ ...style, '--zv-range-pos': `${part * 100}%` } as CSSProperties}
|
|
470
|
+
{...reste}
|
|
471
|
+
/>
|
|
472
|
+
)
|
|
473
|
+
}
|
package/react/icon-data.ts
CHANGED
|
@@ -4,28 +4,45 @@
|
|
|
4
4
|
export type IconId =
|
|
5
5
|
| 'accueil'
|
|
6
6
|
| 'activite'
|
|
7
|
+
| 'aide'
|
|
7
8
|
| 'app-couches'
|
|
8
9
|
| 'archiver'
|
|
10
|
+
| 'attention'
|
|
11
|
+
| 'balance'
|
|
9
12
|
| 'base-de-donnees'
|
|
10
13
|
| 'bouclier-verifie'
|
|
11
14
|
| 'cadenas'
|
|
12
15
|
| 'cadrer'
|
|
16
|
+
| 'calendrier'
|
|
13
17
|
| 'chevron-bas'
|
|
14
18
|
| 'chevron-droit'
|
|
15
19
|
| 'chevron-gauche'
|
|
20
|
+
| 'chevron-haut'
|
|
16
21
|
| 'coche'
|
|
17
22
|
| 'copier'
|
|
18
23
|
| 'corbeille'
|
|
24
|
+
| 'courriel'
|
|
19
25
|
| 'cube'
|
|
20
26
|
| 'deconnexion'
|
|
21
27
|
| 'depot-fichier'
|
|
28
|
+
| 'diplome'
|
|
22
29
|
| 'dossier'
|
|
23
30
|
| 'eclair'
|
|
31
|
+
| 'envoyer'
|
|
32
|
+
| 'epingle'
|
|
24
33
|
| 'equipe'
|
|
25
34
|
| 'etincelles'
|
|
35
|
+
| 'etoile'
|
|
26
36
|
| 'fermer'
|
|
37
|
+
| 'fichier'
|
|
38
|
+
| 'filtre'
|
|
39
|
+
| 'globe'
|
|
40
|
+
| 'graphique'
|
|
27
41
|
| 'horloge'
|
|
42
|
+
| 'imprimante'
|
|
43
|
+
| 'info'
|
|
28
44
|
| 'lien-externe'
|
|
45
|
+
| 'livre-ouvert'
|
|
29
46
|
| 'menu'
|
|
30
47
|
| 'moins'
|
|
31
48
|
| 'oeil'
|
|
@@ -35,33 +52,54 @@ export type IconId =
|
|
|
35
52
|
| 'reglages'
|
|
36
53
|
| 'reseau'
|
|
37
54
|
| 'taches'
|
|
55
|
+
| 'telecharger'
|
|
56
|
+
| 'telephone'
|
|
57
|
+
| 'tri'
|
|
38
58
|
| 'trois-points'
|
|
59
|
+
| 'utilisateur'
|
|
39
60
|
|
|
40
61
|
export const CORPS: Record<IconId, string> = {
|
|
41
62
|
'accueil': "<path d=\"M4 10.67 L12 4 L20 10.67 V20 H4 Z\" /><path d=\"M9.33 20 V13.33 H14.67 V20\" />",
|
|
42
63
|
'activite': "<path d=\"M3.33 12 H8 L10.67 5.33 L14 18.67 L16 12 H20.67\" />",
|
|
64
|
+
'aide': "<circle cx=\"12\" cy=\"12\" r=\"8.67\" /><path d=\"M9.33 9.33 A2.67 2.67 0 0 1 14.53 10.13 C14.53 11.87 12 12.27 12 14\" /><circle cx=\"12\" cy=\"17.07\" r=\"0.8\" fill=\"currentColor\" stroke=\"none\" />",
|
|
43
65
|
'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\" />",
|
|
44
66
|
'archiver': "<rect x=\"3.33\" y=\"4\" width=\"17.33\" height=\"4.67\" rx=\"1.07\" /><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
|
+
'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\" /><circle cx=\"12\" cy=\"16.53\" r=\"0.8\" fill=\"currentColor\" stroke=\"none\" />",
|
|
68
|
+
'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\" />",
|
|
45
69
|
'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\" />",
|
|
46
70
|
'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\" />",
|
|
47
71
|
'cadenas': "<rect x=\"4.67\" y=\"10.67\" width=\"14.67\" height=\"9.33\" rx=\"1.6\" /><path d=\"M8 10.67 V7.33 A4 4 0 0 1 16 7.33 V10.67\" /><circle cx=\"12\" cy=\"15.33\" r=\"1.33\" />",
|
|
48
72
|
'cadrer': "<path d=\"M4 8 V4 H8\" /><path d=\"M20 8 V4 H16\" /><path d=\"M4 16 V20 H8\" /><path d=\"M20 16 V20 H16\" />",
|
|
73
|
+
'calendrier': "<rect x=\"3.33\" y=\"4.67\" width=\"17.33\" height=\"16\" rx=\"1.33\" /><path d=\"M3.33 9.33 H20.67\" /><path d=\"M8 2.67 V6.67\" /><path d=\"M16 2.67 V6.67\" />",
|
|
49
74
|
'chevron-bas': "<path d=\"M6.67 9.33 L12 14.67 L17.33 9.33\" />",
|
|
50
75
|
'chevron-droit': "<path d=\"M9.33 4 L17.33 12 L9.33 20\" />",
|
|
51
76
|
'chevron-gauche': "<path d=\"M14.67 4 L6.67 12 L14.67 20\" />",
|
|
77
|
+
'chevron-haut': "<path d=\"M6.67 14.67 L12 9.33 L17.33 14.67\" />",
|
|
52
78
|
'coche': "<path d=\"M5 12.4 L9.4 16.8 L19 6.8\" />",
|
|
53
79
|
'copier': "<rect x=\"4\" y=\"4\" width=\"12\" height=\"12\" rx=\"1.6\" /><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\" />",
|
|
54
80
|
'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\" />",
|
|
81
|
+
'courriel': "<rect x=\"2.67\" y=\"5.33\" width=\"18.67\" height=\"13.33\" rx=\"1.33\" /><path d=\"M2.67 7.33 L12 13.33 L21.33 7.33\" />",
|
|
55
82
|
'cube': "<path d=\"M3.33 8 L12 4 L20.67 8 V16 L12 20 L3.33 16 Z\" /><path d=\"M3.33 8 L12 12 L20.67 8\" /><path d=\"M12 12 V20\" />",
|
|
56
83
|
'deconnexion': "<path d=\"M14.67 4 H5.33 V20 H14.67\" /><path d=\"M10.67 12 H20\" /><path d=\"M16.67 8 L20.67 12 L16.67 16\" />",
|
|
57
84
|
'depot-fichier': "<path d=\"M12 3.2 V14.4\" /><polyline points=\"7.6,10 12,14.4 16.4,10\" /><path d=\"M4 16.8 v2.4 a1.6 1.6 0 0 0 1.6 1.6 h12.8 a1.6 1.6 0 0 0 1.6-1.6 v-2.4\" />",
|
|
85
|
+
'diplome': "<path d=\"M2.67 9.33 L12 4.67 L21.33 9.33 L12 14 Z\" /><path d=\"M6.67 11.6 V16 C6.67 17.47 9.07 18.67 12 18.67 C14.93 18.67 17.33 17.47 17.33 16 V11.6\" /><path d=\"M21.33 9.33 V14.67\" />",
|
|
58
86
|
'dossier': "<path d=\"M1.41 6.64a1.69 1.69 0 0 1 1.69-1.69h4.8l2.12 2.4h10.87a1.69 1.69 0 0 1 1.69 1.69v9.88a1.69 1.69 0 0 1-1.69 1.69H3.11A1.69 1.69 0 0 1 1.41 20.33V6.64Z\" />",
|
|
59
87
|
'eclair': "<path d=\"M13.33 2.67 L5.33 13.33 H12 L10.67 21.33 L18.67 10.67 H12 Z\" />",
|
|
88
|
+
'envoyer': "<path d=\"M21.33 2.67 L10.67 13.33\" /><path d=\"M21.33 2.67 L14.67 21.33 L10.67 13.33 L2.67 9.33 Z\" />",
|
|
89
|
+
'epingle': "<path d=\"M12 21.33 C7.56 16.89 5.33 13.42 5.33 10.67 A6.67 6.67 0 0 1 18.67 10.67 C18.67 13.42 16.44 16.89 12 21.33 Z\" /><circle cx=\"12\" cy=\"10.67\" r=\"2.4\" />",
|
|
60
90
|
'equipe': "<circle cx=\"9.33\" cy=\"8\" r=\"3.33\" /><path d=\"M3.33 20 C3.33 15.33 6 13.33 9.33 13.33 C12.67 13.33 15.33 15.33 15.33 20\" /><path d=\"M16 5.33 A2.93 2.93 0 0 1 16 11.07\" /><path d=\"M17.33 13.6 C19.73 14.27 20.67 16.4 20.67 20\" />",
|
|
61
91
|
'etincelles': "<path d=\"M12 2.67 L13.87 8.8 L20 10.67 L13.87 12.53 L12 18.67 L10.13 12.53 L4 10.67 L10.13 8.8 Z\" /><path d=\"M19.33 16.67 L20 18.67 L22 19.33 L20 20 L19.33 22 L18.67 20 L16.67 19.33 L18.67 18.67 Z\" />",
|
|
92
|
+
'etoile': "<path d=\"M12 3.33 L14.67 8.53 L20.4 9.37 L16.2 13.4 L17.2 19.07 L12 16.4 L6.8 19.07 L7.8 13.4 L3.6 9.37 L9.33 8.53 Z\" />",
|
|
62
93
|
'fermer': "<path d=\"M5.33 5.33 L18.67 18.67\" /><path d=\"M18.67 5.33 L5.33 18.67\" />",
|
|
94
|
+
'fichier': "<path d=\"M13.33 2.67 H6.67 A1.33 1.33 0 0 0 5.33 4 V20 A1.33 1.33 0 0 0 6.67 21.33 H17.33 A1.33 1.33 0 0 0 18.67 20 V8 Z\" /><path d=\"M13.33 2.67 V8 H18.67\" />",
|
|
95
|
+
'filtre': "<path d=\"M2.67 4 H21.33 L14.67 12.27 V18.67 L9.33 21.33 V12.27 Z\" />",
|
|
96
|
+
'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
|
+
'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\" />",
|
|
63
98
|
'horloge': "<circle cx=\"12\" cy=\"12\" r=\"8\" /><path d=\"M12 6.67 V12 L15.33 14\" />",
|
|
99
|
+
'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\" rx=\"0.8\" />",
|
|
100
|
+
'info': "<circle cx=\"12\" cy=\"12\" r=\"8.67\" /><path d=\"M12 11.33 V16\" /><circle cx=\"12\" cy=\"8\" r=\"0.8\" fill=\"currentColor\" stroke=\"none\" />",
|
|
64
101
|
'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\" />",
|
|
102
|
+
'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\" />",
|
|
65
103
|
'menu': "<path d=\"M0 5.33 H24\" /><path d=\"M0 12 H24\" /><path d=\"M0 18.67 H24\" />",
|
|
66
104
|
'moins': "<path d=\"M4 12 H20\" />",
|
|
67
105
|
'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\" />",
|
|
@@ -71,7 +109,11 @@ export const CORPS: Record<IconId, string> = {
|
|
|
71
109
|
'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\" />",
|
|
72
110
|
'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\" />",
|
|
73
111
|
'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
|
+
'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
|
+
'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
|
+
'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\" />",
|
|
74
115
|
'trois-points': "<circle cx=\"5.33\" cy=\"12\" r=\"1.33\" fill=\"currentColor\" stroke=\"none\" /><circle cx=\"12\" cy=\"12\" r=\"1.33\" fill=\"currentColor\" stroke=\"none\" /><circle cx=\"18.67\" cy=\"12\" r=\"1.33\" fill=\"currentColor\" stroke=\"none\" />",
|
|
116
|
+
'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\" />",
|
|
75
117
|
}
|
|
76
118
|
|
|
77
119
|
/** Nom lisible de chaque icône — sert de titre accessible par défaut à
|
|
@@ -79,28 +121,45 @@ export const CORPS: Record<IconId, string> = {
|
|
|
79
121
|
export const NOMS: Record<IconId, string> = {
|
|
80
122
|
'accueil': "Accueil",
|
|
81
123
|
'activite': "Activité (pouls)",
|
|
124
|
+
'aide': "Aide",
|
|
82
125
|
'app-couches': "Application (couches)",
|
|
83
126
|
'archiver': "Archiver",
|
|
127
|
+
'attention': "Attention (avertissement)",
|
|
128
|
+
'balance': "Balance (justice)",
|
|
84
129
|
'base-de-donnees': "Base de données",
|
|
85
130
|
'bouclier-verifie': "Bouclier vérifié (authentification)",
|
|
86
131
|
'cadenas': "Cadenas (secret)",
|
|
87
132
|
'cadrer': "Cadrer (ajuster à la vue)",
|
|
133
|
+
'calendrier': "Calendrier",
|
|
88
134
|
'chevron-bas': "Chevron bas",
|
|
89
135
|
'chevron-droit': "Chevron droit",
|
|
90
136
|
'chevron-gauche': "Chevron gauche",
|
|
137
|
+
'chevron-haut': "Chevron haut",
|
|
91
138
|
'coche': "Coche",
|
|
92
139
|
'copier': "Copier",
|
|
93
140
|
'corbeille': "Corbeille (supprimer)",
|
|
141
|
+
'courriel': "Courriel",
|
|
94
142
|
'cube': "Cube (stockage objet)",
|
|
95
143
|
'deconnexion': "Déconnexion",
|
|
96
144
|
'depot-fichier': "Dépôt de fichier (zone de glisser-déposer)",
|
|
145
|
+
'diplome': "Diplôme (Campus)",
|
|
97
146
|
'dossier': "Dossier",
|
|
98
147
|
'eclair': "Éclair (action rapide)",
|
|
148
|
+
'envoyer': "Envoyer",
|
|
149
|
+
'epingle': "Épingle (lieu)",
|
|
99
150
|
'equipe': "Équipe (membres)",
|
|
100
151
|
'etincelles': "Étincelles (IA)",
|
|
152
|
+
'etoile': "Étoile (favori)",
|
|
101
153
|
'fermer': "Fermer",
|
|
154
|
+
'fichier': "Fichier (document)",
|
|
155
|
+
'filtre': "Filtre",
|
|
156
|
+
'globe': "Globe (international)",
|
|
157
|
+
'graphique': "Graphique (barres)",
|
|
102
158
|
'horloge': "Horloge (intégration continue)",
|
|
159
|
+
'imprimante': "Imprimante",
|
|
160
|
+
'info': "Information",
|
|
103
161
|
'lien-externe': "Lien externe",
|
|
162
|
+
'livre-ouvert': "Livre ouvert (formation)",
|
|
104
163
|
'menu': "Menu (hamburger)",
|
|
105
164
|
'moins': "Moins",
|
|
106
165
|
'oeil': "Œil (afficher)",
|
|
@@ -110,5 +169,9 @@ export const NOMS: Record<IconId, string> = {
|
|
|
110
169
|
'reglages': "Réglages",
|
|
111
170
|
'reseau': "Réseau (entrée / globe)",
|
|
112
171
|
'taches': "Tâches (liste à cocher)",
|
|
172
|
+
'telecharger': "Télécharger",
|
|
173
|
+
'telephone': "Téléphone",
|
|
174
|
+
'tri': "Tri",
|
|
113
175
|
'trois-points': "Trois points (menu contextuel)",
|
|
176
|
+
'utilisateur': "Utilisateur",
|
|
114
177
|
}
|
package/react/icon.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Icônes — jeu de
|
|
1
|
+
// Icônes — jeu de 55, tracées, sur grille de 24.
|
|
2
2
|
//
|
|
3
3
|
// Le corps du tracé est INLINÉ (voir scripts/build-icon-react.mjs) : pas de
|
|
4
4
|
// <use href="…sprite.svg">, donc rien à résoudre côté bundler et rien à
|
|
@@ -12,7 +12,7 @@ import { CORPS, NOMS, type IconId } from './icon-data.js'
|
|
|
12
12
|
export type { IconId }
|
|
13
13
|
export { NOMS as NOMS_ICONES }
|
|
14
14
|
|
|
15
|
-
/** Les
|
|
15
|
+
/** Les 55 identifiants, triés — pour construire une planche ou un sélecteur. */
|
|
16
16
|
export const ICONES = Object.keys(CORPS) as IconId[]
|
|
17
17
|
|
|
18
18
|
export interface IconProps
|