@arqel-dev/ui 0.11.0 → 0.13.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 (111) hide show
  1. package/SKILL.md +5 -2
  2. package/dist/AreaChart-NMQ2RQGA.js +33 -0
  3. package/dist/AreaChart-NMQ2RQGA.js.map +1 -0
  4. package/dist/ArqelCreatePage-P3G6TKIK.js +8 -0
  5. package/dist/ArqelCreatePage-P3G6TKIK.js.map +1 -0
  6. package/dist/ArqelDashboardPage-PX3Y75AA.js +6 -0
  7. package/dist/ArqelDashboardPage-PX3Y75AA.js.map +1 -0
  8. package/dist/ArqelEditPage-HKQSE6E5.js +8 -0
  9. package/dist/ArqelEditPage-HKQSE6E5.js.map +1 -0
  10. package/dist/ArqelIndexPage-MRH3572Z.js +12 -0
  11. package/dist/ArqelIndexPage-MRH3572Z.js.map +1 -0
  12. package/dist/ArqelShowPage-4FS5MTUN.js +6 -0
  13. package/dist/ArqelShowPage-4FS5MTUN.js.map +1 -0
  14. package/dist/BarChart-KLHKTLO2.js +19 -0
  15. package/dist/BarChart-KLHKTLO2.js.map +1 -0
  16. package/dist/DonutChart-L56R4QF2.js +11 -0
  17. package/dist/DonutChart-L56R4QF2.js.map +1 -0
  18. package/dist/LineChart-GHMPHFZZ.js +30 -0
  19. package/dist/LineChart-GHMPHFZZ.js.map +1 -0
  20. package/dist/PieChart-T2VQOFPP.js +4 -0
  21. package/dist/PieChart-T2VQOFPP.js.map +1 -0
  22. package/dist/RadarChart-CSWR5AO7.js +32 -0
  23. package/dist/RadarChart-CSWR5AO7.js.map +1 -0
  24. package/dist/Topbar-DffcNdcJ.d.ts +50 -0
  25. package/dist/action.d.ts +12 -2
  26. package/dist/action.js +6 -1090
  27. package/dist/action.js.map +1 -1
  28. package/dist/auth.js +1 -10
  29. package/dist/auth.js.map +1 -1
  30. package/dist/chunk-2LTZJ4R7.js +110 -0
  31. package/dist/chunk-2LTZJ4R7.js.map +1 -0
  32. package/dist/chunk-3JJOHYD7.js +57 -0
  33. package/dist/chunk-3JJOHYD7.js.map +1 -0
  34. package/dist/chunk-47HD7ALI.js +370 -0
  35. package/dist/chunk-47HD7ALI.js.map +1 -0
  36. package/dist/chunk-6UJQP7KU.js +737 -0
  37. package/dist/chunk-6UJQP7KU.js.map +1 -0
  38. package/dist/chunk-7LSZADUC.js +50 -0
  39. package/dist/chunk-7LSZADUC.js.map +1 -0
  40. package/dist/chunk-B7ULUJVO.js +11 -0
  41. package/dist/chunk-B7ULUJVO.js.map +1 -0
  42. package/dist/chunk-C5H44NSP.js +37 -0
  43. package/dist/chunk-C5H44NSP.js.map +1 -0
  44. package/dist/chunk-DSULVRB6.js +101 -0
  45. package/dist/chunk-DSULVRB6.js.map +1 -0
  46. package/dist/chunk-ERT7LL6C.js +199 -0
  47. package/dist/chunk-ERT7LL6C.js.map +1 -0
  48. package/dist/chunk-EYAUPJ6J.js +64 -0
  49. package/dist/chunk-EYAUPJ6J.js.map +1 -0
  50. package/dist/chunk-FHRQ7PWE.js +58 -0
  51. package/dist/chunk-FHRQ7PWE.js.map +1 -0
  52. package/dist/chunk-FV7RESKL.js +285 -0
  53. package/dist/chunk-FV7RESKL.js.map +1 -0
  54. package/dist/chunk-K7QY56XN.js +610 -0
  55. package/dist/chunk-K7QY56XN.js.map +1 -0
  56. package/dist/chunk-L3JBBN4H.js +27 -0
  57. package/dist/chunk-L3JBBN4H.js.map +1 -0
  58. package/dist/chunk-MSRTGCTJ.js +597 -0
  59. package/dist/chunk-MSRTGCTJ.js.map +1 -0
  60. package/dist/chunk-MTNFFSVA.js +92 -0
  61. package/dist/chunk-MTNFFSVA.js.map +1 -0
  62. package/dist/chunk-MTZJDFQ6.js +32 -0
  63. package/dist/chunk-MTZJDFQ6.js.map +1 -0
  64. package/dist/chunk-MXUXMHOK.js +16 -0
  65. package/dist/chunk-MXUXMHOK.js.map +1 -0
  66. package/dist/chunk-PXON56HL.js +35 -0
  67. package/dist/chunk-PXON56HL.js.map +1 -0
  68. package/dist/chunk-PZEVKMZX.js +598 -0
  69. package/dist/chunk-PZEVKMZX.js.map +1 -0
  70. package/dist/chunk-R4BOUU6Y.js +55 -0
  71. package/dist/chunk-R4BOUU6Y.js.map +1 -0
  72. package/dist/chunk-RMPOR2YZ.js +12 -0
  73. package/dist/chunk-RMPOR2YZ.js.map +1 -0
  74. package/dist/chunk-RNAMHQM6.js +12 -0
  75. package/dist/chunk-RNAMHQM6.js.map +1 -0
  76. package/dist/chunk-TNJEHYRF.js +387 -0
  77. package/dist/chunk-TNJEHYRF.js.map +1 -0
  78. package/dist/chunk-VSTRVXXZ.js +49 -0
  79. package/dist/chunk-VSTRVXXZ.js.map +1 -0
  80. package/dist/chunk-WKWV36PG.js +140 -0
  81. package/dist/chunk-WKWV36PG.js.map +1 -0
  82. package/dist/cn-By8XWiSA.d.ts +12 -0
  83. package/dist/flash.js +2 -104
  84. package/dist/flash.js.map +1 -1
  85. package/dist/form.js +4 -673
  86. package/dist/form.js.map +1 -1
  87. package/dist/index.d.ts +2 -2
  88. package/dist/index.js +18 -3706
  89. package/dist/index.js.map +1 -1
  90. package/dist/pages.js +23 -3025
  91. package/dist/pages.js.map +1 -1
  92. package/dist/palette.js +1 -283
  93. package/dist/palette.js.map +1 -1
  94. package/dist/primitives.js +3 -658
  95. package/dist/primitives.js.map +1 -1
  96. package/dist/resource.js +5 -720
  97. package/dist/resource.js.map +1 -1
  98. package/dist/shell.d.ts +26 -46
  99. package/dist/shell.js +46 -1039
  100. package/dist/shell.js.map +1 -1
  101. package/dist/table.js +4 -618
  102. package/dist/table.js.map +1 -1
  103. package/dist/utility.js +3 -104
  104. package/dist/utility.js.map +1 -1
  105. package/dist/utils.d.ts +12 -6
  106. package/dist/utils.js +2 -9
  107. package/dist/utils.js.map +1 -1
  108. package/dist/widgets.d.ts +7 -2
  109. package/dist/widgets.js +3 -675
  110. package/dist/widgets.js.map +1 -1
  111. package/package.json +15 -14
