@nomosui/react 0.4.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 (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/SKILL.md +166 -0
  4. package/dist/index.js +4591 -0
  5. package/dist/mcp/bin.js +46371 -0
  6. package/dist/types/catalogue/contract.d.ts +78 -0
  7. package/dist/types/catalogue/registry.d.ts +33 -0
  8. package/dist/types/components/alert/alert.d.ts +30 -0
  9. package/dist/types/components/alert/manifest.d.ts +3 -0
  10. package/dist/types/components/badge/badge.d.ts +25 -0
  11. package/dist/types/components/badge/manifest.d.ts +7 -0
  12. package/dist/types/components/button/button.d.ts +38 -0
  13. package/dist/types/components/button/manifest.d.ts +7 -0
  14. package/dist/types/components/card/card.d.ts +12 -0
  15. package/dist/types/components/card/manifest.d.ts +3 -0
  16. package/dist/types/components/checkbox/checkbox.d.ts +4 -0
  17. package/dist/types/components/checkbox/manifest.d.ts +3 -0
  18. package/dist/types/components/chip/chip.d.ts +36 -0
  19. package/dist/types/components/chip/manifest.d.ts +3 -0
  20. package/dist/types/components/counter/counter.d.ts +14 -0
  21. package/dist/types/components/counter/manifest.d.ts +3 -0
  22. package/dist/types/components/data-table-pagination/data-table-pagination.d.ts +20 -0
  23. package/dist/types/components/data-table-pagination/manifest.d.ts +3 -0
  24. package/dist/types/components/data-table-toolbar/data-table-toolbar.d.ts +34 -0
  25. package/dist/types/components/data-table-toolbar/manifest.d.ts +3 -0
  26. package/dist/types/components/empty-state/empty-state.d.ts +23 -0
  27. package/dist/types/components/empty-state/manifest.d.ts +3 -0
  28. package/dist/types/components/facet-filter/facet-filter.d.ts +23 -0
  29. package/dist/types/components/facet-filter/manifest.d.ts +6 -0
  30. package/dist/types/components/field/field.d.ts +15 -0
  31. package/dist/types/components/field/manifest.d.ts +3 -0
  32. package/dist/types/components/fieldset/fieldset.d.ts +3 -0
  33. package/dist/types/components/fieldset/manifest.d.ts +3 -0
  34. package/dist/types/components/form/form.d.ts +14 -0
  35. package/dist/types/components/form/manifest.d.ts +3 -0
  36. package/dist/types/components/freshness/freshness.d.ts +18 -0
  37. package/dist/types/components/freshness/manifest.d.ts +7 -0
  38. package/dist/types/components/input/input.d.ts +6 -0
  39. package/dist/types/components/input/manifest.d.ts +6 -0
  40. package/dist/types/components/label/label.d.ts +3 -0
  41. package/dist/types/components/label/manifest.d.ts +3 -0
  42. package/dist/types/components/meter/manifest.d.ts +6 -0
  43. package/dist/types/components/meter/meter.d.ts +23 -0
  44. package/dist/types/components/number-field/manifest.d.ts +3 -0
  45. package/dist/types/components/number-field/number-field.d.ts +7 -0
  46. package/dist/types/components/progress-bar/manifest.d.ts +3 -0
  47. package/dist/types/components/progress-bar/progress-bar.d.ts +12 -0
  48. package/dist/types/components/radio/manifest.d.ts +3 -0
  49. package/dist/types/components/radio/radio.d.ts +5 -0
  50. package/dist/types/components/rating/manifest.d.ts +3 -0
  51. package/dist/types/components/rating/rating.d.ts +19 -0
  52. package/dist/types/components/search-field/manifest.d.ts +3 -0
  53. package/dist/types/components/search-field/search-field.d.ts +14 -0
  54. package/dist/types/components/separator/manifest.d.ts +3 -0
  55. package/dist/types/components/separator/separator.d.ts +7 -0
  56. package/dist/types/components/skeleton/manifest.d.ts +3 -0
  57. package/dist/types/components/skeleton/skeleton.d.ts +3 -0
  58. package/dist/types/components/switch/manifest.d.ts +3 -0
  59. package/dist/types/components/switch/switch.d.ts +4 -0
  60. package/dist/types/components/table/manifest.d.ts +7 -0
  61. package/dist/types/components/table/table.d.ts +14 -0
  62. package/dist/types/components/textarea/manifest.d.ts +3 -0
  63. package/dist/types/components/textarea/textarea.d.ts +3 -0
  64. package/dist/types/components/timeline/manifest.d.ts +3 -0
  65. package/dist/types/components/timeline/timeline.d.ts +19 -0
  66. package/dist/types/components/toast/manifest.d.ts +3 -0
  67. package/dist/types/components/toast/toast.d.ts +35 -0
  68. package/dist/types/components/toggle/manifest.d.ts +3 -0
  69. package/dist/types/components/toggle/toggle.d.ts +31 -0
  70. package/dist/types/components/toggle-group/manifest.d.ts +6 -0
  71. package/dist/types/components/toggle-group/toggle-group.d.ts +8 -0
  72. package/dist/types/components/tree/manifest.d.ts +7 -0
  73. package/dist/types/components/tree/tree.d.ts +42 -0
  74. package/dist/types/features/data-table-features.d.ts +29 -0
  75. package/dist/types/features/faceted-data-table.d.ts +99 -0
  76. package/dist/types/features/toast.d.ts +33 -0
  77. package/dist/types/index.d.ts +112 -0
  78. package/dist/types/internal/dropdown-menu.d.ts +27 -0
  79. package/dist/types/internal/select.d.ts +13 -0
  80. package/dist/types/internal/sheet.d.ts +25 -0
  81. package/dist/types/internal/tooltip.d.ts +7 -0
  82. package/dist/types/lib/cn.d.ts +6 -0
  83. package/dist/types/lib/tone.d.ts +12 -0
  84. package/dist/types/tokens/build.d.mts +68 -0
  85. package/dist/types/tokens/resource.d.ts +8 -0
  86. package/dist/types/tokens/skin.d.ts +9 -0
  87. package/package.json +107 -0
  88. package/scripts/install-skill.mjs +28 -0
  89. package/tokens/theme.css +63 -0
  90. package/tokens/tokens.generated.css +271 -0
  91. package/tokens/tokens.json +225 -0
  92. package/tokens/tokens.resource.json +138 -0
package/dist/index.js ADDED
@@ -0,0 +1,4591 @@
1
+ import { Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Circle, CircleAlert, CircleCheck, CircleX, Info, LoaderCircle, Search, Star, TriangleAlert, X } from "lucide-react";
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { cva } from "class-variance-authority";
6
+ import { Slot } from "@radix-ui/react-slot";
7
+ import * as React from "react";
8
+ import { Fragment as Fragment$1, createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
9
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
10
+ import * as SelectPrimitive from "@radix-ui/react-select";
11
+ import { columnFilteringFeature, columnVisibilityFeature, createFilteredRowModel, createPaginatedRowModel, createSortedRowModel, flexRender, globalFilteringFeature, rowPaginationFeature, rowSortingFeature, sortFn_alphanumeric, sortFn_basic, sortFn_datetime, sortFn_text, tableFeatures, useTable } from "@tanstack/react-table";
12
+ import * as SheetPrimitive from "@radix-ui/react-dialog";
13
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
14
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
15
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
16
+ import * as SwitchPrimitive from "@radix-ui/react-switch";
17
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
18
+ import * as TogglePrimitive from "@radix-ui/react-toggle";
19
+ import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
20
+ import { z } from "zod";
21
+ //#region src/lib/cn.ts
22
+ /**
23
+ * Le `cn` du cœur. Il vit ici et pas dans l'app : un composant du design system ne
24
+ * peut pas dépendre d'un utilitaire qui appartient au dashboard.
25
+ */
26
+ var cn = (...inputs) => twMerge(clsx(inputs));
27
+ //#endregion
28
+ //#region src/lib/tone.ts
29
+ /**
30
+ * Le vocabulaire de tons du cœur : sept **intentions**, pas sept couleurs. Un badge de
31
+ * statut choisit l'intention (« danger », « succès »), et c'est le cœur qui sait la
32
+ * rendre — l'app ne manipule plus un nom de couleur.
33
+ *
34
+ * Chaque ton lit deux emplacements sémantiques (`status-<ton>` pour la surface, lue à
35
+ * faible opacité, et `status-<ton>-ink` pour l'encre) : c'est le token qui porte le
36
+ * mode, pas la classe, donc le cœur ne connaît jamais le mode (ADR 0004, garde des tokens).
37
+ */
38
+ var TONES = [
39
+ "neutral",
40
+ "info",
41
+ "progress",
42
+ "attention",
43
+ "warning",
44
+ "danger",
45
+ "success"
46
+ ];
47
+ var toneClasses = {
48
+ neutral: "border-transparent bg-muted text-muted-foreground",
49
+ info: "border-status-info/30 bg-status-info/15 text-status-info-ink",
50
+ progress: "border-status-progress/30 bg-status-progress/15 text-status-progress-ink",
51
+ attention: "border-status-attention/30 bg-status-attention/15 text-status-attention-ink",
52
+ warning: "border-status-warning/30 bg-status-warning/15 text-status-warning-ink",
53
+ danger: "border-status-danger/30 bg-status-danger/15 text-status-danger-ink",
54
+ success: "border-status-success/30 bg-status-success/15 text-status-success-ink"
55
+ };
56
+ //#endregion
57
+ //#region src/components/alert/alert.tsx
58
+ /** L'icône par défaut d'un ton ; `neutral` reste sans icône. L'appelant peut la remplacer. */
59
+ var defaultIcon$1 = {
60
+ info: /* @__PURE__ */ jsx(Info, { className: "size-4" }),
61
+ progress: /* @__PURE__ */ jsx(LoaderCircle, { className: "size-4" }),
62
+ attention: /* @__PURE__ */ jsx(CircleAlert, { className: "size-4" }),
63
+ warning: /* @__PURE__ */ jsx(TriangleAlert, { className: "size-4" }),
64
+ danger: /* @__PURE__ */ jsx(CircleX, { className: "size-4" }),
65
+ success: /* @__PURE__ */ jsx(CircleCheck, { className: "size-4" })
66
+ };
67
+ function Alert({ tone = "info", title, children, action, icon, onClose, closeLabel, className }) {
68
+ return /* @__PURE__ */ jsxs("div", {
69
+ role: tone === "danger" ? "alert" : "status",
70
+ className: cn("flex items-start gap-3 rounded-md border p-4 text-sm", toneClasses[tone], className),
71
+ children: [
72
+ /* @__PURE__ */ jsx("span", {
73
+ "aria-hidden": true,
74
+ className: "mt-0.5 shrink-0",
75
+ children: icon ?? defaultIcon$1[tone] ?? null
76
+ }),
77
+ /* @__PURE__ */ jsxs("div", {
78
+ className: "min-w-0 flex-1",
79
+ children: [/* @__PURE__ */ jsx("p", {
80
+ className: "font-medium",
81
+ children: title
82
+ }), children ? /* @__PURE__ */ jsx("div", {
83
+ className: "mt-0.5 opacity-90",
84
+ children
85
+ }) : null]
86
+ }),
87
+ action ? /* @__PURE__ */ jsx("div", {
88
+ className: "shrink-0",
89
+ children: action
90
+ }) : null,
91
+ onClose ? /* @__PURE__ */ jsx("button", {
92
+ type: "button",
93
+ "aria-label": closeLabel,
94
+ onClick: onClose,
95
+ className: "-m-1 shrink-0 rounded p-1 opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
96
+ children: /* @__PURE__ */ jsx(CircleX, {
97
+ className: "size-4",
98
+ "aria-hidden": true
99
+ })
100
+ }) : null
101
+ ]
102
+ });
103
+ }
104
+ //#endregion
105
+ //#region src/components/alert/manifest.ts
106
+ /** Le manifeste du bandeau (ADR 0005, ADR 0015). Le ton réutilise le vocabulaire `toneClasses`. */
107
+ var alertManifest = {
108
+ name: "alert",
109
+ title: "Alert",
110
+ summary: "Un bandeau inline qui dit une information, un avertissement, un succès ou une erreur. Le ton vient du cœur (une intention, pas une couleur) ; tout texte est fourni par l'app.",
111
+ level: "primitive",
112
+ example: {
113
+ tone: "info",
114
+ title: "information",
115
+ children: "un détail à lire."
116
+ },
117
+ variants: [{
118
+ name: "tone",
119
+ values: [...TONES],
120
+ default: "info",
121
+ description: "L'intention du bandeau — c'est ce que l'appelant choisit selon l'usage, pas son goût."
122
+ }],
123
+ props: [
124
+ {
125
+ name: "title",
126
+ type: "string",
127
+ required: true,
128
+ check: "rendered",
129
+ description: "Le titre du bandeau : court, jamais une phrase."
130
+ },
131
+ {
132
+ name: "children",
133
+ type: "ReactNode",
134
+ required: false,
135
+ check: "content",
136
+ description: "Le contenu détaillé, sous le titre."
137
+ },
138
+ {
139
+ name: "action",
140
+ type: "ReactNode",
141
+ required: false,
142
+ check: "accepted",
143
+ description: "Ce qu’on peut faire : un bouton, un lien."
144
+ },
145
+ {
146
+ name: "icon",
147
+ type: "ReactNode",
148
+ required: false,
149
+ check: "accepted",
150
+ description: "Remplace l'icône par défaut du ton."
151
+ },
152
+ {
153
+ name: "onClose",
154
+ type: "() => void",
155
+ required: false,
156
+ check: "accepted",
157
+ description: "Affiche un bouton de fermeture et le rappelle au clic."
158
+ },
159
+ {
160
+ name: "closeLabel",
161
+ type: "string",
162
+ required: false,
163
+ check: "accepted",
164
+ description: "Le libellé accessible du bouton de fermeture (requis avec `onClose`)."
165
+ },
166
+ {
167
+ name: "className",
168
+ type: "string",
169
+ required: false,
170
+ check: "class",
171
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
172
+ }
173
+ ],
174
+ usages: [{
175
+ when: "signaler un état qui demande l’attention sans bloquer la vue",
176
+ use: "<Alert tone=\"warning\" title={…}>…</Alert>",
177
+ avoid: "un `Alert` pour une information décorative : le bandeau doit dire quelque chose"
178
+ }, {
179
+ when: "un bandeau qu’on peut refermer",
180
+ use: "onClose + closeLabel (les deux ensemble)",
181
+ avoid: "onClose sans closeLabel : le bouton devient muet pour un lecteur d’écran"
182
+ }]
183
+ };
184
+ //#endregion
185
+ //#region src/components/badge/badge.tsx
186
+ /**
187
+ * La config des variantes est exportée à côté de `badgeVariants` : le catalogue la lit
188
+ * pour vérifier que le manifeste ne dérive pas des props réelles (ADR 0005). cva ne
189
+ * porte pas la config sur la fonction qu'il rend, donc on l'écrit ici, une fois.
190
+ */
191
+ var badgeVariantsConfig = {
192
+ variants: { variant: {
193
+ default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
194
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
195
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
196
+ outline: "text-foreground"
197
+ } },
198
+ defaultVariants: { variant: "default" }
199
+ };
200
+ var badgeVariants = cva("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", badgeVariantsConfig);
201
+ var Badge = ({ className, variant, ...props }) => /* @__PURE__ */ jsx("div", {
202
+ className: cn(badgeVariants({ variant }), className),
203
+ ...props
204
+ });
205
+ //#endregion
206
+ //#region src/components/badge/manifest.ts
207
+ /**
208
+ * Le manifeste du Badge (ADR 0005). Il est écrit à la main : le type se génère,
209
+ * l'usage s'écrit. Le test de cohérence du catalogue le confronte aux props réelles —
210
+ * une variante ajoutée au composant sans passer ici fait rougir le test.
211
+ */
212
+ var badgeManifest = {
213
+ name: "badge",
214
+ title: "Badge",
215
+ summary: "Une étiquette courte et non interactive, posée à côté d'un contenu pour le qualifier. Tout attribut HTML est transmis tel quel à l'élément.",
216
+ level: "primitive",
217
+ variants: [{
218
+ name: "variant",
219
+ values: [
220
+ "default",
221
+ "secondary",
222
+ "destructive",
223
+ "outline"
224
+ ],
225
+ default: "default",
226
+ description: "Le ton de l'étiquette — c'est ce que l'appelant choisit selon l'usage, pas selon son goût."
227
+ }],
228
+ props: [
229
+ {
230
+ name: "className",
231
+ type: "string",
232
+ required: false,
233
+ check: "class",
234
+ description: "Les classes de l'appelant, fusionnées après celles de la variante."
235
+ },
236
+ {
237
+ name: "title",
238
+ type: "string",
239
+ required: false,
240
+ check: "attribute",
241
+ description: "L'attribut HTML transmis tel quel, ici le libellé au survol."
242
+ },
243
+ {
244
+ name: "children",
245
+ type: "ReactNode",
246
+ required: true,
247
+ check: "content",
248
+ description: "Le texte porté par l'étiquette : court, jamais une phrase."
249
+ }
250
+ ],
251
+ usages: [
252
+ {
253
+ when: "un statut de production qu'on doit voir au premier coup d'œil",
254
+ use: "variant=\"destructive\"",
255
+ avoid: "variant=\"outline\", qui ne se distingue pas dans une ligne déjà dense"
256
+ },
257
+ {
258
+ when: "une étiquette neutre posée à côté du contenu",
259
+ use: "variant=\"secondary\"",
260
+ avoid: "variant=\"default\", réservé au ton principal de la vue"
261
+ },
262
+ {
263
+ when: "une information à qualifier sans crier (compte, catégorie)",
264
+ use: "variant=\"outline\"",
265
+ avoid: "variant=\"destructive\" pour qualifier sans urgence : le ton doit dire la gravité réelle"
266
+ }
267
+ ]
268
+ };
269
+ //#endregion
270
+ //#region src/components/button/button.tsx
271
+ /**
272
+ * Le bouton du cœur. La config des variantes est exportée à côté de `buttonVariants`,
273
+ * comme celle du Badge : le catalogue la lit pour vérifier que le manifeste ne dérive
274
+ * pas des props réelles (ADR 0005).
275
+ */
276
+ var buttonVariantsConfig = {
277
+ variants: {
278
+ variant: {
279
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
280
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
281
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
282
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
283
+ ghost: "hover:bg-accent hover:text-accent-foreground",
284
+ link: "text-primary underline-offset-4 hover:underline"
285
+ },
286
+ size: {
287
+ default: "h-10 px-4 py-2",
288
+ sm: "h-9 rounded-md px-3",
289
+ lg: "h-11 rounded-md px-8",
290
+ icon: "h-10 w-10"
291
+ }
292
+ },
293
+ defaultVariants: {
294
+ variant: "default",
295
+ size: "default"
296
+ }
297
+ };
298
+ var buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", buttonVariantsConfig);
299
+ function Button({ className, variant, size, asChild = false, ...props }) {
300
+ return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
301
+ className: cn(buttonVariants({
302
+ variant,
303
+ size,
304
+ className
305
+ })),
306
+ ...props
307
+ });
308
+ }
309
+ //#endregion
310
+ //#region src/components/button/manifest.ts
311
+ /**
312
+ * Le manifeste du Bouton (ADR 0005). Il déclare les deux groupes de variantes —
313
+ * `variant` (le ton) et `size` (la taille) — que le test de cohérence confronte à la
314
+ * config réelle.
315
+ */
316
+ var buttonManifest = {
317
+ name: "button",
318
+ title: "Button",
319
+ summary: "Un déclencheur d'action. Le ton (`variant`) dit l'importance, la taille (`size`) dit la densité. Tout attribut HTML de bouton est transmis tel quel.",
320
+ level: "primitive",
321
+ example: { children: "Bouton" },
322
+ variants: [{
323
+ name: "variant",
324
+ values: [
325
+ "default",
326
+ "destructive",
327
+ "outline",
328
+ "secondary",
329
+ "ghost",
330
+ "link"
331
+ ],
332
+ default: "default",
333
+ description: "Le ton de l'action : principale, dangereuse, secondaire, discrète ou lien."
334
+ }, {
335
+ name: "size",
336
+ values: [
337
+ "default",
338
+ "sm",
339
+ "lg",
340
+ "icon"
341
+ ],
342
+ default: "default",
343
+ description: "La taille du contrôle, du plus discret au plus large, ou icon-only."
344
+ }],
345
+ props: [
346
+ {
347
+ name: "className",
348
+ type: "string",
349
+ required: false,
350
+ check: "class",
351
+ description: "Les classes de l'appelant, fusionnées après celles de la variante."
352
+ },
353
+ {
354
+ name: "children",
355
+ type: "ReactNode",
356
+ required: true,
357
+ check: "content",
358
+ description: "Le libellé de l'action, ou une icône seule (avec `aria-label`)."
359
+ },
360
+ {
361
+ name: "onClick",
362
+ type: "(event: MouseEvent) => void",
363
+ required: false,
364
+ check: "accepted",
365
+ description: "L'action déclenchée au clic ; le cœur ne la connaît pas."
366
+ }
367
+ ],
368
+ usages: [
369
+ {
370
+ when: "l'action principale d'une vue",
371
+ use: "variant=\"default\"",
372
+ avoid: "plusieurs `default` côte à côte : le ton principal doit rester unique"
373
+ },
374
+ {
375
+ when: "une action secondaire à côté de la principale",
376
+ use: "variant=\"outline\"",
377
+ avoid: "`ghost` pour une action qu’on doit voir sans survol"
378
+ },
379
+ {
380
+ when: "une icône seule (fermer, paginer)",
381
+ use: "size=\"icon\" + aria-label",
382
+ avoid: "un `icon` sans `aria-label` : le bouton devient muet pour un lecteur d’écran"
383
+ },
384
+ {
385
+ when: "poser le style du bouton sur un lien ou un autre élément",
386
+ use: "asChild avec un seul enfant",
387
+ avoid: "asChild avec plusieurs enfants : Radix Slot n’en accepte qu’un"
388
+ }
389
+ ]
390
+ };
391
+ //#endregion
392
+ //#region src/components/card/card.tsx
393
+ /**
394
+ * La carte : une surface qui regroupe un contenu apparenté — un en-tête (titre +
395
+ * description), un corps, un pied. En parts importables séparément ; aucune part ne
396
+ * nomme un produit, les textes viennent de l'appelant.
397
+ */
398
+ function Card({ className, ...props }) {
399
+ return /* @__PURE__ */ jsx("div", {
400
+ className: cn("rounded-lg border bg-card text-card-foreground shadow-sm", className),
401
+ ...props
402
+ });
403
+ }
404
+ function CardHeader({ className, ...props }) {
405
+ return /* @__PURE__ */ jsx("div", {
406
+ className: cn("flex flex-col space-y-1.5 p-6", className),
407
+ ...props
408
+ });
409
+ }
410
+ function CardTitle({ className, ...props }) {
411
+ return /* @__PURE__ */ jsx("div", {
412
+ className: cn("text-2xl font-semibold leading-none tracking-tight", className),
413
+ ...props
414
+ });
415
+ }
416
+ function CardDescription({ className, ...props }) {
417
+ return /* @__PURE__ */ jsx("div", {
418
+ className: cn("text-sm text-muted-foreground", className),
419
+ ...props
420
+ });
421
+ }
422
+ function CardContent({ className, ...props }) {
423
+ return /* @__PURE__ */ jsx("div", {
424
+ className: cn("p-6 pt-0", className),
425
+ ...props
426
+ });
427
+ }
428
+ function CardFooter({ className, ...props }) {
429
+ return /* @__PURE__ */ jsx("div", {
430
+ className: cn("flex items-center p-6 pt-0", className),
431
+ ...props
432
+ });
433
+ }
434
+ //#endregion
435
+ //#region src/components/card/manifest.ts
436
+ /** Le manifeste de la carte (ADR 0005). Le catalogue enregistre la coquille `Card`. */
437
+ var cardManifest = {
438
+ name: "card",
439
+ title: "Card",
440
+ summary: "Une surface qui regroupe un contenu apparenté : un en-tête (titre, description), un corps et un pied. Livrée en parts importables séparément, sans libellé propre au cœur.",
441
+ level: "primitive",
442
+ example: {
443
+ className: "max-w-sm",
444
+ children: createElement(Fragment$1, null, createElement(CardHeader, null, createElement(CardTitle, null, "Titre"), createElement(CardDescription, null, "Une description courte.")), createElement(CardContent, null, "Le contenu de la carte."))
445
+ },
446
+ variants: [],
447
+ props: [{
448
+ name: "className",
449
+ type: "string",
450
+ required: false,
451
+ check: "class",
452
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
453
+ }, {
454
+ name: "children",
455
+ type: "ReactNode",
456
+ required: true,
457
+ check: "content",
458
+ description: "Les parts de la carte : un en-tête, un corps, un pied."
459
+ }],
460
+ usages: [{
461
+ when: "regrouper un titre, un corps et un pied dans une surface",
462
+ use: "<Card><CardHeader><CardTitle>…</CardTitle></CardHeader><CardContent>…</CardContent></Card>",
463
+ avoid: "un `<div>` stylé au coup par coup : on perd les tokens de bordure et de fond"
464
+ }, {
465
+ when: "des cartes de résumé côte à côte dans une grille",
466
+ use: "plusieurs `Card` de même niveau",
467
+ avoid: "imbriquer des `Card` profondément : une carte n’est pas un layout"
468
+ }]
469
+ };
470
+ //#endregion
471
+ //#region src/components/empty-state/empty-state.tsx
472
+ function EmptyState({ icon, title, description, detail, action, wrap = true, className }) {
473
+ return /* @__PURE__ */ jsxs(Card, {
474
+ className: cn("mx-auto max-w-xl", className),
475
+ children: [
476
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
477
+ icon ? /* @__PURE__ */ jsx("div", {
478
+ className: "mb-2",
479
+ children: icon
480
+ }) : null,
481
+ /* @__PURE__ */ jsx(CardTitle, { children: title }),
482
+ description ? /* @__PURE__ */ jsx(CardDescription, { children: description }) : null
483
+ ] }),
484
+ detail ? /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("pre", {
485
+ className: cn("overflow-x-auto rounded-md bg-muted p-3 text-xs text-muted-foreground", wrap && "whitespace-pre-wrap"),
486
+ children: detail
487
+ }) }) : null,
488
+ action ? /* @__PURE__ */ jsx(CardFooter, {
489
+ className: "gap-2",
490
+ children: action
491
+ }) : null
492
+ ]
493
+ });
494
+ }
495
+ //#endregion
496
+ //#region src/components/empty-state/manifest.ts
497
+ /** Le manifeste de l'état vide (ADR 0005, ADR 0015). Présentation seule, sans moteur. */
498
+ var emptyStateManifest = {
499
+ name: "empty-state",
500
+ title: "EmptyState",
501
+ summary: "L'état vide d'une vue : une carte centrée (icône, titre, description, détail brut, action). Aucun texte propre au cœur — l’app fournit tout, y compris le pourquoi de l’absence.",
502
+ level: "bloc",
503
+ example: {
504
+ title: "aucune donnée",
505
+ description: "le lot est vide.",
506
+ detail: "flux introuvable : 404 (script de sync non lancé)",
507
+ wrap: true
508
+ },
509
+ variants: [],
510
+ props: [
511
+ {
512
+ name: "title",
513
+ type: "string",
514
+ required: true,
515
+ check: "rendered",
516
+ description: "Le titre de l'état : court, jamais une phrase."
517
+ },
518
+ {
519
+ name: "description",
520
+ type: "string",
521
+ required: false,
522
+ check: "rendered",
523
+ description: "Une description d’une phrase, sous le titre."
524
+ },
525
+ {
526
+ name: "detail",
527
+ type: "string",
528
+ required: false,
529
+ check: "rendered",
530
+ description: "Le détail brut (message d’erreur, cause), affiché tel quel."
531
+ },
532
+ {
533
+ name: "wrap",
534
+ type: "boolean",
535
+ required: false,
536
+ default: "true",
537
+ check: "rendered",
538
+ description: "Passe le détail sur plusieurs lignes au lieu de le laisser défiler."
539
+ },
540
+ {
541
+ name: "icon",
542
+ type: "ReactNode",
543
+ required: false,
544
+ check: "accepted",
545
+ description: "L'illustration ou l'icône, fournie par l'appelant."
546
+ },
547
+ {
548
+ name: "action",
549
+ type: "ReactNode",
550
+ required: false,
551
+ check: "accepted",
552
+ description: "Ce qu’on peut faire devant cet état : un bouton, un lien."
553
+ },
554
+ {
555
+ name: "className",
556
+ type: "string",
557
+ required: false,
558
+ check: "class",
559
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
560
+ }
561
+ ],
562
+ usages: [{
563
+ when: "une vue n’a pas de donnée à montrer",
564
+ use: "<EmptyState title={t('empty')} description={…} action={<Button…/>} />",
565
+ avoid: "un `<div>` stylé au coup par coup : on perd les tokens de la carte"
566
+ }, {
567
+ when: "un lot est illisible (pas vide, mais en panne)",
568
+ use: "le `detail` brut, pour nommer la vraie panne plutôt que la masquer",
569
+ avoid: "un zéro ou un vide silencieux là où le signal est illisible"
570
+ }]
571
+ };
572
+ //#endregion
573
+ //#region src/components/chip/chip.tsx
574
+ /** La hauteur d'une puce selon sa taille. */
575
+ var chipSizeClasses = {
576
+ default: "h-6",
577
+ sm: "h-5 text-[11px]"
578
+ };
579
+ /**
580
+ * La puce : une étiquette compacte, éventuellement retirable. Présentation seule — l'app
581
+ * fournit le libellé, l'icône et l'action de retrait (ADR 0015).
582
+ */
583
+ function Chip({ tone = "neutral", size = "default", icon, children, onRemove, removeLabel, disabled = false, className }) {
584
+ return /* @__PURE__ */ jsxs("span", {
585
+ className: cn("inline-flex max-w-full items-center gap-1 rounded-full border px-2.5 text-xs font-medium", chipSizeClasses[size], toneClasses[tone], className),
586
+ children: [
587
+ icon ? /* @__PURE__ */ jsx("span", {
588
+ "aria-hidden": true,
589
+ className: "shrink-0",
590
+ children: icon
591
+ }) : null,
592
+ /* @__PURE__ */ jsx("span", {
593
+ className: "truncate",
594
+ children
595
+ }),
596
+ onRemove ? /* @__PURE__ */ jsx("button", {
597
+ type: "button",
598
+ "aria-label": removeLabel,
599
+ disabled,
600
+ onClick: onRemove,
601
+ className: "-mr-1 shrink-0 rounded-full p-0.5 opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40",
602
+ children: /* @__PURE__ */ jsx(X, {
603
+ className: "size-3",
604
+ "aria-hidden": true
605
+ })
606
+ }) : null
607
+ ]
608
+ });
609
+ }
610
+ //#endregion
611
+ //#region src/components/chip/manifest.ts
612
+ /** Le manifeste de la puce (ADR 0005, ADR 0015). Le ton réutilise `toneClasses`. */
613
+ var chipManifest = {
614
+ name: "chip",
615
+ title: "Chip",
616
+ summary: "Une étiquette compacte, éventuellement retirable. Le ton vient du cœur (une intention) ; l'app fournit le libellé, l'icône et l'action de retrait.",
617
+ level: "primitive",
618
+ example: {
619
+ tone: "neutral",
620
+ size: "default",
621
+ children: "filtre"
622
+ },
623
+ variants: [{
624
+ name: "tone",
625
+ values: [...TONES],
626
+ default: "neutral",
627
+ description: "L'intention de la puce — ce que l'appelant choisit selon l'usage."
628
+ }, {
629
+ name: "size",
630
+ values: ["default", "sm"],
631
+ default: "default",
632
+ description: "La taille de la puce, du plus lisible au plus discret."
633
+ }],
634
+ props: [
635
+ {
636
+ name: "children",
637
+ type: "ReactNode",
638
+ required: true,
639
+ check: "content",
640
+ description: "Le libellé de la puce : court, jamais une phrase."
641
+ },
642
+ {
643
+ name: "icon",
644
+ type: "ReactNode",
645
+ required: false,
646
+ check: "accepted",
647
+ description: "Une icône de tête, fournie par l'appelant."
648
+ },
649
+ {
650
+ name: "onRemove",
651
+ type: "() => void",
652
+ required: false,
653
+ check: "accepted",
654
+ description: "Affiche un bouton de retrait et le rappelle au clic."
655
+ },
656
+ {
657
+ name: "removeLabel",
658
+ type: "string",
659
+ required: false,
660
+ check: "accepted",
661
+ description: "Le libellé accessible du bouton de retrait (requis avec `onRemove`)."
662
+ },
663
+ {
664
+ name: "disabled",
665
+ type: "boolean",
666
+ required: false,
667
+ check: "accepted",
668
+ description: "Désactive le bouton de retrait."
669
+ },
670
+ {
671
+ name: "className",
672
+ type: "string",
673
+ required: false,
674
+ check: "class",
675
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
676
+ }
677
+ ],
678
+ usages: [{
679
+ when: "montrer un filtre actif qu’on peut retirer",
680
+ use: "<Chip onRemove={…} removeLabel={…}>domaine : ui</Chip>",
681
+ avoid: "un `Badge` pour quelque chose de retirable : le `Badge` n’est pas interactif"
682
+ }, {
683
+ when: "qualifier un élément sans action",
684
+ use: "un `Chip` sans `onRemove`",
685
+ avoid: "un `Chip` pour un statut de production : un `Badge` dit l’état sans inviter au clic"
686
+ }]
687
+ };
688
+ //#endregion
689
+ //#region src/components/freshness/freshness.tsx
690
+ function Freshness({ label, title, stale = false, className, ...props }) {
691
+ return /* @__PURE__ */ jsxs("span", {
692
+ className: cn("inline-flex items-center gap-1.5 whitespace-nowrap text-xs", stale ? "text-destructive" : "text-muted-foreground", className),
693
+ title: title ?? void 0,
694
+ ...props,
695
+ children: [/* @__PURE__ */ jsx("span", {
696
+ "aria-hidden": true,
697
+ className: cn("size-1.5 shrink-0 rounded-full", stale ? "bg-destructive" : "bg-muted-foreground/50")
698
+ }), label]
699
+ });
700
+ }
701
+ //#endregion
702
+ //#region src/components/freshness/manifest.ts
703
+ /**
704
+ * Le manifeste de l'indicateur de fraîcheur (ADR 0005). L'âge est injecté par
705
+ * l'appelant : c'est lui qui formate (« il y a 2 heures ») et qui décide du seuil de
706
+ * péremption ; le cœur ne fait que le rendre.
707
+ */
708
+ var freshnessManifest = {
709
+ name: "freshness",
710
+ title: "Freshness",
711
+ summary: "L'âge d'une donnée depuis sa dernière mise à jour, avec une pastille qui passe au rouge quand elle est périmée. Se rend seule, hors d'une table : une cellule comme un bandeau de provenance l’utilisent.",
712
+ level: "primitive",
713
+ example: {
714
+ label: "il y a 2 heures",
715
+ stale: false
716
+ },
717
+ variants: [],
718
+ props: [
719
+ {
720
+ name: "label",
721
+ type: "string",
722
+ required: true,
723
+ check: "rendered",
724
+ description: "L'âge déjà formaté par l'appelant : le cœur n'a pas d'i18n."
725
+ },
726
+ {
727
+ name: "stale",
728
+ type: "boolean",
729
+ required: false,
730
+ check: "rendered",
731
+ description: "Vrai quand la donnée dépasse le seuil de péremption décidé par l'appelant."
732
+ },
733
+ {
734
+ name: "title",
735
+ type: "string | null",
736
+ required: false,
737
+ check: "attribute",
738
+ description: "L'horodatage absolu, transmis comme infobulle HTML."
739
+ },
740
+ {
741
+ name: "className",
742
+ type: "string",
743
+ required: false,
744
+ check: "class",
745
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
746
+ }
747
+ ],
748
+ usages: [{
749
+ when: "une cellule dit depuis quand une donnée n'a pas bougé",
750
+ use: "<Freshness label={relativeTime(row.updated_at)} />",
751
+ avoid: "un `<span>` nu : la pastille est le repère visuel de fraîcheur"
752
+ }, {
753
+ when: "un bandeau dit la fraîcheur d'un instantané",
754
+ use: "<Freshness label={age} title={absolute} stale={olderThanADay} />",
755
+ avoid: "un seuil de péremption dans le cœur : il dépend du domaine, pas du rendu"
756
+ }]
757
+ };
758
+ //#endregion
759
+ //#region src/components/input/input.tsx
760
+ /**
761
+ * Le champ de saisie du cœur : un `<input>` stylé par les tokens. Il n'impose aucune
762
+ * validation ni aucun état d'app — l'appelant passe ses props HTML et ses classes.
763
+ */
764
+ function Input({ className, type, ...props }) {
765
+ return /* @__PURE__ */ jsx("input", {
766
+ type,
767
+ className: cn("flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className),
768
+ ...props
769
+ });
770
+ }
771
+ //#endregion
772
+ //#region src/components/input/manifest.ts
773
+ /**
774
+ * Le manifeste du champ de saisie (ADR 0005). Le cœur ne pose ni validation ni état :
775
+ * l'appelant passe ses props HTML, la valeur et le rappel restent chez lui.
776
+ */
777
+ var inputManifest = {
778
+ name: "input",
779
+ title: "Input",
780
+ summary: "Un champ de saisie d'une ligne. Il porte le style des tokens et transmet tout attribut HTML d'`<input>` ; la valeur et le changement restent à l'appelant.",
781
+ level: "primitive",
782
+ example: { placeholder: "rechercher" },
783
+ variants: [],
784
+ props: [
785
+ {
786
+ name: "className",
787
+ type: "string",
788
+ required: false,
789
+ check: "class",
790
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
791
+ },
792
+ {
793
+ name: "type",
794
+ type: "string",
795
+ required: false,
796
+ check: "attribute",
797
+ description: "Le type HTML du champ (`text`, `search`, `password`…)."
798
+ },
799
+ {
800
+ name: "placeholder",
801
+ type: "string",
802
+ required: false,
803
+ check: "attribute",
804
+ description: "Le texte d'invite, affiché tant que le champ est vide."
805
+ }
806
+ ],
807
+ usages: [{
808
+ when: "un champ de recherche dans une barre d’outils",
809
+ use: "<Input type=\"search\" placeholder={…} />",
810
+ avoid: "un placeholder à la place d’un libellé durable, pour un champ de formulaire"
811
+ }, {
812
+ when: "un champ contrôlé",
813
+ use: "<Input value={v} onChange={…} /> (l’état reste dans l’app)",
814
+ avoid: "attendre un état interne du cœur : il n’en a pas"
815
+ }]
816
+ };
817
+ //#endregion
818
+ //#region src/components/number-field/number-field.tsx
819
+ /**
820
+ * Le champ numérique du cœur : un `<input>` en `type="number"`, donc le pas (`step`) et
821
+ * les bornes (`min`/`max`) viennent du natif. Aucune validation ni i18n : la valeur et le
822
+ * rappel restent chez l'appelant, comme `Input` (ADR 0015).
823
+ */
824
+ function NumberField({ type = "number", ...props }) {
825
+ return /* @__PURE__ */ jsx(Input, {
826
+ type,
827
+ ...props
828
+ });
829
+ }
830
+ //#endregion
831
+ //#region src/components/number-field/manifest.ts
832
+ /** Le manifeste du champ numérique (ADR 0005, ADR 0015). Le natif porte pas et bornes. */
833
+ var numberFieldManifest = {
834
+ name: "number-field",
835
+ title: "NumberField",
836
+ summary: "Un champ numérique (`<input type=\"number\">`) : le pas et les bornes viennent du natif (`step`, `min`, `max`). La valeur et le rappel restent à l'appelant.",
837
+ level: "primitive",
838
+ example: {
839
+ placeholder: "0",
840
+ step: 1,
841
+ min: 0,
842
+ max: 10
843
+ },
844
+ variants: [],
845
+ props: [
846
+ {
847
+ name: "step",
848
+ type: "number",
849
+ required: false,
850
+ check: "attribute",
851
+ description: "Le pas du compteur natif."
852
+ },
853
+ {
854
+ name: "min",
855
+ type: "number",
856
+ required: false,
857
+ check: "attribute",
858
+ description: "La borne basse."
859
+ },
860
+ {
861
+ name: "max",
862
+ type: "number",
863
+ required: false,
864
+ check: "attribute",
865
+ description: "La borne haute."
866
+ },
867
+ {
868
+ name: "placeholder",
869
+ type: "string",
870
+ required: false,
871
+ check: "attribute",
872
+ description: "Le texte d'invite, affiché tant que le champ est vide."
873
+ },
874
+ {
875
+ name: "value",
876
+ type: "string",
877
+ required: false,
878
+ check: "accepted",
879
+ description: "La valeur courante, contrôlée par l’appelant."
880
+ },
881
+ {
882
+ name: "onChange",
883
+ type: "ChangeEventHandler<HTMLInputElement>",
884
+ required: false,
885
+ check: "accepted",
886
+ description: "Le rappel de saisie."
887
+ },
888
+ {
889
+ name: "disabled",
890
+ type: "boolean",
891
+ required: false,
892
+ check: "accepted",
893
+ description: "Désactive le champ."
894
+ },
895
+ {
896
+ name: "className",
897
+ type: "string",
898
+ required: false,
899
+ check: "class",
900
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
901
+ }
902
+ ],
903
+ usages: [{
904
+ when: "poser un nombre borné avec un pas",
905
+ use: "<NumberField min={0} max={100} step={5} value={v} onChange={…} />",
906
+ avoid: "attendre une validation ou un formatage du cœur : ils appartiennent à l’app"
907
+ }, {
908
+ when: "un champ contrôlé",
909
+ use: "<NumberField value={v} onChange={…} /> (l’état reste dans l’app)",
910
+ avoid: "un état interne : le cœur n’en a pas"
911
+ }]
912
+ };
913
+ //#endregion
914
+ //#region src/components/search-field/search-field.tsx
915
+ function SearchField({ value, onChange, clearLabel, placeholder, className }) {
916
+ return /* @__PURE__ */ jsxs("div", {
917
+ className: cn("relative", className),
918
+ children: [
919
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }),
920
+ /* @__PURE__ */ jsx(Input, {
921
+ type: "search",
922
+ value,
923
+ onChange: (event) => onChange(event.target.value),
924
+ placeholder,
925
+ className: "pl-9 pr-9"
926
+ }),
927
+ value ? /* @__PURE__ */ jsx("button", {
928
+ type: "button",
929
+ "aria-label": clearLabel,
930
+ onClick: () => onChange(""),
931
+ className: "absolute right-2 top-1/2 grid size-6 -translate-y-1/2 place-items-center rounded text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
932
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
933
+ }) : null
934
+ ]
935
+ });
936
+ }
937
+ //#endregion
938
+ //#region src/components/search-field/manifest.ts
939
+ /** Le manifeste du champ de recherche (ADR 0005, ADR 0015). Le cœur ne connaît pas l'i18n. */
940
+ var searchFieldManifest = {
941
+ name: "search-field",
942
+ title: "SearchField",
943
+ summary: "Un champ de recherche avec icône et bouton d'effacement, contrôlé par props (`value` + `onChange`). Le libellé « effacer » est injecté : le cœur n'a pas d'i18n.",
944
+ level: "primitive",
945
+ example: {
946
+ value: "",
947
+ onChange: () => {},
948
+ clearLabel: "Effacer",
949
+ placeholder: "rechercher"
950
+ },
951
+ variants: [],
952
+ props: [
953
+ {
954
+ name: "value",
955
+ type: "string",
956
+ required: true,
957
+ check: "accepted",
958
+ description: "Le terme courant, contrôlé par l’appelant."
959
+ },
960
+ {
961
+ name: "onChange",
962
+ type: "(value: string) => void",
963
+ required: true,
964
+ check: "accepted",
965
+ description: "Le rappel de saisie **et** d’effacement (le bouton renvoie la chaîne vide)."
966
+ },
967
+ {
968
+ name: "clearLabel",
969
+ type: "string",
970
+ required: true,
971
+ check: "accepted",
972
+ description: "Le libellé accessible du bouton d'effacement."
973
+ },
974
+ {
975
+ name: "placeholder",
976
+ type: "string",
977
+ required: false,
978
+ check: "attribute",
979
+ description: "Le texte d'invite, affiché tant que le champ est vide."
980
+ },
981
+ {
982
+ name: "className",
983
+ type: "string",
984
+ required: false,
985
+ check: "class",
986
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
987
+ }
988
+ ],
989
+ usages: [{
990
+ when: "chercher dans une liste ou une barre d’outils",
991
+ use: "<SearchField value={v} onChange={set} clearLabel={t('clear')} />",
992
+ avoid: "un `Input type=\"search\"` nu quand il faut l’icône et l’effacement"
993
+ }, {
994
+ when: "effacer la recherche",
995
+ use: "le bouton intégré, qui rappelle `onChange(\"\")`",
996
+ avoid: "gérer l’effacement dans l’app quand le champ est déjà rempli"
997
+ }]
998
+ };
999
+ //#endregion
1000
+ //#region src/components/label/label.tsx
1001
+ /** Le libellé d'un champ : associé au contrôle par `htmlFor`. Tout texte vient de l'app. */
1002
+ function Label({ className, ...props }) {
1003
+ return /* @__PURE__ */ jsx("label", {
1004
+ className: cn("text-sm font-medium leading-none", className),
1005
+ ...props
1006
+ });
1007
+ }
1008
+ //#endregion
1009
+ //#region src/components/label/manifest.ts
1010
+ /** Le manifeste du libellé (ADR 0005, ADR 0015). */
1011
+ var labelManifest = {
1012
+ name: "label",
1013
+ title: "Label",
1014
+ summary: "Le libellé d'un champ, associé au contrôle par `htmlFor`. Le texte vient de l'appelant (le cœur n'a pas d'i18n) ; la typographie vient des tokens.",
1015
+ level: "primitive",
1016
+ example: { children: "Libellé" },
1017
+ variants: [],
1018
+ props: [
1019
+ {
1020
+ name: "htmlFor",
1021
+ type: "string",
1022
+ required: false,
1023
+ check: "accepted",
1024
+ description: "L'`id` du contrôle associé : clique le libellé et le champ prend le focus (rendu en attribut `for`)."
1025
+ },
1026
+ {
1027
+ name: "className",
1028
+ type: "string",
1029
+ required: false,
1030
+ check: "class",
1031
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
1032
+ },
1033
+ {
1034
+ name: "children",
1035
+ type: "ReactNode",
1036
+ required: true,
1037
+ check: "content",
1038
+ description: "Le texte du libellé, injecté par l'app."
1039
+ }
1040
+ ],
1041
+ usages: [{
1042
+ when: "nommer un champ et l’associer à son contrôle",
1043
+ use: "<Label htmlFor=\"email\">Adresse</Label>",
1044
+ avoid: "un libellé sans `htmlFor` : le clic ne donne pas le focus au champ"
1045
+ }, {
1046
+ when: "poser un libellé hors d’un `Field`",
1047
+ use: "le même atome, avec ton propre espacement",
1048
+ avoid: "coder un `<label>` stylé à la main : on perd la typographie des tokens"
1049
+ }]
1050
+ };
1051
+ //#endregion
1052
+ //#region src/components/fieldset/fieldset.tsx
1053
+ /** Un regroupement de champs apparentés ; l'app fournit le titre (`legend`). */
1054
+ function Fieldset({ className, ...props }) {
1055
+ return /* @__PURE__ */ jsx("fieldset", {
1056
+ className: cn("flex flex-col gap-4", className),
1057
+ ...props
1058
+ });
1059
+ }
1060
+ //#endregion
1061
+ //#region src/components/fieldset/manifest.ts
1062
+ /** Le manifeste du regroupement de champs (ADR 0005, ADR 0015). */
1063
+ var fieldsetManifest = {
1064
+ name: "fieldset",
1065
+ title: "Fieldset",
1066
+ summary: "Un regroupement de champs apparentés. Sémantique (`fieldset`) et espacement des tokens ; le titre du groupe (`legend`) vient de l'app.",
1067
+ level: "primitive",
1068
+ example: { children: "groupe de champs" },
1069
+ variants: [],
1070
+ props: [{
1071
+ name: "className",
1072
+ type: "string",
1073
+ required: false,
1074
+ check: "class",
1075
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
1076
+ }, {
1077
+ name: "children",
1078
+ type: "ReactNode",
1079
+ required: true,
1080
+ check: "content",
1081
+ description: "Les champs regroupés (et leur `legend`)."
1082
+ }],
1083
+ usages: [{
1084
+ when: "regrouper des champs apparentés d’un formulaire",
1085
+ use: "<Fieldset><legend>…</legend> …champs… </Fieldset>",
1086
+ avoid: "un `<div>` : on perd la sémantique de groupe de formulaire"
1087
+ }]
1088
+ };
1089
+ //#endregion
1090
+ //#region src/components/field/field.tsx
1091
+ function Field({ label, hint, error, htmlFor, className, children }) {
1092
+ return /* @__PURE__ */ jsxs("div", {
1093
+ className: cn("flex flex-col gap-1.5", className),
1094
+ children: [
1095
+ label ? /* @__PURE__ */ jsx(Label, {
1096
+ htmlFor,
1097
+ children: label
1098
+ }) : null,
1099
+ children,
1100
+ error ? /* @__PURE__ */ jsx("p", {
1101
+ className: "text-xs text-destructive",
1102
+ children: error
1103
+ }) : hint ? /* @__PURE__ */ jsx("p", {
1104
+ className: "text-xs text-muted-foreground",
1105
+ children: hint
1106
+ }) : null
1107
+ ]
1108
+ });
1109
+ }
1110
+ //#endregion
1111
+ //#region src/components/field/manifest.ts
1112
+ /** Le manifeste de l'emplacement de champ (ADR 0005, ADR 0015). */
1113
+ var fieldManifest = {
1114
+ name: "field",
1115
+ title: "Field",
1116
+ summary: "L'emplacement d'un champ : libellé, contrôle (fourni par l'app), puis aide ou message d'erreur. Aucun moteur : l'app possède l'état, la validation et les textes.",
1117
+ level: "primitive",
1118
+ example: {
1119
+ label: "Libellé",
1120
+ hint: "Aide",
1121
+ children: "champ"
1122
+ },
1123
+ variants: [],
1124
+ props: [
1125
+ {
1126
+ name: "label",
1127
+ type: "ReactNode",
1128
+ required: false,
1129
+ check: "rendered",
1130
+ description: "Le libellé du champ ; absent, aucun libellé n'est rendu."
1131
+ },
1132
+ {
1133
+ name: "hint",
1134
+ type: "ReactNode",
1135
+ required: false,
1136
+ check: "rendered",
1137
+ description: "L'aide sous le champ, affichée quand il n'y a pas d'erreur."
1138
+ },
1139
+ {
1140
+ name: "error",
1141
+ type: "string | null",
1142
+ required: false,
1143
+ check: "rendered",
1144
+ description: "Le message d'erreur, **déjà formaté** par l'app : il remplace l'aide."
1145
+ },
1146
+ {
1147
+ name: "htmlFor",
1148
+ type: "string",
1149
+ required: false,
1150
+ check: "accepted",
1151
+ description: "L'`id` du contrôle, transmis au libellé en attribut `for`."
1152
+ },
1153
+ {
1154
+ name: "className",
1155
+ type: "string",
1156
+ required: false,
1157
+ check: "class",
1158
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
1159
+ },
1160
+ {
1161
+ name: "children",
1162
+ type: "ReactNode",
1163
+ required: true,
1164
+ check: "content",
1165
+ description: "Le contrôle (Input, Select, …), fourni par l’appelant."
1166
+ }
1167
+ ],
1168
+ usages: [{
1169
+ when: "poser un libellé, un contrôle et une aide",
1170
+ use: "<Field label=\"Adresse\" hint=\"…\"><Input … /></Field>",
1171
+ avoid: "attendre une validation du cœur : le message d’erreur est injecté, pas calculé"
1172
+ }, {
1173
+ when: "montrer une erreur de validation",
1174
+ use: "<Field label=\"Adresse\" error={message}>…</Field>",
1175
+ avoid: "passer la règle ou le champ fautif : le cœur ne connaît que le texte final"
1176
+ }]
1177
+ };
1178
+ //#endregion
1179
+ //#region src/components/form/form.tsx
1180
+ function Form({ columns = 1, actions, children, className, ...props }) {
1181
+ return /* @__PURE__ */ jsxs("form", {
1182
+ className: cn("flex flex-col gap-6", className),
1183
+ ...props,
1184
+ children: [/* @__PURE__ */ jsx("div", {
1185
+ className: cn("grid gap-4", columns === 2 && "md:grid-cols-2"),
1186
+ children
1187
+ }), actions ? /* @__PURE__ */ jsx("div", {
1188
+ className: "flex items-center justify-end gap-2",
1189
+ children: actions
1190
+ }) : null]
1191
+ });
1192
+ }
1193
+ //#endregion
1194
+ //#region src/components/form/manifest.ts
1195
+ /** Le manifeste du bloc de formulaire (ADR 0005, ADR 0015). Mise en page seule, sans moteur. */
1196
+ var formManifest = {
1197
+ name: "form",
1198
+ title: "Form",
1199
+ summary: "La mise en page d'un formulaire : une grille de champs et une zone d'actions, dans un `<form>`. Aucune validation, aucun état, aucun texte — l'app fournit champs et actions.",
1200
+ level: "bloc",
1201
+ example: {
1202
+ children: "un champ",
1203
+ columns: 1,
1204
+ actions: "actions"
1205
+ },
1206
+ variants: [],
1207
+ props: [
1208
+ {
1209
+ name: "columns",
1210
+ type: "1 | 2",
1211
+ required: false,
1212
+ default: "1",
1213
+ check: "accepted",
1214
+ description: "Le nombre de colonnes de la grille de champs à partir du palier `md`."
1215
+ },
1216
+ {
1217
+ name: "actions",
1218
+ type: "ReactNode",
1219
+ required: false,
1220
+ check: "accepted",
1221
+ description: "La zone d'actions sous les champs (boutons fournis par l'app)."
1222
+ },
1223
+ {
1224
+ name: "onSubmit",
1225
+ type: "FormEventHandler<HTMLFormElement>",
1226
+ required: false,
1227
+ check: "accepted",
1228
+ description: "La soumission, gérée par l’appelant (le cœur ne soumet rien)."
1229
+ },
1230
+ {
1231
+ name: "className",
1232
+ type: "string",
1233
+ required: false,
1234
+ check: "class",
1235
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
1236
+ }
1237
+ ],
1238
+ usages: [{
1239
+ when: "mettre en page un formulaire contrôlé",
1240
+ use: "<Form actions={<Button type=\"submit\">…</Button>}><Fieldset>…</Fieldset></Form>",
1241
+ avoid: "attendre du cœur qu’il valide, soumette ou affiche une erreur : il ne le fait pas"
1242
+ }, {
1243
+ when: "ranger des champs sur deux colonnes",
1244
+ use: "<Form columns={2}> (deux colonnes à partir du palier `md`)",
1245
+ avoid: "une grille maison dans l’app quand le bloc la fournit déjà"
1246
+ }]
1247
+ };
1248
+ //#endregion
1249
+ //#region src/internal/dropdown-menu.tsx
1250
+ var DropdownMenu = DropdownMenuPrimitive.Root;
1251
+ var DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
1252
+ var DropdownMenuSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ jsxs(DropdownMenuPrimitive.SubTrigger, {
1253
+ ref,
1254
+ className: cn("flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", inset && "pl-8", className),
1255
+ ...props,
1256
+ children: [children, /* @__PURE__ */ jsx(ChevronRight, { className: "ml-auto" })]
1257
+ }));
1258
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
1259
+ var DropdownMenuSubContent = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DropdownMenuPrimitive.SubContent, {
1260
+ ref,
1261
+ className: cn("z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]", className),
1262
+ ...props
1263
+ }));
1264
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
1265
+ var DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.Content, {
1266
+ ref,
1267
+ sideOffset,
1268
+ className: cn("z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-32 overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]", className),
1269
+ ...props
1270
+ }) }));
1271
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
1272
+ var DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx(DropdownMenuPrimitive.Item, {
1273
+ ref,
1274
+ className: cn("relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", inset && "pl-8", className),
1275
+ ...props
1276
+ }));
1277
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
1278
+ var DropdownMenuCheckboxItem = React.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs(DropdownMenuPrimitive.CheckboxItem, {
1279
+ ref,
1280
+ className: cn("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className),
1281
+ checked,
1282
+ ...props,
1283
+ children: [/* @__PURE__ */ jsx("span", {
1284
+ className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center",
1285
+ children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) })
1286
+ }), children]
1287
+ }));
1288
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
1289
+ var DropdownMenuRadioItem = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(DropdownMenuPrimitive.RadioItem, {
1290
+ ref,
1291
+ className: cn("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className),
1292
+ ...props,
1293
+ children: [/* @__PURE__ */ jsx("span", {
1294
+ className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center",
1295
+ children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(Circle, { className: "h-2 w-2 fill-current" }) })
1296
+ }), children]
1297
+ }));
1298
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
1299
+ var DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx(DropdownMenuPrimitive.Label, {
1300
+ ref,
1301
+ className: cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className),
1302
+ ...props
1303
+ }));
1304
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
1305
+ var DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DropdownMenuPrimitive.Separator, {
1306
+ ref,
1307
+ className: cn("-mx-1 my-1 h-px bg-muted", className),
1308
+ ...props
1309
+ }));
1310
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
1311
+ var DropdownMenuShortcut = ({ className, ...props }) => {
1312
+ return /* @__PURE__ */ jsx("span", {
1313
+ className: cn("ml-auto text-xs tracking-widest opacity-60", className),
1314
+ ...props
1315
+ });
1316
+ };
1317
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
1318
+ //#endregion
1319
+ //#region src/components/facet-filter/facet-filter.tsx
1320
+ function FacetFilter({ label, options, selected, onChange, clearLabel, className }) {
1321
+ const toggle = (value) => {
1322
+ onChange(selected.includes(value) ? selected.filter((item) => item !== value) : [...selected, value]);
1323
+ };
1324
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
1325
+ asChild: true,
1326
+ children: /* @__PURE__ */ jsxs(Button, {
1327
+ variant: "outline",
1328
+ size: "sm",
1329
+ className: cn("h-8 gap-1 border-dashed", className),
1330
+ children: [
1331
+ label,
1332
+ selected.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { className: "mx-1 h-4 w-px bg-border" }), /* @__PURE__ */ jsx(Badge, {
1333
+ variant: "secondary",
1334
+ className: "rounded px-1 text-[10px]",
1335
+ children: selected.length
1336
+ })] }) : null,
1337
+ /* @__PURE__ */ jsx(ChevronDown, { className: "size-3.5 opacity-60" })
1338
+ ]
1339
+ })
1340
+ }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
1341
+ align: "start",
1342
+ className: "max-h-72 w-56 overflow-y-auto",
1343
+ children: [options.map((option) => {
1344
+ const isSelected = selected.includes(option.value);
1345
+ return /* @__PURE__ */ jsxs(DropdownMenuItem, {
1346
+ onSelect: (event) => {
1347
+ event.preventDefault();
1348
+ toggle(option.value);
1349
+ },
1350
+ className: "gap-2",
1351
+ children: [
1352
+ /* @__PURE__ */ jsx("span", {
1353
+ className: cn("grid size-4 shrink-0 place-items-center rounded border", isSelected ? "border-primary bg-primary text-primary-foreground" : "opacity-60"),
1354
+ children: isSelected ? /* @__PURE__ */ jsx(Check, { className: "size-3" }) : null
1355
+ }),
1356
+ /* @__PURE__ */ jsx("span", {
1357
+ className: "flex-1 truncate",
1358
+ children: option.label
1359
+ }),
1360
+ /* @__PURE__ */ jsx("span", {
1361
+ className: "text-xs tabular-nums text-muted-foreground",
1362
+ children: option.count
1363
+ })
1364
+ ]
1365
+ }, option.value);
1366
+ }), selected.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsx(DropdownMenuItem, {
1367
+ onSelect: () => onChange([]),
1368
+ className: "justify-center text-xs text-muted-foreground",
1369
+ children: clearLabel
1370
+ })] }) : null]
1371
+ })] });
1372
+ }
1373
+ //#endregion
1374
+ //#region src/components/facet-filter/manifest.ts
1375
+ /**
1376
+ * Le manifeste du filtre à facettes (ADR 0005). Les options, la sélection et les
1377
+ * libellés sont injectés : c'est ce qui l'importe hors d'une table.
1378
+ */
1379
+ var facetFilterManifest = {
1380
+ name: "facet-filter",
1381
+ title: "FacetFilter",
1382
+ summary: "Un menu à choix multiples sur une facette : chaque option porte son compte, et la sélection se cumule. Rien ne vient de l’app — options, sélection et libellé « effacer » sont injectés.",
1383
+ level: "primitive",
1384
+ example: {
1385
+ label: "dépôt",
1386
+ options: [{
1387
+ value: "workout-app",
1388
+ label: "workout-app",
1389
+ count: 3
1390
+ }, {
1391
+ value: "mijote",
1392
+ label: "mijote",
1393
+ count: 1
1394
+ }],
1395
+ selected: ["workout-app"],
1396
+ onChange: () => {},
1397
+ clearLabel: "Effacer"
1398
+ },
1399
+ variants: [],
1400
+ props: [
1401
+ {
1402
+ name: "label",
1403
+ type: "string",
1404
+ required: true,
1405
+ check: "rendered",
1406
+ description: "Le nom de la facette, porté par le déclencheur."
1407
+ },
1408
+ {
1409
+ name: "options",
1410
+ type: "FacetOption[]",
1411
+ required: true,
1412
+ check: "accepted",
1413
+ description: "Les valeurs filtrables, chacune avec son libellé et son compte."
1414
+ },
1415
+ {
1416
+ name: "selected",
1417
+ type: "string[]",
1418
+ required: true,
1419
+ check: "accepted",
1420
+ description: "Les valeurs déjà retenues ; le compte s’affiche sur le déclencheur."
1421
+ },
1422
+ {
1423
+ name: "onChange",
1424
+ type: "(next: string[]) => void",
1425
+ required: true,
1426
+ check: "accepted",
1427
+ description: "Le rappel de sélection : la liste complète des valeurs retenues."
1428
+ },
1429
+ {
1430
+ name: "clearLabel",
1431
+ type: "string",
1432
+ required: true,
1433
+ check: "accepted",
1434
+ description: "Le libellé de l'entrée qui efface la sélection (le cœur n'a pas d'i18n)."
1435
+ },
1436
+ {
1437
+ name: "className",
1438
+ type: "string",
1439
+ required: false,
1440
+ check: "class",
1441
+ description: "Les classes de l'appelant, fusionnées sur le déclencheur."
1442
+ }
1443
+ ],
1444
+ usages: [{
1445
+ when: "filtrer une liste par une de ses dimensions",
1446
+ use: "<FacetFilter label=\"dépôt\" options={…} selected={…} onChange={…} clearLabel={…} />",
1447
+ avoid: "coder une facette produit dans le cœur : les options sont des données d’app"
1448
+ }, {
1449
+ when: "filtrer autre chose qu’une table (une carte, une liste)",
1450
+ use: "le même atome, avec les options de la source",
1451
+ avoid: "le coupler à un query-param : l’état de sélection appartient à l’appelant"
1452
+ }]
1453
+ };
1454
+ //#endregion
1455
+ //#region src/components/data-table-toolbar/data-table-toolbar.tsx
1456
+ function DataTableToolbar({ globalFilter, onGlobalFilterChange, placeholder, facets, shownLabel, canReset, resetLabel, clearFacetLabel, onReset, columns, actions }) {
1457
+ return /* @__PURE__ */ jsxs("div", {
1458
+ className: "flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between",
1459
+ children: [/* @__PURE__ */ jsxs("div", {
1460
+ className: "relative w-full lg:max-w-xs",
1461
+ children: [/* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ jsx(Input, {
1462
+ value: globalFilter,
1463
+ onChange: (event) => onGlobalFilterChange(event.target.value),
1464
+ placeholder,
1465
+ className: "h-8 pl-9"
1466
+ })]
1467
+ }), /* @__PURE__ */ jsxs("div", {
1468
+ className: "flex flex-wrap items-center gap-2",
1469
+ children: [
1470
+ facets.map((facet) => /* @__PURE__ */ jsx(FacetFilter, {
1471
+ label: facet.label,
1472
+ options: facet.options,
1473
+ selected: facet.selected,
1474
+ onChange: facet.onChange,
1475
+ clearLabel: clearFacetLabel
1476
+ }, facet.id)),
1477
+ columns ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
1478
+ asChild: true,
1479
+ children: /* @__PURE__ */ jsxs(Button, {
1480
+ variant: "outline",
1481
+ size: "sm",
1482
+ className: "h-8 gap-1 border-dashed",
1483
+ children: [columns.label, /* @__PURE__ */ jsx(ChevronDown, { className: "size-3.5 opacity-60" })]
1484
+ })
1485
+ }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
1486
+ align: "start",
1487
+ className: "w-56",
1488
+ children: [
1489
+ columns.options.map((option) => /* @__PURE__ */ jsxs(DropdownMenuItem, {
1490
+ onSelect: (event) => {
1491
+ event.preventDefault();
1492
+ columns.onToggle(option.id, !option.visible);
1493
+ },
1494
+ className: "gap-2",
1495
+ children: [
1496
+ /* @__PURE__ */ jsx("span", {
1497
+ className: cn("grid size-4 shrink-0 place-items-center rounded border", option.visible ? "border-primary bg-primary text-primary-foreground" : "opacity-60"),
1498
+ children: option.visible ? /* @__PURE__ */ jsx(Check, { className: "size-3" }) : null
1499
+ }),
1500
+ /* @__PURE__ */ jsx("span", {
1501
+ className: "flex-1 truncate",
1502
+ children: option.label
1503
+ }),
1504
+ columns.onMove ? /* @__PURE__ */ jsxs("span", {
1505
+ className: "flex items-center gap-0.5",
1506
+ children: [/* @__PURE__ */ jsx("button", {
1507
+ type: "button",
1508
+ "aria-label": columns.moveUpLabel?.(option.label) ?? option.label,
1509
+ className: "rounded p-0.5 text-muted-foreground hover:text-foreground",
1510
+ onClick: (event) => {
1511
+ event.stopPropagation();
1512
+ event.preventDefault();
1513
+ columns.onMove?.(option.id, -1);
1514
+ },
1515
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "size-3.5" })
1516
+ }), /* @__PURE__ */ jsx("button", {
1517
+ type: "button",
1518
+ "aria-label": columns.moveDownLabel?.(option.label) ?? option.label,
1519
+ className: "rounded p-0.5 text-muted-foreground hover:text-foreground",
1520
+ onClick: (event) => {
1521
+ event.stopPropagation();
1522
+ event.preventDefault();
1523
+ columns.onMove?.(option.id, 1);
1524
+ },
1525
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-3.5" })
1526
+ })]
1527
+ }) : null
1528
+ ]
1529
+ }, option.id)),
1530
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
1531
+ /* @__PURE__ */ jsx(DropdownMenuItem, {
1532
+ onSelect: () => columns.options.forEach((option) => columns.onToggle(option.id, true)),
1533
+ className: "justify-center text-xs text-muted-foreground",
1534
+ children: columns.clearLabel
1535
+ })
1536
+ ]
1537
+ })] }) : null,
1538
+ canReset ? /* @__PURE__ */ jsxs(Button, {
1539
+ variant: "ghost",
1540
+ size: "sm",
1541
+ className: "h-8",
1542
+ onClick: onReset,
1543
+ children: [resetLabel, /* @__PURE__ */ jsx(X, { className: "ml-1 size-3.5" })]
1544
+ }) : null,
1545
+ /* @__PURE__ */ jsx("span", {
1546
+ className: "ml-1 hidden text-xs text-muted-foreground sm:inline",
1547
+ children: shownLabel
1548
+ }),
1549
+ actions
1550
+ ]
1551
+ })]
1552
+ });
1553
+ }
1554
+ //#endregion
1555
+ //#region src/components/data-table-toolbar/manifest.ts
1556
+ /** Le manifeste de la barre d'outils de table (ADR 0005). Aucun libellé propre au cœur. */
1557
+ var dataTableToolbarManifest = {
1558
+ name: "data-table-toolbar",
1559
+ title: "DataTableToolbar",
1560
+ summary: "La recherche, les facettes, la gestion des colonnes et le compte affiché d’une table. Elle ne connaît ni l'i18n de l'app ni ses query-params : état et libellés sont injectés.",
1561
+ level: "bloc",
1562
+ example: {
1563
+ globalFilter: "",
1564
+ onGlobalFilterChange: () => {},
1565
+ placeholder: "rechercher",
1566
+ facets: [],
1567
+ shownLabel: "0 / 0",
1568
+ canReset: true,
1569
+ resetLabel: "Réinitialiser",
1570
+ clearFacetLabel: "Effacer",
1571
+ onReset: () => {},
1572
+ columns: {
1573
+ label: "Colonnes",
1574
+ clearLabel: "Tout afficher",
1575
+ options: [{
1576
+ id: "name",
1577
+ label: "nom",
1578
+ visible: true
1579
+ }],
1580
+ onToggle: () => {}
1581
+ }
1582
+ },
1583
+ variants: [],
1584
+ props: [
1585
+ {
1586
+ name: "placeholder",
1587
+ type: "string",
1588
+ required: true,
1589
+ check: "attribute",
1590
+ description: "Le texte d'invite du champ de recherche."
1591
+ },
1592
+ {
1593
+ name: "shownLabel",
1594
+ type: "string",
1595
+ required: true,
1596
+ check: "rendered",
1597
+ description: "Le compte affiché, déjà formaté par l’appelant."
1598
+ },
1599
+ {
1600
+ name: "resetLabel",
1601
+ type: "string",
1602
+ required: true,
1603
+ check: "rendered",
1604
+ description: "Le libellé du bouton qui efface filtres et recherche."
1605
+ },
1606
+ {
1607
+ name: "canReset",
1608
+ type: "boolean",
1609
+ required: true,
1610
+ check: "rendered",
1611
+ description: "Vrai quand il y a un filtre à effacer : le bouton apparaît alors."
1612
+ },
1613
+ {
1614
+ name: "facets",
1615
+ type: "Facet[]",
1616
+ required: true,
1617
+ check: "accepted",
1618
+ description: "Les facettes liées (valeur, options, sélection, rappel)."
1619
+ },
1620
+ {
1621
+ name: "globalFilter",
1622
+ type: "string",
1623
+ required: true,
1624
+ check: "accepted",
1625
+ description: "Le terme de recherche courant, contrôlé par l’appelant."
1626
+ },
1627
+ {
1628
+ name: "onGlobalFilterChange",
1629
+ type: "(value: string) => void",
1630
+ required: true,
1631
+ check: "accepted",
1632
+ description: "Le rappel de saisie du champ de recherche."
1633
+ },
1634
+ {
1635
+ name: "clearFacetLabel",
1636
+ type: "string",
1637
+ required: true,
1638
+ check: "accepted",
1639
+ description: "Le libellé d'effacement transmis à chaque filtre à facettes."
1640
+ },
1641
+ {
1642
+ name: "onReset",
1643
+ type: "() => void",
1644
+ required: true,
1645
+ check: "accepted",
1646
+ description: "Le rappel du bouton de réinitialisation."
1647
+ },
1648
+ {
1649
+ name: "columns",
1650
+ type: "{ label: string; clearLabel: string; options: DataTableColumnOption[]; onToggle(id: string, visible: boolean): void; onMove?(id: string, direction: number): void; moveUpLabel?(label: string): string; moveDownLabel?(label: string): string }",
1651
+ required: false,
1652
+ check: "accepted",
1653
+ description: "La gestion des colonnes : libellé du menu, colonnes masquables, bascule d'affichage et (fourni) déplacement haut/bas. Absente, aucun menu."
1654
+ },
1655
+ {
1656
+ name: "actions",
1657
+ type: "ReactNode",
1658
+ required: false,
1659
+ check: "accepted",
1660
+ description: "Des actions complémentaires posées à droite de la barre."
1661
+ }
1662
+ ],
1663
+ usages: [{
1664
+ when: "poser la recherche et les facettes d’une table",
1665
+ use: "<DataTableToolbar … labels injectés />",
1666
+ avoid: "lire un query-param dans le cœur : l’état appartient à l’appelant"
1667
+ }, {
1668
+ when: "laisser masquer/réafficher des colonnes",
1669
+ use: "la prop `columns` (liste + rappel) ; absente, pas de menu",
1670
+ avoid: "décider dans le cœur quelles colonnes sont masquables : la liste vient de l’app"
1671
+ }]
1672
+ };
1673
+ //#endregion
1674
+ //#region src/internal/select.tsx
1675
+ var Select = SelectPrimitive.Root;
1676
+ var SelectGroup = SelectPrimitive.Group;
1677
+ var SelectValue = SelectPrimitive.Value;
1678
+ var SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(SelectPrimitive.Trigger, {
1679
+ ref,
1680
+ className: cn("flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className),
1681
+ ...props,
1682
+ children: [children, /* @__PURE__ */ jsx(SelectPrimitive.Icon, {
1683
+ asChild: true,
1684
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 opacity-50" })
1685
+ })]
1686
+ }));
1687
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
1688
+ var SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SelectPrimitive.ScrollUpButton, {
1689
+ ref,
1690
+ className: cn("flex cursor-default items-center justify-center py-1", className),
1691
+ ...props,
1692
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-4 w-4" })
1693
+ }));
1694
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
1695
+ var SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SelectPrimitive.ScrollDownButton, {
1696
+ ref,
1697
+ className: cn("flex cursor-default items-center justify-center py-1", className),
1698
+ ...props,
1699
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
1700
+ }));
1701
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
1702
+ var SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsx(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(SelectPrimitive.Content, {
1703
+ ref,
1704
+ className: cn("relative z-50 max-h-[--radix-select-content-available-height] min-w-32 overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
1705
+ position,
1706
+ ...props,
1707
+ children: [
1708
+ /* @__PURE__ */ jsx(SelectScrollUpButton, {}),
1709
+ /* @__PURE__ */ jsx(SelectPrimitive.Viewport, {
1710
+ className: cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"),
1711
+ children
1712
+ }),
1713
+ /* @__PURE__ */ jsx(SelectScrollDownButton, {})
1714
+ ]
1715
+ }) }));
1716
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
1717
+ var SelectLabel = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SelectPrimitive.Label, {
1718
+ ref,
1719
+ className: cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className),
1720
+ ...props
1721
+ }));
1722
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
1723
+ var SelectItem = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(SelectPrimitive.Item, {
1724
+ ref,
1725
+ className: cn("relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className),
1726
+ ...props,
1727
+ children: [/* @__PURE__ */ jsx("span", {
1728
+ className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center",
1729
+ children: /* @__PURE__ */ jsx(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) })
1730
+ }), /* @__PURE__ */ jsx(SelectPrimitive.ItemText, { children })]
1731
+ }));
1732
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
1733
+ var SelectSeparator = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SelectPrimitive.Separator, {
1734
+ ref,
1735
+ className: cn("-mx-1 my-1 h-px bg-muted", className),
1736
+ ...props
1737
+ }));
1738
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
1739
+ //#endregion
1740
+ //#region src/components/data-table-pagination/data-table-pagination.tsx
1741
+ function DataTablePagination({ page, pageCount, pageSize, onPageSizeChange, onPrevious, onNext, canPrevious, canNext, rowsLabel, pageOf, previousLabel, nextLabel }) {
1742
+ return /* @__PURE__ */ jsxs("div", {
1743
+ className: "flex items-center justify-between gap-3",
1744
+ children: [/* @__PURE__ */ jsxs("div", {
1745
+ className: "flex items-center gap-2 text-xs text-muted-foreground",
1746
+ children: [/* @__PURE__ */ jsx("span", { children: rowsLabel }), /* @__PURE__ */ jsxs(Select, {
1747
+ value: String(pageSize),
1748
+ onValueChange: (value) => onPageSizeChange(Number(value)),
1749
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
1750
+ className: "h-8 w-18",
1751
+ children: /* @__PURE__ */ jsx(SelectValue, {})
1752
+ }), /* @__PURE__ */ jsx(SelectContent, { children: [
1753
+ 10,
1754
+ 25,
1755
+ 50,
1756
+ 100
1757
+ ].map((size) => /* @__PURE__ */ jsx(SelectItem, {
1758
+ value: String(size),
1759
+ children: size
1760
+ }, size)) })]
1761
+ })]
1762
+ }), /* @__PURE__ */ jsxs("div", {
1763
+ className: "flex items-center gap-2",
1764
+ children: [
1765
+ /* @__PURE__ */ jsx("span", {
1766
+ className: "text-xs text-muted-foreground",
1767
+ children: pageOf(page, pageCount)
1768
+ }),
1769
+ /* @__PURE__ */ jsx(Button, {
1770
+ variant: "outline",
1771
+ size: "icon",
1772
+ className: "size-8",
1773
+ onClick: onPrevious,
1774
+ disabled: !canPrevious,
1775
+ "aria-label": previousLabel,
1776
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" })
1777
+ }),
1778
+ /* @__PURE__ */ jsx(Button, {
1779
+ variant: "outline",
1780
+ size: "icon",
1781
+ className: "size-8",
1782
+ onClick: onNext,
1783
+ disabled: !canNext,
1784
+ "aria-label": nextLabel,
1785
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })
1786
+ })
1787
+ ]
1788
+ })]
1789
+ });
1790
+ }
1791
+ //#endregion
1792
+ //#region src/components/data-table-pagination/manifest.ts
1793
+ /** Le manifeste de la pagination (ADR 0005). Elle ne connaît pas TanStack, seulement des nombres. */
1794
+ var dataTablePaginationManifest = {
1795
+ name: "data-table-pagination",
1796
+ title: "DataTablePagination",
1797
+ summary: "Le choix de la taille de page et les bornes de navigation. Elle ne dépend pas de la table : page, bornes et rappels suffisent, donc elle se rend hors d'une table.",
1798
+ level: "bloc",
1799
+ example: {
1800
+ page: 1,
1801
+ pageCount: 3,
1802
+ pageSize: 25,
1803
+ onPageSizeChange: () => {},
1804
+ onPrevious: () => {},
1805
+ onNext: () => {},
1806
+ canPrevious: false,
1807
+ canNext: true,
1808
+ rowsLabel: "Lignes",
1809
+ pageOf: (page, total) => `${page} / ${total}`,
1810
+ previousLabel: "Page précédente",
1811
+ nextLabel: "Page suivante"
1812
+ },
1813
+ variants: [],
1814
+ props: [
1815
+ {
1816
+ name: "rowsLabel",
1817
+ type: "string",
1818
+ required: true,
1819
+ check: "rendered",
1820
+ description: "Le libellé devant le sélecteur de taille de page."
1821
+ },
1822
+ {
1823
+ name: "pageOf",
1824
+ type: "(page: number, total: number) => string",
1825
+ required: true,
1826
+ check: "rendered",
1827
+ description: "Le formateur « page sur total », injecté par l’appelant."
1828
+ },
1829
+ {
1830
+ name: "previousLabel",
1831
+ type: "string",
1832
+ required: true,
1833
+ check: "rendered",
1834
+ description: "Le libellé d’accessibilité du bouton précédent."
1835
+ },
1836
+ {
1837
+ name: "nextLabel",
1838
+ type: "string",
1839
+ required: true,
1840
+ check: "rendered",
1841
+ description: "Le libellé d’accessibilité du bouton suivant."
1842
+ },
1843
+ {
1844
+ name: "page",
1845
+ type: "number",
1846
+ required: true,
1847
+ check: "rendered",
1848
+ description: "La page courante, à partir de 1."
1849
+ },
1850
+ {
1851
+ name: "pageCount",
1852
+ type: "number",
1853
+ required: true,
1854
+ check: "rendered",
1855
+ description: "Le nombre total de pages."
1856
+ },
1857
+ {
1858
+ name: "pageSize",
1859
+ type: "number",
1860
+ required: true,
1861
+ check: "rendered",
1862
+ description: "Le nombre de lignes par page."
1863
+ },
1864
+ {
1865
+ name: "canPrevious",
1866
+ type: "boolean",
1867
+ required: true,
1868
+ check: "rendered",
1869
+ description: "Vrai si une page précédente existe."
1870
+ },
1871
+ {
1872
+ name: "canNext",
1873
+ type: "boolean",
1874
+ required: true,
1875
+ check: "rendered",
1876
+ description: "Vrai si une page suivante existe."
1877
+ },
1878
+ {
1879
+ name: "onPageSizeChange",
1880
+ type: "(size: number) => void",
1881
+ required: true,
1882
+ check: "accepted",
1883
+ description: "Le rappel de changement de taille de page."
1884
+ },
1885
+ {
1886
+ name: "onPrevious",
1887
+ type: "() => void",
1888
+ required: true,
1889
+ check: "accepted",
1890
+ description: "Le rappel de navigation vers la page précédente."
1891
+ },
1892
+ {
1893
+ name: "onNext",
1894
+ type: "() => void",
1895
+ required: true,
1896
+ check: "accepted",
1897
+ description: "Le rappel de navigation vers la page suivante."
1898
+ }
1899
+ ],
1900
+ usages: [{
1901
+ when: "naviguer dans une liste paginée",
1902
+ use: "<DataTablePagination page={…} pageCount={…} onNext={…} … />",
1903
+ avoid: "lui passer une table : elle n’a besoin que des nombres et des rappels"
1904
+ }]
1905
+ };
1906
+ //#endregion
1907
+ //#region src/components/table/table.tsx
1908
+ /**
1909
+ * La table dense : une grille de lignes, en parts importables séparément. Aucune part
1910
+ * ne nomme un produit ni ne lit l'i18n de l'app — les libellés viennent de l'appelant,
1911
+ * les cellules de ce qu'il rend (ADR 0010).
1912
+ *
1913
+ * Les classes de l'appelant se fusionnent après celles du cœur, jamais avant.
1914
+ */
1915
+ function Table({ className, ...props }) {
1916
+ return /* @__PURE__ */ jsx("div", {
1917
+ className: "relative w-full overflow-auto",
1918
+ children: /* @__PURE__ */ jsx("table", {
1919
+ className: cn("w-full caption-bottom text-sm", className),
1920
+ ...props
1921
+ })
1922
+ });
1923
+ }
1924
+ function TableHeader({ className, ...props }) {
1925
+ return /* @__PURE__ */ jsx("thead", {
1926
+ className: cn("[&_tr]:border-b", className),
1927
+ ...props
1928
+ });
1929
+ }
1930
+ function TableBody({ className, ...props }) {
1931
+ return /* @__PURE__ */ jsx("tbody", {
1932
+ className: cn("[&_tr:last-child]:border-0", className),
1933
+ ...props
1934
+ });
1935
+ }
1936
+ function TableRow({ className, ...props }) {
1937
+ return /* @__PURE__ */ jsx("tr", {
1938
+ className: cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className),
1939
+ ...props
1940
+ });
1941
+ }
1942
+ function TableHead({ className, ...props }) {
1943
+ return /* @__PURE__ */ jsx("th", {
1944
+ className: cn("h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", className),
1945
+ ...props
1946
+ });
1947
+ }
1948
+ function TableCell({ className, ...props }) {
1949
+ return /* @__PURE__ */ jsx("td", {
1950
+ className: cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className),
1951
+ ...props
1952
+ });
1953
+ }
1954
+ //#endregion
1955
+ //#region src/features/data-table-features.ts
1956
+ /**
1957
+ * Le seul jeu de fonctionnalités que partagent les tables d'objets routés : filtrage
1958
+ * par facettes des colonnes, recherche globale, tri et pagination.
1959
+ */
1960
+ var dataTableFeatures = tableFeatures({
1961
+ columnFilteringFeature,
1962
+ columnVisibilityFeature,
1963
+ globalFilteringFeature,
1964
+ rowSortingFeature,
1965
+ rowPaginationFeature,
1966
+ filteredRowModel: createFilteredRowModel(),
1967
+ sortedRowModel: createSortedRowModel(),
1968
+ paginatedRowModel: createPaginatedRowModel(),
1969
+ columnMeta: {},
1970
+ sortFns: {
1971
+ alphanumeric: sortFn_alphanumeric,
1972
+ basic: sortFn_basic,
1973
+ datetime: sortFn_datetime,
1974
+ text: sortFn_text
1975
+ }
1976
+ });
1977
+ //#endregion
1978
+ //#region src/internal/sheet.tsx
1979
+ var Sheet = SheetPrimitive.Root;
1980
+ var SheetPortal = SheetPrimitive.Portal;
1981
+ var SheetOverlay = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Overlay, {
1982
+ className: cn("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className),
1983
+ ...props,
1984
+ ref
1985
+ }));
1986
+ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
1987
+ var sheetVariants = cva("fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", {
1988
+ variants: { side: {
1989
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
1990
+ bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
1991
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
1992
+ right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm"
1993
+ } },
1994
+ defaultVariants: { side: "right" }
1995
+ });
1996
+ var SheetContent = React.forwardRef(({ side = "right", className, children, ...props }, ref) => /* @__PURE__ */ jsxs(SheetPortal, { children: [/* @__PURE__ */ jsx(SheetOverlay, {}), /* @__PURE__ */ jsxs(SheetPrimitive.Content, {
1997
+ ref,
1998
+ className: cn(sheetVariants({ side }), className),
1999
+ ...props,
2000
+ children: [children, /* @__PURE__ */ jsxs(SheetPrimitive.Close, {
2001
+ className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary",
2002
+ children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
2003
+ className: "sr-only",
2004
+ children: "Close"
2005
+ })]
2006
+ })]
2007
+ })] }));
2008
+ SheetContent.displayName = SheetPrimitive.Content.displayName;
2009
+ var SheetHeader = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
2010
+ className: cn("flex flex-col space-y-2 text-center sm:text-left", className),
2011
+ ...props
2012
+ });
2013
+ SheetHeader.displayName = "SheetHeader";
2014
+ var SheetFooter = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
2015
+ className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className),
2016
+ ...props
2017
+ });
2018
+ SheetFooter.displayName = "SheetFooter";
2019
+ var SheetTitle = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Title, {
2020
+ ref,
2021
+ className: cn("text-lg font-semibold text-foreground", className),
2022
+ ...props
2023
+ }));
2024
+ SheetTitle.displayName = SheetPrimitive.Title.displayName;
2025
+ var SheetDescription = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Description, {
2026
+ ref,
2027
+ className: cn("text-sm text-muted-foreground", className),
2028
+ ...props
2029
+ }));
2030
+ SheetDescription.displayName = SheetPrimitive.Description.displayName;
2031
+ //#endregion
2032
+ //#region src/features/faceted-data-table.tsx
2033
+ function resolve$1(updater, previous) {
2034
+ return typeof updater === "function" ? updater(previous) : updater;
2035
+ }
2036
+ function SelectionCheckbox({ checked, indeterminate = false, label, onChange }) {
2037
+ return /* @__PURE__ */ jsx("input", {
2038
+ type: "checkbox",
2039
+ "aria-label": label,
2040
+ checked,
2041
+ ref: (node) => {
2042
+ if (node) node.indeterminate = indeterminate;
2043
+ },
2044
+ onClick: (event) => event.stopPropagation(),
2045
+ onChange: (event) => onChange(event.target.checked)
2046
+ });
2047
+ }
2048
+ function FacetedDataTable({ data, renderColumns, facets, globalFilterFn, labels, detail, selection, columnManager, rowKey, state, onStateChange, actions }) {
2049
+ const placement = detail?.placement ?? "overlay";
2050
+ const controlled = state !== void 0;
2051
+ const keyOf = useCallback((row) => rowKey ? rowKey(row) : String(data.indexOf(row)), [rowKey, data]);
2052
+ const [internal, setInternal] = useState(() => ({
2053
+ sorting: [],
2054
+ globalFilter: "",
2055
+ columnFilters: [],
2056
+ pagination: {
2057
+ pageIndex: 0,
2058
+ pageSize: 25
2059
+ },
2060
+ openedKey: null,
2061
+ selection: [],
2062
+ columnVisibility: {},
2063
+ columnOrder: []
2064
+ }));
2065
+ const current = state ?? internal;
2066
+ const [draggingId, setDraggingId] = useState(null);
2067
+ const update = useCallback((patch) => {
2068
+ const next = {
2069
+ ...current,
2070
+ ...patch
2071
+ };
2072
+ if (controlled) onStateChange?.(next);
2073
+ else setInternal(next);
2074
+ }, [
2075
+ controlled,
2076
+ current,
2077
+ onStateChange
2078
+ ]);
2079
+ const resetPerPage = (patch) => update({
2080
+ ...patch,
2081
+ pagination: {
2082
+ ...current.pagination,
2083
+ pageIndex: 0
2084
+ }
2085
+ });
2086
+ const openDetail = useCallback((row) => {
2087
+ const key = keyOf(row);
2088
+ if (placement === "inline") update({ openedKey: current.openedKey === key ? null : key });
2089
+ else update({ openedKey: key });
2090
+ }, [
2091
+ current.openedKey,
2092
+ keyOf,
2093
+ placement,
2094
+ update
2095
+ ]);
2096
+ const baseColumns = useMemo(() => renderColumns(openDetail), [renderColumns, openDetail]);
2097
+ const toggleRow = (key) => update({ selection: current.selection.includes(key) ? current.selection.filter((selected) => selected !== key) : [...current.selection, key] });
2098
+ const selectedRows = data.filter((row) => current.selection.includes(keyOf(row)));
2099
+ const selectionColumn = useMemo(() => ({
2100
+ id: "__select__",
2101
+ enableSorting: false,
2102
+ enableGlobalFilter: false,
2103
+ header: () => null,
2104
+ cell: ({ row }) => /* @__PURE__ */ jsx(SelectionCheckbox, {
2105
+ checked: current.selection.includes(keyOf(row.original)),
2106
+ label: selection?.selectRowLabel(row.original) ?? "",
2107
+ onChange: () => toggleRow(keyOf(row.original))
2108
+ })
2109
+ }), [
2110
+ selection,
2111
+ current.selection,
2112
+ keyOf
2113
+ ]);
2114
+ const orderedBase = useMemo(() => {
2115
+ const order = current.columnOrder;
2116
+ return [...baseColumns].sort((a, b) => {
2117
+ const ai = order.indexOf(String(a.id));
2118
+ const bi = order.indexOf(String(b.id));
2119
+ return (ai === -1 ? Number.POSITIVE_INFINITY : ai) - (bi === -1 ? Number.POSITIVE_INFINITY : bi);
2120
+ });
2121
+ }, [baseColumns, current.columnOrder]);
2122
+ const orderedIds = orderedBase.map((column) => String(column.id));
2123
+ const moveColumn = (id, direction) => {
2124
+ const from = orderedIds.indexOf(id);
2125
+ const to = from + direction;
2126
+ if (from === -1 || to < 0 || to >= orderedIds.length) return;
2127
+ const next = [...orderedIds];
2128
+ const [moved] = next.splice(from, 1);
2129
+ next.splice(to, 0, moved);
2130
+ update({ columnOrder: next });
2131
+ };
2132
+ const moveColumnBefore = (draggedId, targetId) => {
2133
+ const from = orderedIds.indexOf(draggedId);
2134
+ const to = orderedIds.indexOf(targetId);
2135
+ if (from === -1 || to === -1 || from === to) return;
2136
+ const next = [...orderedIds];
2137
+ const [moved] = next.splice(from, 1);
2138
+ next.splice(to, 0, moved);
2139
+ update({ columnOrder: next });
2140
+ };
2141
+ const columns = useMemo(() => selection ? [selectionColumn, ...orderedBase] : orderedBase, [
2142
+ selection,
2143
+ selectionColumn,
2144
+ orderedBase
2145
+ ]);
2146
+ const defaultSorting = useMemo(() => columns.flatMap((column) => column.id && column.meta?.defaultSort ? [{
2147
+ id: column.id,
2148
+ desc: column.meta.defaultSort === "desc"
2149
+ }] : []), [columns]);
2150
+ const sorting = current.sorting.length ? current.sorting : defaultSorting;
2151
+ const table = useTable({
2152
+ features: dataTableFeatures,
2153
+ data,
2154
+ columns,
2155
+ state: {
2156
+ sorting,
2157
+ globalFilter: current.globalFilter,
2158
+ columnFilters: current.columnFilters,
2159
+ pagination: current.pagination,
2160
+ columnVisibility: current.columnVisibility
2161
+ },
2162
+ onSortingChange: (updater) => resetPerPage({ sorting: resolve$1(updater, current.sorting) }),
2163
+ onGlobalFilterChange: (updater) => resetPerPage({ globalFilter: resolve$1(updater, current.globalFilter) }),
2164
+ onColumnFiltersChange: (updater) => resetPerPage({ columnFilters: resolve$1(updater, current.columnFilters) }),
2165
+ onPaginationChange: (updater) => update({ pagination: resolve$1(updater, current.pagination) }),
2166
+ onColumnVisibilityChange: (updater) => update({ columnVisibility: resolve$1(updater, current.columnVisibility) }),
2167
+ globalFilterFn
2168
+ });
2169
+ const selectedFor = (id) => current.columnFilters.find((filter) => filter.id === id)?.value ?? [];
2170
+ const setFacet = (id, values) => resetPerPage({ columnFilters: [...current.columnFilters.filter((filter) => filter.id !== id), ...values.length ? [{
2171
+ id,
2172
+ value: values
2173
+ }] : []] });
2174
+ const boundFacets = facets.map(({ id, label, options }) => ({
2175
+ id,
2176
+ label,
2177
+ options,
2178
+ selected: selectedFor(id),
2179
+ onChange: (next) => setFacet(id, next)
2180
+ }));
2181
+ const rows = table.getRowModel().rows;
2182
+ const filteredCount = table.getFilteredRowModel().rows.length;
2183
+ const columnCount = columns.length;
2184
+ const canReset = current.globalFilter.length > 0 || current.columnFilters.length > 0;
2185
+ const rowClick = detail ? (row) => openDetail(row) : void 0;
2186
+ const openedRow = current.openedKey == null ? null : data.find((row) => keyOf(row) === current.openedKey) ?? null;
2187
+ const filteredKeys = table.getFilteredRowModel().rows.map((row) => keyOf(row.original));
2188
+ const allSelected = filteredKeys.length > 0 && filteredKeys.every((key) => current.selection.includes(key));
2189
+ const someSelected = filteredKeys.some((key) => current.selection.includes(key));
2190
+ const toggleAll = () => update({ selection: allSelected ? current.selection.filter((key) => !filteredKeys.includes(key)) : [.../* @__PURE__ */ new Set([...current.selection, ...filteredKeys])] });
2191
+ return /* @__PURE__ */ jsxs("div", {
2192
+ className: "flex flex-col gap-4",
2193
+ children: [
2194
+ /* @__PURE__ */ jsx(DataTableToolbar, {
2195
+ globalFilter: current.globalFilter,
2196
+ onGlobalFilterChange: (value) => resetPerPage({ globalFilter: value }),
2197
+ placeholder: labels.search,
2198
+ facets: boundFacets,
2199
+ shownLabel: labels.shown(filteredCount, data.length, labels.unit),
2200
+ canReset,
2201
+ resetLabel: labels.reset,
2202
+ clearFacetLabel: labels.clear,
2203
+ onReset: () => resetPerPage({
2204
+ globalFilter: "",
2205
+ columnFilters: []
2206
+ }),
2207
+ columns: columnManager ? {
2208
+ label: columnManager.label,
2209
+ clearLabel: columnManager.clearLabel,
2210
+ options: [...columnManager.columns].sort((a, b) => orderedIds.indexOf(a.id) - orderedIds.indexOf(b.id)).map((column) => ({
2211
+ ...column,
2212
+ visible: current.columnVisibility[column.id] !== false
2213
+ })),
2214
+ onToggle: (id, visible) => update({ columnVisibility: {
2215
+ ...current.columnVisibility,
2216
+ [id]: visible
2217
+ } }),
2218
+ onMove: moveColumn,
2219
+ moveUpLabel: columnManager.moveUpLabel,
2220
+ moveDownLabel: columnManager.moveDownLabel
2221
+ } : void 0,
2222
+ actions
2223
+ }),
2224
+ selection && selectedRows.length ? /* @__PURE__ */ jsxs("div", {
2225
+ className: "flex flex-wrap items-center gap-3 rounded-md border bg-muted/30 px-3 py-2 text-xs",
2226
+ children: [
2227
+ /* @__PURE__ */ jsx("span", {
2228
+ className: "font-medium",
2229
+ children: selection.selectedLabel(selectedRows.length)
2230
+ }),
2231
+ /* @__PURE__ */ jsx("div", {
2232
+ className: "flex flex-wrap items-center gap-2",
2233
+ children: selection.bulkActions?.(selectedRows)
2234
+ }),
2235
+ /* @__PURE__ */ jsx(Button, {
2236
+ variant: "ghost",
2237
+ size: "sm",
2238
+ className: "ml-auto h-8",
2239
+ onClick: () => update({ selection: [] }),
2240
+ children: selection.clearLabel
2241
+ })
2242
+ ]
2243
+ }) : null,
2244
+ /* @__PURE__ */ jsx("div", {
2245
+ className: "rounded-md border",
2246
+ children: /* @__PURE__ */ jsxs(Table, { children: [/* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(TableHead, {
2247
+ className: "whitespace-nowrap",
2248
+ draggable: Boolean(columnManager) && header.column.id !== "__select__",
2249
+ onDragStart: columnManager ? () => setDraggingId(String(header.column.id)) : void 0,
2250
+ onDragOver: columnManager ? (event) => event.preventDefault() : void 0,
2251
+ onDrop: columnManager ? () => {
2252
+ if (draggingId) moveColumnBefore(draggingId, String(header.column.id));
2253
+ setDraggingId(null);
2254
+ } : void 0,
2255
+ children: header.column.id === "__select__" ? /* @__PURE__ */ jsx(SelectionCheckbox, {
2256
+ checked: allSelected,
2257
+ indeterminate: someSelected && !allSelected,
2258
+ label: selection?.selectAllLabel ?? "",
2259
+ onChange: toggleAll
2260
+ }) : header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())
2261
+ }, header.id)) }, headerGroup.id)) }), /* @__PURE__ */ jsx(TableBody, { children: rows.length ? rows.map((row) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(TableRow, {
2262
+ onClick: rowClick ? () => rowClick(row.original) : void 0,
2263
+ className: rowClick ? "cursor-pointer" : void 0,
2264
+ "data-selected": current.selection.includes(keyOf(row.original)) || void 0,
2265
+ children: row.getAllCells().map((cell) => /* @__PURE__ */ jsx(TableCell, {
2266
+ className: "py-2",
2267
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
2268
+ }, cell.id))
2269
+ }), placement === "inline" && current.openedKey === keyOf(row.original) && detail ? /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
2270
+ colSpan: columnCount,
2271
+ className: "bg-muted/30",
2272
+ children: detail.render(row.original)
2273
+ }) }) : null] }, row.id)) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
2274
+ colSpan: columnCount,
2275
+ className: "h-24 text-center text-sm text-muted-foreground",
2276
+ children: labels.empty
2277
+ }) }) })] })
2278
+ }),
2279
+ /* @__PURE__ */ jsx(DataTablePagination, {
2280
+ page: table.getPageCount() === 0 ? 0 : current.pagination.pageIndex + 1,
2281
+ pageCount: table.getPageCount(),
2282
+ pageSize: current.pagination.pageSize,
2283
+ onPageSizeChange: (size) => update({ pagination: {
2284
+ pageIndex: 0,
2285
+ pageSize: size
2286
+ } }),
2287
+ onPrevious: () => update({ pagination: {
2288
+ ...current.pagination,
2289
+ pageIndex: Math.max(0, current.pagination.pageIndex - 1)
2290
+ } }),
2291
+ onNext: () => update({ pagination: {
2292
+ ...current.pagination,
2293
+ pageIndex: Math.min(table.getPageCount() - 1, current.pagination.pageIndex + 1)
2294
+ } }),
2295
+ canPrevious: table.getCanPreviousPage(),
2296
+ canNext: table.getCanNextPage(),
2297
+ rowsLabel: labels.rows,
2298
+ pageOf: labels.pageOf,
2299
+ previousLabel: labels.previous,
2300
+ nextLabel: labels.next
2301
+ }),
2302
+ placement === "overlay" && detail ? /* @__PURE__ */ jsx(Sheet, {
2303
+ open: current.openedKey != null,
2304
+ onOpenChange: (open) => update({ openedKey: open ? current.openedKey : null }),
2305
+ children: /* @__PURE__ */ jsx(SheetContent, {
2306
+ side: "right",
2307
+ className: "w-full overflow-y-auto sm:max-w-2xl",
2308
+ children: openedRow ? detail.render(openedRow) : null
2309
+ })
2310
+ }) : null
2311
+ ]
2312
+ });
2313
+ }
2314
+ //#endregion
2315
+ //#region src/internal/tooltip.tsx
2316
+ var TooltipProvider = TooltipPrimitive.Provider;
2317
+ var TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(TooltipPrimitive.Content, {
2318
+ ref,
2319
+ sideOffset,
2320
+ className: cn("z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]", className),
2321
+ ...props
2322
+ }));
2323
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
2324
+ //#endregion
2325
+ //#region src/components/meter/meter.tsx
2326
+ /**
2327
+ * Le Meter : une barre qui montre où en est une valeur sur une échelle, avec la marque
2328
+ * du seuil quand il y en a un. Il sert une cellule de table **et** une carte de santé —
2329
+ * c'est ce qui en fait un atome et pas un détail interne à la table (ADR 0010).
2330
+ *
2331
+ * Aucun libellé ne vient de l'app : l'appelant passe le texte du seuil. Le cœur ne
2332
+ * connaît ni l'i18n de l'app ni un mot produit.
2333
+ */
2334
+ function clamp01$1(value) {
2335
+ return Math.max(0, Math.min(1, value));
2336
+ }
2337
+ /** Une valeur non numérique est une donnée cassée : on la ramène à 0 plutôt que de
2338
+ * produire un `width: NaN%`, que le navigateur refuse — et jsdom avec lui. */
2339
+ function finite$1(value) {
2340
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
2341
+ }
2342
+ function Meter({ label, value, max, threshold, decimals = 2, confidence, thresholdLabel, className, ...props }) {
2343
+ const shown = finite$1(value);
2344
+ const peak = finite$1(max);
2345
+ const ratio = peak > 0 ? clamp01$1(shown / peak) : 0;
2346
+ const mark = threshold != null && peak > 0 ? clamp01$1(finite$1(threshold) / peak) : null;
2347
+ return /* @__PURE__ */ jsxs("div", {
2348
+ className: cn("flex items-center gap-3", className),
2349
+ ...props,
2350
+ children: [
2351
+ /* @__PURE__ */ jsx("span", {
2352
+ className: "w-40 shrink-0 truncate font-mono text-xs text-muted-foreground",
2353
+ title: label,
2354
+ children: label
2355
+ }),
2356
+ /* @__PURE__ */ jsxs("div", {
2357
+ className: "relative h-2 flex-1 rounded-full bg-muted",
2358
+ children: [/* @__PURE__ */ jsx("div", {
2359
+ className: "h-full rounded-full bg-primary",
2360
+ style: { width: `${ratio * 100}%` }
2361
+ }), mark != null ? /* @__PURE__ */ jsx("div", {
2362
+ className: "absolute top-1/2 h-3.5 w-0.5 -translate-y-1/2 rounded-full bg-foreground/60",
2363
+ style: { left: `calc(${mark * 100}% - 1px)` },
2364
+ title: thresholdLabel ? thresholdLabel(threshold) : void 0
2365
+ }) : null]
2366
+ }),
2367
+ /* @__PURE__ */ jsx("span", {
2368
+ className: "w-10 shrink-0 text-right font-mono text-xs tabular-nums",
2369
+ children: shown.toFixed(decimals)
2370
+ }),
2371
+ /* @__PURE__ */ jsx("span", {
2372
+ className: "w-11 shrink-0 text-right font-mono text-[11px] tabular-nums text-muted-foreground",
2373
+ children: typeof confidence === "number" && Number.isFinite(confidence) ? `c${confidence.toFixed(2)}` : ""
2374
+ })
2375
+ ]
2376
+ });
2377
+ }
2378
+ /** La même barre, en plus court : une cellule de table dense. */
2379
+ function CompactMeter({ label, value, max, threshold, className, ...props }) {
2380
+ const shown = finite$1(value);
2381
+ const peak = finite$1(max);
2382
+ const ratio = peak > 0 ? clamp01$1(shown / peak) : 0;
2383
+ const mark = threshold != null && peak > 0 ? clamp01$1(finite$1(threshold) / peak) : null;
2384
+ return /* @__PURE__ */ jsxs("div", {
2385
+ className: cn("flex items-center gap-2", className),
2386
+ ...props,
2387
+ children: [
2388
+ /* @__PURE__ */ jsx("span", {
2389
+ className: "w-9 shrink-0 font-mono text-[10px] text-muted-foreground",
2390
+ children: label
2391
+ }),
2392
+ /* @__PURE__ */ jsxs("div", {
2393
+ className: "relative h-1.5 w-24 rounded-full bg-muted",
2394
+ children: [/* @__PURE__ */ jsx("div", {
2395
+ className: "h-full rounded-full bg-primary",
2396
+ style: { width: `${ratio * 100}%` }
2397
+ }), mark != null ? /* @__PURE__ */ jsx("div", {
2398
+ className: "absolute top-1/2 h-2.5 w-px -translate-y-1/2 bg-foreground/60",
2399
+ style: { left: `${mark * 100}%` }
2400
+ }) : null]
2401
+ }),
2402
+ /* @__PURE__ */ jsx("span", {
2403
+ className: "w-8 shrink-0 text-right font-mono text-[10px] tabular-nums",
2404
+ children: shown.toFixed(2)
2405
+ })
2406
+ ]
2407
+ });
2408
+ }
2409
+ //#endregion
2410
+ //#region src/components/meter/manifest.ts
2411
+ /**
2412
+ * Le manifeste du Meter (ADR 0005). Il documente les deux tailles du même atome :
2413
+ * la barre large, qui a besoin du libellé du seuil, et la barre compacte d'une cellule.
2414
+ */
2415
+ var meterManifest = {
2416
+ name: "meter",
2417
+ title: "Meter",
2418
+ summary: "Une barre qui situe une valeur sur une échelle, avec la marque du seuil. Se rend seule, hors de toute table : une carte de santé l'utilise au même titre qu'une cellule.",
2419
+ level: "primitive",
2420
+ example: {
2421
+ label: "spec",
2422
+ value: 2,
2423
+ max: 3,
2424
+ threshold: 2
2425
+ },
2426
+ variants: [],
2427
+ props: [
2428
+ {
2429
+ name: "value",
2430
+ type: "number",
2431
+ required: true,
2432
+ check: "rendered",
2433
+ description: "La valeur mesurée, bornée à l'échelle : c'est elle qui remplit la barre."
2434
+ },
2435
+ {
2436
+ name: "max",
2437
+ type: "number",
2438
+ required: true,
2439
+ check: "rendered",
2440
+ description: "Le haut de l'échelle : 3 pour un score, 1 pour un Noul."
2441
+ },
2442
+ {
2443
+ name: "label",
2444
+ type: "string",
2445
+ required: true,
2446
+ check: "rendered",
2447
+ description: "Le nom de ce qui est mesuré, à gauche de la barre."
2448
+ },
2449
+ {
2450
+ name: "threshold",
2451
+ type: "number | null",
2452
+ required: false,
2453
+ check: "rendered",
2454
+ description: "Le seuil comparé, marqué sur la barre ; absent, la barre n'a pas de marque."
2455
+ },
2456
+ {
2457
+ name: "confidence",
2458
+ type: "number | null",
2459
+ required: false,
2460
+ check: "rendered",
2461
+ description: "La confiance attachée à la valeur, affichée en `c0.00` à droite."
2462
+ },
2463
+ {
2464
+ name: "thresholdLabel",
2465
+ type: "(threshold: number) => string",
2466
+ required: false,
2467
+ check: "rendered",
2468
+ description: "Le texte du seuil, injecté par l'appelant : le cœur n'a pas d'i18n (ADR 0010)."
2469
+ },
2470
+ {
2471
+ name: "className",
2472
+ type: "string",
2473
+ required: false,
2474
+ check: "class",
2475
+ description: "Les classes de l'appelant, fusionnées après celles du composant."
2476
+ }
2477
+ ],
2478
+ usages: [
2479
+ {
2480
+ when: "une carte de santé montre l'avancement d'une mesure",
2481
+ use: "<Meter label=\"sessions\" value={12} max={20} />",
2482
+ avoid: "la barre compacte, illisible hors d’une cellule dense"
2483
+ },
2484
+ {
2485
+ when: "une cellule de table dense porte deux mesures côte à côte",
2486
+ use: "<CompactMeter label=\"spec\" value={2} max={3} threshold={2} />",
2487
+ avoid: "la barre large : elle ne tient pas dans une colonne"
2488
+ },
2489
+ {
2490
+ when: "un seuil est marqué sur la barre",
2491
+ use: "thresholdLabel={t.meter.threshold} (le texte vient de l’app)",
2492
+ avoid: "un libellé en dur dans le cœur : il serait monolingue et lié à un produit"
2493
+ }
2494
+ ]
2495
+ };
2496
+ //#endregion
2497
+ //#region src/components/separator/separator.tsx
2498
+ /**
2499
+ * Un séparateur : un trait de bordure qui sépare deux contenus, horizontal ou vertical.
2500
+ * Décoratif par défaut (Radix), donc invisible aux lecteurs d'écran sauf demande.
2501
+ */
2502
+ function Separator({ className, orientation = "horizontal", decorative = true, ...props }) {
2503
+ return /* @__PURE__ */ jsx(SeparatorPrimitive.Root, {
2504
+ decorative,
2505
+ orientation,
2506
+ className: cn("shrink-0 bg-border", orientation === "horizontal" ? "h-px w-full" : "h-full w-px", className),
2507
+ ...props
2508
+ });
2509
+ }
2510
+ //#endregion
2511
+ //#region src/components/separator/manifest.ts
2512
+ /** Le manifeste du séparateur (ADR 0005). */
2513
+ var separatorManifest = {
2514
+ name: "separator",
2515
+ title: "Separator",
2516
+ summary: "Un trait de bordure qui sépare deux contenus, horizontal ou vertical. Décoratif par défaut : il ne porte aucun sens pour un lecteur d’écran sauf demande explicite.",
2517
+ level: "primitive",
2518
+ example: { orientation: "horizontal" },
2519
+ variants: [],
2520
+ props: [
2521
+ {
2522
+ name: "orientation",
2523
+ type: "'horizontal' | 'vertical'",
2524
+ required: false,
2525
+ check: "rendered",
2526
+ description: "Le sens du trait ; horizontal par défaut."
2527
+ },
2528
+ {
2529
+ name: "decorative",
2530
+ type: "boolean",
2531
+ required: false,
2532
+ check: "accepted",
2533
+ description: "Décoratif (vrai par défaut) : masqué aux technologies d’assistance."
2534
+ },
2535
+ {
2536
+ name: "className",
2537
+ type: "string",
2538
+ required: false,
2539
+ check: "class",
2540
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
2541
+ }
2542
+ ],
2543
+ usages: [{
2544
+ when: "séparer deux blocs dans une carte ou une barre",
2545
+ use: "<Separator />",
2546
+ avoid: "une bordure manuelle : le séparateur porte la couleur de bordure des tokens"
2547
+ }, {
2548
+ when: "un trait vertical entre deux contrôles alignés",
2549
+ use: "<Separator orientation=\"vertical\" className=\"h-5\" />",
2550
+ avoid: "oublier la hauteur : un séparateur vertical n’a pas de taille propre"
2551
+ }]
2552
+ };
2553
+ //#endregion
2554
+ //#region src/components/skeleton/skeleton.tsx
2555
+ /** Un bloc de chargement : une surface pulsante qui tient la place d'un contenu. */
2556
+ function Skeleton({ className, ...props }) {
2557
+ return /* @__PURE__ */ jsx("div", {
2558
+ className: cn("animate-pulse rounded-md bg-muted", className),
2559
+ ...props
2560
+ });
2561
+ }
2562
+ //#endregion
2563
+ //#region src/components/skeleton/manifest.ts
2564
+ /** Le manifeste du squelette de chargement (ADR 0005). */
2565
+ var skeletonManifest = {
2566
+ name: "skeleton",
2567
+ title: "Skeleton",
2568
+ summary: "Un bloc qui pulse pour tenir la place d'un contenu en cours de chargement. Sa largeur et sa hauteur viennent des classes de l’appelant.",
2569
+ level: "primitive",
2570
+ example: { className: "h-8 w-72" },
2571
+ variants: [],
2572
+ props: [{
2573
+ name: "className",
2574
+ type: "string",
2575
+ required: false,
2576
+ check: "class",
2577
+ description: "Les classes de l'appelant (dimensions) : le cœur ne connaît que le style de pulsation."
2578
+ }, {
2579
+ name: "children",
2580
+ type: "ReactNode",
2581
+ required: false,
2582
+ check: "content",
2583
+ description: "Rarement utilisé : un squelette est une surface vide."
2584
+ }],
2585
+ usages: [{
2586
+ when: "tenir la place d’un texte ou d’un bloc pendant le chargement",
2587
+ use: "<Skeleton className=\"h-8 w-72\" />",
2588
+ avoid: "un `Skeleton` aux dimensions par défaut : il n’a pas de taille propre"
2589
+ }, {
2590
+ when: "plusieurs lignes de chargement",
2591
+ use: "plusieurs `Skeleton` empilés",
2592
+ avoid: "un spinner : le squelette montre la forme du contenu qui arrive"
2593
+ }]
2594
+ };
2595
+ //#endregion
2596
+ //#region src/components/table/manifest.ts
2597
+ /**
2598
+ * Le manifeste de la table (ADR 0005). La table est un atome composé : le catalogue
2599
+ * enregistre la coquille `Table`, et le manifeste documente le jeu de parts — c'est
2600
+ * l'ensemble qui se pose, pas la coquille seule.
2601
+ */
2602
+ var tableManifest = {
2603
+ name: "table",
2604
+ title: "Table",
2605
+ summary: "Une grille dense de lignes, livrée en parts importables séparément (Table, TableHeader, TableBody, TableRow, TableHead, TableCell). Aucune part ne nomme un produit : les libellés et les cellules viennent de l'appelant.",
2606
+ level: "primitive",
2607
+ example: { children: createElement(Fragment$1, null, createElement(TableHeader, null, createElement(TableRow, null, createElement(TableHead, null, "nom"), createElement(TableHead, null, "valeur"))), createElement(TableBody, null, createElement(TableRow, null, createElement(TableCell, null, "spec"), createElement(TableCell, null, "2 / 3")))) },
2608
+ variants: [],
2609
+ props: [{
2610
+ name: "className",
2611
+ type: "string",
2612
+ required: false,
2613
+ check: "class",
2614
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
2615
+ }, {
2616
+ name: "children",
2617
+ type: "ReactNode",
2618
+ required: true,
2619
+ check: "content",
2620
+ description: "Les parts de la table : un en-tête et un corps, ou une fiche dense."
2621
+ }],
2622
+ usages: [{
2623
+ when: "une grille dense de lignes dans un pupitre",
2624
+ use: "<Table><TableHeader>…</TableHeader><TableBody>…</TableBody></Table>",
2625
+ avoid: "un `<table>` brut : il perd les tokens de bordure, de survol et de densité"
2626
+ }, {
2627
+ when: "une fiche dense hors de toute table",
2628
+ use: "importer Table / TableRow / TableCell séparément",
2629
+ avoid: "réimplémenter une grille en `<div>` : la sémantique de table est perdue"
2630
+ }]
2631
+ };
2632
+ //#endregion
2633
+ //#region src/components/textarea/textarea.tsx
2634
+ /** Un champ de saisie multiligne, stylé par les tokens. Rien d'autre que les props HTML. */
2635
+ function Textarea({ className, ...props }) {
2636
+ return /* @__PURE__ */ jsx("textarea", {
2637
+ className: cn("flex min-h-16 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className),
2638
+ ...props
2639
+ });
2640
+ }
2641
+ //#endregion
2642
+ //#region src/components/textarea/manifest.ts
2643
+ /** Le manifeste du champ multiligne (ADR 0005, ADR 0015). */
2644
+ var textareaManifest = {
2645
+ name: "textarea",
2646
+ title: "Textarea",
2647
+ summary: "Un champ de saisie multiligne. Il porte le style des tokens et transmet tout attribut HTML de `<textarea>` ; la valeur et le changement restent à l'appelant.",
2648
+ level: "primitive",
2649
+ example: { placeholder: "votre message" },
2650
+ variants: [],
2651
+ props: [{
2652
+ name: "placeholder",
2653
+ type: "string",
2654
+ required: false,
2655
+ check: "attribute",
2656
+ description: "Le texte d'invite, affiché tant que le champ est vide."
2657
+ }, {
2658
+ name: "className",
2659
+ type: "string",
2660
+ required: false,
2661
+ check: "class",
2662
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
2663
+ }],
2664
+ usages: [{
2665
+ when: "saisir un texte de plusieurs lignes",
2666
+ use: "<Textarea rows={4} placeholder={…} />",
2667
+ avoid: "un `<Input>` pour un texte long : le clavier et la hauteur ne s’y prêtent pas"
2668
+ }, {
2669
+ when: "un champ contrôlé dans un `Field`",
2670
+ use: "<Field label={…}><Textarea value={v} onChange={…} /></Field>",
2671
+ avoid: "attendre un état interne du cœur : il n’en a pas"
2672
+ }]
2673
+ };
2674
+ //#endregion
2675
+ //#region src/components/checkbox/checkbox.tsx
2676
+ /** Une case à cocher contrôlée. Le cœur fournit l'apparence et l'a11y (Radix), pas l'état. */
2677
+ function Checkbox({ className, ...props }) {
2678
+ return /* @__PURE__ */ jsx(CheckboxPrimitive.Root, {
2679
+ className: cn("size-4 shrink-0 rounded border border-input ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", className),
2680
+ ...props,
2681
+ children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, {
2682
+ className: "grid place-items-center text-current",
2683
+ children: /* @__PURE__ */ jsx(Check, { className: "size-3.5" })
2684
+ })
2685
+ });
2686
+ }
2687
+ //#endregion
2688
+ //#region src/components/checkbox/manifest.ts
2689
+ /** Le manifeste de la case à cocher (ADR 0005, ADR 0015). */
2690
+ var checkboxManifest = {
2691
+ name: "checkbox",
2692
+ title: "Checkbox",
2693
+ summary: "Une case à cocher, contrôlée par props (`checked` + `onCheckedChange`). Le cœur ne possède ni l'état ni la validation ; l'a11y vient de Radix.",
2694
+ level: "primitive",
2695
+ example: {
2696
+ checked: true,
2697
+ onCheckedChange: () => {}
2698
+ },
2699
+ variants: [],
2700
+ props: [
2701
+ {
2702
+ name: "checked",
2703
+ type: "boolean",
2704
+ required: false,
2705
+ check: "rendered",
2706
+ description: "Cochée ou non ; la valeur est chez l’appelant."
2707
+ },
2708
+ {
2709
+ name: "onCheckedChange",
2710
+ type: "(checked: boolean) => void",
2711
+ required: false,
2712
+ check: "accepted",
2713
+ description: "Le rappel de bascule."
2714
+ },
2715
+ {
2716
+ name: "disabled",
2717
+ type: "boolean",
2718
+ required: false,
2719
+ check: "accepted",
2720
+ description: "Désactive la case."
2721
+ },
2722
+ {
2723
+ name: "className",
2724
+ type: "string",
2725
+ required: false,
2726
+ check: "class",
2727
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
2728
+ }
2729
+ ],
2730
+ usages: [{
2731
+ when: "cocher une option indépendante",
2732
+ use: "<Checkbox checked={v} onCheckedChange={set} />",
2733
+ avoid: "attendre un état interne : la valeur reste dans l’app"
2734
+ }, {
2735
+ when: "nommer la case",
2736
+ use: "un `Label htmlFor` ou un `aria-label`",
2737
+ avoid: "une case sans libellé : elle devient muette pour un lecteur d’écran"
2738
+ }]
2739
+ };
2740
+ //#endregion
2741
+ //#region src/components/switch/switch.tsx
2742
+ /** Un interrupteur contrôlé : le cœur fournit l'apparence et l'a11y (Radix), pas l'état. */
2743
+ function Switch({ className, ...props }) {
2744
+ return /* @__PURE__ */ jsx(SwitchPrimitive.Root, {
2745
+ className: cn("peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", className),
2746
+ ...props,
2747
+ children: /* @__PURE__ */ jsx(SwitchPrimitive.Thumb, { className: "pointer-events-none block size-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0" })
2748
+ });
2749
+ }
2750
+ //#endregion
2751
+ //#region src/components/switch/manifest.ts
2752
+ /** Le manifeste de l'interrupteur (ADR 0005, ADR 0015). */
2753
+ var switchManifest = {
2754
+ name: "switch",
2755
+ title: "Switch",
2756
+ summary: "Un interrupteur qui bascule un état tout de suite, contrôlé par props (`checked` + `onCheckedChange`). Le cœur ne possède ni l'état ni l'action.",
2757
+ level: "primitive",
2758
+ example: {
2759
+ checked: true,
2760
+ onCheckedChange: () => {}
2761
+ },
2762
+ variants: [],
2763
+ props: [
2764
+ {
2765
+ name: "checked",
2766
+ type: "boolean",
2767
+ required: false,
2768
+ check: "rendered",
2769
+ description: "Activé ou non ; la valeur est chez l’appelant."
2770
+ },
2771
+ {
2772
+ name: "onCheckedChange",
2773
+ type: "(checked: boolean) => void",
2774
+ required: false,
2775
+ check: "accepted",
2776
+ description: "Le rappel de bascule."
2777
+ },
2778
+ {
2779
+ name: "disabled",
2780
+ type: "boolean",
2781
+ required: false,
2782
+ check: "accepted",
2783
+ description: "Désactive l’interrupteur."
2784
+ },
2785
+ {
2786
+ name: "className",
2787
+ type: "string",
2788
+ required: false,
2789
+ check: "class",
2790
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
2791
+ }
2792
+ ],
2793
+ usages: [{
2794
+ when: "activer/désactiver une option qui prend effet immédiatement",
2795
+ use: "<Switch checked={v} onCheckedChange={set} aria-label={…} />",
2796
+ avoid: "un `Switch` pour un choix qui exige un bouton « Enregistrer » : préférer une `Checkbox`"
2797
+ }]
2798
+ };
2799
+ //#endregion
2800
+ //#region src/components/radio/radio.tsx
2801
+ /** Un groupe de boutons radio ; l'app possède la valeur et le rappel. */
2802
+ function RadioGroup({ className, ...props }) {
2803
+ return /* @__PURE__ */ jsx(RadioGroupPrimitive.Root, {
2804
+ className: cn("grid gap-2", className),
2805
+ ...props
2806
+ });
2807
+ }
2808
+ function RadioGroupItem({ className, ...props }) {
2809
+ return /* @__PURE__ */ jsx(RadioGroupPrimitive.Item, {
2810
+ className: cn("size-4 shrink-0 rounded-full border border-input text-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary", className),
2811
+ ...props,
2812
+ children: /* @__PURE__ */ jsx(RadioGroupPrimitive.Indicator, {
2813
+ className: "grid place-items-center",
2814
+ children: /* @__PURE__ */ jsx(Circle, { className: "size-2 fill-current" })
2815
+ })
2816
+ });
2817
+ }
2818
+ //#endregion
2819
+ //#region src/components/radio/manifest.ts
2820
+ /** Le manifeste du groupe radio (ADR 0005, ADR 0015). */
2821
+ var radioManifest = {
2822
+ name: "radio",
2823
+ title: "RadioGroup",
2824
+ summary: "Un groupe de boutons radio où une seule option est retenue. Le cœur possède le groupe et ses items ; la valeur et le rappel restent à l’appelant.",
2825
+ level: "primitive",
2826
+ example: {
2827
+ value: "a",
2828
+ onValueChange: () => {},
2829
+ children: createElement(Fragment$1, null, createElement("label", { className: "flex items-center gap-2" }, createElement(RadioGroupItem, { value: "a" }), "Option A"), createElement("label", { className: "flex items-center gap-2" }, createElement(RadioGroupItem, { value: "b" }), "Option B"))
2830
+ },
2831
+ variants: [],
2832
+ props: [
2833
+ {
2834
+ name: "className",
2835
+ type: "string",
2836
+ required: false,
2837
+ check: "class",
2838
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
2839
+ },
2840
+ {
2841
+ name: "children",
2842
+ type: "ReactNode",
2843
+ required: true,
2844
+ check: "content",
2845
+ description: "Les `RadioGroupItem`, chacun avec sa valeur."
2846
+ },
2847
+ {
2848
+ name: "value",
2849
+ type: "string",
2850
+ required: false,
2851
+ check: "accepted",
2852
+ description: "La valeur retenue ; la valeur est chez l’appelant."
2853
+ },
2854
+ {
2855
+ name: "onValueChange",
2856
+ type: "(value: string) => void",
2857
+ required: false,
2858
+ check: "accepted",
2859
+ description: "Le rappel de choix."
2860
+ }
2861
+ ],
2862
+ usages: [{
2863
+ when: "choisir une seule option parmi quelques-unes",
2864
+ use: "<RadioGroup value={v} onValueChange={set}><RadioGroupItem value=\"a\" /></RadioGroup>",
2865
+ avoid: "des `Checkbox` pour un choix exclusif : elles autorisent plusieurs valeurs"
2866
+ }]
2867
+ };
2868
+ //#endregion
2869
+ //#region src/components/toggle/toggle.tsx
2870
+ /**
2871
+ * Le bouton à deux états : pressé ou non. Un `Switch` dit un réglage qui persiste ; un
2872
+ * `Toggle` est une bascule ponctuelle (mode d'affichage, filtre). Contrôlé par props —
2873
+ * `pressed` + `onPressedChange` — ou laissé libre via `defaultPressed` (ADR 0015).
2874
+ */
2875
+ var toggleVariantsConfig = {
2876
+ variants: {
2877
+ variant: {
2878
+ default: "bg-transparent",
2879
+ outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground"
2880
+ },
2881
+ size: {
2882
+ default: "h-10 px-3 min-w-10",
2883
+ sm: "h-9 px-2.5 min-w-9",
2884
+ lg: "h-11 px-5 min-w-11"
2885
+ }
2886
+ },
2887
+ defaultVariants: {
2888
+ variant: "default",
2889
+ size: "default"
2890
+ }
2891
+ };
2892
+ var toggleVariants = cva("inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", toggleVariantsConfig);
2893
+ function Toggle({ className, variant, size, ...props }) {
2894
+ return /* @__PURE__ */ jsx(TogglePrimitive.Root, {
2895
+ className: cn(toggleVariants({
2896
+ variant,
2897
+ size
2898
+ }), className),
2899
+ ...props
2900
+ });
2901
+ }
2902
+ //#endregion
2903
+ //#region src/components/toggle/manifest.ts
2904
+ /** Le manifeste de la bascule (ADR 0005, ADR 0015). Deux états, contrôlés par props. */
2905
+ var toggleManifest = {
2906
+ name: "toggle",
2907
+ title: "Toggle",
2908
+ summary: "Un bouton à deux états : pressé ou non. Une bascule ponctuelle (mode, filtre), pas un réglage qui persiste comme `Switch`. Contrôlé par props, a11y par Radix.",
2909
+ level: "primitive",
2910
+ example: {
2911
+ children: "compact",
2912
+ defaultPressed: true
2913
+ },
2914
+ variants: [{
2915
+ name: "variant",
2916
+ values: ["default", "outline"],
2917
+ default: "default",
2918
+ description: "Le ton du bouton — plein discret, ou bordé pour une barre d’outils."
2919
+ }, {
2920
+ name: "size",
2921
+ values: [
2922
+ "default",
2923
+ "sm",
2924
+ "lg"
2925
+ ],
2926
+ default: "default",
2927
+ description: "La taille du contrôle, du plus discret au plus large."
2928
+ }],
2929
+ props: [
2930
+ {
2931
+ name: "children",
2932
+ type: "ReactNode",
2933
+ required: true,
2934
+ check: "content",
2935
+ description: "Le libellé ou l’icône de la bascule."
2936
+ },
2937
+ {
2938
+ name: "pressed",
2939
+ type: "boolean",
2940
+ required: false,
2941
+ check: "accepted",
2942
+ description: "L’état pressé, contrôlé par l’appelant."
2943
+ },
2944
+ {
2945
+ name: "onPressedChange",
2946
+ type: "(pressed: boolean) => void",
2947
+ required: false,
2948
+ check: "accepted",
2949
+ description: "Le rappel de bascule."
2950
+ },
2951
+ {
2952
+ name: "disabled",
2953
+ type: "boolean",
2954
+ required: false,
2955
+ check: "accepted",
2956
+ description: "Désactive la bascule."
2957
+ },
2958
+ {
2959
+ name: "className",
2960
+ type: "string",
2961
+ required: false,
2962
+ check: "class",
2963
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
2964
+ }
2965
+ ],
2966
+ usages: [{
2967
+ when: "un mode ou un filtre qu’on active ponctuellement",
2968
+ use: "<Toggle pressed={v} onPressedChange={set}>compact</Toggle>",
2969
+ avoid: "un `Toggle` pour un réglage qui persiste : c’est un `Switch`"
2970
+ }, {
2971
+ when: "laisser la bascule libre de son état",
2972
+ use: "defaultPressed (non contrôlé)",
2973
+ avoid: "mélanger `pressed` et `defaultPressed` : l’un annule l’autre"
2974
+ }]
2975
+ };
2976
+ //#endregion
2977
+ //#region src/components/toggle-group/toggle-group.tsx
2978
+ /**
2979
+ * Le groupe de bascules : un choix segmenté (`type="single"`) ou multiple. Le variant et
2980
+ * la taille se posent sur le groupe et se propagent aux items ; l'état appartient à
2981
+ * l'app (ADR 0015). Les items sont des parts importables séparément.
2982
+ */
2983
+ var ToggleGroupContext = createContext({
2984
+ variant: "default",
2985
+ size: "default"
2986
+ });
2987
+ function ToggleGroup({ className, variant, size, children, ...props }) {
2988
+ return /* @__PURE__ */ jsx(ToggleGroupPrimitive.Root, {
2989
+ className: cn("flex items-center gap-1", className),
2990
+ ...props,
2991
+ children: /* @__PURE__ */ jsx(ToggleGroupContext.Provider, {
2992
+ value: {
2993
+ variant,
2994
+ size
2995
+ },
2996
+ children
2997
+ })
2998
+ });
2999
+ }
3000
+ function ToggleGroupItem({ className, variant, size, ...props }) {
3001
+ const context = useContext(ToggleGroupContext);
3002
+ return /* @__PURE__ */ jsx(ToggleGroupPrimitive.Item, {
3003
+ className: cn(toggleVariants({
3004
+ variant: context.variant ?? variant,
3005
+ size: context.size ?? size
3006
+ }), className),
3007
+ ...props
3008
+ });
3009
+ }
3010
+ //#endregion
3011
+ //#region src/components/toggle-group/manifest.ts
3012
+ /**
3013
+ * Le manifeste du groupe de bascules (ADR 0005, ADR 0015). Le catalogue enregistre le
3014
+ * groupe ; les items sont des parts importables.
3015
+ */
3016
+ var toggleGroupManifest = {
3017
+ name: "toggle-group",
3018
+ title: "ToggleGroup",
3019
+ summary: "Un choix segmenté (`type=\"single\"`) ou multiple de bascules. Variant et taille se posent sur le groupe et se propagent aux items ; l'état appartient à l'appelant.",
3020
+ level: "primitive",
3021
+ example: {
3022
+ type: "single",
3023
+ variant: "outline",
3024
+ size: "sm",
3025
+ children: createElement(Fragment$1, null, createElement(ToggleGroupItem, { value: "un" }, "un"), createElement(ToggleGroupItem, { value: "deux" }, "deux"))
3026
+ },
3027
+ variants: [{
3028
+ name: "variant",
3029
+ values: ["default", "outline"],
3030
+ default: "default",
3031
+ description: "Le ton des bascules du groupe."
3032
+ }, {
3033
+ name: "size",
3034
+ values: [
3035
+ "default",
3036
+ "sm",
3037
+ "lg"
3038
+ ],
3039
+ default: "default",
3040
+ description: "La taille des bascules du groupe."
3041
+ }],
3042
+ props: [
3043
+ {
3044
+ name: "children",
3045
+ type: "ReactNode",
3046
+ required: true,
3047
+ check: "content",
3048
+ description: "Les items du groupe (`ToggleGroupItem`)."
3049
+ },
3050
+ {
3051
+ name: "value",
3052
+ type: "string",
3053
+ required: false,
3054
+ check: "accepted",
3055
+ description: "La valeur sélectionnée, contrôlée par l’appelant."
3056
+ },
3057
+ {
3058
+ name: "onValueChange",
3059
+ type: "(value: string) => void",
3060
+ required: false,
3061
+ check: "accepted",
3062
+ description: "Le rappel de sélection."
3063
+ },
3064
+ {
3065
+ name: "disabled",
3066
+ type: "boolean",
3067
+ required: false,
3068
+ check: "accepted",
3069
+ description: "Désactive tout le groupe."
3070
+ },
3071
+ {
3072
+ name: "className",
3073
+ type: "string",
3074
+ required: false,
3075
+ check: "class",
3076
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
3077
+ }
3078
+ ],
3079
+ usages: [{
3080
+ when: "choisir une seule vue parmi quelques-unes (un segment)",
3081
+ use: "<ToggleGroup type=\"single\" value={v} onValueChange={set}>…</ToggleGroup>",
3082
+ avoid: "un `Select` pour deux ou trois choix courts : un segment se lit d’un coup"
3083
+ }, {
3084
+ when: "choisir plusieurs options indépendantes",
3085
+ use: "type=\"multiple\" (Radix), l’état reste dans l’app",
3086
+ avoid: "attendre un état interne : le cœur n’en a pas"
3087
+ }]
3088
+ };
3089
+ //#endregion
3090
+ //#region src/components/toast/toast.tsx
3091
+ /** L'icône par défaut d'un ton ; `neutral` et `progress` restent sans icône. */
3092
+ var defaultIcon = {
3093
+ info: /* @__PURE__ */ jsx(Info, { className: "size-4" }),
3094
+ attention: /* @__PURE__ */ jsx(CircleAlert, { className: "size-4" }),
3095
+ warning: /* @__PURE__ */ jsx(TriangleAlert, { className: "size-4" }),
3096
+ danger: /* @__PURE__ */ jsx(CircleX, { className: "size-4" }),
3097
+ success: /* @__PURE__ */ jsx(CircleCheck, { className: "size-4" })
3098
+ };
3099
+ /**
3100
+ * La notification : une carte flottante qui dit ce qu'une action a produit. Présentation
3101
+ * seule — la **file** et les minuteurs vivent dans `ToastProvider` (ADR 0020). L'app
3102
+ * fournit le message, l'action et le libellé de fermeture.
3103
+ */
3104
+ function Toast({ tone = "info", message, description, action, icon, onClose, closeLabel, className }) {
3105
+ return /* @__PURE__ */ jsxs("div", {
3106
+ role: "status",
3107
+ className: cn("pointer-events-auto flex w-full items-start gap-3 rounded-md border p-3 text-sm shadow-lg", toneClasses[tone], className),
3108
+ children: [
3109
+ /* @__PURE__ */ jsx("span", {
3110
+ "aria-hidden": true,
3111
+ className: "mt-0.5 shrink-0",
3112
+ children: icon ?? defaultIcon[tone] ?? null
3113
+ }),
3114
+ /* @__PURE__ */ jsxs("div", {
3115
+ className: "min-w-0 flex-1",
3116
+ children: [/* @__PURE__ */ jsx("p", {
3117
+ className: "font-medium",
3118
+ children: message
3119
+ }), description ? /* @__PURE__ */ jsx("p", {
3120
+ className: "mt-0.5 text-xs opacity-90",
3121
+ children: description
3122
+ }) : null]
3123
+ }),
3124
+ action ? /* @__PURE__ */ jsx("div", {
3125
+ className: "shrink-0",
3126
+ children: action
3127
+ }) : null,
3128
+ onClose ? /* @__PURE__ */ jsx("button", {
3129
+ type: "button",
3130
+ "aria-label": closeLabel,
3131
+ onClick: onClose,
3132
+ className: "-m-1 shrink-0 rounded p-1 opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
3133
+ children: /* @__PURE__ */ jsx(CircleX, {
3134
+ className: "size-4",
3135
+ "aria-hidden": true
3136
+ })
3137
+ }) : null
3138
+ ]
3139
+ });
3140
+ }
3141
+ //#endregion
3142
+ //#region src/components/toast/manifest.ts
3143
+ /** Le manifeste de la notification (ADR 0005, ADR 0020). Le ton réutilise `toneClasses`. */
3144
+ var toastManifest = {
3145
+ name: "toast",
3146
+ title: "Toast",
3147
+ summary: "Une notification flottante : un message, un détail, une action, une fermeture. Le ton vient du cœur ; la file et les minuteurs vivent dans `ToastProvider`.",
3148
+ level: "primitive",
3149
+ example: {
3150
+ tone: "success",
3151
+ message: "réindexé",
3152
+ description: "12 éléments"
3153
+ },
3154
+ variants: [{
3155
+ name: "tone",
3156
+ values: [...TONES],
3157
+ default: "info",
3158
+ description: "L'intention de la notification — ce que l'appelant choisit selon l'usage."
3159
+ }],
3160
+ props: [
3161
+ {
3162
+ name: "message",
3163
+ type: "ReactNode",
3164
+ required: true,
3165
+ check: "rendered",
3166
+ description: "Le message principal : une ligne, jamais une phrase."
3167
+ },
3168
+ {
3169
+ name: "description",
3170
+ type: "ReactNode",
3171
+ required: false,
3172
+ check: "rendered",
3173
+ description: "Un détail sous le message, plus discret."
3174
+ },
3175
+ {
3176
+ name: "action",
3177
+ type: "ReactNode",
3178
+ required: false,
3179
+ check: "accepted",
3180
+ description: "Une action : un bouton, un lien."
3181
+ },
3182
+ {
3183
+ name: "icon",
3184
+ type: "ReactNode",
3185
+ required: false,
3186
+ check: "accepted",
3187
+ description: "Remplace l'icône par défaut du ton."
3188
+ },
3189
+ {
3190
+ name: "onClose",
3191
+ type: "() => void",
3192
+ required: false,
3193
+ check: "accepted",
3194
+ description: "Affiche un bouton de fermeture et le rappelle au clic."
3195
+ },
3196
+ {
3197
+ name: "closeLabel",
3198
+ type: "string",
3199
+ required: false,
3200
+ check: "accepted",
3201
+ description: "Le libellé accessible du bouton de fermeture (requis avec `onClose`)."
3202
+ },
3203
+ {
3204
+ name: "className",
3205
+ type: "string",
3206
+ required: false,
3207
+ check: "class",
3208
+ description: "Les classes de l'appelant, fusionnées après celles du cœur."
3209
+ }
3210
+ ],
3211
+ usages: [{
3212
+ when: "dire ce qu’une action a produit, sans bloquer la vue",
3213
+ use: "via `useToast().show({ message })` (le `ToastProvider` gère la file)",
3214
+ avoid: "un `Toast` monté à la main : la file et l’auto-dismiss vivent dans le provider"
3215
+ }, {
3216
+ when: "signaler un échec qu’on doit voir",
3217
+ use: "tone \"danger\" et un `description` qui nomme la cause",
3218
+ avoid: "un toast pour une erreur de chargement de page : c’est un `EmptyState`"
3219
+ }]
3220
+ };
3221
+ //#endregion
3222
+ //#region src/features/toast.tsx
3223
+ var ToastContext = createContext(null);
3224
+ function useToast() {
3225
+ const api = useContext(ToastContext);
3226
+ if (!api) throw new Error("useToast doit être utilisé sous <ToastProvider>.");
3227
+ return api;
3228
+ }
3229
+ var DEFAULT_DURATION = 4e3;
3230
+ function ToastProvider({ children, closeLabel, duration = DEFAULT_DURATION }) {
3231
+ const [items, setItems] = useState([]);
3232
+ const idRef = useRef(0);
3233
+ const timers = useRef(/* @__PURE__ */ new Map());
3234
+ const clearTimer = useCallback((id) => {
3235
+ const timer = timers.current.get(id);
3236
+ if (timer) {
3237
+ clearTimeout(timer);
3238
+ timers.current.delete(id);
3239
+ }
3240
+ }, []);
3241
+ const dismiss = useCallback((id) => {
3242
+ clearTimer(id);
3243
+ setItems((previous) => previous.filter((item) => item.id !== id));
3244
+ }, [clearTimer]);
3245
+ const show = useCallback((options) => {
3246
+ const id = `toast-${++idRef.current}`;
3247
+ setItems((previous) => [...previous, {
3248
+ ...options,
3249
+ id
3250
+ }]);
3251
+ return id;
3252
+ }, []);
3253
+ const update = useCallback((id, options) => {
3254
+ setItems((previous) => previous.map((item) => item.id === id ? {
3255
+ ...item,
3256
+ ...options
3257
+ } : item));
3258
+ }, []);
3259
+ useEffect(() => {
3260
+ for (const item of items) {
3261
+ if (item.persistent) {
3262
+ clearTimer(item.id);
3263
+ continue;
3264
+ }
3265
+ if (!timers.current.has(item.id)) timers.current.set(item.id, setTimeout(() => dismiss(item.id), item.duration ?? duration));
3266
+ }
3267
+ for (const [id, timer] of timers.current) if (!items.some((item) => item.id === id)) {
3268
+ clearTimeout(timer);
3269
+ timers.current.delete(id);
3270
+ }
3271
+ }, [
3272
+ items,
3273
+ duration,
3274
+ dismiss,
3275
+ clearTimer
3276
+ ]);
3277
+ useEffect(() => {
3278
+ const armed = timers.current;
3279
+ return () => {
3280
+ for (const timer of armed.values()) clearTimeout(timer);
3281
+ armed.clear();
3282
+ };
3283
+ }, []);
3284
+ const api = useMemo(() => ({
3285
+ show,
3286
+ update,
3287
+ dismiss
3288
+ }), [
3289
+ show,
3290
+ update,
3291
+ dismiss
3292
+ ]);
3293
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, {
3294
+ value: api,
3295
+ children: [children, /* @__PURE__ */ jsx("div", {
3296
+ "aria-live": "polite",
3297
+ className: "pointer-events-none fixed inset-x-0 bottom-0 z-50 flex flex-col items-center gap-2 p-4 sm:items-end",
3298
+ children: items.map((item) => {
3299
+ const common = {
3300
+ tone: item.tone,
3301
+ message: item.message,
3302
+ description: item.description,
3303
+ action: item.action
3304
+ };
3305
+ return /* @__PURE__ */ jsx("div", {
3306
+ className: "pointer-events-auto w-full max-w-sm",
3307
+ onMouseEnter: () => clearTimer(item.id),
3308
+ onMouseLeave: () => {
3309
+ if (!item.persistent && !timers.current.has(item.id)) timers.current.set(item.id, setTimeout(() => dismiss(item.id), item.duration ?? duration));
3310
+ },
3311
+ children: closeLabel ? /* @__PURE__ */ jsx(Toast, {
3312
+ ...common,
3313
+ onClose: () => dismiss(item.id),
3314
+ closeLabel
3315
+ }) : /* @__PURE__ */ jsx(Toast, { ...common })
3316
+ }, item.id);
3317
+ })
3318
+ })]
3319
+ });
3320
+ }
3321
+ //#endregion
3322
+ //#region src/components/tree/tree.tsx
3323
+ /** Aplati les nœuds **visibles** (les enfants d'un nœud fermé n'y sont pas) pour piloter
3324
+ * le focus et les flèches comme une seule liste. */
3325
+ function flatten$1(nodes, expanded, level = 1, parentId = null) {
3326
+ const out = [];
3327
+ nodes.forEach((node, index) => {
3328
+ const hasChildren = Boolean(node.children && node.children.length);
3329
+ out.push({
3330
+ node,
3331
+ level,
3332
+ parentId,
3333
+ hasChildren,
3334
+ posinset: index + 1,
3335
+ setsize: nodes.length
3336
+ });
3337
+ if (hasChildren && expanded.has(node.id)) out.push(...flatten$1(node.children ?? [], expanded, level + 1, node.id));
3338
+ });
3339
+ return out;
3340
+ }
3341
+ function TreeBase({ nodes, expandedIds = [], onToggle, selectedIds = [], onSelect, multi = false, ariaLabel, className }) {
3342
+ const expanded = useMemo(() => new Set(expandedIds), [expandedIds]);
3343
+ const selected = useMemo(() => new Set(selectedIds), [selectedIds]);
3344
+ const visible = useMemo(() => flatten$1(nodes, expanded), [nodes, expanded]);
3345
+ const [focusedId, setFocusedId] = useState(visible[0]?.node.id ?? null);
3346
+ const items = useRef(/* @__PURE__ */ new Map());
3347
+ const interacted = useRef(false);
3348
+ const activeId = visible.some((entry) => entry.node.id === focusedId) ? focusedId : visible[0]?.node.id ?? null;
3349
+ useEffect(() => {
3350
+ if (interacted.current && activeId) items.current.get(activeId)?.focus();
3351
+ }, [activeId]);
3352
+ const focusAt = useCallback((index) => {
3353
+ const target = visible[index];
3354
+ if (!target) return;
3355
+ interacted.current = true;
3356
+ setFocusedId(target.node.id);
3357
+ items.current.get(target.node.id)?.focus();
3358
+ }, [visible]);
3359
+ function onKeyDown(event) {
3360
+ const index = visible.findIndex((entry) => entry.node.id === activeId);
3361
+ if (index < 0) return;
3362
+ const current = visible[index];
3363
+ const parentIndex = () => visible.findIndex((entry) => entry.node.id === current.parentId);
3364
+ switch (event.key) {
3365
+ case "ArrowDown":
3366
+ event.preventDefault();
3367
+ focusAt(Math.min(index + 1, visible.length - 1));
3368
+ break;
3369
+ case "ArrowUp":
3370
+ event.preventDefault();
3371
+ focusAt(Math.max(index - 1, 0));
3372
+ break;
3373
+ case "Home":
3374
+ event.preventDefault();
3375
+ focusAt(0);
3376
+ break;
3377
+ case "End":
3378
+ event.preventDefault();
3379
+ focusAt(visible.length - 1);
3380
+ break;
3381
+ case "ArrowRight":
3382
+ event.preventDefault();
3383
+ if (current.hasChildren && !expanded.has(current.node.id)) onToggle?.(current.node.id);
3384
+ else if (current.hasChildren) focusAt(index + 1);
3385
+ break;
3386
+ case "ArrowLeft":
3387
+ event.preventDefault();
3388
+ if (current.hasChildren && expanded.has(current.node.id)) onToggle?.(current.node.id);
3389
+ else if (parentIndex() >= 0) focusAt(parentIndex());
3390
+ break;
3391
+ case "Enter":
3392
+ case " ":
3393
+ event.preventDefault();
3394
+ onSelect?.(current.node.id);
3395
+ }
3396
+ }
3397
+ return /* @__PURE__ */ jsx("div", {
3398
+ role: "tree",
3399
+ "aria-label": ariaLabel,
3400
+ "aria-multiselectable": multi || void 0,
3401
+ onKeyDown,
3402
+ className: cn("flex flex-col", className),
3403
+ children: visible.map((entry) => {
3404
+ const { node, level, hasChildren, posinset, setsize } = entry;
3405
+ const isExpanded = hasChildren && expanded.has(node.id);
3406
+ return /* @__PURE__ */ jsxs("div", {
3407
+ ref: (element) => {
3408
+ if (element) items.current.set(node.id, element);
3409
+ else items.current.delete(node.id);
3410
+ },
3411
+ role: "treeitem",
3412
+ "aria-level": level,
3413
+ "aria-posinset": posinset,
3414
+ "aria-setsize": setsize,
3415
+ "aria-selected": selected.has(node.id),
3416
+ "aria-expanded": hasChildren ? isExpanded : void 0,
3417
+ tabIndex: node.id === activeId ? 0 : -1,
3418
+ onClick: () => {
3419
+ interacted.current = true;
3420
+ setFocusedId(node.id);
3421
+ items.current.get(node.id)?.focus();
3422
+ onSelect?.(node.id);
3423
+ },
3424
+ style: { paddingLeft: `${(level - 1) * 16}px` },
3425
+ className: cn("flex cursor-default items-center gap-1 rounded px-1.5 py-1 text-sm outline-none", "focus-visible:ring-2 focus-visible:ring-ring", selected.has(node.id) && "bg-accent text-accent-foreground"),
3426
+ children: [hasChildren ? /* @__PURE__ */ jsx("span", {
3427
+ "aria-hidden": true,
3428
+ onClick: (event) => {
3429
+ event.stopPropagation();
3430
+ onToggle?.(node.id);
3431
+ },
3432
+ className: "flex size-4 shrink-0 items-center justify-center text-muted-foreground",
3433
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: cn("size-4 transition-transform", isExpanded && "rotate-90") })
3434
+ }) : /* @__PURE__ */ jsx("span", {
3435
+ "aria-hidden": true,
3436
+ className: "size-4 shrink-0"
3437
+ }), /* @__PURE__ */ jsx("span", {
3438
+ className: "truncate",
3439
+ children: node.label
3440
+ })]
3441
+ }, node.id);
3442
+ })
3443
+ });
3444
+ }
3445
+ function Tree({ selectedId, ...props }) {
3446
+ return /* @__PURE__ */ jsx(TreeBase, {
3447
+ ...props,
3448
+ selectedIds: selectedId ? [selectedId] : []
3449
+ });
3450
+ }
3451
+ function SelectionTree(props) {
3452
+ return /* @__PURE__ */ jsx(TreeBase, {
3453
+ ...props,
3454
+ multi: true
3455
+ });
3456
+ }
3457
+ //#endregion
3458
+ //#region src/components/tree/manifest.ts
3459
+ /**
3460
+ * Le manifeste de l'Arbre (ADR 0005, ADR 0015). Il documente les deux exports du même
3461
+ * atome : `Tree` (navigation, sélection simple) et `SelectionTree` (sélection multiple),
3462
+ * comme le Meter documente ses deux tailles.
3463
+ */
3464
+ var treeManifest = {
3465
+ name: "tree",
3466
+ title: "Arbre",
3467
+ summary: "Une hiérarchie de nœuds, l'ouverture et la sélection **portées par l'app** (props contrôlées + rappels). Deux exports : `Tree` navigue, `SelectionTree` coche plusieurs nœuds. Le focus clavier est interne, la donnée ne l’est jamais.",
3468
+ level: "bloc",
3469
+ example: {
3470
+ nodes: [{
3471
+ id: "src",
3472
+ label: "src",
3473
+ children: [{
3474
+ id: "src/components",
3475
+ label: "components"
3476
+ }, {
3477
+ id: "src/lib",
3478
+ label: "lib"
3479
+ }]
3480
+ }, {
3481
+ id: "docs",
3482
+ label: "docs"
3483
+ }],
3484
+ expandedIds: ["src"],
3485
+ selectedId: "src/lib",
3486
+ ariaLabel: "Arborescence de démonstration"
3487
+ },
3488
+ variants: [],
3489
+ props: [
3490
+ {
3491
+ name: "nodes",
3492
+ type: "TreeNode[]",
3493
+ required: true,
3494
+ check: "accepted",
3495
+ description: "La hiérarchie : chaque nœud a un `id`, un `label` et d'éventuels `children`."
3496
+ },
3497
+ {
3498
+ name: "expandedIds",
3499
+ type: "string[]",
3500
+ required: false,
3501
+ check: "accepted",
3502
+ description: "Les ids des nœuds ouverts (contrôlé) : l'app porte l'expansion, pas l'arbre."
3503
+ },
3504
+ {
3505
+ name: "selectedId",
3506
+ type: "string | null",
3507
+ required: false,
3508
+ check: "rendered",
3509
+ description: "Le nœud sélectionné de `Tree` (contrôlé, sélection simple)."
3510
+ },
3511
+ {
3512
+ name: "onToggle",
3513
+ type: "(id: string) => void",
3514
+ required: false,
3515
+ check: "accepted",
3516
+ description: "Appelé pour ouvrir/fermer un nœud (flèche droite/gauche, chevron)."
3517
+ },
3518
+ {
3519
+ name: "onSelect",
3520
+ type: "(id: string) => void",
3521
+ required: false,
3522
+ check: "accepted",
3523
+ description: "Appelé au clic ou à Entrée/Espace — l'app décide de la suite."
3524
+ },
3525
+ {
3526
+ name: "ariaLabel",
3527
+ type: "string",
3528
+ required: false,
3529
+ check: "rendered",
3530
+ description: "Le nom accessible de l'arbre : le cœur n'a pas d'i18n (ADR 0015)."
3531
+ },
3532
+ {
3533
+ name: "className",
3534
+ type: "string",
3535
+ required: false,
3536
+ check: "class",
3537
+ description: "Les classes de l'appelant, fusionnées après celles du composant."
3538
+ }
3539
+ ],
3540
+ usages: [
3541
+ {
3542
+ when: "une navigation hiérarchique (fichiers, catégories) où un nœud est actif",
3543
+ use: "<Tree nodes={nodes} expandedIds={open} onToggle={…} selectedId={active} onSelect={…} />",
3544
+ avoid: "laisser l’arbre posséder l’état : la donnée appartient à l’app"
3545
+ },
3546
+ {
3547
+ when: "cocher plusieurs nœuds (filtres, périmètre)",
3548
+ use: "<SelectionTree nodes={nodes} expandedIds={open} onToggle={…} selectedIds={checked} onSelect={…} />",
3549
+ avoid: "<Tree>, qui ne porte qu’un nœud actif à la fois"
3550
+ },
3551
+ {
3552
+ when: "le nom accessible de l’arbre",
3553
+ use: "ariaLabel={t.tree.label} (le texte vient de l’app)",
3554
+ avoid: "un aria-label en dur dans le cœur : il serait monolingue"
3555
+ }
3556
+ ]
3557
+ };
3558
+ //#endregion
3559
+ //#region src/components/counter/counter.tsx
3560
+ function Counter({ value, suffix, label, className, ...props }) {
3561
+ return /* @__PURE__ */ jsxs("span", {
3562
+ className: cn("inline-flex items-baseline gap-1", className),
3563
+ ...props,
3564
+ children: [
3565
+ /* @__PURE__ */ jsx("span", {
3566
+ className: "font-mono text-2xl font-semibold tabular-nums",
3567
+ children: value
3568
+ }),
3569
+ suffix ? /* @__PURE__ */ jsx("span", {
3570
+ className: "text-sm text-muted-foreground",
3571
+ children: suffix
3572
+ }) : null,
3573
+ label ? /* @__PURE__ */ jsx("span", {
3574
+ className: "text-xs text-muted-foreground",
3575
+ children: label
3576
+ }) : null
3577
+ ]
3578
+ });
3579
+ }
3580
+ //#endregion
3581
+ //#region src/components/counter/manifest.ts
3582
+ /** Le manifeste du Counter (ADR 0005). */
3583
+ var counterManifest = {
3584
+ name: "counter",
3585
+ title: "Compteur",
3586
+ summary: "Un nombre mis en avant, avec un suffixe et un libellé fournis par l’app. Aucun formatage ni unité dans le cœur : le texte et le nombre viennent de l’appelant.",
3587
+ level: "primitive",
3588
+ example: {
3589
+ value: 128,
3590
+ suffix: "issues",
3591
+ label: "ouvertes"
3592
+ },
3593
+ variants: [],
3594
+ props: [
3595
+ {
3596
+ name: "value",
3597
+ type: "number",
3598
+ required: true,
3599
+ check: "rendered",
3600
+ description: "Le nombre mis en avant."
3601
+ },
3602
+ {
3603
+ name: "suffix",
3604
+ type: "string",
3605
+ required: false,
3606
+ check: "rendered",
3607
+ description: "L'unité ou le signe qui suit le nombre, fourni par l'appelant."
3608
+ },
3609
+ {
3610
+ name: "label",
3611
+ type: "string",
3612
+ required: false,
3613
+ check: "rendered",
3614
+ description: "Le nom de ce qui est compté."
3615
+ },
3616
+ {
3617
+ name: "className",
3618
+ type: "string",
3619
+ required: false,
3620
+ check: "class",
3621
+ description: "Les classes de l'appelant, fusionnées après celles du composant."
3622
+ }
3623
+ ],
3624
+ usages: [{
3625
+ when: "un total à lire au premier coup d’œil (issues ouvertes, PR en attente)",
3626
+ use: "<Counter value={128} suffix=\"issues\" label=\"ouvertes\" />",
3627
+ avoid: "un nombre noyé dans une phrase : le compteur est fait pour être vu seul"
3628
+ }, {
3629
+ when: "un chiffre sans unité",
3630
+ use: "<Counter value={7} />",
3631
+ avoid: "inventer une unité dans le cœur : elle appartient à l’app"
3632
+ }]
3633
+ };
3634
+ //#endregion
3635
+ //#region src/components/progress-bar/progress-bar.tsx
3636
+ /**
3637
+ * La barre de progression (ADR 0015) : une valeur sur un total, sémantique ARIA
3638
+ * `progressbar` (`aria-valuenow/min/max`). Là où le `Meter` situe une valeur sur une
3639
+ * échelle avec un seuil, elle montre l'avancement d'une tâche. Aucun libellé n'est
3640
+ * inventé : l'appelant fournit `label`, ou rien.
3641
+ */
3642
+ function clamp01(value) {
3643
+ return Math.max(0, Math.min(1, value));
3644
+ }
3645
+ /** Une valeur non numérique est une donnée cassée : on la ramène à 0 plutôt qu'un CSS invalide. */
3646
+ function finite(value) {
3647
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
3648
+ }
3649
+ function ProgressBar({ value, max = 100, label, showValue = false, className, ...props }) {
3650
+ const shown = finite(value);
3651
+ const peak = finite(max);
3652
+ const ratio = peak > 0 ? clamp01(shown / peak) : 0;
3653
+ return /* @__PURE__ */ jsxs("div", {
3654
+ className: cn("flex flex-col gap-1", className),
3655
+ ...props,
3656
+ children: [label || showValue ? /* @__PURE__ */ jsxs("div", {
3657
+ className: "flex items-baseline justify-between gap-2 text-xs",
3658
+ children: [label ? /* @__PURE__ */ jsx("span", {
3659
+ className: "text-muted-foreground",
3660
+ children: label
3661
+ }) : /* @__PURE__ */ jsx("span", {}), showValue ? /* @__PURE__ */ jsxs("span", {
3662
+ className: "font-mono tabular-nums",
3663
+ children: [Math.round(ratio * 100), "%"]
3664
+ }) : null]
3665
+ }) : null, /* @__PURE__ */ jsx("div", {
3666
+ role: "progressbar",
3667
+ "aria-label": label,
3668
+ "aria-valuenow": shown,
3669
+ "aria-valuemin": 0,
3670
+ "aria-valuemax": peak,
3671
+ className: "h-2 w-full overflow-hidden rounded-full bg-muted",
3672
+ children: /* @__PURE__ */ jsx("div", {
3673
+ className: "h-full rounded-full bg-primary",
3674
+ style: { width: `${ratio * 100}%` }
3675
+ })
3676
+ })]
3677
+ });
3678
+ }
3679
+ //#endregion
3680
+ //#region src/components/progress-bar/manifest.ts
3681
+ /** Le manifeste de la ProgressBar (ADR 0005). */
3682
+ var progressBarManifest = {
3683
+ name: "progress-bar",
3684
+ title: "Barre de progression",
3685
+ summary: "L'avancement d'une tâche sur un total, sémantique ARIA `progressbar`. À préférer au `Meter` quand il n’y a ni échelle ni seuil, juste une part accomplie.",
3686
+ level: "primitive",
3687
+ example: {
3688
+ value: 60,
3689
+ max: 100,
3690
+ label: "synchronisation",
3691
+ showValue: true
3692
+ },
3693
+ variants: [],
3694
+ props: [
3695
+ {
3696
+ name: "value",
3697
+ type: "number",
3698
+ required: true,
3699
+ check: "rendered",
3700
+ description: "L'avancement, borné à `0..max` : c'est lui qui remplit la barre."
3701
+ },
3702
+ {
3703
+ name: "max",
3704
+ type: "number",
3705
+ required: false,
3706
+ check: "rendered",
3707
+ description: "Le total (100 par défaut)."
3708
+ },
3709
+ {
3710
+ name: "label",
3711
+ type: "string",
3712
+ required: false,
3713
+ check: "rendered",
3714
+ description: "Le nom de ce qui avance ; sert de nom accessible au rôle progressbar."
3715
+ },
3716
+ {
3717
+ name: "showValue",
3718
+ type: "boolean",
3719
+ required: false,
3720
+ check: "rendered",
3721
+ description: "Affiche le pourcentage calculé à droite."
3722
+ },
3723
+ {
3724
+ name: "className",
3725
+ type: "string",
3726
+ required: false,
3727
+ check: "class",
3728
+ description: "Les classes de l'appelant, fusionnées après celles du composant."
3729
+ }
3730
+ ],
3731
+ usages: [{
3732
+ when: "une tâche qui avance vers un total (import, synchronisation)",
3733
+ use: "<ProgressBar value={60} max={100} label=\"import\" showValue />",
3734
+ avoid: "le Meter, réservé à une valeur située avec un seuil"
3735
+ }, {
3736
+ when: "une part accomplie sans échelle ni seuil",
3737
+ use: "<ProgressBar value={3} max={10} />",
3738
+ avoid: "un libellé en dur dans le cœur : le texte vient de l’app"
3739
+ }]
3740
+ };
3741
+ //#endregion
3742
+ //#region src/components/rating/rating.tsx
3743
+ function clamp(value, min, max) {
3744
+ return Math.min(Math.max(value, min), max);
3745
+ }
3746
+ function Rating({ value, max = 5, onValueChange, readOnly = false, ariaLabel, className }) {
3747
+ const count = Math.max(0, Math.floor(max));
3748
+ const stars = Array.from({ length: count }, (_, index) => index + 1);
3749
+ const interactive = !readOnly && Boolean(onValueChange);
3750
+ const buttons = useRef(/* @__PURE__ */ new Map());
3751
+ const fill = (star) => star <= value ? "fill-current text-primary" : "text-muted-foreground";
3752
+ if (!interactive) return /* @__PURE__ */ jsx("span", {
3753
+ role: "img",
3754
+ "aria-label": ariaLabel,
3755
+ className: cn("inline-flex items-center gap-0.5", className),
3756
+ children: stars.map((star) => /* @__PURE__ */ jsx(Star, {
3757
+ "aria-hidden": true,
3758
+ className: cn("size-4", fill(star))
3759
+ }, star))
3760
+ });
3761
+ function select(next) {
3762
+ const chosen = clamp(next, 1, count);
3763
+ onValueChange?.(chosen);
3764
+ buttons.current.get(chosen)?.focus();
3765
+ }
3766
+ function onKeyDown(event) {
3767
+ switch (event.key) {
3768
+ case "ArrowRight":
3769
+ case "ArrowUp":
3770
+ event.preventDefault();
3771
+ select((value || 0) + 1);
3772
+ break;
3773
+ case "ArrowLeft":
3774
+ case "ArrowDown":
3775
+ event.preventDefault();
3776
+ select((value || 1) - 1);
3777
+ break;
3778
+ case "Home":
3779
+ event.preventDefault();
3780
+ select(1);
3781
+ break;
3782
+ case "End":
3783
+ event.preventDefault();
3784
+ select(count);
3785
+ }
3786
+ }
3787
+ return /* @__PURE__ */ jsx("div", {
3788
+ role: "radiogroup",
3789
+ "aria-label": ariaLabel,
3790
+ onKeyDown,
3791
+ className: cn("inline-flex items-center gap-0.5", className),
3792
+ children: stars.map((star) => /* @__PURE__ */ jsx("button", {
3793
+ ref: (element) => {
3794
+ if (element) buttons.current.set(star, element);
3795
+ else buttons.current.delete(star);
3796
+ },
3797
+ type: "button",
3798
+ role: "radio",
3799
+ "aria-checked": star === value,
3800
+ "aria-label": String(star),
3801
+ tabIndex: star === (value || 1) ? 0 : -1,
3802
+ onClick: () => select(star),
3803
+ className: "rounded p-0.5 outline-none focus-visible:ring-2 focus-visible:ring-ring",
3804
+ children: /* @__PURE__ */ jsx(Star, {
3805
+ "aria-hidden": true,
3806
+ className: cn("size-4", fill(star))
3807
+ })
3808
+ }, star))
3809
+ });
3810
+ }
3811
+ //#endregion
3812
+ //#region src/components/rating/manifest.ts
3813
+ /** Le manifeste du Rating (ADR 0005). */
3814
+ var ratingManifest = {
3815
+ name: "rating",
3816
+ title: "Note",
3817
+ summary: "Une note sur `max` étoiles, **contrôlée**. Interactive seulement si l’app fournit `onValueChange` ; sinon un affichage. A11y `radiogroup` (flèches, Home/End).",
3818
+ level: "primitive",
3819
+ example: {
3820
+ value: 3,
3821
+ max: 5,
3822
+ ariaLabel: "Note",
3823
+ onValueChange: () => {}
3824
+ },
3825
+ variants: [],
3826
+ props: [
3827
+ {
3828
+ name: "value",
3829
+ type: "number",
3830
+ required: true,
3831
+ check: "rendered",
3832
+ description: "La note actuelle, de 0 à `max`."
3833
+ },
3834
+ {
3835
+ name: "max",
3836
+ type: "number",
3837
+ required: false,
3838
+ check: "rendered",
3839
+ description: "Le nombre d'étoiles (5 par défaut)."
3840
+ },
3841
+ {
3842
+ name: "onValueChange",
3843
+ type: "(value: number) => void",
3844
+ required: false,
3845
+ check: "accepted",
3846
+ description: "Rend la note interactive quand fourni ; l'app décide de la suite."
3847
+ },
3848
+ {
3849
+ name: "readOnly",
3850
+ type: "boolean",
3851
+ required: false,
3852
+ check: "rendered",
3853
+ description: "Force l’affichage seul, même si `onValueChange` est fourni."
3854
+ },
3855
+ {
3856
+ name: "ariaLabel",
3857
+ type: "string",
3858
+ required: false,
3859
+ check: "rendered",
3860
+ description: "Le nom accessible du groupe : le cœur n'a pas d'i18n."
3861
+ },
3862
+ {
3863
+ name: "className",
3864
+ type: "string",
3865
+ required: false,
3866
+ check: "class",
3867
+ description: "Les classes de l'appelant, fusionnées après celles du composant."
3868
+ }
3869
+ ],
3870
+ usages: [{
3871
+ when: "recueillir une note de l’utilisateur",
3872
+ use: "<Rating value={note} onValueChange={setNote} ariaLabel={t.rating.label} />",
3873
+ avoid: "laisser le Rating posséder la valeur : la donnée appartient à l’app"
3874
+ }, {
3875
+ when: "montrer une note déjà posée",
3876
+ use: "<Rating value={4} readOnly ariaLabel={t.rating.label} />",
3877
+ avoid: "un `onValueChange` sans intention de le consommer"
3878
+ }]
3879
+ };
3880
+ //#endregion
3881
+ //#region src/components/timeline/timeline.tsx
3882
+ function Timeline({ items, ariaLabel, className, ...props }) {
3883
+ return /* @__PURE__ */ jsx("ol", {
3884
+ "aria-label": ariaLabel,
3885
+ className: cn("relative flex flex-col", className),
3886
+ ...props,
3887
+ children: items.map((item, index) => /* @__PURE__ */ jsxs("li", {
3888
+ className: "relative flex gap-3 pb-4 last:pb-0",
3889
+ children: [
3890
+ index < items.length - 1 ? /* @__PURE__ */ jsx("span", {
3891
+ "aria-hidden": true,
3892
+ className: "absolute left-1 top-4 h-full w-px bg-border"
3893
+ }) : null,
3894
+ /* @__PURE__ */ jsx("span", {
3895
+ "aria-hidden": true,
3896
+ className: "relative z-10 mt-1 size-2.5 shrink-0 rounded-full bg-primary ring-4 ring-background"
3897
+ }),
3898
+ /* @__PURE__ */ jsxs("div", {
3899
+ className: "min-w-0",
3900
+ children: [/* @__PURE__ */ jsxs("div", {
3901
+ className: "flex flex-wrap items-baseline gap-x-2",
3902
+ children: [/* @__PURE__ */ jsx("span", {
3903
+ className: "text-sm font-medium",
3904
+ children: item.label
3905
+ }), /* @__PURE__ */ jsx("time", {
3906
+ className: "font-mono text-xs text-muted-foreground",
3907
+ children: item.at
3908
+ })]
3909
+ }), item.detail ? /* @__PURE__ */ jsx("p", {
3910
+ className: "text-xs text-muted-foreground",
3911
+ children: item.detail
3912
+ }) : null]
3913
+ })
3914
+ ]
3915
+ }, item.id))
3916
+ });
3917
+ }
3918
+ //#endregion
3919
+ //#region src/components/timeline/manifest.ts
3920
+ /** Le manifeste de la Timeline (ADR 0005). */
3921
+ var timelineManifest = {
3922
+ name: "timeline",
3923
+ title: "Frise",
3924
+ summary: "Une suite d'événements datés, du plus récent au plus ancien. Présentation seule : l'ordre, la date formatée et le texte viennent de l'appelant.",
3925
+ level: "bloc",
3926
+ example: {
3927
+ ariaLabel: "Activité récente",
3928
+ items: [
3929
+ {
3930
+ id: "a",
3931
+ at: "2026-10-01 09:12",
3932
+ label: "sync health",
3933
+ detail: "les deux produits, 0 gap"
3934
+ },
3935
+ {
3936
+ id: "b",
3937
+ at: "2026-10-01 08:40",
3938
+ label: "sync prs",
3939
+ detail: "3 PR routées"
3940
+ },
3941
+ {
3942
+ id: "c",
3943
+ at: "2026-09-30 18:05",
3944
+ label: "sync triage"
3945
+ }
3946
+ ]
3947
+ },
3948
+ variants: [],
3949
+ props: [
3950
+ {
3951
+ name: "items",
3952
+ type: "TimelineItem[]",
3953
+ required: true,
3954
+ check: "accepted",
3955
+ description: "Les événements : `id`, `at` (date déjà formatée), `label`, `detail?`."
3956
+ },
3957
+ {
3958
+ name: "ariaLabel",
3959
+ type: "string",
3960
+ required: false,
3961
+ check: "rendered",
3962
+ description: "Le nom accessible de la frise : le cœur n'a pas d'i18n."
3963
+ },
3964
+ {
3965
+ name: "className",
3966
+ type: "string",
3967
+ required: false,
3968
+ check: "class",
3969
+ description: "Les classes de l'appelant, fusionnées après celles du composant."
3970
+ }
3971
+ ],
3972
+ usages: [{
3973
+ when: "montrer une suite d’événements récents (synchronisations, déploiements)",
3974
+ use: "<Timeline items={events} ariaLabel={t.timeline.label} />",
3975
+ avoid: "faire formater la date par le cœur : le format appartient à l’app"
3976
+ }, {
3977
+ when: "un seul événement, sans suite",
3978
+ use: "une ligne simple, pas une frise",
3979
+ avoid: "une frise d’un seul point, qui suggère une chronologie inexistante"
3980
+ }]
3981
+ };
3982
+ //#endregion
3983
+ //#region src/catalogue/contract.ts
3984
+ /**
3985
+ * Le contrat d'un manifeste de composant (ADR 0005).
3986
+ *
3987
+ * Le manifeste est écrit à la main, colocalisé avec son composant
3988
+ * (`src/components/<nom>/manifest.ts`), et validé ici : une erreur de forme se voit à
3989
+ * l'import du catalogue, pas dans la page de style ni dans le serveur MCP.
3990
+ *
3991
+ * `usages` est obligatoire et non vide : c'est la partie qu'un agent vient chercher —
3992
+ * l'intention (« quel badge pour un statut de production »), pas seulement le type.
3993
+ */
3994
+ var componentPropSchema = z.strictObject({
3995
+ name: z.string().min(1),
3996
+ type: z.string().min(1),
3997
+ required: z.boolean(),
3998
+ default: z.string().optional(),
3999
+ /** Comment le test de cohérence vérifie que la prop est réellement acceptée. */
4000
+ check: z.enum([
4001
+ "attribute",
4002
+ "class",
4003
+ "content",
4004
+ "rendered",
4005
+ "accepted"
4006
+ ]),
4007
+ description: z.string().min(1)
4008
+ });
4009
+ var componentVariantSchema = z.strictObject({
4010
+ /** Le nom de la prop qui porte les variantes (`variant`, `size`, `tone`…). */
4011
+ name: z.string().min(1),
4012
+ values: z.array(z.string().min(1)).min(1),
4013
+ default: z.string().optional(),
4014
+ description: z.string().min(1)
4015
+ });
4016
+ var componentUsageSchema = z.strictObject({
4017
+ when: z.string().min(1),
4018
+ use: z.string().min(1),
4019
+ avoid: z.string().optional()
4020
+ });
4021
+ var componentManifestSchema = z.strictObject({
4022
+ name: z.string().regex(/^[a-z][a-z0-9-]*$/, "le nom du composant est en kebab-case, comme son dossier"),
4023
+ title: z.string().min(1),
4024
+ summary: z.string().min(1),
4025
+ level: z.enum([
4026
+ "jeton",
4027
+ "primitive",
4028
+ "bloc"
4029
+ ]),
4030
+ props: z.array(componentPropSchema),
4031
+ variants: z.array(componentVariantSchema),
4032
+ /**
4033
+ * Les props avec lesquelles le composant se rend : la page de style s'en sert, et le
4034
+ * test de cohérence s'en sert comme base pour éprouver une prop « rendue ». Un
4035
+ * composant sans exemple reste documentable, mais il ne se rend pas tout seul.
4036
+ */
4037
+ example: z.record(z.string(), z.unknown()).optional(),
4038
+ usages: z.array(componentUsageSchema).min(1, "un manifeste porte des usages (l'intention), pas seulement des props")
4039
+ });
4040
+ /** Le message d'erreur d'un manifeste invalide : quel composant, quel champ, quoi. */
4041
+ function errorMessage(schemaName, error) {
4042
+ return `Catalogue : le manifeste de \`${schemaName}\` ne respecte pas le contrat — ${error.issues.map((issue) => `${issue.path.join(".") || "(racine)"} : ${issue.message}`).join(" ; ")}`;
4043
+ }
4044
+ //#endregion
4045
+ //#region src/catalogue/registry.ts
4046
+ var catalogueEntries = [
4047
+ {
4048
+ manifest: alertManifest,
4049
+ component: Alert,
4050
+ variantsConfig: { tone: {
4051
+ variants: { tone: toneClasses },
4052
+ defaultVariants: { tone: "info" }
4053
+ } }
4054
+ },
4055
+ {
4056
+ manifest: badgeManifest,
4057
+ component: Badge,
4058
+ variantsConfig: { variant: badgeVariantsConfig }
4059
+ },
4060
+ {
4061
+ manifest: buttonManifest,
4062
+ component: Button,
4063
+ variantsConfig: {
4064
+ variant: buttonVariantsConfig,
4065
+ size: buttonVariantsConfig
4066
+ }
4067
+ },
4068
+ {
4069
+ manifest: cardManifest,
4070
+ component: Card,
4071
+ variantsConfig: {}
4072
+ },
4073
+ {
4074
+ manifest: emptyStateManifest,
4075
+ component: EmptyState,
4076
+ variantsConfig: {}
4077
+ },
4078
+ {
4079
+ manifest: chipManifest,
4080
+ component: Chip,
4081
+ variantsConfig: {
4082
+ tone: {
4083
+ variants: { tone: toneClasses },
4084
+ defaultVariants: { tone: "neutral" }
4085
+ },
4086
+ size: {
4087
+ variants: { size: chipSizeClasses },
4088
+ defaultVariants: { size: "default" }
4089
+ }
4090
+ }
4091
+ },
4092
+ {
4093
+ manifest: freshnessManifest,
4094
+ component: Freshness,
4095
+ variantsConfig: {}
4096
+ },
4097
+ {
4098
+ manifest: inputManifest,
4099
+ component: Input,
4100
+ variantsConfig: {}
4101
+ },
4102
+ {
4103
+ manifest: numberFieldManifest,
4104
+ component: NumberField,
4105
+ variantsConfig: {}
4106
+ },
4107
+ {
4108
+ manifest: searchFieldManifest,
4109
+ component: SearchField,
4110
+ variantsConfig: {}
4111
+ },
4112
+ {
4113
+ manifest: labelManifest,
4114
+ component: Label,
4115
+ variantsConfig: {}
4116
+ },
4117
+ {
4118
+ manifest: fieldsetManifest,
4119
+ component: Fieldset,
4120
+ variantsConfig: {}
4121
+ },
4122
+ {
4123
+ manifest: fieldManifest,
4124
+ component: Field,
4125
+ variantsConfig: {}
4126
+ },
4127
+ {
4128
+ manifest: formManifest,
4129
+ component: Form,
4130
+ variantsConfig: {}
4131
+ },
4132
+ {
4133
+ manifest: facetFilterManifest,
4134
+ component: FacetFilter,
4135
+ variantsConfig: {}
4136
+ },
4137
+ {
4138
+ manifest: dataTableToolbarManifest,
4139
+ component: DataTableToolbar,
4140
+ variantsConfig: {}
4141
+ },
4142
+ {
4143
+ manifest: dataTablePaginationManifest,
4144
+ component: DataTablePagination,
4145
+ variantsConfig: {}
4146
+ },
4147
+ {
4148
+ manifest: meterManifest,
4149
+ component: Meter,
4150
+ variantsConfig: {}
4151
+ },
4152
+ {
4153
+ manifest: separatorManifest,
4154
+ component: Separator,
4155
+ variantsConfig: {}
4156
+ },
4157
+ {
4158
+ manifest: skeletonManifest,
4159
+ component: Skeleton,
4160
+ variantsConfig: {}
4161
+ },
4162
+ {
4163
+ manifest: tableManifest,
4164
+ component: Table,
4165
+ variantsConfig: {}
4166
+ },
4167
+ {
4168
+ manifest: textareaManifest,
4169
+ component: Textarea,
4170
+ variantsConfig: {}
4171
+ },
4172
+ {
4173
+ manifest: checkboxManifest,
4174
+ component: Checkbox,
4175
+ variantsConfig: {}
4176
+ },
4177
+ {
4178
+ manifest: switchManifest,
4179
+ component: Switch,
4180
+ variantsConfig: {}
4181
+ },
4182
+ {
4183
+ manifest: radioManifest,
4184
+ component: RadioGroup,
4185
+ variantsConfig: {}
4186
+ },
4187
+ {
4188
+ manifest: toggleManifest,
4189
+ component: Toggle,
4190
+ variantsConfig: {
4191
+ variant: toggleVariantsConfig,
4192
+ size: toggleVariantsConfig
4193
+ }
4194
+ },
4195
+ {
4196
+ manifest: toggleGroupManifest,
4197
+ component: ToggleGroup,
4198
+ variantsConfig: {
4199
+ variant: toggleVariantsConfig,
4200
+ size: toggleVariantsConfig
4201
+ }
4202
+ },
4203
+ {
4204
+ manifest: toastManifest,
4205
+ component: Toast,
4206
+ variantsConfig: { tone: {
4207
+ variants: { tone: toneClasses },
4208
+ defaultVariants: { tone: "info" }
4209
+ } }
4210
+ },
4211
+ {
4212
+ manifest: treeManifest,
4213
+ component: Tree,
4214
+ variantsConfig: {}
4215
+ },
4216
+ {
4217
+ manifest: counterManifest,
4218
+ component: Counter,
4219
+ variantsConfig: {}
4220
+ },
4221
+ {
4222
+ manifest: progressBarManifest,
4223
+ component: ProgressBar,
4224
+ variantsConfig: {}
4225
+ },
4226
+ {
4227
+ manifest: ratingManifest,
4228
+ component: Rating,
4229
+ variantsConfig: {}
4230
+ },
4231
+ {
4232
+ manifest: timelineManifest,
4233
+ component: Timeline,
4234
+ variantsConfig: {}
4235
+ }
4236
+ ];
4237
+ /**
4238
+ * Valide l'index : chaque manifeste respecte le contrat, et deux composants ne portent
4239
+ * pas le même nom. Une entrée refusée dit laquelle et pourquoi.
4240
+ */
4241
+ function validateCatalogue(entries) {
4242
+ const seen = /* @__PURE__ */ new Set();
4243
+ return entries.map((entry) => {
4244
+ const name = entry.manifest?.name ?? "(sans nom)";
4245
+ const parsed = componentManifestSchema.safeParse(entry.manifest);
4246
+ if (!parsed.success) throw new Error(errorMessage(name, parsed.error));
4247
+ if (seen.has(parsed.data.name)) throw new Error(`Catalogue : deux manifestes portent le nom \`${parsed.data.name}\`.`);
4248
+ seen.add(parsed.data.name);
4249
+ return {
4250
+ ...entry,
4251
+ manifest: parsed.data
4252
+ };
4253
+ });
4254
+ }
4255
+ /** Le catalogue validé — c'est celui-là qu'on expose. */
4256
+ var catalogue = validateCatalogue(catalogueEntries);
4257
+ /** Les noms des composants du catalogue, dans l'ordre de l'index. */
4258
+ var componentNames = catalogue.map((entry) => entry.manifest.name);
4259
+ /** Le composant et son manifeste, ou une erreur qui dit ce qui manque. */
4260
+ function findComponent(name) {
4261
+ const entry = catalogue.find((candidate) => candidate.manifest.name === name);
4262
+ if (!entry) throw new Error(`Catalogue : composant introuvable : \`${name}\` (connus : ${componentNames.join(", ")}).`);
4263
+ return entry;
4264
+ }
4265
+ //#endregion
4266
+ //#region src/tokens/build.mjs
4267
+ /**
4268
+ * La dérivation des tokens : une source JSON, deux rendus (ADR 0004).
4269
+ *
4270
+ * `renderCss` produit les custom properties CSS que l'app consomme ; `buildResource`
4271
+ * produit l'inventaire plat que le serveur MCP du design system servira.
4272
+ *
4273
+ * Ce module est pur : il ne lit ni n'écrit aucun fichier, il prend le document DTCG
4274
+ * déjà parsé et rend du texte. Le CLI (`scripts/build-tokens.mjs`) fait l'entrée/sortie,
4275
+ * et les tests importent ces fonctions pour vérifier qu'un emplacement oublié les fait
4276
+ * échouer plutôt que de produire un blanc silencieux (ADR 0009).
4277
+ */
4278
+ /** Un chemin d'alias DTCG : `{primitive.color.ink.950}`. */
4279
+ var ALIAS = /^\{([^}]+)\}$/;
4280
+ function modes$1(doc) {
4281
+ const declared = doc?.$extensions?.["org.nomos"]?.modes;
4282
+ if (!Array.isArray(declared) || declared.length === 0) throw new Error("tokens.json : `$extensions[\"org.nomos\"].modes` doit déclarer au moins un mode.");
4283
+ return [...declared];
4284
+ }
4285
+ function defaultMode$1(doc) {
4286
+ const declared = doc?.$extensions?.["org.nomos"]?.defaultMode;
4287
+ const all = modes$1(doc);
4288
+ if (!declared || !all.includes(declared)) throw new Error(`tokens.json : \`defaultMode\` doit être l'un des modes déclarés (${all.join(", ")}).`);
4289
+ return declared;
4290
+ }
4291
+ /** Les densités déclarées (ADR 0008) : un nom et son multiplicateur d'espacement. */
4292
+ function densities$1(doc) {
4293
+ const declared = doc?.$extensions?.["org.nomos"]?.densities;
4294
+ if (!declared || typeof declared !== "object" || Object.keys(declared).length === 0) throw new Error("tokens.json : `$extensions[\"org.nomos\"].densities` doit déclarer au moins une densité.");
4295
+ return declared;
4296
+ }
4297
+ function defaultDensity$1(doc) {
4298
+ const declared = doc?.$extensions?.["org.nomos"]?.defaultDensity;
4299
+ const all = Object.keys(densities$1(doc));
4300
+ if (!declared || !all.includes(declared)) throw new Error(`tokens.json : \`defaultDensity\` doit être l'une des densités déclarées (${all.join(", ")}).`);
4301
+ return declared;
4302
+ }
4303
+ /** Aplatit un nœud DTCG en `chemin -> token`, en s'arrêtant aux feuilles (`$value`). */
4304
+ function flatten(node, prefix = []) {
4305
+ const tokens = /* @__PURE__ */ new Map();
4306
+ for (const [key, value] of Object.entries(node ?? {})) {
4307
+ if (key.startsWith("$")) continue;
4308
+ const path = [...prefix, key];
4309
+ if (value && typeof value === "object" && "$value" in value) tokens.set(path.join("."), value);
4310
+ else if (value && typeof value === "object") for (const [p, t] of flatten(value, path)) tokens.set(p, t);
4311
+ }
4312
+ return tokens;
4313
+ }
4314
+ function semanticTokens(doc) {
4315
+ return flatten(doc.semantic);
4316
+ }
4317
+ /**
4318
+ * Tous les tokens du document, aplatis depuis sa racine : c'est là qu'un alias DTCG
4319
+ * résout son chemin (`{primitive.color.ink.950}` part de la racine du fichier, pas du
4320
+ * groupe `primitive`).
4321
+ */
4322
+ function allTokens(doc) {
4323
+ return flatten(doc);
4324
+ }
4325
+ /**
4326
+ * Résout un token sémantique pour un mode jusqu'à sa valeur concrète : une chaîne
4327
+ * d'alias `{primitive…}` est suivie jusqu'au bout, avec détection de cycle.
4328
+ */
4329
+ function resolve(doc, path, mode) {
4330
+ const known = allTokens(doc);
4331
+ const token = semanticTokens(doc).get(path);
4332
+ if (!token) throw new Error(`tokens.json : token sémantique introuvable : ${path}`);
4333
+ const value = token.$value;
4334
+ let raw = value;
4335
+ if (value && typeof value === "object" && !Array.isArray(value) && !("colorSpace" in value) && !("value" in value)) {
4336
+ if (!(mode in value)) throw new Error(`tokens.json : le mode \`${mode}\` n'emplit pas l'emplacement \`${path}\` (modes présents : ${Object.keys(value).join(", ")}).`);
4337
+ raw = value[mode];
4338
+ }
4339
+ const seen = /* @__PURE__ */ new Set();
4340
+ while (typeof raw === "string" && ALIAS.test(raw)) {
4341
+ const target = raw.match(ALIAS)[1];
4342
+ if (seen.has(target)) throw new Error(`tokens.json : alias cyclique autour de \`${target}\` (${path}).`);
4343
+ seen.add(target);
4344
+ const found = known.get(target);
4345
+ if (!found) throw new Error(`tokens.json : \`${path}\` pointe vers \`${target}\`, qui n'existe pas dans \`primitive\`.`);
4346
+ raw = found.$value;
4347
+ }
4348
+ return raw;
4349
+ }
4350
+ /** Rend une valeur DTCG en texte CSS. `type` n'est lu que pour une valeur tableau. */
4351
+ function cssValue(value, type) {
4352
+ if (typeof value === "string") return value;
4353
+ if (typeof value === "number") return String(value);
4354
+ if (Array.isArray(value)) {
4355
+ if (type === "fontFamily") return value.map((f) => f.includes(" ") ? `"${f}"` : f).join(", ");
4356
+ throw new Error(type ? `tokens.json : valeur tableau non rendue pour le type \`${type}\`.` : "tokens.json : une valeur tableau exige le `$type` de son token (aucun reçu).");
4357
+ }
4358
+ if (value && typeof value === "object") {
4359
+ if (value.colorSpace === "hsl") {
4360
+ const [h, s, l] = value.components;
4361
+ return `${h} ${s}% ${l}%`;
4362
+ }
4363
+ if ("value" in value && "unit" in value) return `${value.value}${value.unit}`;
4364
+ }
4365
+ throw new Error(`tokens.json : valeur non rendue : ${JSON.stringify(value)}`);
4366
+ }
4367
+ /** Le nom de la custom property d'un emplacement sémantique. */
4368
+ function cssName(path, namespace) {
4369
+ return `${namespace}-${path.replace(/\./g, "-")}`;
4370
+ }
4371
+ /** L'ensemble des emplacements sémantiques d'un mode, `nom CSS -> valeur`. */
4372
+ function slotsFor(doc, mode) {
4373
+ const namespace = doc?.$extensions?.["org.nomos"]?.cssNamespace ?? "--nomos";
4374
+ const slots = {};
4375
+ for (const [path, token] of semanticTokens(doc)) slots[cssName(path, namespace)] = cssValue(resolve(doc, path, mode), token.$type);
4376
+ return slots;
4377
+ }
4378
+ var HEADER = `/*
4379
+ * Généré par scripts/build-tokens.mjs — ne pas éditer à la main.
4380
+ * Source unique : tokens/tokens.json (format DTCG, ADR 0004).
4381
+ * Rejouer : npm run tokens
4382
+ */`;
4383
+ /**
4384
+ * Le CSS dérivé. Les couleurs et les dimensions remplissent chaque mode à l'identique :
4385
+ * `:root` porte le mode par défaut, `@media (prefers-color-scheme: light)` suit le
4386
+ * système tant qu'aucune classe n'a été posée, puis les classes `.dark` / `.light` et
4387
+ * `[data-theme=…]` tranchent explicitement (ADR 0009).
4388
+ *
4389
+ * `color-scheme` accompagne chaque mode : sans lui, les contrôles natifs (barres de
4390
+ * défilement, champs) restent au thème du système dans un thème forcé.
4391
+ */
4392
+ function renderCss(doc) {
4393
+ const namespace = doc?.$extensions?.["org.nomos"]?.cssNamespace ?? "--nomos";
4394
+ const def = defaultMode$1(doc);
4395
+ const other = modes$1(doc).find((m) => m !== def);
4396
+ const block = (mode, indent) => {
4397
+ const pad = " ".repeat(indent);
4398
+ const lines = [`${pad}color-scheme: ${mode};`];
4399
+ for (const [name, value] of Object.entries(slotsFor(doc, mode))) lines.push(`${pad}${name}: ${value};`);
4400
+ return lines.join("\n");
4401
+ };
4402
+ const selectorBlock = (selector, mode, indent = 2) => `${" ".repeat(indent)}${selector} {\n${block(mode, indent + 2)}\n${" ".repeat(indent)}}`;
4403
+ const parts = [
4404
+ HEADER,
4405
+ "",
4406
+ "@layer base {",
4407
+ selectorBlock(":root", def),
4408
+ ""
4409
+ ];
4410
+ if (other === "light" || other === "dark") parts.push(` /* Aucun mode posé sur la racine : c’est le système qui décide (ADR 0009). */`, ` @media (prefers-color-scheme: ${other}) {`, ` :root:not(${modes$1(doc).map((m) => `.${m}`).join("):not(")}) {`, block(other, 6), " }", " }", "");
4411
+ for (const mode of modes$1(doc)) parts.push(selectorBlock(`.${mode}, [data-theme='${mode}']`, mode), "");
4412
+ parts.push(` :root { ${namespace}-density: ${densities$1(doc)[defaultDensity$1(doc)]}; }`, "");
4413
+ for (const [name, value] of Object.entries(densities$1(doc))) parts.push(` [data-density='${name}'], .${name} { ${namespace}-density: ${value}; }`, "");
4414
+ parts.push("}", "", `/* namespace : ${namespace} — un thème d'app remplace ces emplacements (ADR 0003). */`, "");
4415
+ return parts.join("\n");
4416
+ }
4417
+ //#endregion
4418
+ //#region src/tokens/skin.ts
4419
+ function resolveSkin(defaultDoc, overlay) {
4420
+ if (overlay == null) return defaultDoc;
4421
+ if (overlay.primitive) throw new Error("skin : un skin ne porte pas de `primitive` — il ne remplace que des emplacements sémantiques (ADR 0022).");
4422
+ const known = semanticTokens(defaultDoc);
4423
+ const overlaid = semanticTokens(overlay);
4424
+ if (overlaid.size === 0) return defaultDoc;
4425
+ const merged = structuredClone(defaultDoc);
4426
+ merged.semantic ??= {};
4427
+ for (const [path, token] of overlaid) {
4428
+ const base = known.get(path);
4429
+ if (!base) throw new Error(`skin : emplacement inconnu \`${path}\` — l'interface de thème est celle du défaut du cœur (ADR 0022).`);
4430
+ setSemantic(merged.semantic, path, {
4431
+ ...base.$type ? { $type: base.$type } : {},
4432
+ ...token
4433
+ });
4434
+ }
4435
+ return merged;
4436
+ }
4437
+ /** Pose un token à son chemin (`color.background`) en créant les groupes manquants. */
4438
+ function setSemantic(root, path, token) {
4439
+ const keys = path.split(".");
4440
+ let node = root;
4441
+ for (const key of keys.slice(0, -1)) {
4442
+ node[key] ??= {};
4443
+ node = node[key];
4444
+ }
4445
+ node[keys[keys.length - 1]] = token;
4446
+ }
4447
+ //#endregion
4448
+ //#region src/tokens/resource.ts
4449
+ /**
4450
+ * La ressource de tokens, servie telle quelle à la page de style (et, plus tard, au
4451
+ * serveur MCP). C'est le rendu dérivé de `tokens/tokens.json` par
4452
+ * `npm run tokens` — ne pas l'éditer à la main : le test des tokens échoue si le
4453
+ * fichier committé n'est plus celui que la source produit.
4454
+ */
4455
+ var tokensResource = {
4456
+ $description: "La ressource `tokens` : la source unique aplatie, un inventaire par mode. Dérivée de `tokens/tokens.json` — ne pas éditer.",
4457
+ namespace: "--nomos",
4458
+ defaultMode: "dark",
4459
+ modes: ["dark", "light"],
4460
+ densities: {
4461
+ "comfortable": 1,
4462
+ "compact": .8
4463
+ },
4464
+ defaultDensity: "comfortable",
4465
+ slots: {
4466
+ "dark": {
4467
+ "--nomos-color-background": "240 7% 8%",
4468
+ "--nomos-color-foreground": "240 10% 94%",
4469
+ "--nomos-color-card": "240 17% 12%",
4470
+ "--nomos-color-card-foreground": "240 10% 94%",
4471
+ "--nomos-color-popover": "240 17% 12%",
4472
+ "--nomos-color-popover-foreground": "240 10% 94%",
4473
+ "--nomos-color-primary": "174 100% 39%",
4474
+ "--nomos-color-primary-foreground": "0 0% 0%",
4475
+ "--nomos-color-secondary": "240 8% 16%",
4476
+ "--nomos-color-secondary-foreground": "240 10% 94%",
4477
+ "--nomos-color-muted": "240 8% 19%",
4478
+ "--nomos-color-muted-foreground": "0 0% 60%",
4479
+ "--nomos-color-accent": "240 8% 16%",
4480
+ "--nomos-color-accent-foreground": "240 10% 94%",
4481
+ "--nomos-color-destructive": "0 72% 71%",
4482
+ "--nomos-color-destructive-foreground": "0 0% 0%",
4483
+ "--nomos-color-border": "240 8% 19%",
4484
+ "--nomos-color-input": "240 8% 19%",
4485
+ "--nomos-color-ring": "174 100% 39%",
4486
+ "--nomos-color-brand": "#00c9b1",
4487
+ "--nomos-color-status-info": "199 89% 48%",
4488
+ "--nomos-color-status-info-ink": "198 93% 74%",
4489
+ "--nomos-color-status-progress": "258 90% 66%",
4490
+ "--nomos-color-status-progress-ink": "255 92% 85%",
4491
+ "--nomos-color-status-attention": "38 92% 50%",
4492
+ "--nomos-color-status-attention-ink": "46 97% 65%",
4493
+ "--nomos-color-status-warning": "25 95% 53%",
4494
+ "--nomos-color-status-warning-ink": "31 97% 72%",
4495
+ "--nomos-color-status-danger": "0 84% 60%",
4496
+ "--nomos-color-status-danger-ink": "0 94% 82%",
4497
+ "--nomos-color-status-success": "160 84% 39%",
4498
+ "--nomos-color-status-success-ink": "156 72% 67%",
4499
+ "--nomos-radius-pill": "9999px",
4500
+ "--nomos-radius-control": "0.625rem",
4501
+ "--nomos-radius-field": "0.5rem",
4502
+ "--nomos-radius-chip": "0.375rem",
4503
+ "--nomos-space-hairline": "2px",
4504
+ "--nomos-space-tight": "4px",
4505
+ "--nomos-space-snug": "8px",
4506
+ "--nomos-space-base": "12px",
4507
+ "--nomos-space-comfy": "16px",
4508
+ "--nomos-space-loose": "24px",
4509
+ "--nomos-space-section": "32px",
4510
+ "--nomos-type-size-micro": "0.6875rem",
4511
+ "--nomos-type-size-caption": "0.75rem",
4512
+ "--nomos-type-size-body": "0.875rem",
4513
+ "--nomos-type-size-lead": "1rem",
4514
+ "--nomos-type-size-title": "1.125rem",
4515
+ "--nomos-type-size-display": "1.5rem",
4516
+ "--nomos-type-leading-micro": "0.875rem",
4517
+ "--nomos-type-leading-caption": "1rem",
4518
+ "--nomos-type-leading-body": "1.25rem",
4519
+ "--nomos-type-leading-lead": "1.5rem",
4520
+ "--nomos-type-leading-title": "1.75rem",
4521
+ "--nomos-type-leading-display": "2rem",
4522
+ "--nomos-type-weight-regular": "400",
4523
+ "--nomos-type-weight-medium": "500",
4524
+ "--nomos-type-weight-strong": "600",
4525
+ "--nomos-type-family-sans": "ui-sans-serif, system-ui, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif"
4526
+ },
4527
+ "light": {
4528
+ "--nomos-color-background": "0 0% 100%",
4529
+ "--nomos-color-foreground": "240 10% 10%",
4530
+ "--nomos-color-card": "0 0% 98%",
4531
+ "--nomos-color-card-foreground": "240 10% 10%",
4532
+ "--nomos-color-popover": "0 0% 100%",
4533
+ "--nomos-color-popover-foreground": "240 10% 10%",
4534
+ "--nomos-color-primary": "174 100% 35%",
4535
+ "--nomos-color-primary-foreground": "0 0% 100%",
4536
+ "--nomos-color-secondary": "240 5% 92%",
4537
+ "--nomos-color-secondary-foreground": "240 10% 10%",
4538
+ "--nomos-color-muted": "240 5% 92%",
4539
+ "--nomos-color-muted-foreground": "240 4% 46%",
4540
+ "--nomos-color-accent": "240 5% 92%",
4541
+ "--nomos-color-accent-foreground": "240 10% 10%",
4542
+ "--nomos-color-destructive": "0 72% 51%",
4543
+ "--nomos-color-destructive-foreground": "0 0% 100%",
4544
+ "--nomos-color-border": "240 6% 85%",
4545
+ "--nomos-color-input": "240 6% 85%",
4546
+ "--nomos-color-ring": "174 100% 35%",
4547
+ "--nomos-color-brand": "#00c9b1",
4548
+ "--nomos-color-status-info": "199 89% 48%",
4549
+ "--nomos-color-status-info-ink": "201 96% 32%",
4550
+ "--nomos-color-status-progress": "258 90% 66%",
4551
+ "--nomos-color-status-progress-ink": "263 70% 50%",
4552
+ "--nomos-color-status-attention": "38 92% 50%",
4553
+ "--nomos-color-status-attention-ink": "26 90% 37%",
4554
+ "--nomos-color-status-warning": "25 95% 53%",
4555
+ "--nomos-color-status-warning-ink": "17 88% 40%",
4556
+ "--nomos-color-status-danger": "0 84% 60%",
4557
+ "--nomos-color-status-danger-ink": "0 74% 42%",
4558
+ "--nomos-color-status-success": "160 84% 39%",
4559
+ "--nomos-color-status-success-ink": "161 94% 25%",
4560
+ "--nomos-radius-pill": "9999px",
4561
+ "--nomos-radius-control": "0.625rem",
4562
+ "--nomos-radius-field": "0.5rem",
4563
+ "--nomos-radius-chip": "0.375rem",
4564
+ "--nomos-space-hairline": "2px",
4565
+ "--nomos-space-tight": "4px",
4566
+ "--nomos-space-snug": "8px",
4567
+ "--nomos-space-base": "12px",
4568
+ "--nomos-space-comfy": "16px",
4569
+ "--nomos-space-loose": "24px",
4570
+ "--nomos-space-section": "32px",
4571
+ "--nomos-type-size-micro": "0.6875rem",
4572
+ "--nomos-type-size-caption": "0.75rem",
4573
+ "--nomos-type-size-body": "0.875rem",
4574
+ "--nomos-type-size-lead": "1rem",
4575
+ "--nomos-type-size-title": "1.125rem",
4576
+ "--nomos-type-size-display": "1.5rem",
4577
+ "--nomos-type-leading-micro": "0.875rem",
4578
+ "--nomos-type-leading-caption": "1rem",
4579
+ "--nomos-type-leading-body": "1.25rem",
4580
+ "--nomos-type-leading-lead": "1.5rem",
4581
+ "--nomos-type-leading-title": "1.75rem",
4582
+ "--nomos-type-leading-display": "2rem",
4583
+ "--nomos-type-weight-regular": "400",
4584
+ "--nomos-type-weight-medium": "500",
4585
+ "--nomos-type-weight-strong": "600",
4586
+ "--nomos-type-family-sans": "ui-sans-serif, system-ui, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif"
4587
+ }
4588
+ }
4589
+ };
4590
+ //#endregion
4591
+ export { Alert, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Chip, CompactMeter, Counter, DataTablePagination, DataTableToolbar, EmptyState, FacetFilter, FacetedDataTable, Field, Fieldset, Form, Freshness, Input, Label, Meter, NumberField, ProgressBar, RadioGroup, RadioGroupItem, Rating, SearchField, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, SelectionTree, Separator, SheetDescription, SheetHeader, SheetTitle, Skeleton, Switch, TONES, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea, Timeline, Toast, ToastProvider, Toggle, ToggleGroup, ToggleGroupItem, TooltipProvider, Tree, alertManifest, badgeManifest, badgeVariants, badgeVariantsConfig, buttonManifest, buttonVariants, buttonVariantsConfig, cardManifest, catalogue, checkboxManifest, chipManifest, cn, componentManifestSchema, componentNames, counterManifest, dataTableFeatures, dataTablePaginationManifest, dataTableToolbarManifest, emptyStateManifest, facetFilterManifest, fieldManifest, fieldsetManifest, findComponent, formManifest, freshnessManifest, inputManifest, labelManifest, meterManifest, numberFieldManifest, progressBarManifest, radioManifest, ratingManifest, renderCss, resolveSkin, searchFieldManifest, separatorManifest, skeletonManifest, switchManifest, tableManifest, textareaManifest, timelineManifest, toastManifest, toggleGroupManifest, toggleManifest, toggleVariants, toggleVariantsConfig, tokensResource, toneClasses, treeManifest, useToast, validateCatalogue };