package/dist/index.js CHANGED
@@ -1,3708 +1,20 @@
1
- import * as React16 from 'react';
2
- import { useId, useState, useMemo, useEffect, useCallback, useRef } from 'react';
3
- import { cva } from 'class-variance-authority';
4
- import { Slot, Checkbox as Checkbox$1, Label as Label$1, Separator as Separator$1, Select as Select$1, Dialog as Dialog$1, DropdownMenu as DropdownMenu$1, Tooltip as Tooltip$1 } from 'radix-ui';
5
- import { clsx } from 'clsx';
6
- import { twMerge } from 'tailwind-merge';
7
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import { isFieldEntry, isLayoutEntry } from '@arqel-dev/types/forms';
9
- import { CheckIcon, ChevronDownIcon, ChevronUpIcon, XIcon, PanelLeftIcon } from 'lucide-react';
10
- import { useCanAccess, useFlash, useNavigation } from '@arqel-dev/hooks';
11
- import { useReactTable, getCoreRowModel, flexRender } from '@tanstack/react-table';
12
- import { useTheme } from '@arqel-dev/react/providers';
13
- import { usePage } from '@inertiajs/react';
14
-
15
- // src/action/ActionButton.tsx
16
- function cn(...inputs) {
17
- return twMerge(clsx(inputs));
18
- }
19
- var buttonVariants = cva(
20
- "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
21
- {
22
- variants: {
23
- variant: {
24
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
25
- destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
26
- outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
27
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
28
- ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
29
- link: "text-primary underline-offset-4 hover:underline"
30
- },
31
- size: {
32
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
33
- xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
34
- sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
35
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
36
- icon: "size-9",
37
- "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
38
- "icon-sm": "size-8",
39
- "icon-lg": "size-10"
40
- }
41
- },
42
- defaultVariants: {
43
- variant: "default",
44
- size: "default"
45
- }
46
- }
47
- );
48
- function Button({
49
- className,
50
- variant = "default",
51
- size = "default",
52
- asChild = false,
53
- ...props
54
- }) {
55
- const Comp = asChild ? Slot.Root : "button";
56
- return /* @__PURE__ */ jsx(
57
- Comp,
58
- {
59
- "data-slot": "button",
60
- "data-variant": variant,
61
- "data-size": size,
62
- className: cn(buttonVariants({ variant, size, className })),
63
- ...props
64
- }
65
- );
66
- }
67
- function FormActions({
68
- submitLabel = "Save",
69
- cancelLabel = "Cancel",
70
- onCancel,
71
- processing = false,
72
- disabled = false,
73
- extra,
74
- className
75
- }) {
76
- return /* @__PURE__ */ jsxs(
77
- "div",
78
- {
79
- className: cn(
80
- "flex flex-wrap items-center justify-end gap-2 border-t border-border pt-4",
81
- className
82
- ),
83
- children: [
84
- extra && /* @__PURE__ */ jsx("div", { className: "mr-auto", children: extra }),
85
- onCancel && /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: onCancel, disabled: processing, children: cancelLabel }),
86
- /* @__PURE__ */ jsx(Button, { type: "submit", disabled: processing || disabled, children: processing ? "Saving\u2026" : submitLabel })
87
- ]
88
- }
89
- );
90
- }
91
-
92
- // src/form/FieldRegistry.tsx
93
- var registry = /* @__PURE__ */ new Map();
94
- function registerField(name, component) {
95
- registry.set(name, component);
96
- }
97
- function getFieldComponent(name) {
98
- return registry.get(name);
99
- }
100
- function clearFieldRegistry() {
101
- registry.clear();
102
- }
103
- var inputClasses = cn(
104
- "h-9 rounded-sm border border-[var(--input)]",
105
- "bg-background px-3 text-sm",
106
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
107
- "disabled:cursor-not-allowed disabled:opacity-50"
108
- );
109
- function NativeFieldInput(props) {
110
- const { field } = props;
111
- switch (field.type) {
112
- case "text":
113
- case "email":
114
- case "url":
115
- case "password":
116
- case "slug":
117
- return /* @__PURE__ */ jsx(TextLike, { ...props, field });
118
- case "textarea":
119
- return /* @__PURE__ */ jsx(Textarea, { ...props, field });
120
- case "number":
121
- case "currency":
122
- return /* @__PURE__ */ jsx(NumericInput, { ...props, field });
123
- case "boolean":
124
- case "toggle":
125
- return /* @__PURE__ */ jsx(Checkbox, { ...props, field });
126
- case "select":
127
- return /* @__PURE__ */ jsx(SelectInput, { ...props, field });
128
- case "multiSelect":
129
- case "hasMany":
130
- return /* @__PURE__ */ jsx(MultiSelectInput, { ...props, field });
131
- case "radio":
132
- return /* @__PURE__ */ jsx(RadioGroup, { ...props, field });
133
- case "belongsTo":
134
- return /* @__PURE__ */ jsx(BelongsToInput, { ...props, field });
135
- case "date":
136
- case "dateTime":
137
- return /* @__PURE__ */ jsx(DateInput, { ...props, field });
138
- case "file":
139
- case "image":
140
- return /* @__PURE__ */ jsx(FileInput, { ...props });
141
- default:
142
- return null;
143
- }
144
- }
145
- function TextLike({
146
- field,
147
- value,
148
- onChange,
149
- disabled,
150
- inputId,
151
- describedBy
152
- }) {
153
- const htmlType = field.type === "email" ? "email" : field.type === "password" ? "password" : field.type === "url" ? "url" : "text";
154
- const isDisabled = disabled || field.disabled || field.readonly;
155
- return /* @__PURE__ */ jsx(
156
- "input",
157
- {
158
- id: inputId,
159
- type: htmlType,
160
- className: inputClasses,
161
- value: typeof value === "string" ? value : (value ?? "")?.toString() ?? "",
162
- placeholder: field.placeholder ?? void 0,
163
- disabled: isDisabled,
164
- readOnly: field.readonly === true,
165
- required: field.required === true,
166
- "aria-describedby": describedBy,
167
- onChange: (e) => onChange(e.target.value)
168
- }
169
- );
170
- }
171
- function Textarea({
172
- field,
173
- value,
174
- onChange,
175
- disabled,
176
- inputId,
177
- describedBy
178
- }) {
179
- return /* @__PURE__ */ jsx(
180
- "textarea",
181
- {
182
- id: inputId,
183
- className: cn(inputClasses, "h-auto min-h-[5rem] py-2"),
184
- value: typeof value === "string" ? value : "",
185
- placeholder: field.placeholder ?? void 0,
186
- disabled: disabled || field.disabled || field.readonly,
187
- readOnly: field.readonly === true,
188
- required: field.required === true,
189
- rows: 4,
190
- "aria-describedby": describedBy,
191
- onChange: (e) => onChange(e.target.value)
192
- }
193
- );
194
- }
195
- function NumericInput({
196
- field,
197
- value,
198
- onChange,
199
- disabled,
200
- inputId,
201
- describedBy
202
- }) {
203
- return /* @__PURE__ */ jsx(
204
- "input",
205
- {
206
- id: inputId,
207
- type: "number",
208
- className: inputClasses,
209
- value: value === null || value === void 0 ? "" : String(value),
210
- placeholder: field.placeholder ?? void 0,
211
- disabled: disabled || field.disabled || field.readonly,
212
- readOnly: field.readonly === true,
213
- required: field.required === true,
214
- "aria-describedby": describedBy,
215
- onChange: (e) => {
216
- const raw = e.target.value;
217
- if (raw === "") {
218
- onChange(null);
219
- return;
220
- }
221
- const num = Number(raw);
222
- onChange(Number.isNaN(num) ? null : num);
223
- }
224
- }
225
- );
226
- }
227
- function Checkbox({
228
- field,
229
- value,
230
- onChange,
231
- disabled,
232
- inputId,
233
- describedBy
234
- }) {
235
- return /* @__PURE__ */ jsx(
236
- "input",
237
- {
238
- id: inputId,
239
- type: "checkbox",
240
- className: "h-4 w-4 rounded border-[var(--input)]",
241
- checked: value === true,
242
- disabled: disabled || field.disabled || field.readonly,
243
- "aria-describedby": describedBy,
244
- onChange: (e) => onChange(e.target.checked)
245
- }
246
- );
247
- }
248
- function normaliseOptions(options) {
249
- if (!options) return [];
250
- if (Array.isArray(options)) return options;
251
- return Object.entries(options).map(([value, label]) => ({ value, label }));
252
- }
253
- function SelectInput({
254
- field,
255
- value,
256
- onChange,
257
- disabled,
258
- inputId,
259
- describedBy
260
- }) {
261
- const options = normaliseOptions(field.props.options);
262
- return /* @__PURE__ */ jsxs(
263
- "select",
264
- {
265
- id: inputId,
266
- className: inputClasses,
267
- value: value === null || value === void 0 ? "" : String(value),
268
- disabled: disabled || field.disabled || field.readonly,
269
- required: field.required === true,
270
- "aria-describedby": describedBy,
271
- onChange: (e) => onChange(e.target.value === "" ? null : e.target.value),
272
- children: [
273
- field.placeholder && /* @__PURE__ */ jsx("option", { value: "", children: field.placeholder }),
274
- options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
275
- ]
276
- }
277
- );
278
- }
279
- function MultiSelectInput({
280
- field,
281
- value,
282
- onChange,
283
- disabled,
284
- inputId,
285
- describedBy
286
- }) {
287
- const arr = Array.isArray(value) ? value : [];
288
- const options = field.type === "multiSelect" ? normaliseOptions(field.props.options) : [];
289
- return /* @__PURE__ */ jsx(
290
- "select",
291
- {
292
- id: inputId,
293
- multiple: true,
294
- className: cn(inputClasses, "h-auto min-h-[6rem] py-1"),
295
- value: arr.map(String),
296
- disabled: disabled || field.disabled || field.readonly,
297
- "aria-describedby": describedBy,
298
- onChange: (e) => {
299
- const next = Array.from(e.target.selectedOptions, (o) => o.value);
300
- onChange(next);
301
- },
302
- children: options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
303
- }
304
- );
305
- }
306
- function RadioGroup({
307
- field,
308
- value,
309
- onChange,
310
- disabled,
311
- inputId,
312
- describedBy
313
- }) {
314
- return /* @__PURE__ */ jsx(
315
- "div",
316
- {
317
- id: inputId,
318
- className: "flex flex-col gap-1",
319
- "aria-describedby": describedBy,
320
- role: "radiogroup",
321
- children: (field.props.options ?? []).map((opt) => /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-sm", children: [
322
- /* @__PURE__ */ jsx(
323
- "input",
324
- {
325
- type: "radio",
326
- name: field.name,
327
- value: String(opt.value),
328
- checked: String(value ?? "") === String(opt.value),
329
- disabled: disabled || field.disabled || field.readonly,
330
- onChange: () => onChange(opt.value)
331
- }
332
- ),
333
- opt.label
334
- ] }, opt.value))
335
- }
336
- );
337
- }
338
- function BelongsToInput({
339
- field,
340
- value,
341
- onChange,
342
- disabled,
343
- inputId,
344
- describedBy
345
- }) {
346
- return /* @__PURE__ */ jsx(
347
- "input",
348
- {
349
- id: inputId,
350
- type: "text",
351
- className: inputClasses,
352
- value: value === null || value === void 0 ? "" : String(value),
353
- placeholder: field.placeholder ?? `Search ${field.props.relatedResource}\u2026`,
354
- disabled: disabled || field.disabled || field.readonly,
355
- readOnly: field.readonly === true,
356
- "aria-describedby": describedBy,
357
- onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
358
- }
359
- );
360
- }
361
- function DateInput({
362
- field,
363
- value,
364
- onChange,
365
- disabled,
366
- inputId,
367
- describedBy
368
- }) {
369
- return /* @__PURE__ */ jsx(
370
- "input",
371
- {
372
- id: inputId,
373
- type: field.type === "dateTime" ? "datetime-local" : "date",
374
- className: inputClasses,
375
- value: typeof value === "string" ? value : "",
376
- disabled: disabled || field.disabled || field.readonly,
377
- readOnly: field.readonly === true,
378
- required: field.required === true,
379
- "aria-describedby": describedBy,
380
- onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
381
- }
382
- );
383
- }
384
- function FileInput({
385
- field,
386
- onChange,
387
- disabled,
388
- inputId,
389
- describedBy
390
- }) {
391
- return /* @__PURE__ */ jsx(
392
- "input",
393
- {
394
- id: inputId,
395
- type: "file",
396
- className: "text-sm",
397
- disabled: disabled || field.disabled || field.readonly,
398
- "aria-describedby": describedBy,
399
- onChange: (e) => onChange(e.target.files?.[0] ?? null)
400
- }
401
- );
402
- }
403
- function FieldRenderer(props) {
404
- const { field, errors, className } = props;
405
- const id = useId();
406
- const errorId = `${id}-error`;
407
- const helperId = `${id}-helper`;
408
- const hasError = errors && errors.length > 0;
409
- const Custom = field.component ? getFieldComponent(field.component) : void 0;
410
- const Input2 = Custom ?? NativeFieldInput;
411
- const describedBy = [field.helperText ? helperId : null, hasError ? errorId : null].filter(Boolean).join(" ");
412
- return /* @__PURE__ */ jsxs(
413
- "div",
414
- {
415
- "data-arqel-field": field.name,
416
- "data-field-type": field.type,
417
- className: cn("flex flex-col gap-1", className),
418
- children: [
419
- field.label !== null && field.label !== "" && /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "text-sm font-medium", children: [
420
- field.label,
421
- field.required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-destructive", children: "*" })
422
- ] }),
423
- /* @__PURE__ */ jsx(Input2, { ...props, inputId: id, describedBy: describedBy || void 0 }),
424
- field.helperText && !hasError && /* @__PURE__ */ jsx("p", { id: helperId, className: "text-xs text-muted-foreground", children: field.helperText }),
425
- hasError && /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-xs text-destructive", children: errors[0] })
426
- ]
427
- }
428
- );
429
- }
430
- function FormFieldset({ config, children, className }) {
431
- return /* @__PURE__ */ jsxs("fieldset", { className: cn("rounded-md border border-border p-4", className), children: [
432
- /* @__PURE__ */ jsx("legend", { className: "px-1 text-sm font-medium", children: config.legend }),
433
- /* @__PURE__ */ jsx(
434
- "div",
435
- {
436
- className: "grid gap-4",
437
- style: { gridTemplateColumns: `repeat(${config.columns ?? 1}, minmax(0, 1fr))` },
438
- children
439
- }
440
- )
441
- ] });
442
- }
443
- function FormGrid({ config, children, className }) {
444
- const columns = typeof config.columns === "number" ? config.columns : config.columns["md"] ?? 1;
445
- return /* @__PURE__ */ jsx(
446
- "div",
447
- {
448
- className: cn("grid", className),
449
- style: {
450
- gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
451
- gap: config.gap ?? "1rem"
452
- },
453
- children
454
- }
455
- );
456
- }
457
- function FormSection({ config, children, className }) {
458
- const [open, setOpen] = useState(!config.collapsed);
459
- const collapsible = config.collapsible === true;
460
- const heading = /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
461
- /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
462
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: config.heading }),
463
- config.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: config.description })
464
- ] }),
465
- collapsible && /* @__PURE__ */ jsx(
466
- "button",
467
- {
468
- type: "button",
469
- "aria-expanded": open,
470
- onClick: () => setOpen((v) => !v),
471
- className: "text-sm text-muted-foreground hover:underline",
472
- children: open ? "Hide" : "Show"
473
- }
474
- )
475
- ] });
476
- const body = open && /* @__PURE__ */ jsx(
477
- "div",
478
- {
479
- className: "grid gap-4",
480
- style: { gridTemplateColumns: `repeat(${config.columns ?? 1}, minmax(0, 1fr))` },
481
- children
482
- }
483
- );
484
- return /* @__PURE__ */ jsx(
485
- "section",
486
- {
487
- "data-arqel-section": "",
488
- className: cn(
489
- "rounded-md border border-border bg-background",
490
- config.compact ? "p-3" : "p-4",
491
- className
492
- ),
493
- children: config.aside ? /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[16rem_1fr]", children: [
494
- /* @__PURE__ */ jsx("div", { children: heading }),
495
- /* @__PURE__ */ jsx("div", { children: body })
496
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
497
- /* @__PURE__ */ jsx("div", { className: "mb-3", children: heading }),
498
- body
499
- ] })
500
- }
501
- );
502
- }
503
- function FormTabs({ config, tabs, className }) {
504
- const [active, setActive] = useState(config.defaultTab ?? tabs[0]?.id ?? null);
505
- const baseId = useId();
506
- const refs = useRef([]);
507
- if (tabs.length === 0 || active === null) return null;
508
- const activeIndex = Math.max(
509
- 0,
510
- tabs.findIndex((t) => t.id === active)
511
- );
512
- const activeTab = tabs[activeIndex];
513
- if (!activeTab) return null;
514
- const focus = (i) => {
515
- const target = tabs[i];
516
- if (!target) return;
517
- setActive(target.id);
518
- refs.current[i]?.focus();
519
- };
520
- const onKeyDown = (e) => {
521
- if (e.key === "ArrowRight") {
522
- e.preventDefault();
523
- focus((activeIndex + 1) % tabs.length);
524
- } else if (e.key === "ArrowLeft") {
525
- e.preventDefault();
526
- focus((activeIndex - 1 + tabs.length) % tabs.length);
527
- } else if (e.key === "Home") {
528
- e.preventDefault();
529
- focus(0);
530
- } else if (e.key === "End") {
531
- e.preventDefault();
532
- focus(tabs.length - 1);
533
- }
534
- };
535
- return /* @__PURE__ */ jsxs("div", { className, children: [
536
- /* @__PURE__ */ jsx(
537
- "div",
538
- {
539
- role: "tablist",
540
- "aria-orientation": config.orientation,
541
- className: "flex gap-1 border-b border-border",
542
- children: tabs.map((tab, i) => {
543
- const selected = tab.id === active;
544
- return /* @__PURE__ */ jsxs(
545
- "button",
546
- {
547
- ref: (el) => {
548
- refs.current[i] = el;
549
- },
550
- type: "button",
551
- role: "tab",
552
- id: `${baseId}-tab-${tab.id}`,
553
- "aria-controls": `${baseId}-panel-${tab.id}`,
554
- "aria-selected": selected,
555
- tabIndex: selected ? 0 : -1,
556
- onClick: () => setActive(tab.id),
557
- onKeyDown,
558
- className: cn(
559
- "-mb-px border-b-2 px-3 py-2 text-sm transition-colors",
560
- selected ? "border-primary font-medium text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
561
- ),
562
- children: [
563
- tab.label,
564
- tab.badge !== void 0 && tab.badge !== null && /* @__PURE__ */ jsx("span", { className: "ml-1 rounded-full bg-muted px-1.5 text-xs", children: tab.badge })
565
- ]
566
- },
567
- tab.id
568
- );
569
- })
570
- }
571
- ),
572
- /* @__PURE__ */ jsx(
573
- "div",
574
- {
575
- role: "tabpanel",
576
- id: `${baseId}-panel-${activeTab.id}`,
577
- "aria-labelledby": `${baseId}-tab-${activeTab.id}`,
578
- className: "pt-4",
579
- children: activeTab.content
580
- }
581
- )
582
- ] });
583
- }
584
- function FormRenderer({
585
- schema,
586
- fields,
587
- values,
588
- errors = {},
589
- onChange,
590
- disabled = false,
591
- className
592
- }) {
593
- const fieldsByName = new Map(fields.map((f) => [f.name, f]));
594
- const renderEntry = (entry, key) => {
595
- if (isFieldEntry(entry)) {
596
- return renderFieldEntry(entry, key);
597
- }
598
- if (isLayoutEntry(entry)) {
599
- return renderLayoutEntry(entry, key);
600
- }
601
- return null;
602
- };
603
- const renderFieldEntry = (entry, key) => {
604
- const field = fieldsByName.get(entry.name);
605
- if (!field) return null;
606
- return /* @__PURE__ */ jsx(
607
- FieldRenderer,
608
- {
609
- field,
610
- value: values[entry.name],
611
- errors: errors[entry.name],
612
- disabled: disabled || schema.disabled === true,
613
- onChange: (value) => onChange(entry.name, value)
614
- },
615
- key
616
- );
617
- };
618
- const renderChildren = (entries) => {
619
- if (!entries) return null;
620
- return entries.map((child, i) => renderEntry(child, `${i}`));
621
- };
622
- const renderLayoutEntry = (entry, key) => {
623
- switch (entry.type) {
624
- case "section":
625
- return /* @__PURE__ */ jsx(FormSection, { config: entry.props, children: renderChildren(entry.schema) }, key);
626
- case "fieldset":
627
- return /* @__PURE__ */ jsx(FormFieldset, { config: entry.props, children: renderChildren(entry.schema) }, key);
628
- case "grid":
629
- case "columns":
630
- return /* @__PURE__ */ jsx(
631
- FormGrid,
632
- {
633
- config: entry.type === "columns" ? { columns: entry.props.columns } : entry.props,
634
- children: renderChildren(entry.schema)
635
- },
636
- key
637
- );
638
- case "group":
639
- return /* @__PURE__ */ jsx(
640
- "div",
641
- {
642
- className: cn(
643
- "flex gap-4",
644
- entry.props.orientation === "vertical" ? "flex-col" : "flex-row"
645
- ),
646
- children: renderChildren(entry.schema)
647
- },
648
- key
649
- );
650
- case "tabs": {
651
- const tabs = (entry.schema ?? []).filter((c) => isLayoutEntry(c) && c.type === "tab").map((tab) => ({
652
- id: tab.props.id,
653
- label: tab.props.label,
654
- badge: tab.props.badge,
655
- content: renderChildren(tab.schema) ?? /* @__PURE__ */ jsx("span", {}, `tab-${tab.props.id}-empty`)
656
- }));
657
- return /* @__PURE__ */ jsx(FormTabs, { config: entry.props, tabs }, key);
658
- }
659
- case "tab":
660
- return null;
661
- }
662
- };
663
- const columns = schema.columns ?? 1;
664
- return /* @__PURE__ */ jsx(
665
- "div",
666
- {
667
- "data-arqel-form": "",
668
- className: cn("grid gap-4", className),
669
- style: columns > 1 ? { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` } : void 0,
670
- children: schema.schema.map((entry, i) => renderEntry(entry, `${i}`))
671
- }
672
- );
673
- }
674
- function Dialog({
675
- ...props
676
- }) {
677
- return /* @__PURE__ */ jsx(Dialog$1.Root, { "data-slot": "dialog", ...props });
678
- }
679
- function DialogPortal({
680
- ...props
681
- }) {
682
- return /* @__PURE__ */ jsx(Dialog$1.Portal, { "data-slot": "dialog-portal", ...props });
683
- }
684
- function DialogClose({
685
- ...props
686
- }) {
687
- return /* @__PURE__ */ jsx(Dialog$1.Close, { "data-slot": "dialog-close", ...props });
688
- }
689
- function DialogOverlay({
690
- className,
691
- ...props
692
- }) {
693
- return /* @__PURE__ */ jsx(
694
- Dialog$1.Overlay,
695
- {
696
- "data-slot": "dialog-overlay",
697
- className: cn(
698
- "fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
699
- className
700
- ),
701
- ...props
702
- }
703
- );
704
- }
705
- function DialogContent({
706
- className,
707
- children,
708
- showCloseButton = true,
709
- ...props
710
- }) {
711
- return /* @__PURE__ */ jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
712
- /* @__PURE__ */ jsx(DialogOverlay, {}),
713
- /* @__PURE__ */ jsxs(
714
- Dialog$1.Content,
715
- {
716
- "data-slot": "dialog-content",
717
- className: cn(
718
- "fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
719
- className
720
- ),
721
- ...props,
722
- children: [
723
- children,
724
- showCloseButton && /* @__PURE__ */ jsxs(
725
- Dialog$1.Close,
726
- {
727
- "data-slot": "dialog-close",
728
- className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
729
- children: [
730
- /* @__PURE__ */ jsx(XIcon, {}),
731
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
732
- ]
733
- }
734
- )
735
- ]
736
- }
737
- )
738
- ] });
739
- }
740
- function DialogHeader({ className, ...props }) {
741
- return /* @__PURE__ */ jsx(
742
- "div",
743
- {
744
- "data-slot": "dialog-header",
745
- className: cn("flex flex-col gap-2 text-center sm:text-left", className),
746
- ...props
747
- }
748
- );
749
- }
750
- function DialogFooter({
751
- className,
752
- showCloseButton = false,
753
- children,
754
- ...props
755
- }) {
756
- return /* @__PURE__ */ jsxs(
757
- "div",
758
- {
759
- "data-slot": "dialog-footer",
760
- className: cn(
761
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
762
- className
763
- ),
764
- ...props,
765
- children: [
766
- children,
767
- showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", children: "Close" }) })
768
- ]
769
- }
770
- );
771
- }
772
- function DialogTitle({
773
- className,
774
- ...props
775
- }) {
776
- return /* @__PURE__ */ jsx(
777
- Dialog$1.Title,
778
- {
779
- "data-slot": "dialog-title",
780
- className: cn("text-lg leading-none font-semibold", className),
781
- ...props
782
- }
783
- );
784
- }
785
- function DialogDescription({
786
- className,
787
- ...props
788
- }) {
789
- return /* @__PURE__ */ jsx(
790
- Dialog$1.Description,
791
- {
792
- "data-slot": "dialog-description",
793
- className: cn("text-sm text-muted-foreground", className),
794
- ...props
795
- }
796
- );
797
- }
798
- var SIZE_CLASSES = {
799
- sm: "sm:max-w-[24rem]",
800
- md: "sm:max-w-[32rem]",
801
- lg: "sm:max-w-[44rem]",
802
- xl: "sm:max-w-[56rem]",
803
- full: "sm:max-w-[90vw] h-[90vh]"
804
- };
805
- function ActionFormModal({
806
- open,
807
- onOpenChange,
808
- action,
809
- fields,
810
- onSubmit,
811
- processing = false,
812
- errors = {},
813
- initialValues = {}
814
- }) {
815
- const [values, setValues] = useState(initialValues);
816
- const size = action.modalSize ?? "md";
817
- const schema = useMemo(
818
- () => ({
819
- schema: (action.form ?? []).map((entry) => ({
820
- kind: "field",
821
- name: entry.name,
822
- type: entry.type
823
- })),
824
- columns: 1,
825
- model: null,
826
- inline: false,
827
- disabled: false
828
- }),
829
- [action.form]
830
- );
831
- return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => onOpenChange(next), children: /* @__PURE__ */ jsxs(DialogContent, { className: cn("overflow-auto", SIZE_CLASSES[size]), children: [
832
- /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: action.label }) }),
833
- /* @__PURE__ */ jsxs(
834
- "form",
835
- {
836
- className: "flex flex-col gap-4",
837
- onSubmit: (e) => {
838
- e.preventDefault();
839
- onSubmit(values);
840
- },
841
- children: [
842
- /* @__PURE__ */ jsx(
843
- FormRenderer,
844
- {
845
- schema,
846
- fields,
847
- values,
848
- errors,
849
- onChange: (name, value) => setValues((prev) => ({ ...prev, [name]: value })),
850
- disabled: processing
851
- }
852
- ),
853
- /* @__PURE__ */ jsx(
854
- FormActions,
855
- {
856
- processing,
857
- submitLabel: action.label,
858
- extra: /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", disabled: processing, children: "Cancel" }) })
859
- }
860
- )
861
- ]
862
- }
863
- )
864
- ] }) });
865
- }
866
- var COLOR_VARIANT = {
867
- destructive: "destructive",
868
- warning: "default",
869
- info: "default"
870
- };
871
- function ConfirmDialog({
872
- open,
873
- onOpenChange,
874
- config,
875
- onConfirm,
876
- processing = false
877
- }) {
878
- const [typed, setTyped] = useState("");
879
- useEffect(() => {
880
- if (!open) setTyped("");
881
- }, [open]);
882
- const requiresText = config?.requiresText;
883
- const submitDisabled = processing || requiresText !== void 0 && typed !== requiresText;
884
- const variant = COLOR_VARIANT[config?.color ?? "destructive"];
885
- const submitLabel = config?.submitLabel ?? (config?.color === "info" ? "Confirm" : "Delete");
886
- const cancelLabel = config?.cancelLabel ?? "Cancel";
887
- return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => onOpenChange(next), children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-[28rem]", showCloseButton: false, children: [
888
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
889
- /* @__PURE__ */ jsx(DialogTitle, { children: config?.heading ?? "Are you sure?" }),
890
- config?.description && /* @__PURE__ */ jsx(DialogDescription, { children: config.description })
891
- ] }),
892
- requiresText !== void 0 && /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
893
- /* @__PURE__ */ jsxs("span", { children: [
894
- "Type ",
895
- /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: requiresText }),
896
- " to confirm"
897
- ] }),
898
- /* @__PURE__ */ jsx(
899
- "input",
900
- {
901
- type: "text",
902
- ref: (el) => el?.focus(),
903
- value: typed,
904
- onChange: (e) => setTyped(e.target.value),
905
- onKeyDown: (e) => {
906
- if (e.key === "Enter" && !submitDisabled) {
907
- e.preventDefault();
908
- onConfirm();
909
- }
910
- },
911
- className: "h-9 rounded-md border border-input bg-background px-3 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
912
- }
913
- )
914
- ] }),
915
- /* @__PURE__ */ jsxs(DialogFooter, { children: [
916
- /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", disabled: processing, children: cancelLabel }) }),
917
- /* @__PURE__ */ jsx(Button, { type: "button", variant, disabled: submitDisabled, onClick: onConfirm, children: processing ? "Working\u2026" : submitLabel })
918
- ] })
919
- ] }) });
920
- }
921
- var COLOR_TO_VARIANT = {
922
- primary: "default",
923
- secondary: "outline",
924
- destructive: "destructive",
925
- success: "default",
926
- warning: "default",
927
- info: "default"
928
- };
929
- var VARIANT_OVERRIDE = {
930
- default: "default",
931
- outline: "outline",
932
- ghost: "ghost",
933
- destructive: "destructive"
934
- };
935
- function ActionButton({
936
- action,
937
- formFields = [],
938
- onInvoke,
939
- processing = false,
940
- errors,
941
- size = "default",
942
- className
943
- }) {
944
- const [confirmOpen, setConfirmOpen] = useState(false);
945
- const [formOpen, setFormOpen] = useState(false);
946
- const variant = VARIANT_OVERRIDE[action.variant] ?? COLOR_TO_VARIANT[action.color];
947
- const handleClick = () => {
948
- if (action.disabled) return;
949
- if (action.requiresConfirmation) {
950
- setConfirmOpen(true);
951
- } else if (action.form && action.form.length > 0) {
952
- setFormOpen(true);
953
- } else {
954
- onInvoke();
955
- }
956
- };
957
- const handleConfirm = () => {
958
- setConfirmOpen(false);
959
- if (action.form && action.form.length > 0) {
960
- setFormOpen(true);
961
- } else {
962
- onInvoke();
963
- }
964
- };
965
- const handleFormSubmit = (values) => {
966
- onInvoke(values);
967
- setFormOpen(false);
968
- };
969
- return /* @__PURE__ */ jsxs(Fragment, { children: [
970
- /* @__PURE__ */ jsx(
971
- Button,
972
- {
973
- variant,
974
- size,
975
- disabled: action.disabled === true || processing,
976
- title: action.tooltip,
977
- onClick: handleClick,
978
- className,
979
- children: action.label
980
- }
981
- ),
982
- action.requiresConfirmation && /* @__PURE__ */ jsx(
983
- ConfirmDialog,
984
- {
985
- open: confirmOpen,
986
- onOpenChange: setConfirmOpen,
987
- config: action.confirmation,
988
- onConfirm: handleConfirm,
989
- processing
990
- }
991
- ),
992
- action.form && action.form.length > 0 && /* @__PURE__ */ jsx(
993
- ActionFormModal,
994
- {
995
- open: formOpen,
996
- onOpenChange: setFormOpen,
997
- action,
998
- fields: formFields,
999
- onSubmit: handleFormSubmit,
1000
- processing,
1001
- errors
1002
- }
1003
- )
1004
- ] });
1005
- }
1006
- function DropdownMenu({
1007
- ...props
1008
- }) {
1009
- return /* @__PURE__ */ jsx(DropdownMenu$1.Root, { "data-slot": "dropdown-menu", ...props });
1010
- }
1011
- function DropdownMenuTrigger({
1012
- ...props
1013
- }) {
1014
- return /* @__PURE__ */ jsx(
1015
- DropdownMenu$1.Trigger,
1016
- {
1017
- "data-slot": "dropdown-menu-trigger",
1018
- ...props
1019
- }
1020
- );
1021
- }
1022
- function DropdownMenuContent({
1023
- className,
1024
- sideOffset = 4,
1025
- ...props
1026
- }) {
1027
- return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, { children: /* @__PURE__ */ jsx(
1028
- DropdownMenu$1.Content,
1029
- {
1030
- "data-slot": "dropdown-menu-content",
1031
- sideOffset,
1032
- className: cn(
1033
- "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
1034
- className
1035
- ),
1036
- ...props
1037
- }
1038
- ) });
1039
- }
1040
- function DropdownMenuItem({
1041
- className,
1042
- inset,
1043
- variant = "default",
1044
- ...props
1045
- }) {
1046
- return /* @__PURE__ */ jsx(
1047
- DropdownMenu$1.Item,
1048
- {
1049
- "data-slot": "dropdown-menu-item",
1050
- "data-inset": inset,
1051
- "data-variant": variant,
1052
- className: cn(
1053
- "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
1054
- className
1055
- ),
1056
- ...props
1057
- }
1058
- );
1059
- }
1060
- function ActionMenu({
1061
- actions,
1062
- formFieldsByAction = {},
1063
- onInvoke,
1064
- inlineThreshold = 3,
1065
- processing = false,
1066
- trigger,
1067
- className
1068
- }) {
1069
- if (actions.length === 0) return null;
1070
- if (actions.length <= inlineThreshold) {
1071
- return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap items-center gap-1", className), children: actions.map((action) => /* @__PURE__ */ jsx(
1072
- ActionButton,
1073
- {
1074
- action,
1075
- formFields: formFieldsByAction[action.name] ?? [],
1076
- onInvoke: (values) => onInvoke(action, values),
1077
- processing,
1078
- size: "sm"
1079
- },
1080
- action.name
1081
- )) });
1082
- }
1083
- return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1084
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: trigger ?? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Actions", children: "\u22EF" }) }),
1085
- /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", sideOffset: 6, className: cn("min-w-[12rem]", className), children: actions.map((action) => /* @__PURE__ */ jsx(
1086
- DropdownMenuItem,
1087
- {
1088
- disabled: action.disabled === true,
1089
- variant: action.color === "destructive" ? "destructive" : "default",
1090
- onSelect: () => {
1091
- if (action.disabled) return;
1092
- onInvoke(action);
1093
- },
1094
- children: action.label
1095
- },
1096
- action.name
1097
- )) })
1098
- ] });
1099
- }
1100
- function CanAccess({ ability, record, children, fallback = null }) {
1101
- const allowed = useCanAccess(ability, record);
1102
- return /* @__PURE__ */ jsx(Fragment, { children: allowed ? children : fallback });
1103
- }
1104
- var KIND_CLASSES = {
1105
- success: "border-[var(--chart-2)]/60 bg-[var(--chart-2)]/15 text-foreground",
1106
- error: "border-destructive/60 bg-destructive/15 text-foreground",
1107
- warning: "border-[var(--chart-4)]/60 bg-[var(--chart-4)]/15 text-foreground",
1108
- info: "border-border bg-muted text-foreground"
1109
- };
1110
- var KIND_GLYPH = {
1111
- success: "\u2713",
1112
- error: "\u2717",
1113
- warning: "!",
1114
- info: "i"
1115
- };
1116
- function FlashToast({
1117
- kind,
1118
- message,
1119
- onDismiss,
1120
- durationMs = 5e3,
1121
- className
1122
- }) {
1123
- useEffect(() => {
1124
- if (durationMs <= 0) return void 0;
1125
- const id = window.setTimeout(onDismiss, durationMs);
1126
- return () => window.clearTimeout(id);
1127
- }, [durationMs, onDismiss]);
1128
- return /* @__PURE__ */ jsxs(
1129
- "div",
1130
- {
1131
- role: kind === "error" ? "alert" : "status",
1132
- "aria-live": kind === "error" ? "assertive" : "polite",
1133
- className: cn(
1134
- "pointer-events-auto flex items-start gap-3 rounded-md border px-4 py-3 shadow-md",
1135
- "min-w-[18rem] max-w-md text-sm",
1136
- KIND_CLASSES[kind],
1137
- className
1138
- ),
1139
- children: [
1140
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "font-bold leading-5", children: KIND_GLYPH[kind] }),
1141
- /* @__PURE__ */ jsx("span", { className: "flex-1", children: message }),
1142
- /* @__PURE__ */ jsx(
1143
- "button",
1144
- {
1145
- type: "button",
1146
- "aria-label": "Dismiss",
1147
- onClick: onDismiss,
1148
- className: "text-muted-foreground hover:text-foreground",
1149
- children: "\u2715"
1150
- }
1151
- )
1152
- ]
1153
- }
1154
- );
1155
- }
1156
- var POSITION_CLASSES = {
1157
- "top-right": "top-4 right-4 items-end",
1158
- "top-left": "top-4 left-4 items-start",
1159
- "bottom-right": "bottom-4 right-4 items-end",
1160
- "bottom-left": "bottom-4 left-4 items-start"
1161
- };
1162
- var nextId = 0;
1163
- function FlashContainer({
1164
- position = "top-right",
1165
- durationMs = 5e3,
1166
- className
1167
- }) {
1168
- const [toasts, setToasts] = useState([]);
1169
- const handleMessage = useCallback((kind, message) => {
1170
- setToasts((prev) => [...prev, { id: ++nextId, kind, message }]);
1171
- }, []);
1172
- useFlash({ onMessage: handleMessage });
1173
- const dismiss = (id) => setToasts((prev) => prev.filter((t) => t.id !== id));
1174
- return /* @__PURE__ */ jsx(
1175
- "div",
1176
- {
1177
- "data-arqel-flash": "",
1178
- className: cn(
1179
- "pointer-events-none fixed z-50 flex flex-col gap-2",
1180
- POSITION_CLASSES[position],
1181
- className
1182
- ),
1183
- children: toasts.map((toast) => /* @__PURE__ */ jsx(
1184
- FlashToast,
1185
- {
1186
- kind: toast.kind,
1187
- message: toast.message,
1188
- durationMs,
1189
- onDismiss: () => dismiss(toast.id)
1190
- },
1191
- toast.id
1192
- ))
1193
- }
1194
- );
1195
- }
1196
- var RECENT_KEY = "arqel:cmdpal:recent";
1197
- var RECENT_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
1198
- var RECENT_LIMIT = 5;
1199
- var DEBOUNCE_MS = 150;
1200
- function readRecents() {
1201
- try {
1202
- const raw = globalThis.localStorage?.getItem(RECENT_KEY);
1203
- if (!raw) return [];
1204
- const parsed = JSON.parse(raw);
1205
- if (!Array.isArray(parsed)) return [];
1206
- const now = Date.now();
1207
- return parsed.filter(
1208
- (e) => typeof e === "object" && e !== null && typeof e.id === "string" && typeof e.count === "number" && typeof e.lastUsed === "number" && now - e.lastUsed <= RECENT_WINDOW_MS && typeof e.command === "object"
1209
- );
1210
- } catch {
1211
- return [];
1212
- }
1213
- }
1214
- function writeRecents(entries) {
1215
- try {
1216
- globalThis.localStorage?.setItem(RECENT_KEY, JSON.stringify(entries));
1217
- } catch {
1218
- }
1219
- }
1220
- function recordUsage(command) {
1221
- const entries = readRecents();
1222
- const now = Date.now();
1223
- const idx = entries.findIndex((e) => e.id === command.id);
1224
- if (idx >= 0) {
1225
- const existing = entries[idx];
1226
- if (existing) {
1227
- entries[idx] = {
1228
- ...existing,
1229
- count: existing.count + 1,
1230
- lastUsed: now,
1231
- command
1232
- };
1233
- }
1234
- } else {
1235
- entries.push({ id: command.id, count: 1, lastUsed: now, command });
1236
- }
1237
- const fresh = entries.filter((e) => now - e.lastUsed <= RECENT_WINDOW_MS);
1238
- writeRecents(fresh);
1239
- }
1240
- function topRecents() {
1241
- return readRecents().slice().sort((a, b) => b.count === a.count ? b.lastUsed - a.lastUsed : b.count - a.count).slice(0, RECENT_LIMIT).map((e) => e.command);
1242
- }
1243
- function groupByCategory(commands) {
1244
- const order = [];
1245
- const buckets = /* @__PURE__ */ new Map();
1246
- for (const cmd of commands) {
1247
- const key = cmd.category ?? "General";
1248
- if (!buckets.has(key)) {
1249
- buckets.set(key, []);
1250
- order.push(key);
1251
- }
1252
- buckets.get(key)?.push(cmd);
1253
- }
1254
- return order.map((category) => ({ category, commands: buckets.get(category) ?? [] }));
1255
- }
1256
- function flattenGroups(groups) {
1257
- return groups.flatMap((g) => g.commands);
1258
- }
1259
- var dialogStyle = {
1260
- border: "none",
1261
- borderRadius: "0.75rem",
1262
- padding: 0,
1263
- width: "min(640px, 92vw)",
1264
- maxHeight: "70vh",
1265
- background: "var(--background, #fff)",
1266
- color: "var(--foreground, #111)"
1267
- };
1268
- function CommandPalette({ endpoint = "/admin/commands" }) {
1269
- const dialogRef = useRef(null);
1270
- const inputRef = useRef(null);
1271
- const debounceTimer = useRef(null);
1272
- const [open, setOpen] = useState(false);
1273
- const [query, setQuery] = useState("");
1274
- const [results, setResults] = useState([]);
1275
- const [recents, setRecents] = useState([]);
1276
- const [highlight, setHighlight] = useState(0);
1277
- const titleId = useId();
1278
- const hintId = useId();
1279
- const listId = useId();
1280
- const optBaseId = useId();
1281
- const visibleCommands = query.trim().length === 0 ? recents : results;
1282
- const groups = query.trim().length === 0 && recents.length > 0 ? [{ category: "Recent", commands: recents }] : groupByCategory(visibleCommands);
1283
- const flat = flattenGroups(groups);
1284
- useEffect(() => {
1285
- function onKey(event) {
1286
- const isToggle = (event.key === "k" || event.key === "K") && (event.metaKey || event.ctrlKey);
1287
- if (!isToggle) return;
1288
- event.preventDefault();
1289
- setOpen((prev) => !prev);
1290
- }
1291
- window.addEventListener("keydown", onKey);
1292
- return () => window.removeEventListener("keydown", onKey);
1293
- }, []);
1294
- useEffect(() => {
1295
- const dialog = dialogRef.current;
1296
- if (!dialog) return;
1297
- if (open && !dialog.open) {
1298
- try {
1299
- dialog.showModal();
1300
- } catch {
1301
- }
1302
- setRecents(topRecents());
1303
- setQuery("");
1304
- setResults([]);
1305
- setHighlight(0);
1306
- queueMicrotask(() => inputRef.current?.focus());
1307
- } else if (!open && dialog.open) {
1308
- dialog.close();
1309
- }
1310
- }, [open]);
1311
- useEffect(() => {
1312
- if (!open) return;
1313
- if (query.trim().length === 0) {
1314
- setResults([]);
1315
- return;
1316
- }
1317
- if (debounceTimer.current) clearTimeout(debounceTimer.current);
1318
- debounceTimer.current = setTimeout(() => {
1319
- const url = `${endpoint}?q=${encodeURIComponent(query)}`;
1320
- fetch(url, { headers: { Accept: "application/json" } }).then((res) => res.ok ? res.json() : { commands: [] }).then((data) => {
1321
- setResults(Array.isArray(data?.commands) ? data.commands : []);
1322
- setHighlight(0);
1323
- }).catch(() => {
1324
- setResults([]);
1325
- });
1326
- }, DEBOUNCE_MS);
1327
- return () => {
1328
- if (debounceTimer.current) clearTimeout(debounceTimer.current);
1329
- };
1330
- }, [query, open, endpoint]);
1331
- const close = useCallback(() => setOpen(false), []);
1332
- const execute = useCallback((command) => {
1333
- recordUsage(command);
1334
- setOpen(false);
1335
- window.location.assign(command.url);
1336
- }, []);
1337
- const onKeyDown = useCallback(
1338
- (event) => {
1339
- if (event.key === "Escape") {
1340
- event.preventDefault();
1341
- close();
1342
- return;
1343
- }
1344
- if (flat.length === 0) return;
1345
- if (event.key === "ArrowDown") {
1346
- event.preventDefault();
1347
- setHighlight((h) => (h + 1) % flat.length);
1348
- } else if (event.key === "ArrowUp") {
1349
- event.preventDefault();
1350
- setHighlight((h) => (h - 1 + flat.length) % flat.length);
1351
- } else if (event.key === "Enter") {
1352
- event.preventDefault();
1353
- const target = flat[highlight];
1354
- if (target) execute(target);
1355
- }
1356
- },
1357
- [flat, highlight, close, execute]
1358
- );
1359
- const onDialogClick = useCallback(
1360
- (event) => {
1361
- if (event.target === dialogRef.current) close();
1362
- },
1363
- [close]
1364
- );
1365
- const optionId = (idx) => `${optBaseId}-opt-${idx}`;
1366
- const activeOptionId = flat.length > 0 ? optionId(highlight) : void 0;
1367
- return (
1368
- // biome-ignore lint/a11y/useKeyWithClickEvents: dialog backdrop click closes; Escape (onCancel) handles keyboard close
1369
- /* @__PURE__ */ jsx(
1370
- "dialog",
1371
- {
1372
- ref: dialogRef,
1373
- "aria-labelledby": titleId,
1374
- "aria-describedby": hintId,
1375
- onClick: onDialogClick,
1376
- onClose: () => setOpen(false),
1377
- onCancel: (event) => {
1378
- event.preventDefault();
1379
- close();
1380
- },
1381
- style: dialogStyle,
1382
- className: "backdrop:bg-black/40",
1383
- "data-arqel-cmdpal": "",
1384
- children: /* @__PURE__ */ jsxs("div", { onKeyDown, className: "flex flex-col", children: [
1385
- /* @__PURE__ */ jsx("h2", { id: titleId, className: "sr-only", children: "Command palette" }),
1386
- /* @__PURE__ */ jsx(
1387
- "input",
1388
- {
1389
- ref: inputRef,
1390
- type: "text",
1391
- role: "combobox",
1392
- "aria-expanded": open,
1393
- "aria-controls": listId,
1394
- "aria-autocomplete": "list",
1395
- ...activeOptionId ? { "aria-activedescendant": activeOptionId } : {},
1396
- placeholder: "Type a command\u2026",
1397
- value: query,
1398
- onChange: (e) => setQuery(e.target.value),
1399
- className: "w-full border-b border-[var(--border,#e5e7eb)] bg-transparent px-4 py-3 text-base outline-none"
1400
- }
1401
- ),
1402
- /* @__PURE__ */ jsxs("div", { role: "status", "aria-live": "polite", className: "sr-only", children: [
1403
- flat.length,
1404
- " commands"
1405
- ] }),
1406
- /* @__PURE__ */ jsxs(
1407
- "ul",
1408
- {
1409
- id: listId,
1410
- role: "listbox",
1411
- "aria-label": "Commands",
1412
- className: "max-h-[50vh] overflow-y-auto py-2",
1413
- children: [
1414
- groups.length === 0 && /* @__PURE__ */ jsx("li", { role: "presentation", className: "px-4 py-3 text-sm opacity-60", children: "No commands found" }),
1415
- groups.map((group) => {
1416
- const startIdx = flat.indexOf(group.commands[0] ?? {});
1417
- return /* @__PURE__ */ jsxs("li", { role: "presentation", children: [
1418
- /* @__PURE__ */ jsx(
1419
- "div",
1420
- {
1421
- role: "presentation",
1422
- className: "px-4 pt-2 pb-1 text-xs font-semibold uppercase tracking-wide opacity-60",
1423
- children: group.category
1424
- }
1425
- ),
1426
- /* @__PURE__ */ jsx("ul", { role: "presentation", className: "m-0 list-none p-0", children: group.commands.map((cmd, j) => {
1427
- const idx = startIdx + j;
1428
- const isActive = idx === highlight;
1429
- return (
1430
- // biome-ignore lint/a11y/useFocusableInteractive: focus stays on combobox; selection via aria-activedescendant
1431
- // biome-ignore lint/a11y/useKeyWithClickEvents: keyboard nav handled by combobox input ArrowUp/Down/Enter handler
1432
- /* @__PURE__ */ jsxs(
1433
- "li",
1434
- {
1435
- id: optionId(idx),
1436
- role: "option",
1437
- "aria-selected": isActive,
1438
- "data-active": isActive ? "" : void 0,
1439
- onMouseEnter: () => setHighlight(idx),
1440
- onClick: () => execute(cmd),
1441
- className: "flex cursor-pointer items-center gap-3 px-4 py-2 text-sm " + (isActive ? "bg-[var(--accent,#f1f5f9)]" : ""),
1442
- children: [
1443
- /* @__PURE__ */ jsx("span", { className: "flex-1", children: cmd.label }),
1444
- cmd.description ? /* @__PURE__ */ jsx("span", { className: "text-xs opacity-60", children: cmd.description }) : null
1445
- ]
1446
- },
1447
- cmd.id
1448
- )
1449
- );
1450
- }) })
1451
- ] }, group.category);
1452
- })
1453
- ]
1454
- }
1455
- ),
1456
- /* @__PURE__ */ jsxs(
1457
- "footer",
1458
- {
1459
- id: hintId,
1460
- className: "flex items-center gap-3 border-t border-[var(--border,#e5e7eb)] px-4 py-2 text-xs opacity-70",
1461
- children: [
1462
- /* @__PURE__ */ jsx("span", { children: "\u2191\u2193 navigate" }),
1463
- /* @__PURE__ */ jsx("span", { children: "\u21B5 select" }),
1464
- /* @__PURE__ */ jsx("span", { children: "esc close" })
1465
- ]
1466
- }
1467
- )
1468
- ] })
1469
- }
1470
- )
1471
- );
1472
- }
1473
- var alertVariants = cva(
1474
- "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
1475
- {
1476
- variants: {
1477
- variant: {
1478
- default: "bg-card text-card-foreground",
1479
- destructive: "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current"
1480
- }
1481
- },
1482
- defaultVariants: {
1483
- variant: "default"
1484
- }
1485
- }
1486
- );
1487
- function Alert({
1488
- className,
1489
- variant,
1490
- ...props
1491
- }) {
1492
- return /* @__PURE__ */ jsx(
1493
- "div",
1494
- {
1495
- "data-slot": "alert",
1496
- role: "alert",
1497
- className: cn(alertVariants({ variant }), className),
1498
- ...props
1499
- }
1500
- );
1501
- }
1502
- function AlertTitle({ className, ...props }) {
1503
- return /* @__PURE__ */ jsx(
1504
- "div",
1505
- {
1506
- "data-slot": "alert-title",
1507
- className: cn(
1508
- "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
1509
- className
1510
- ),
1511
- ...props
1512
- }
1513
- );
1514
- }
1515
- function AlertDescription({
1516
- className,
1517
- ...props
1518
- }) {
1519
- return /* @__PURE__ */ jsx(
1520
- "div",
1521
- {
1522
- "data-slot": "alert-description",
1523
- className: cn(
1524
- "col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed",
1525
- className
1526
- ),
1527
- ...props
1528
- }
1529
- );
1530
- }
1531
- var badgeVariants = cva(
1532
- "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
1533
- {
1534
- variants: {
1535
- variant: {
1536
- default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
1537
- secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
1538
- destructive: "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
1539
- outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
1540
- ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
1541
- link: "text-primary underline-offset-4 [a&]:hover:underline"
1542
- }
1543
- },
1544
- defaultVariants: {
1545
- variant: "default"
1546
- }
1547
- }
1548
- );
1549
- function Badge({
1550
- className,
1551
- variant = "default",
1552
- asChild = false,
1553
- ...props
1554
- }) {
1555
- const Comp = asChild ? Slot.Root : "span";
1556
- return /* @__PURE__ */ jsx(
1557
- Comp,
1558
- {
1559
- "data-slot": "badge",
1560
- "data-variant": variant,
1561
- className: cn(badgeVariants({ variant }), className),
1562
- ...props
1563
- }
1564
- );
1565
- }
1566
- function Card({ className, ...props }) {
1567
- return /* @__PURE__ */ jsx(
1568
- "div",
1569
- {
1570
- "data-slot": "card",
1571
- className: cn(
1572
- "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
1573
- className
1574
- ),
1575
- ...props
1576
- }
1577
- );
1578
- }
1579
- function CardHeader({ className, ...props }) {
1580
- return /* @__PURE__ */ jsx(
1581
- "div",
1582
- {
1583
- "data-slot": "card-header",
1584
- className: cn(
1585
- "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
1586
- className
1587
- ),
1588
- ...props
1589
- }
1590
- );
1591
- }
1592
- function CardTitle({ className, ...props }) {
1593
- return /* @__PURE__ */ jsx(
1594
- "div",
1595
- {
1596
- "data-slot": "card-title",
1597
- className: cn("leading-none font-semibold", className),
1598
- ...props
1599
- }
1600
- );
1601
- }
1602
- function CardDescription({ className, ...props }) {
1603
- return /* @__PURE__ */ jsx(
1604
- "div",
1605
- {
1606
- "data-slot": "card-description",
1607
- className: cn("text-sm text-muted-foreground", className),
1608
- ...props
1609
- }
1610
- );
1611
- }
1612
- function CardAction({ className, ...props }) {
1613
- return /* @__PURE__ */ jsx(
1614
- "div",
1615
- {
1616
- "data-slot": "card-action",
1617
- className: cn(
1618
- "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
1619
- className
1620
- ),
1621
- ...props
1622
- }
1623
- );
1624
- }
1625
- function CardContent({ className, ...props }) {
1626
- return /* @__PURE__ */ jsx(
1627
- "div",
1628
- {
1629
- "data-slot": "card-content",
1630
- className: cn("px-6", className),
1631
- ...props
1632
- }
1633
- );
1634
- }
1635
- function CardFooter({ className, ...props }) {
1636
- return /* @__PURE__ */ jsx(
1637
- "div",
1638
- {
1639
- "data-slot": "card-footer",
1640
- className: cn("flex items-center px-6 [.border-t]:pt-6", className),
1641
- ...props
1642
- }
1643
- );
1644
- }
1645
- function Checkbox2({
1646
- className,
1647
- ...props
1648
- }) {
1649
- return /* @__PURE__ */ jsx(
1650
- Checkbox$1.Root,
1651
- {
1652
- "data-slot": "checkbox",
1653
- className: cn(
1654
- "peer size-4 shrink-0 rounded-[4px] border border-input shadow-xs transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary",
1655
- className
1656
- ),
1657
- ...props,
1658
- children: /* @__PURE__ */ jsx(
1659
- Checkbox$1.Indicator,
1660
- {
1661
- "data-slot": "checkbox-indicator",
1662
- className: "grid place-content-center text-current transition-none",
1663
- children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5" })
1664
- }
1665
- )
1666
- }
1667
- );
1668
- }
1669
- function Label({
1670
- className,
1671
- ...props
1672
- }) {
1673
- return /* @__PURE__ */ jsx(
1674
- Label$1.Root,
1675
- {
1676
- "data-slot": "label",
1677
- className: cn(
1678
- "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
1679
- className
1680
- ),
1681
- ...props
1682
- }
1683
- );
1684
- }
1685
- function Separator({
1686
- className,
1687
- orientation = "horizontal",
1688
- decorative = true,
1689
- ...props
1690
- }) {
1691
- return /* @__PURE__ */ jsx(
1692
- Separator$1.Root,
1693
- {
1694
- "data-slot": "separator",
1695
- decorative,
1696
- orientation,
1697
- className: cn(
1698
- "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
1699
- className
1700
- ),
1701
- ...props
1702
- }
1703
- );
1704
- }
1705
- function FieldSet({ className, ...props }) {
1706
- return /* @__PURE__ */ jsx(
1707
- "fieldset",
1708
- {
1709
- "data-slot": "field-set",
1710
- className: cn(
1711
- "flex flex-col gap-6",
1712
- "has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
1713
- className
1714
- ),
1715
- ...props
1716
- }
1717
- );
1718
- }
1719
- function FieldLegend({
1720
- className,
1721
- variant = "legend",
1722
- ...props
1723
- }) {
1724
- return /* @__PURE__ */ jsx(
1725
- "legend",
1726
- {
1727
- "data-slot": "field-legend",
1728
- "data-variant": variant,
1729
- className: cn(
1730
- "mb-3 font-medium",
1731
- "data-[variant=legend]:text-base",
1732
- "data-[variant=label]:text-sm",
1733
- className
1734
- ),
1735
- ...props
1736
- }
1737
- );
1738
- }
1739
- function FieldGroup({ className, ...props }) {
1740
- return /* @__PURE__ */ jsx(
1741
- "div",
1742
- {
1743
- "data-slot": "field-group",
1744
- className: cn(
1745
- "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
1746
- className
1747
- ),
1748
- ...props
1749
- }
1750
- );
1751
- }
1752
- var fieldVariants = cva(
1753
- "group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
1754
- {
1755
- variants: {
1756
- orientation: {
1757
- vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
1758
- horizontal: [
1759
- "flex-row items-center",
1760
- "[&>[data-slot=field-label]]:flex-auto",
1761
- "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
1762
- ],
1763
- responsive: [
1764
- "flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
1765
- "@md/field-group:[&>[data-slot=field-label]]:flex-auto",
1766
- "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
1767
- ]
1768
- }
1769
- },
1770
- defaultVariants: {
1771
- orientation: "vertical"
1772
- }
1773
- }
1774
- );
1775
- function Field({
1776
- className,
1777
- orientation = "vertical",
1778
- ...props
1779
- }) {
1780
- return /* @__PURE__ */ jsx(
1781
- "div",
1782
- {
1783
- role: "group",
1784
- "data-slot": "field",
1785
- "data-orientation": orientation,
1786
- className: cn(fieldVariants({ orientation }), className),
1787
- ...props
1788
- }
1789
- );
1790
- }
1791
- function FieldContent({ className, ...props }) {
1792
- return /* @__PURE__ */ jsx(
1793
- "div",
1794
- {
1795
- "data-slot": "field-content",
1796
- className: cn(
1797
- "group/field-content flex flex-1 flex-col gap-1.5 leading-snug",
1798
- className
1799
- ),
1800
- ...props
1801
- }
1802
- );
1803
- }
1804
- function FieldLabel({
1805
- className,
1806
- ...props
1807
- }) {
1808
- return /* @__PURE__ */ jsx(
1809
- Label,
1810
- {
1811
- "data-slot": "field-label",
1812
- className: cn(
1813
- "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
1814
- "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
1815
- "has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10",
1816
- className
1817
- ),
1818
- ...props
1819
- }
1820
- );
1821
- }
1822
- function FieldTitle({ className, ...props }) {
1823
- return /* @__PURE__ */ jsx(
1824
- "div",
1825
- {
1826
- "data-slot": "field-label",
1827
- className: cn(
1828
- "flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
1829
- className
1830
- ),
1831
- ...props
1832
- }
1833
- );
1834
- }
1835
- function FieldDescription({ className, ...props }) {
1836
- return /* @__PURE__ */ jsx(
1837
- "p",
1838
- {
1839
- "data-slot": "field-description",
1840
- className: cn(
1841
- "text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
1842
- "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
1843
- "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
1844
- className
1845
- ),
1846
- ...props
1847
- }
1848
- );
1849
- }
1850
- function FieldSeparator({
1851
- children,
1852
- className,
1853
- ...props
1854
- }) {
1855
- return /* @__PURE__ */ jsxs(
1856
- "div",
1857
- {
1858
- "data-slot": "field-separator",
1859
- "data-content": !!children,
1860
- className: cn(
1861
- "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
1862
- className
1863
- ),
1864
- ...props,
1865
- children: [
1866
- /* @__PURE__ */ jsx(Separator, { className: "absolute inset-0 top-1/2" }),
1867
- children && /* @__PURE__ */ jsx(
1868
- "span",
1869
- {
1870
- className: "relative mx-auto block w-fit bg-background px-2 text-muted-foreground",
1871
- "data-slot": "field-separator-content",
1872
- children
1873
- }
1874
- )
1875
- ]
1876
- }
1877
- );
1878
- }
1879
- function FieldError({
1880
- className,
1881
- children,
1882
- errors,
1883
- ...props
1884
- }) {
1885
- const content = useMemo(() => {
1886
- if (children) {
1887
- return children;
1888
- }
1889
- if (!errors?.length) {
1890
- return null;
1891
- }
1892
- const uniqueErrors = [
1893
- ...new Map(errors.map((error) => [error?.message, error])).values()
1894
- ];
1895
- if (uniqueErrors?.length == 1) {
1896
- return uniqueErrors[0]?.message;
1897
- }
1898
- return /* @__PURE__ */ jsx("ul", { className: "ml-4 flex list-disc flex-col gap-1", children: uniqueErrors.map(
1899
- (error, index) => error?.message && /* @__PURE__ */ jsx("li", { children: error.message }, index)
1900
- ) });
1901
- }, [children, errors]);
1902
- if (!content) {
1903
- return null;
1904
- }
1905
- return /* @__PURE__ */ jsx(
1906
- "div",
1907
- {
1908
- role: "alert",
1909
- "data-slot": "field-error",
1910
- className: cn("text-sm font-normal text-destructive", className),
1911
- ...props,
1912
- children: content
1913
- }
1914
- );
1915
- }
1916
- function Input({ className, type, ...props }) {
1917
- return /* @__PURE__ */ jsx(
1918
- "input",
1919
- {
1920
- type,
1921
- "data-slot": "input",
1922
- className: cn(
1923
- "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
1924
- "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
1925
- "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
1926
- className
1927
- ),
1928
- ...props
1929
- }
1930
- );
1931
- }
1932
- function Select({
1933
- ...props
1934
- }) {
1935
- return /* @__PURE__ */ jsx(Select$1.Root, { "data-slot": "select", ...props });
1936
- }
1937
- function SelectGroup({
1938
- ...props
1939
- }) {
1940
- return /* @__PURE__ */ jsx(Select$1.Group, { "data-slot": "select-group", ...props });
1941
- }
1942
- function SelectValue({
1943
- ...props
1944
- }) {
1945
- return /* @__PURE__ */ jsx(Select$1.Value, { "data-slot": "select-value", ...props });
1946
- }
1947
- function SelectTrigger({
1948
- className,
1949
- size = "default",
1950
- children,
1951
- ...props
1952
- }) {
1953
- return /* @__PURE__ */ jsxs(
1954
- Select$1.Trigger,
1955
- {
1956
- "data-slot": "select-trigger",
1957
- "data-size": size,
1958
- className: cn(
1959
- "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
1960
- className
1961
- ),
1962
- ...props,
1963
- children: [
1964
- children,
1965
- /* @__PURE__ */ jsx(Select$1.Icon, { asChild: true, children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4 opacity-50" }) })
1966
- ]
1967
- }
1968
- );
1969
- }
1970
- function SelectContent({
1971
- className,
1972
- children,
1973
- position = "item-aligned",
1974
- align = "center",
1975
- ...props
1976
- }) {
1977
- return /* @__PURE__ */ jsx(Select$1.Portal, { children: /* @__PURE__ */ jsxs(
1978
- Select$1.Content,
1979
- {
1980
- "data-slot": "select-content",
1981
- className: cn(
1982
- "relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
1983
- 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",
1984
- className
1985
- ),
1986
- position,
1987
- align,
1988
- ...props,
1989
- children: [
1990
- /* @__PURE__ */ jsx(SelectScrollUpButton, {}),
1991
- /* @__PURE__ */ jsx(
1992
- Select$1.Viewport,
1993
- {
1994
- className: cn(
1995
- "p-1",
1996
- position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
1997
- ),
1998
- children
1999
- }
2000
- ),
2001
- /* @__PURE__ */ jsx(SelectScrollDownButton, {})
2002
- ]
2003
- }
2004
- ) });
2005
- }
2006
- function SelectLabel({
2007
- className,
2008
- ...props
2009
- }) {
2010
- return /* @__PURE__ */ jsx(
2011
- Select$1.Label,
2012
- {
2013
- "data-slot": "select-label",
2014
- className: cn("px-2 py-1.5 text-xs text-muted-foreground", className),
2015
- ...props
2016
- }
2017
- );
2018
- }
2019
- function SelectItem({
2020
- className,
2021
- children,
2022
- ...props
2023
- }) {
2024
- return /* @__PURE__ */ jsxs(
2025
- Select$1.Item,
2026
- {
2027
- "data-slot": "select-item",
2028
- className: cn(
2029
- "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
2030
- className
2031
- ),
2032
- ...props,
2033
- children: [
2034
- /* @__PURE__ */ jsx(
2035
- "span",
2036
- {
2037
- "data-slot": "select-item-indicator",
2038
- className: "absolute right-2 flex size-3.5 items-center justify-center",
2039
- children: /* @__PURE__ */ jsx(Select$1.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-4" }) })
2040
- }
2041
- ),
2042
- /* @__PURE__ */ jsx(Select$1.ItemText, { children })
2043
- ]
2044
- }
2045
- );
2046
- }
2047
- function SelectSeparator({
2048
- className,
2049
- ...props
2050
- }) {
2051
- return /* @__PURE__ */ jsx(
2052
- Select$1.Separator,
2053
- {
2054
- "data-slot": "select-separator",
2055
- className: cn("pointer-events-none -mx-1 my-1 h-px bg-border", className),
2056
- ...props
2057
- }
2058
- );
2059
- }
2060
- function SelectScrollUpButton({
2061
- className,
2062
- ...props
2063
- }) {
2064
- return /* @__PURE__ */ jsx(
2065
- Select$1.ScrollUpButton,
2066
- {
2067
- "data-slot": "select-scroll-up-button",
2068
- className: cn(
2069
- "flex cursor-default items-center justify-center py-1",
2070
- className
2071
- ),
2072
- ...props,
2073
- children: /* @__PURE__ */ jsx(ChevronUpIcon, { className: "size-4" })
2074
- }
2075
- );
2076
- }
2077
- function SelectScrollDownButton({
2078
- className,
2079
- ...props
2080
- }) {
2081
- return /* @__PURE__ */ jsx(
2082
- Select$1.ScrollDownButton,
2083
- {
2084
- "data-slot": "select-scroll-down-button",
2085
- className: cn(
2086
- "flex cursor-default items-center justify-center py-1",
2087
- className
2088
- ),
2089
- ...props,
2090
- children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4" })
2091
- }
2092
- );
2093
- }
2094
- function Skeleton({ className, ...props }) {
2095
- return /* @__PURE__ */ jsx(
2096
- "div",
2097
- {
2098
- "data-slot": "skeleton",
2099
- className: cn("animate-pulse rounded-md bg-accent", className),
2100
- ...props
2101
- }
2102
- );
2103
- }
2104
- function Textarea2({ className, ...props }) {
2105
- return /* @__PURE__ */ jsx(
2106
- "textarea",
2107
- {
2108
- "data-slot": "textarea",
2109
- className: cn(
2110
- "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
2111
- className
2112
- ),
2113
- ...props
2114
- }
2115
- );
2116
- }
2117
- function TableCell({ column, value }) {
2118
- switch (column.type) {
2119
- case "text":
2120
- return /* @__PURE__ */ jsx(TextCell, { column, value });
2121
- case "badge":
2122
- return /* @__PURE__ */ jsx(BadgeCell, { column, value });
2123
- case "boolean":
2124
- return /* @__PURE__ */ jsx(BooleanCell, { column, value });
2125
- case "date":
2126
- return /* @__PURE__ */ jsx(DateCell, { column, value });
2127
- case "number":
2128
- return /* @__PURE__ */ jsx(NumberCell, { column, value });
2129
- case "icon":
2130
- return /* @__PURE__ */ jsx(IconCell, { column });
2131
- case "image":
2132
- return /* @__PURE__ */ jsx(ImageCell, { column, value });
2133
- case "relationship":
2134
- return /* @__PURE__ */ jsx(RelationshipCell, { column, value });
2135
- case "computed":
2136
- return /* @__PURE__ */ jsx(ComputedCell, { column, value });
2137
- }
2138
- }
2139
- function asString(value) {
2140
- if (value === null || value === void 0) return "";
2141
- if (typeof value === "string") return value;
2142
- if (typeof value === "number" || typeof value === "boolean") return String(value);
2143
- return JSON.stringify(value);
2144
- }
2145
- function TextCell({ column, value }) {
2146
- const str = asString(value);
2147
- const truncated = column.props.truncate && str.length > column.props.truncate ? `${str.slice(0, column.props.truncate)}\u2026` : str;
2148
- return /* @__PURE__ */ jsx(
2149
- "span",
2150
- {
2151
- className: cn(
2152
- column.props.weight === "bold" && "font-bold",
2153
- column.props.weight === "medium" && "font-medium"
2154
- ),
2155
- children: truncated
2156
- }
2157
- );
2158
- }
2159
- function BadgeCell({ column, value }) {
2160
- const option = column.props.options?.find((o) => o.value === value);
2161
- const label = option?.label ?? asString(value);
2162
- return /* @__PURE__ */ jsx(
2163
- "span",
2164
- {
2165
- className: cn(
2166
- "inline-flex items-center px-2 py-0.5 text-xs",
2167
- column.props.pill ? "rounded-full" : "rounded-sm",
2168
- "bg-muted text-foreground"
2169
- ),
2170
- children: label
2171
- }
2172
- );
2173
- }
2174
- function BooleanCell({ column, value }) {
2175
- const truthy = Boolean(value);
2176
- return /* @__PURE__ */ jsx("span", { role: "img", "aria-label": truthy ? "true" : "false", children: truthy ? column.props.trueIcon ?? "\u2713" : column.props.falseIcon ?? "\u2014" });
2177
- }
2178
- function DateCell({ column, value }) {
2179
- if (!value) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
2180
- const raw = String(value);
2181
- const date = new Date(raw);
2182
- if (Number.isNaN(date.getTime())) return /* @__PURE__ */ jsx("span", { children: raw });
2183
- if (column.props.mode === "since") {
2184
- return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: formatRelative(date) });
2185
- }
2186
- if (column.props.mode === "datetime") {
2187
- return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: date.toLocaleString() });
2188
- }
2189
- return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: date.toLocaleDateString() });
2190
- }
2191
- function formatRelative(date) {
2192
- const seconds = Math.round((Date.now() - date.getTime()) / 1e3);
2193
- if (seconds < 60) return `${seconds}s ago`;
2194
- const minutes = Math.round(seconds / 60);
2195
- if (minutes < 60) return `${minutes}m ago`;
2196
- const hours = Math.round(minutes / 60);
2197
- if (hours < 24) return `${hours}h ago`;
2198
- const days = Math.round(hours / 24);
2199
- return `${days}d ago`;
2200
- }
2201
- function NumberCell({ column, value }) {
2202
- if (value === null || value === void 0) return /* @__PURE__ */ jsx("span", { children: "\u2014" });
2203
- const num = typeof value === "number" ? value : Number(value);
2204
- if (Number.isNaN(num)) return /* @__PURE__ */ jsx("span", { children: asString(value) });
2205
- const formatted = num.toFixed(column.props.decimals ?? 0);
2206
- const [whole, dec] = formatted.split(".");
2207
- const safeWhole = whole ?? "0";
2208
- const ts = column.props.thousandsSeparator;
2209
- const finalWhole = ts ? safeWhole.replace(/\B(?=(\d{3})+(?!\d))/g, ts) : safeWhole;
2210
- const ds = column.props.decimalSeparator ?? ".";
2211
- return /* @__PURE__ */ jsxs("span", { children: [
2212
- column.props.prefix ?? "",
2213
- finalWhole,
2214
- dec ? `${ds}${dec}` : "",
2215
- column.props.suffix ?? ""
2216
- ] });
2217
- }
2218
- function IconCell({ column }) {
2219
- return /* @__PURE__ */ jsx("span", { role: "img", "aria-label": column.props.icon, title: column.props.icon, children: column.props.icon });
2220
- }
2221
- function ImageCell({ column, value }) {
2222
- const src = asString(value);
2223
- if (!src) return null;
2224
- const size = column.props.size ?? 32;
2225
- return /* @__PURE__ */ jsx(
2226
- "img",
2227
- {
2228
- src,
2229
- alt: "",
2230
- style: { width: size, height: size },
2231
- className: cn(column.props.shape === "circular" ? "rounded-full" : "rounded")
2232
- }
2233
- );
2234
- }
2235
- function RelationshipCell({ column, value }) {
2236
- if (value === null || value === void 0) return /* @__PURE__ */ jsx("span", { children: "\u2014" });
2237
- if (typeof value === "object") {
2238
- const attr = value[column.props.attribute];
2239
- return /* @__PURE__ */ jsx("span", { children: asString(attr) });
2240
- }
2241
- return /* @__PURE__ */ jsx("span", { children: asString(value) });
2242
- }
2243
- function ComputedCell({ column, value }) {
2244
- if (value === null || value === void 0) {
2245
- return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: column.props.placeholder ?? "\u2014" });
2246
- }
2247
- return /* @__PURE__ */ jsx("span", { children: asString(value) });
2248
- }
2249
- function DataTable({
2250
- columns,
2251
- records,
2252
- enableSelection = false,
2253
- selectedIds = [],
2254
- onSelectionChange,
2255
- sort = null,
2256
- onSortChange,
2257
- rowActions,
2258
- loading = false,
2259
- emptyState,
2260
- className
2261
- }) {
2262
- const lastClickedIndexRef = useRef(null);
2263
- const visibleColumns = useMemo(() => columns.filter((col) => !col.hidden), [columns]);
2264
- const tableColumns = useMemo(
2265
- () => visibleColumns.map((col) => ({
2266
- id: col.name,
2267
- accessorFn: (row) => row[col.name],
2268
- header: () => col.label ?? col.name,
2269
- cell: (info) => /* @__PURE__ */ jsx(TableCell, { column: col, value: info.getValue() }),
2270
- enableSorting: col.sortable
2271
- })),
2272
- [visibleColumns]
2273
- );
2274
- const table = useReactTable({
2275
- data: records,
2276
- columns: tableColumns,
2277
- getCoreRowModel: getCoreRowModel()
2278
- });
2279
- const allSelected = enableSelection && records.length > 0 && selectedIds.length === records.length;
2280
- const someSelected = enableSelection && selectedIds.length > 0 && selectedIds.length < records.length;
2281
- const toggleAll = useCallback(() => {
2282
- if (!onSelectionChange) return;
2283
- if (allSelected) onSelectionChange([]);
2284
- else onSelectionChange(records.map((r) => r.id));
2285
- }, [allSelected, records, onSelectionChange]);
2286
- const toggleRow = useCallback(
2287
- (id, index, shiftKey) => {
2288
- if (!onSelectionChange) return;
2289
- const set = new Set(selectedIds);
2290
- const lastIndex = lastClickedIndexRef.current;
2291
- if (shiftKey && lastIndex !== null) {
2292
- const [from, to] = lastIndex < index ? [lastIndex, index] : [index, lastIndex];
2293
- const shouldSelect = !set.has(id);
2294
- for (let i = from; i <= to; i++) {
2295
- const rowId = records[i]?.id;
2296
- if (rowId === void 0) continue;
2297
- if (shouldSelect) set.add(rowId);
2298
- else set.delete(rowId);
2299
- }
2300
- } else if (set.has(id)) {
2301
- set.delete(id);
2302
- } else {
2303
- set.add(id);
2304
- }
2305
- lastClickedIndexRef.current = index;
2306
- onSelectionChange(Array.from(set));
2307
- },
2308
- [records, selectedIds, onSelectionChange]
2309
- );
2310
- const headerSortDirection = (columnName) => {
2311
- if (!sort || sort.column !== columnName) return null;
2312
- return sort.direction === "desc" ? "desc" : "asc";
2313
- };
2314
- return /* @__PURE__ */ jsx("div", { className: cn("w-full overflow-x-auto", className), children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
2315
- /* @__PURE__ */ jsx("thead", { className: "sticky top-0 bg-background", children: table.getHeaderGroups().map((group) => /* @__PURE__ */ jsxs("tr", { className: "border-b border-border", children: [
2316
- enableSelection && /* @__PURE__ */ jsx("th", { scope: "col", className: "w-10 px-3 py-2 text-left", children: /* @__PURE__ */ jsx(
2317
- "input",
2318
- {
2319
- type: "checkbox",
2320
- "aria-label": "Select all rows",
2321
- checked: allSelected,
2322
- ref: (el) => {
2323
- if (el) el.indeterminate = someSelected;
2324
- },
2325
- onChange: toggleAll
2326
- }
2327
- ) }),
2328
- group.headers.map((header) => {
2329
- const col = visibleColumns.find((c) => c.name === header.column.id);
2330
- const sortable = col?.sortable && onSortChange;
2331
- const direction = headerSortDirection(header.column.id);
2332
- return /* @__PURE__ */ jsx(
2333
- "th",
2334
- {
2335
- scope: "col",
2336
- className: cn(
2337
- "px-3 py-2 text-left font-medium text-muted-foreground",
2338
- col?.align === "center" && "text-center",
2339
- col?.align === "end" && "text-right"
2340
- ),
2341
- "aria-sort": direction === "asc" ? "ascending" : direction === "desc" ? "descending" : "none",
2342
- children: sortable ? /* @__PURE__ */ jsxs(
2343
- "button",
2344
- {
2345
- type: "button",
2346
- className: "inline-flex items-center gap-1 hover:text-foreground",
2347
- onClick: () => onSortChange(header.column.id, direction === "asc" ? "desc" : "asc"),
2348
- children: [
2349
- flexRender(header.column.columnDef.header, header.getContext()),
2350
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: direction === "asc" ? "\u25B2" : direction === "desc" ? "\u25BC" : "\u2195" })
2351
- ]
2352
- }
2353
- ) : flexRender(header.column.columnDef.header, header.getContext())
2354
- },
2355
- header.id
2356
- );
2357
- }),
2358
- rowActions && /* @__PURE__ */ jsx("th", { scope: "col", className: "w-24 px-3 py-2 text-right", "aria-label": "Actions" })
2359
- ] }, group.id)) }),
2360
- /* @__PURE__ */ jsxs("tbody", { children: [
2361
- loading && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
2362
- "td",
2363
- {
2364
- colSpan: visibleColumns.length + (enableSelection ? 1 : 0) + (rowActions ? 1 : 0),
2365
- className: "px-3 py-4 text-center text-muted-foreground",
2366
- children: "Loading\u2026"
2367
- }
2368
- ) }),
2369
- !loading && records.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
2370
- "td",
2371
- {
2372
- colSpan: visibleColumns.length + (enableSelection ? 1 : 0) + (rowActions ? 1 : 0),
2373
- className: "px-3 py-8 text-center text-muted-foreground",
2374
- children: emptyState ?? "No records found."
2375
- }
2376
- ) }),
2377
- !loading && table.getRowModel().rows.map((row, index) => {
2378
- const record = row.original;
2379
- const checked = selectedIds.includes(record.id);
2380
- return /* @__PURE__ */ jsxs(
2381
- "tr",
2382
- {
2383
- "data-selected": checked || void 0,
2384
- className: cn("border-b border-border", checked && "bg-muted"),
2385
- children: [
2386
- enableSelection && /* @__PURE__ */ jsx("td", { className: "w-10 px-3 py-2", children: /* @__PURE__ */ jsx(
2387
- "input",
2388
- {
2389
- type: "checkbox",
2390
- "aria-label": `Select row ${record.id}`,
2391
- checked,
2392
- onChange: (event) => {
2393
- const native = event.nativeEvent;
2394
- toggleRow(record.id, index, native.shiftKey === true);
2395
- }
2396
- }
2397
- ) }),
2398
- row.getVisibleCells().map((cell) => {
2399
- const col = visibleColumns.find((c) => c.name === cell.column.id);
2400
- return /* @__PURE__ */ jsx(
2401
- "td",
2402
- {
2403
- className: cn(
2404
- "px-3 py-2",
2405
- col?.align === "center" && "text-center",
2406
- col?.align === "end" && "text-right",
2407
- col?.hiddenOnMobile && "hidden md:table-cell"
2408
- ),
2409
- children: flexRender(cell.column.columnDef.cell, cell.getContext())
2410
- },
2411
- cell.id
2412
- );
2413
- }),
2414
- rowActions && /* @__PURE__ */ jsx("td", { className: "w-24 px-3 py-2 text-right", children: rowActions(record) })
2415
- ]
2416
- },
2417
- row.id
2418
- );
2419
- })
2420
- ] })
2421
- ] }) });
2422
- }
2423
- function TableFilters({
2424
- filters,
2425
- values,
2426
- onChange,
2427
- onClearAll,
2428
- className
2429
- }) {
2430
- if (filters.length === 0) return null;
2431
- const activeCount = filters.reduce((acc, f) => {
2432
- const v = values[f.name];
2433
- return v !== void 0 && v !== null && v !== "" ? acc + 1 : acc;
2434
- }, 0);
2435
- return /* @__PURE__ */ jsxs("fieldset", { className: cn("flex flex-wrap items-end gap-2 border-0 p-0", className), children: [
2436
- /* @__PURE__ */ jsx("legend", { className: "sr-only", children: "Filters" }),
2437
- filters.map((filter) => /* @__PURE__ */ jsx(
2438
- FilterControl,
2439
- {
2440
- filter,
2441
- value: values[filter.name],
2442
- onChange: (v) => onChange(filter.name, v)
2443
- },
2444
- filter.name
2445
- )),
2446
- activeCount > 0 && onClearAll && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: onClearAll, children: [
2447
- "Clear filters (",
2448
- activeCount,
2449
- ")"
2450
- ] })
2451
- ] });
2452
- }
2453
- function FilterControl({
2454
- filter,
2455
- value,
2456
- onChange
2457
- }) {
2458
- switch (filter.type) {
2459
- case "select":
2460
- return /* @__PURE__ */ jsx(SelectFilter, { filter, value, onChange });
2461
- case "multiSelect":
2462
- return /* @__PURE__ */ jsx(MultiSelectFilter, { filter, value, onChange });
2463
- case "text":
2464
- return /* @__PURE__ */ jsx(TextFilter, { filter, value, onChange });
2465
- case "ternary":
2466
- return /* @__PURE__ */ jsx(TernaryFilter, { filter, value, onChange });
2467
- case "dateRange":
2468
- case "scope":
2469
- return null;
2470
- }
2471
- }
2472
- function controlClasses() {
2473
- return cn(
2474
- "h-9 rounded-sm border border-[var(--input)]",
2475
- "bg-background px-3 text-sm",
2476
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2477
- );
2478
- }
2479
- function SelectFilter({
2480
- filter,
2481
- value,
2482
- onChange
2483
- }) {
2484
- return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
2485
- filter.label ?? filter.name,
2486
- /* @__PURE__ */ jsxs(
2487
- "select",
2488
- {
2489
- className: controlClasses(),
2490
- value: value === void 0 || value === null ? "" : String(value),
2491
- onChange: (e) => onChange(e.target.value === "" ? null : e.target.value),
2492
- children: [
2493
- /* @__PURE__ */ jsx("option", { value: "", children: "All" }),
2494
- filter.props.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
2495
- ]
2496
- }
2497
- )
2498
- ] });
2499
- }
2500
- function MultiSelectFilter({
2501
- filter,
2502
- value,
2503
- onChange
2504
- }) {
2505
- const arr = Array.isArray(value) ? value : [];
2506
- return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
2507
- filter.label ?? filter.name,
2508
- /* @__PURE__ */ jsx(
2509
- "select",
2510
- {
2511
- multiple: true,
2512
- className: cn(controlClasses(), "h-auto min-h-[2.25rem] py-1"),
2513
- value: arr.map(String),
2514
- onChange: (e) => {
2515
- const next = Array.from(e.target.selectedOptions, (o) => o.value);
2516
- onChange(next.length === 0 ? null : next);
2517
- },
2518
- children: filter.props.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
2519
- }
2520
- )
2521
- ] });
2522
- }
2523
- function TextFilter({
2524
- filter,
2525
- value,
2526
- onChange
2527
- }) {
2528
- return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
2529
- filter.label ?? filter.name,
2530
- /* @__PURE__ */ jsx(
2531
- "input",
2532
- {
2533
- type: "text",
2534
- className: controlClasses(),
2535
- value: typeof value === "string" ? value : "",
2536
- onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
2537
- }
2538
- )
2539
- ] });
2540
- }
2541
- function TernaryFilter({
2542
- filter,
2543
- value,
2544
- onChange
2545
- }) {
2546
- const current = value === void 0 || value === null ? "all" : String(value);
2547
- return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
2548
- filter.label ?? filter.name,
2549
- /* @__PURE__ */ jsxs(
2550
- "select",
2551
- {
2552
- className: controlClasses(),
2553
- value: current,
2554
- onChange: (e) => {
2555
- const v = e.target.value;
2556
- onChange(v === "all" ? null : v);
2557
- },
2558
- children: [
2559
- /* @__PURE__ */ jsx("option", { value: "all", children: filter.props.allLabel ?? "All" }),
2560
- /* @__PURE__ */ jsx("option", { value: "true", children: filter.props.trueLabel ?? "Yes" }),
2561
- /* @__PURE__ */ jsx("option", { value: "false", children: filter.props.falseLabel ?? "No" })
2562
- ]
2563
- }
2564
- )
2565
- ] });
2566
- }
2567
- function TablePagination({
2568
- meta,
2569
- onPageChange,
2570
- onPerPageChange,
2571
- perPageOptions = [10, 25, 50, 100],
2572
- className
2573
- }) {
2574
- const from = (meta.currentPage - 1) * meta.perPage + 1;
2575
- const to = Math.min(meta.currentPage * meta.perPage, meta.total);
2576
- const isFirst = meta.currentPage <= 1;
2577
- const isLast = meta.currentPage >= meta.lastPage;
2578
- return /* @__PURE__ */ jsxs(
2579
- "nav",
2580
- {
2581
- className: cn(
2582
- "flex flex-wrap items-center justify-between gap-2 border-t border-border px-3 py-2 text-sm",
2583
- className
2584
- ),
2585
- "aria-label": "Pagination",
2586
- children: [
2587
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: meta.total === 0 ? "No results" : `${from}\u2013${to} of ${meta.total}` }),
2588
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2589
- onPerPageChange && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
2590
- "Per page",
2591
- /* @__PURE__ */ jsx(
2592
- "select",
2593
- {
2594
- className: "h-8 rounded-sm border border-[var(--input)] bg-background px-2",
2595
- value: meta.perPage,
2596
- onChange: (e) => onPerPageChange(Number(e.target.value)),
2597
- children: perPageOptions.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: n }, n))
2598
- }
2599
- )
2600
- ] }),
2601
- /* @__PURE__ */ jsx(
2602
- Button,
2603
- {
2604
- variant: "outline",
2605
- size: "sm",
2606
- disabled: isFirst,
2607
- onClick: () => onPageChange(meta.currentPage - 1),
2608
- "aria-label": "Previous page",
2609
- children: "Prev"
2610
- }
2611
- ),
2612
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
2613
- meta.currentPage,
2614
- " / ",
2615
- meta.lastPage
2616
- ] }),
2617
- /* @__PURE__ */ jsx(
2618
- Button,
2619
- {
2620
- variant: "outline",
2621
- size: "sm",
2622
- disabled: isLast,
2623
- onClick: () => onPageChange(meta.currentPage + 1),
2624
- "aria-label": "Next page",
2625
- children: "Next"
2626
- }
2627
- )
2628
- ] })
2629
- ]
2630
- }
2631
- );
2632
- }
2633
- function TableToolbar({
2634
- search,
2635
- filters,
2636
- selectedCount = 0,
2637
- bulkActions,
2638
- onClearSelection,
2639
- className
2640
- }) {
2641
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3 px-3 py-3", className), children: [
2642
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-2", children: [
2643
- search,
2644
- filters
2645
- ] }),
2646
- selectedCount > 0 && /* @__PURE__ */ jsxs(
2647
- "section",
2648
- {
2649
- className: "flex flex-wrap items-center gap-2 rounded-sm border border-border bg-muted px-3 py-2",
2650
- "aria-label": "Bulk actions",
2651
- children: [
2652
- /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
2653
- selectedCount,
2654
- " selected"
2655
- ] }),
2656
- /* @__PURE__ */ jsxs("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: [
2657
- bulkActions,
2658
- onClearSelection && /* @__PURE__ */ jsx(
2659
- "button",
2660
- {
2661
- type: "button",
2662
- className: "text-sm text-muted-foreground hover:underline",
2663
- onClick: onClearSelection,
2664
- children: "Clear"
2665
- }
2666
- )
2667
- ] })
2668
- ]
2669
- }
2670
- )
2671
- ] });
2672
- }
2673
- function ResourceIndex({
2674
- resource,
2675
- records,
2676
- pagination,
2677
- columns,
2678
- filters,
2679
- search,
2680
- sort,
2681
- selectedIds = [],
2682
- filterValues = {},
2683
- onSelectionChange,
2684
- onSortChange,
2685
- onPageChange,
2686
- onPerPageChange,
2687
- onSearchChange,
2688
- onFilterChange,
2689
- onClearFilters,
2690
- searchSlot,
2691
- toolbarActions,
2692
- rowActions,
2693
- bulkActions,
2694
- emptyState,
2695
- loading = false,
2696
- className
2697
- }) {
2698
- const enableSelection = Boolean(onSelectionChange);
2699
- const renderSearch = () => {
2700
- if (searchSlot) return searchSlot;
2701
- if (!onSearchChange) return null;
2702
- return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
2703
- "Search",
2704
- /* @__PURE__ */ jsx(
2705
- "input",
2706
- {
2707
- type: "search",
2708
- placeholder: `Search ${resource.pluralLabel.toLowerCase()}\u2026`,
2709
- className: "h-9 w-64 rounded-sm border border-[var(--input)] bg-background px-3 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2710
- value: search ?? "",
2711
- onChange: (e) => onSearchChange(e.target.value)
2712
- }
2713
- )
2714
- ] });
2715
- };
2716
- const normalisedSort = sort?.column ? {
2717
- column: sort.column,
2718
- direction: sort.direction === "desc" ? "desc" : "asc"
2719
- } : null;
2720
- return /* @__PURE__ */ jsxs(
2721
- "section",
2722
- {
2723
- className: cn("flex flex-col rounded-md border border-border bg-background", className),
2724
- "aria-label": resource.pluralLabel,
2725
- children: [
2726
- /* @__PURE__ */ jsxs("header", { className: "flex flex-wrap items-center gap-3 border-b border-border px-4 py-3", children: [
2727
- /* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold", children: resource.pluralLabel }),
2728
- /* @__PURE__ */ jsx("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: toolbarActions })
2729
- ] }),
2730
- /* @__PURE__ */ jsx(
2731
- TableToolbar,
2732
- {
2733
- search: renderSearch(),
2734
- filters: filters.length > 0 ? /* @__PURE__ */ jsx(
2735
- TableFilters,
2736
- {
2737
- filters,
2738
- values: filterValues,
2739
- onChange: (name, value) => onFilterChange?.(name, value),
2740
- onClearAll: onClearFilters
2741
- }
2742
- ) : null,
2743
- selectedCount: selectedIds.length,
2744
- bulkActions,
2745
- onClearSelection: onSelectionChange ? () => onSelectionChange([]) : void 0
2746
- }
2747
- ),
2748
- /* @__PURE__ */ jsx(
2749
- DataTable,
2750
- {
2751
- columns,
2752
- records,
2753
- enableSelection,
2754
- selectedIds,
2755
- onSelectionChange,
2756
- sort: normalisedSort,
2757
- onSortChange,
2758
- rowActions: rowActions ? (record) => rowActions(record) : void 0,
2759
- loading,
2760
- emptyState
2761
- }
2762
- ),
2763
- pagination && /* @__PURE__ */ jsx(
2764
- TablePagination,
2765
- {
2766
- meta: pagination,
2767
- onPageChange: (page) => onPageChange?.(page),
2768
- onPerPageChange
2769
- }
2770
- )
2771
- ]
2772
- }
2773
- );
2774
- }
2775
- var MOBILE_BREAKPOINT = 768;
2776
- function useIsMobile() {
2777
- const [isMobile, setIsMobile] = React16.useState(void 0);
2778
- React16.useEffect(() => {
2779
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
2780
- const onChange = () => {
2781
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
2782
- };
2783
- mql.addEventListener("change", onChange);
2784
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
2785
- return () => mql.removeEventListener("change", onChange);
2786
- }, []);
2787
- return !!isMobile;
2788
- }
2789
- function Sheet({ ...props }) {
2790
- return /* @__PURE__ */ jsx(Dialog$1.Root, { "data-slot": "sheet", ...props });
2791
- }
2792
- function SheetPortal({
2793
- ...props
2794
- }) {
2795
- return /* @__PURE__ */ jsx(Dialog$1.Portal, { "data-slot": "sheet-portal", ...props });
2796
- }
2797
- function SheetOverlay({
2798
- className,
2799
- ...props
2800
- }) {
2801
- return /* @__PURE__ */ jsx(
2802
- Dialog$1.Overlay,
2803
- {
2804
- "data-slot": "sheet-overlay",
2805
- className: cn(
2806
- "fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
2807
- className
2808
- ),
2809
- ...props
2810
- }
2811
- );
2812
- }
2813
- function SheetContent({
2814
- className,
2815
- children,
2816
- side = "right",
2817
- showCloseButton = true,
2818
- ...props
2819
- }) {
2820
- return /* @__PURE__ */ jsxs(SheetPortal, { children: [
2821
- /* @__PURE__ */ jsx(SheetOverlay, {}),
2822
- /* @__PURE__ */ jsxs(
2823
- Dialog$1.Content,
2824
- {
2825
- "data-slot": "sheet-content",
2826
- className: cn(
2827
- "fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
2828
- side === "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",
2829
- side === "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",
2830
- side === "top" && "inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
2831
- side === "bottom" && "inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
2832
- className
2833
- ),
2834
- ...props,
2835
- children: [
2836
- children,
2837
- showCloseButton && /* @__PURE__ */ jsxs(Dialog$1.Close, { className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary", children: [
2838
- /* @__PURE__ */ jsx(XIcon, { className: "size-4" }),
2839
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
2840
- ] })
2841
- ]
2842
- }
2843
- )
2844
- ] });
2845
- }
2846
- function SheetHeader({ className, ...props }) {
2847
- return /* @__PURE__ */ jsx(
2848
- "div",
2849
- {
2850
- "data-slot": "sheet-header",
2851
- className: cn("flex flex-col gap-1.5 p-4", className),
2852
- ...props
2853
- }
2854
- );
2855
- }
2856
- function SheetTitle({
2857
- className,
2858
- ...props
2859
- }) {
2860
- return /* @__PURE__ */ jsx(
2861
- Dialog$1.Title,
2862
- {
2863
- "data-slot": "sheet-title",
2864
- className: cn("font-semibold text-foreground", className),
2865
- ...props
2866
- }
2867
- );
2868
- }
2869
- function SheetDescription({
2870
- className,
2871
- ...props
2872
- }) {
2873
- return /* @__PURE__ */ jsx(
2874
- Dialog$1.Description,
2875
- {
2876
- "data-slot": "sheet-description",
2877
- className: cn("text-sm text-muted-foreground", className),
2878
- ...props
2879
- }
2880
- );
2881
- }
2882
- function TooltipProvider({
2883
- delayDuration = 0,
2884
- ...props
2885
- }) {
2886
- return /* @__PURE__ */ jsx(
2887
- Tooltip$1.Provider,
2888
- {
2889
- "data-slot": "tooltip-provider",
2890
- delayDuration,
2891
- ...props
2892
- }
2893
- );
2894
- }
2895
- function Tooltip({
2896
- ...props
2897
- }) {
2898
- return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
2899
- }
2900
- function TooltipTrigger({
2901
- ...props
2902
- }) {
2903
- return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
2904
- }
2905
- function TooltipContent({
2906
- className,
2907
- sideOffset = 0,
2908
- children,
2909
- ...props
2910
- }) {
2911
- return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
2912
- Tooltip$1.Content,
2913
- {
2914
- "data-slot": "tooltip-content",
2915
- sideOffset,
2916
- className: cn(
2917
- "z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
2918
- className
2919
- ),
2920
- ...props,
2921
- children: [
2922
- children,
2923
- /* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" })
2924
- ]
2925
- }
2926
- ) });
2927
- }
2928
- var SIDEBAR_COOKIE_NAME = "sidebar_state";
2929
- var SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
2930
- var SIDEBAR_WIDTH = "16rem";
2931
- var SIDEBAR_WIDTH_MOBILE = "18rem";
2932
- var SIDEBAR_WIDTH_ICON = "3rem";
2933
- var SIDEBAR_KEYBOARD_SHORTCUT = "b";
2934
- var SidebarContext = React16.createContext(null);
2935
- function useSidebar() {
2936
- const context = React16.useContext(SidebarContext);
2937
- if (!context) {
2938
- throw new Error("useSidebar must be used within a SidebarProvider.");
2939
- }
2940
- return context;
2941
- }
2942
- function SidebarProvider({
2943
- defaultOpen = true,
2944
- open: openProp,
2945
- onOpenChange: setOpenProp,
2946
- className,
2947
- style,
2948
- children,
2949
- ...props
2950
- }) {
2951
- const isMobile = useIsMobile();
2952
- const [openMobile, setOpenMobile] = React16.useState(false);
2953
- const [_open, _setOpen] = React16.useState(defaultOpen);
2954
- const open = openProp ?? _open;
2955
- const setOpen = React16.useCallback(
2956
- (value) => {
2957
- const openState = typeof value === "function" ? value(open) : value;
2958
- if (setOpenProp) {
2959
- setOpenProp(openState);
2960
- } else {
2961
- _setOpen(openState);
2962
- }
2963
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
2964
- },
2965
- [setOpenProp, open]
2966
- );
2967
- const toggleSidebar = React16.useCallback(() => {
2968
- return isMobile ? setOpenMobile((open2) => !open2) : setOpen((open2) => !open2);
2969
- }, [isMobile, setOpen, setOpenMobile]);
2970
- React16.useEffect(() => {
2971
- const handleKeyDown = (event) => {
2972
- if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
2973
- event.preventDefault();
2974
- toggleSidebar();
2975
- }
2976
- };
2977
- window.addEventListener("keydown", handleKeyDown);
2978
- return () => window.removeEventListener("keydown", handleKeyDown);
2979
- }, [toggleSidebar]);
2980
- const state = open ? "expanded" : "collapsed";
2981
- const contextValue = React16.useMemo(
2982
- () => ({
2983
- state,
2984
- open,
2985
- setOpen,
2986
- isMobile,
2987
- openMobile,
2988
- setOpenMobile,
2989
- toggleSidebar
2990
- }),
2991
- [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
2992
- );
2993
- return /* @__PURE__ */ jsx(SidebarContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx(
2994
- "div",
2995
- {
2996
- "data-slot": "sidebar-wrapper",
2997
- style: {
2998
- "--sidebar-width": SIDEBAR_WIDTH,
2999
- "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
3000
- ...style
3001
- },
3002
- className: cn(
3003
- "group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",
3004
- className
3005
- ),
3006
- ...props,
3007
- children
3008
- }
3009
- ) }) });
3010
- }
3011
- function Sidebar({
3012
- side = "left",
3013
- variant = "sidebar",
3014
- collapsible = "offcanvas",
3015
- className,
3016
- children,
3017
- ...props
3018
- }) {
3019
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
3020
- if (collapsible === "none") {
3021
- return /* @__PURE__ */ jsx(
3022
- "div",
3023
- {
3024
- "data-slot": "sidebar",
3025
- className: cn(
3026
- "flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
3027
- className
3028
- ),
3029
- ...props,
3030
- children
3031
- }
3032
- );
3033
- }
3034
- if (isMobile) {
3035
- return /* @__PURE__ */ jsx(Sheet, { open: openMobile, onOpenChange: setOpenMobile, ...props, children: /* @__PURE__ */ jsxs(
3036
- SheetContent,
3037
- {
3038
- "data-sidebar": "sidebar",
3039
- "data-slot": "sidebar",
3040
- "data-mobile": "true",
3041
- className: "w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden",
3042
- style: {
3043
- "--sidebar-width": SIDEBAR_WIDTH_MOBILE
3044
- },
3045
- side,
3046
- children: [
3047
- /* @__PURE__ */ jsxs(SheetHeader, { className: "sr-only", children: [
3048
- /* @__PURE__ */ jsx(SheetTitle, { children: "Sidebar" }),
3049
- /* @__PURE__ */ jsx(SheetDescription, { children: "Displays the mobile sidebar." })
3050
- ] }),
3051
- /* @__PURE__ */ jsx("div", { className: "flex h-full w-full flex-col", children })
3052
- ]
3053
- }
3054
- ) });
3055
- }
3056
- return /* @__PURE__ */ jsxs(
3057
- "div",
3058
- {
3059
- className: "group peer hidden text-sidebar-foreground md:block",
3060
- "data-state": state,
3061
- "data-collapsible": state === "collapsed" ? collapsible : "",
3062
- "data-variant": variant,
3063
- "data-side": side,
3064
- "data-slot": "sidebar",
3065
- children: [
3066
- /* @__PURE__ */ jsx(
3067
- "div",
3068
- {
3069
- "data-slot": "sidebar-gap",
3070
- className: cn(
3071
- "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
3072
- "group-data-[collapsible=offcanvas]:w-0",
3073
- "group-data-[side=right]:rotate-180",
3074
- variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
3075
- )
3076
- }
3077
- ),
3078
- /* @__PURE__ */ jsx(
3079
- "div",
3080
- {
3081
- "data-slot": "sidebar-container",
3082
- className: cn(
3083
- "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
3084
- side === "left" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
3085
- // Adjust the padding for floating and inset variants.
3086
- variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
3087
- className
3088
- ),
3089
- ...props,
3090
- children: /* @__PURE__ */ jsx(
3091
- "div",
3092
- {
3093
- "data-sidebar": "sidebar",
3094
- "data-slot": "sidebar-inner",
3095
- className: "flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow-sm",
3096
- children
3097
- }
3098
- )
3099
- }
3100
- )
3101
- ]
3102
- }
3103
- );
3104
- }
3105
- function SidebarTrigger({
3106
- className,
3107
- onClick,
3108
- ...props
3109
- }) {
3110
- const { toggleSidebar } = useSidebar();
3111
- return /* @__PURE__ */ jsxs(
3112
- Button,
3113
- {
3114
- "data-sidebar": "trigger",
3115
- "data-slot": "sidebar-trigger",
3116
- variant: "ghost",
3117
- size: "icon",
3118
- className: cn("size-7", className),
3119
- onClick: (event) => {
3120
- onClick?.(event);
3121
- toggleSidebar();
3122
- },
3123
- ...props,
3124
- children: [
3125
- /* @__PURE__ */ jsx(PanelLeftIcon, {}),
3126
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Toggle Sidebar" })
3127
- ]
3128
- }
3129
- );
3130
- }
3131
- function SidebarInset({ className, ...props }) {
3132
- return /* @__PURE__ */ jsx(
3133
- "main",
3134
- {
3135
- "data-slot": "sidebar-inset",
3136
- className: cn(
3137
- "relative flex w-full flex-1 flex-col bg-background",
3138
- "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
3139
- className
3140
- ),
3141
- ...props
3142
- }
3143
- );
3144
- }
3145
- function SidebarHeader({ className, ...props }) {
3146
- return /* @__PURE__ */ jsx(
3147
- "div",
3148
- {
3149
- "data-slot": "sidebar-header",
3150
- "data-sidebar": "header",
3151
- className: cn("flex flex-col gap-2 p-2", className),
3152
- ...props
3153
- }
3154
- );
3155
- }
3156
- function SidebarFooter({ className, ...props }) {
3157
- return /* @__PURE__ */ jsx(
3158
- "div",
3159
- {
3160
- "data-slot": "sidebar-footer",
3161
- "data-sidebar": "footer",
3162
- className: cn("flex flex-col gap-2 p-2", className),
3163
- ...props
3164
- }
3165
- );
3166
- }
3167
- function SidebarContent({ className, ...props }) {
3168
- return /* @__PURE__ */ jsx(
3169
- "div",
3170
- {
3171
- "data-slot": "sidebar-content",
3172
- "data-sidebar": "content",
3173
- className: cn(
3174
- "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
3175
- className
3176
- ),
3177
- ...props
3178
- }
3179
- );
3180
- }
3181
- function SidebarGroup({ className, ...props }) {
3182
- return /* @__PURE__ */ jsx(
3183
- "div",
3184
- {
3185
- "data-slot": "sidebar-group",
3186
- "data-sidebar": "group",
3187
- className: cn("relative flex w-full min-w-0 flex-col p-2", className),
3188
- ...props
3189
- }
3190
- );
3191
- }
3192
- function SidebarGroupLabel({
3193
- className,
3194
- asChild = false,
3195
- ...props
3196
- }) {
3197
- const Comp = asChild ? Slot.Root : "div";
3198
- return /* @__PURE__ */ jsx(
3199
- Comp,
3200
- {
3201
- "data-slot": "sidebar-group-label",
3202
- "data-sidebar": "group-label",
3203
- className: cn(
3204
- "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
3205
- "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
3206
- className
3207
- ),
3208
- ...props
3209
- }
3210
- );
3211
- }
3212
- function SidebarGroupContent({
3213
- className,
3214
- ...props
3215
- }) {
3216
- return /* @__PURE__ */ jsx(
3217
- "div",
3218
- {
3219
- "data-slot": "sidebar-group-content",
3220
- "data-sidebar": "group-content",
3221
- className: cn("w-full text-sm", className),
3222
- ...props
3223
- }
3224
- );
3225
- }
3226
- function SidebarMenu({ className, ...props }) {
3227
- return /* @__PURE__ */ jsx(
3228
- "ul",
3229
- {
3230
- "data-slot": "sidebar-menu",
3231
- "data-sidebar": "menu",
3232
- className: cn("flex w-full min-w-0 flex-col gap-1", className),
3233
- ...props
3234
- }
3235
- );
3236
- }
3237
- function SidebarMenuItem({ className, ...props }) {
3238
- return /* @__PURE__ */ jsx(
3239
- "li",
3240
- {
3241
- "data-slot": "sidebar-menu-item",
3242
- "data-sidebar": "menu-item",
3243
- className: cn("group/menu-item relative", className),
3244
- ...props
3245
- }
3246
- );
3247
- }
3248
- var sidebarMenuButtonVariants = cva(
3249
- "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
3250
- {
3251
- variants: {
3252
- variant: {
3253
- default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
3254
- outline: "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]"
3255
- },
3256
- size: {
3257
- default: "h-8 text-sm",
3258
- sm: "h-7 text-xs",
3259
- lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!"
3260
- }
3261
- },
3262
- defaultVariants: {
3263
- variant: "default",
3264
- size: "default"
3265
- }
3266
- }
3267
- );
3268
- function SidebarMenuButton({
3269
- asChild = false,
3270
- isActive = false,
3271
- variant = "default",
3272
- size = "default",
3273
- tooltip,
3274
- className,
3275
- ...props
3276
- }) {
3277
- const Comp = asChild ? Slot.Root : "button";
3278
- const { isMobile, state } = useSidebar();
3279
- const button = /* @__PURE__ */ jsx(
3280
- Comp,
3281
- {
3282
- "data-slot": "sidebar-menu-button",
3283
- "data-sidebar": "menu-button",
3284
- "data-size": size,
3285
- "data-active": isActive,
3286
- className: cn(sidebarMenuButtonVariants({ variant, size }), className),
3287
- ...props
3288
- }
3289
- );
3290
- if (!tooltip) {
3291
- return button;
3292
- }
3293
- if (typeof tooltip === "string") {
3294
- tooltip = {
3295
- children: tooltip
3296
- };
3297
- }
3298
- return /* @__PURE__ */ jsxs(Tooltip, { children: [
3299
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: button }),
3300
- /* @__PURE__ */ jsx(
3301
- TooltipContent,
3302
- {
3303
- side: "right",
3304
- align: "center",
3305
- hidden: state !== "collapsed" || isMobile,
3306
- ...tooltip
3307
- }
3308
- )
3309
- ] });
3310
- }
3311
- function AppShell({
3312
- variant = "sidebar-left",
3313
- sidebar,
3314
- topbar,
3315
- footer,
3316
- children,
3317
- className
3318
- }) {
3319
- if (variant === "full-width") {
3320
- return /* @__PURE__ */ jsxs(
3321
- "div",
3322
- {
3323
- "data-arqel-shell": "full-width",
3324
- className: cn("min-h-screen bg-background text-foreground", className),
3325
- children: [
3326
- /* @__PURE__ */ jsx(CommandPalette, {}),
3327
- children
3328
- ]
3329
- }
3330
- );
3331
- }
3332
- if (variant === "topbar-only") {
3333
- return /* @__PURE__ */ jsxs(
3334
- "div",
3335
- {
3336
- "data-arqel-shell": "topbar-only",
3337
- className: cn("flex min-h-screen flex-col bg-background text-foreground", className),
3338
- children: [
3339
- /* @__PURE__ */ jsx(CommandPalette, {}),
3340
- topbar,
3341
- /* @__PURE__ */ jsx("main", { className: "flex-1", children }),
3342
- footer
3343
- ]
3344
- }
3345
- );
3346
- }
3347
- const sidebarSide = variant === "sidebar-right" ? "right" : "left";
3348
- return /* @__PURE__ */ jsx(SidebarProvider, { children: /* @__PURE__ */ jsxs(
3349
- "div",
3350
- {
3351
- "data-arqel-shell": variant,
3352
- "data-sidebar-side": sidebarSide,
3353
- className: cn(
3354
- "flex min-h-svh w-full bg-background text-foreground",
3355
- sidebarSide === "right" && "flex-row-reverse",
3356
- className
3357
- ),
3358
- children: [
3359
- /* @__PURE__ */ jsx(CommandPalette, {}),
3360
- sidebar,
3361
- /* @__PURE__ */ jsxs(SidebarInset, { children: [
3362
- topbar,
3363
- /* @__PURE__ */ jsx("main", { className: "flex-1", children }),
3364
- footer
3365
- ] })
3366
- ]
3367
- }
3368
- ) });
3369
- }
3370
- function Footer({ children, className }) {
3371
- return /* @__PURE__ */ jsx(
3372
- "footer",
3373
- {
3374
- "data-arqel-footer": "",
3375
- className: cn(
3376
- "shrink-0 border-t border-border bg-background px-4 py-3 text-sm text-muted-foreground",
3377
- className
3378
- ),
3379
- children
3380
- }
3381
- );
3382
- }
3383
- var MAX_WIDTH_CLASSES = {
3384
- none: "",
3385
- md: "max-w-md",
3386
- lg: "max-w-lg",
3387
- xl: "max-w-xl",
3388
- "2xl": "max-w-2xl",
3389
- "4xl": "max-w-4xl",
3390
- "6xl": "max-w-6xl",
3391
- "7xl": "max-w-7xl"
3392
- };
3393
- function MainContent({
3394
- breadcrumbs,
3395
- header,
3396
- children,
3397
- maxWidth = "7xl",
3398
- className
3399
- }) {
3400
- return /* @__PURE__ */ jsxs(
3401
- "div",
3402
- {
3403
- "data-arqel-main": "",
3404
- className: cn(
3405
- "mx-auto w-full px-4 py-6 sm:px-6 lg:px-8",
3406
- MAX_WIDTH_CLASSES[maxWidth],
3407
- className
3408
- ),
3409
- children: [
3410
- breadcrumbs && /* @__PURE__ */ jsx("div", { className: "mb-3", children: breadcrumbs }),
3411
- header && /* @__PURE__ */ jsx("div", { className: "mb-6", children: header }),
3412
- children
3413
- ]
3414
- }
3415
- );
3416
- }
3417
- function Sidebar2({ items: itemsProp, brand, footer, className }) {
3418
- const { items: sharedItems } = useNavigation();
3419
- const items = itemsProp ?? sharedItems;
3420
- const grouped = useMemo(() => groupItems(items), [items]);
3421
- return /* @__PURE__ */ jsxs(Sidebar, { collapsible: "icon", className, children: [
3422
- brand ? /* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 px-2 py-1.5 text-sm font-semibold", children: brand }) }) : null,
3423
- /* @__PURE__ */ jsx(SidebarContent, { children: grouped.map(([group, list]) => /* @__PURE__ */ jsxs(SidebarGroup, { children: [
3424
- group ? /* @__PURE__ */ jsx(SidebarGroupLabel, { children: group }) : null,
3425
- /* @__PURE__ */ jsx(SidebarGroupContent, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: list.map((item) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(SidebarMenuButton, { asChild: Boolean(item.url), isActive: Boolean(item.active), children: item.url ? /* @__PURE__ */ jsx("a", { href: item.url, children: /* @__PURE__ */ jsx("span", { children: item.label }) }) : /* @__PURE__ */ jsx("span", { children: item.label }) }) }, item.url ?? item.label)) }) })
3426
- ] }, group ?? "__ungrouped__")) }),
3427
- footer ? /* @__PURE__ */ jsx(SidebarFooter, { children: footer }) : null
3428
- ] });
3429
- }
3430
- function groupItems(items) {
3431
- const groups = /* @__PURE__ */ new Map();
3432
- for (const item of items) {
3433
- const key = item.group ?? void 0;
3434
- const list = groups.get(key) ?? [];
3435
- list.push(item);
3436
- groups.set(key, list);
3437
- }
3438
- return Array.from(groups.entries());
3439
- }
3440
- function Topbar({
3441
- brand,
3442
- search,
3443
- userMenu,
3444
- tenantSwitcher,
3445
- onMobileMenuClick,
3446
- className
3447
- }) {
3448
- const { resolved, toggle } = useTheme();
3449
- return /* @__PURE__ */ jsxs(
3450
- "header",
3451
- {
3452
- "data-arqel-topbar": "",
3453
- className: cn(
3454
- "flex h-14 shrink-0 items-center gap-2 border-b border-border bg-background px-4",
3455
- className
3456
- ),
3457
- children: [
3458
- /* @__PURE__ */ jsx(SidebarTrigger, { className: "-ml-1" }),
3459
- brand && /* @__PURE__ */ jsx("div", { className: "flex items-center", children: brand }),
3460
- /* @__PURE__ */ jsx("div", { className: "hidden flex-1 md:block", children: search }),
3461
- /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2 md:ml-0", children: [
3462
- tenantSwitcher,
3463
- /* @__PURE__ */ jsx(
3464
- Button,
3465
- {
3466
- variant: "ghost",
3467
- size: "icon",
3468
- "aria-label": `Switch to ${resolved === "dark" ? "light" : "dark"} theme`,
3469
- onClick: toggle,
3470
- children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: resolved === "dark" ? "\u2600" : "\u263E" })
3471
- }
3472
- ),
3473
- userMenu
3474
- ] })
3475
- ]
3476
- }
3477
- );
3478
- }
3479
- function Breadcrumbs(props) {
3480
- if (props.items === void 0) {
3481
- return /* @__PURE__ */ jsx(SharedBreadcrumbs, { ...props });
3482
- }
3483
- return /* @__PURE__ */ jsx(BreadcrumbsList, { ...props, items: props.items });
3484
- }
3485
- function SharedBreadcrumbs(props) {
3486
- const page = usePage();
3487
- const items = page.props.panel?.breadcrumbs ?? [];
3488
- return /* @__PURE__ */ jsx(BreadcrumbsList, { ...props, items });
3489
- }
3490
- function BreadcrumbsList({
3491
- items,
3492
- separator = /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "/" }),
3493
- className
3494
- }) {
3495
- if (items.length === 0) return null;
3496
- return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className: cn("text-sm", className), children: /* @__PURE__ */ jsx("ol", { className: "flex flex-wrap items-center gap-2 text-muted-foreground", children: items.map((item, index) => {
3497
- const isLast = index === items.length - 1;
3498
- return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
3499
- isLast || !item.url ? /* @__PURE__ */ jsx("span", { "aria-current": isLast ? "page" : void 0, className: "text-foreground", children: item.label }) : /* @__PURE__ */ jsx("a", { href: item.url, className: "hover:underline", children: item.label }),
3500
- !isLast && separator
3501
- ] }, `${item.label}-${item.url ?? "leaf"}`);
3502
- }) }) });
3503
- }
3504
- function EmptyState({ title, description, icon, action, className }) {
3505
- return /* @__PURE__ */ jsxs(
3506
- "div",
3507
- {
3508
- className: cn(
3509
- "flex flex-col items-center gap-3 px-4 py-12 text-center text-muted-foreground",
3510
- className
3511
- ),
3512
- children: [
3513
- icon && /* @__PURE__ */ jsx("div", { className: "text-3xl", children: icon }),
3514
- /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-foreground", children: title }),
3515
- description && /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm", children: description }),
3516
- action && /* @__PURE__ */ jsx("div", { className: "mt-2", children: action })
3517
- ]
3518
- }
3519
- );
3520
- }
3521
- function ErrorState({ status, title, description, action, className }) {
3522
- return /* @__PURE__ */ jsxs(
3523
- "div",
3524
- {
3525
- role: "alert",
3526
- className: cn("flex flex-col items-center gap-3 px-4 py-16 text-center", className),
3527
- children: [
3528
- status && /* @__PURE__ */ jsx("span", { className: "text-sm font-mono text-muted-foreground", children: status }),
3529
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: title }),
3530
- description && /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm text-muted-foreground", children: description }),
3531
- action && /* @__PURE__ */ jsx("div", { className: "mt-2", children: action })
3532
- ]
3533
- }
3534
- );
3535
- }
3536
- var VARIANT_CLASSES = {
3537
- line: "h-4 w-full",
3538
- block: "h-24 w-full",
3539
- circle: "h-10 w-10 rounded-full"
3540
- };
3541
- function LoadingSkeleton({
3542
- variant = "line",
3543
- width,
3544
- height,
3545
- count = 1,
3546
- className
3547
- }) {
3548
- const items = Array.from({ length: count }, (_, i) => i);
3549
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", "aria-hidden": "true", children: items.map((i) => /* @__PURE__ */ jsx(
3550
- "div",
3551
- {
3552
- style: { width, height },
3553
- className: cn(
3554
- "animate-pulse bg-muted",
3555
- variant !== "circle" && "rounded-sm",
3556
- VARIANT_CLASSES[variant],
3557
- className
3558
- )
3559
- },
3560
- i
3561
- )) });
3562
- }
3563
- function PageHeader({ title, description, actions, className }) {
3564
- return /* @__PURE__ */ jsxs("header", { className: cn("flex flex-wrap items-start gap-4", className), children: [
3565
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3566
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: title }),
3567
- description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description })
3568
- ] }),
3569
- actions && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: actions })
3570
- ] });
3571
- }
3572
- function resolveColumnSpan(columnSpan) {
3573
- if (columnSpan === void 0) return void 0;
3574
- if (typeof columnSpan === "number") return `col-span-${columnSpan}`;
3575
- return columnSpan;
3576
- }
3577
- function WidgetWrapper({
3578
- heading,
3579
- description,
3580
- loading = false,
3581
- error = null,
3582
- onRetry,
3583
- columnSpan,
3584
- className,
3585
- children
3586
- }) {
3587
- const baseClass = cn(
3588
- "rounded border bg-background p-4",
3589
- resolveColumnSpan(columnSpan),
3590
- className
3591
- );
3592
- if (loading) {
3593
- return /* @__PURE__ */ jsx(
3594
- "section",
3595
- {
3596
- "aria-label": heading,
3597
- "aria-busy": "true",
3598
- className: baseClass,
3599
- "data-widget-state": "loading",
3600
- children: /* @__PURE__ */ jsx("div", { className: "animate-pulse h-32 bg-muted rounded" })
3601
- }
3602
- );
3603
- }
3604
- if (error) {
3605
- return /* @__PURE__ */ jsx("section", { "aria-label": heading, className: baseClass, "data-widget-state": "error", children: /* @__PURE__ */ jsxs("div", { role: "alert", className: "flex flex-col gap-2 text-sm text-destructive", children: [
3606
- /* @__PURE__ */ jsx("p", { children: error.message }),
3607
- onRetry && /* @__PURE__ */ jsx(
3608
- "button",
3609
- {
3610
- type: "button",
3611
- onClick: onRetry,
3612
- className: "self-start rounded border px-2 py-1 text-xs text-foreground hover:bg-muted",
3613
- children: "Retry"
3614
- }
3615
- )
3616
- ] }) });
3617
- }
3618
- return /* @__PURE__ */ jsxs("section", { "aria-label": heading, className: baseClass, "data-widget-state": "ready", children: [
3619
- heading && /* @__PURE__ */ jsx("h2", { className: "text-sm font-medium text-muted-foreground", children: heading }),
3620
- description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: description }),
3621
- /* @__PURE__ */ jsx("div", { className: cn(heading || description ? "mt-3" : void 0), children })
3622
- ] });
3623
- }
3624
- var COLOR_TEXT = {
3625
- primary: "text-primary",
3626
- secondary: "text-secondary-foreground",
3627
- success: "text-green-600",
3628
- warning: "text-amber-600",
3629
- danger: "text-red-600",
3630
- info: "text-sky-600"
3631
- };
3632
- var COLOR_STROKE = {
3633
- primary: "stroke-primary",
3634
- secondary: "stroke-secondary-foreground",
3635
- success: "stroke-green-500",
3636
- warning: "stroke-amber-500",
3637
- danger: "stroke-red-500",
3638
- info: "stroke-sky-500"
3639
- };
3640
- function Sparkline({ points, color }) {
3641
- if (points.length < 2) return null;
3642
- const min = Math.min(...points);
3643
- const max = Math.max(...points);
3644
- const range = max - min || 1;
3645
- const stepX = 100 / (points.length - 1);
3646
- const coords = points.map((value, index) => {
3647
- const x = index * stepX;
3648
- const y = 30 - (value - min) / range * 30;
3649
- return `${x.toFixed(2)},${y.toFixed(2)}`;
3650
- }).join(" ");
3651
- return /* @__PURE__ */ jsx(
3652
- "svg",
3653
- {
3654
- viewBox: "0 0 100 30",
3655
- preserveAspectRatio: "none",
3656
- className: cn("mt-3 h-8 w-full", COLOR_STROKE[color]),
3657
- role: "img",
3658
- "aria-label": "Trend sparkline",
3659
- "data-testid": "stat-card-sparkline",
3660
- children: /* @__PURE__ */ jsx(
3661
- "polyline",
3662
- {
3663
- fill: "none",
3664
- stroke: "currentColor",
3665
- strokeWidth: "1.5",
3666
- strokeLinecap: "round",
3667
- strokeLinejoin: "round",
3668
- points: coords
3669
- }
3670
- )
3671
- }
3672
- );
3673
- }
3674
- function StatCard({ widget, className, columnSpan }) {
3675
- const { heading, description, value, statDescription, descriptionIcon, color, chart, url } = widget;
3676
- const hasChart = Array.isArray(chart) && chart.length >= 2;
3677
- const body = /* @__PURE__ */ jsxs(Fragment, { children: [
3678
- /* @__PURE__ */ jsx("div", { className: cn("text-4xl font-bold tabular-nums", COLOR_TEXT[color]), children: value ?? "\u2014" }),
3679
- statDescription && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-1 text-xs text-muted-foreground", children: [
3680
- descriptionIcon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-testid": "stat-card-icon", children: descriptionIcon }),
3681
- /* @__PURE__ */ jsx("span", { children: statDescription })
3682
- ] }),
3683
- hasChart && /* @__PURE__ */ jsx(Sparkline, { points: chart, color })
3684
- ] });
3685
- const inner = url ? /* @__PURE__ */ jsx(
3686
- "a",
3687
- {
3688
- href: url,
3689
- className: "block rounded outline-none focus-visible:ring-2 focus-visible:ring-ring",
3690
- "data-testid": "stat-card-link",
3691
- children: body
3692
- }
3693
- ) : body;
3694
- return /* @__PURE__ */ jsx(
3695
- WidgetWrapper,
3696
- {
3697
- heading,
3698
- description,
3699
- columnSpan,
3700
- className,
3701
- children: inner
3702
- }
3703
- );
3704
- }
3705
-
3706
- export { ActionButton, ActionFormModal, ActionMenu, Alert, AlertDescription, AlertTitle, AppShell, Badge, Breadcrumbs, Button, CanAccess, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox2 as Checkbox, CommandPalette, ConfirmDialog, DataTable, EmptyState, ErrorState, Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldRenderer, FieldSeparator, FieldSet, FieldTitle, FlashContainer, FlashToast, Footer, FormActions, FormRenderer, Input, Label, LoadingSkeleton, MainContent, PageHeader, ResourceIndex, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, Sidebar2 as Sidebar, Skeleton, StatCard, TableFilters, TablePagination, TableToolbar, Textarea2 as Textarea, Topbar, WidgetWrapper, badgeVariants, buttonVariants, clearFieldRegistry, cn, getFieldComponent, registerField };
1
+ export { Breadcrumbs, EmptyState, ErrorState, LoadingSkeleton } from './chunk-MTNFFSVA.js';
2
+ export { StatCard, WidgetWrapper } from './chunk-WKWV36PG.js';
3
+ export { Alert, AlertDescription, AlertTitle, Badge, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle, Label, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Textarea, badgeVariants } from './chunk-K7QY56XN.js';
4
+ export { PageHeader } from './chunk-MXUXMHOK.js';
5
+ export { AppShell, Footer, MainContent, Sidebar, Topbar } from './chunk-6UJQP7KU.js';
6
+ export { CommandPalette } from './chunk-FV7RESKL.js';
7
+ export { Input, Separator, Skeleton } from './chunk-R4BOUU6Y.js';
8
+ export { ResourceIndex } from './chunk-2LTZJ4R7.js';
9
+ export { DataTable, TableFilters, TablePagination, TableToolbar } from './chunk-PZEVKMZX.js';
10
+ import './chunk-RMPOR2YZ.js';
11
+ export { ActionButton, ActionFormModal, ActionMenu, ConfirmDialog } from './chunk-TNJEHYRF.js';
12
+ import './chunk-EYAUPJ6J.js';
13
+ export { FormActions } from './chunk-MTZJDFQ6.js';
14
+ export { FieldRenderer, FormRenderer, clearFieldRegistry, getFieldComponent, registerField } from './chunk-MSRTGCTJ.js';
15
+ export { Button, buttonVariants } from './chunk-FHRQ7PWE.js';
16
+ export { CanAccess } from './chunk-RNAMHQM6.js';
17
+ export { FlashContainer, FlashToast } from './chunk-DSULVRB6.js';
18
+ export { cn } from './chunk-B7ULUJVO.js';
3707
19
  //# sourceMappingURL=index.js.map
3708
20
  //# sourceMappingURL=index.js.map