@page-speed/forms 0.5.2 → 0.5.3

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 (53) hide show
  1. package/dist/chunk-232KNGJI.js +207 -0
  2. package/dist/chunk-232KNGJI.js.map +1 -0
  3. package/dist/chunk-24RPM43T.js +373 -0
  4. package/dist/chunk-24RPM43T.js.map +1 -0
  5. package/dist/chunk-27JUYRDE.cjs +173 -0
  6. package/dist/chunk-27JUYRDE.cjs.map +1 -0
  7. package/dist/chunk-5NT5T5XY.js +4136 -0
  8. package/dist/chunk-5NT5T5XY.js.map +1 -0
  9. package/dist/chunk-AVAKC6R7.cjs +236 -0
  10. package/dist/chunk-AVAKC6R7.cjs.map +1 -0
  11. package/dist/chunk-DKLLPKZN.cjs +238 -0
  12. package/dist/chunk-DKLLPKZN.cjs.map +1 -0
  13. package/dist/chunk-EX6CRLKG.cjs +397 -0
  14. package/dist/chunk-EX6CRLKG.cjs.map +1 -0
  15. package/dist/chunk-H6NNFV64.js +127 -0
  16. package/dist/chunk-H6NNFV64.js.map +1 -0
  17. package/dist/chunk-JBEWTBFH.js +217 -0
  18. package/dist/chunk-JBEWTBFH.js.map +1 -0
  19. package/dist/chunk-JBEZLX3H.cjs +138 -0
  20. package/dist/chunk-JBEZLX3H.cjs.map +1 -0
  21. package/dist/chunk-VLGZG2VP.js +150 -0
  22. package/dist/chunk-VLGZG2VP.js.map +1 -0
  23. package/dist/chunk-ZYFTT6DB.cjs +4169 -0
  24. package/dist/chunk-ZYFTT6DB.cjs.map +1 -0
  25. package/dist/core.cjs +23 -733
  26. package/dist/core.cjs.map +1 -1
  27. package/dist/core.js +3 -716
  28. package/dist/core.js.map +1 -1
  29. package/dist/index.cjs +43 -738
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +3 -716
  32. package/dist/index.js.map +1 -1
  33. package/dist/inputs.cjs +44 -4359
  34. package/dist/inputs.cjs.map +1 -1
  35. package/dist/inputs.js +2 -4337
  36. package/dist/inputs.js.map +1 -1
  37. package/dist/integration.cjs +51 -4645
  38. package/dist/integration.cjs.map +1 -1
  39. package/dist/integration.js +37 -4631
  40. package/dist/integration.js.map +1 -1
  41. package/dist/validation-rules.cjs +75 -231
  42. package/dist/validation-rules.cjs.map +1 -1
  43. package/dist/validation-rules.js +1 -215
  44. package/dist/validation-rules.js.map +1 -1
  45. package/dist/validation-utils.cjs +43 -133
  46. package/dist/validation-utils.cjs.map +1 -1
  47. package/dist/validation-utils.js +1 -125
  48. package/dist/validation-utils.js.map +1 -1
  49. package/dist/validation.cjs +115 -364
  50. package/dist/validation.cjs.map +1 -1
  51. package/dist/validation.js +2 -339
  52. package/dist/validation.js.map +1 -1
  53. package/package.json +1 -1
@@ -0,0 +1,4136 @@
1
+ import { cn, INPUT_AUTOFILL_RESET_CLASSES, Field, FieldLabel, FieldDescription, LabelGroup, Button, buttonVariants } from './chunk-232KNGJI.js';
2
+ import * as React21 from 'react';
3
+ import { Dialog as Dialog$1, Checkbox as Checkbox$1, RadioGroup as RadioGroup$1, Switch as Switch$1, Select as Select$1, Popover as Popover$1 } from 'radix-ui';
4
+ import { Command as Command$1 } from 'cmdk';
5
+ import { useDirection } from '@radix-ui/react-direction';
6
+ import { Slot } from '@radix-ui/react-slot';
7
+ import { getDefaultClassNames, DayPicker } from 'react-day-picker';
8
+
9
+ var Input = React21.forwardRef(
10
+ ({ className, type, ...props }, ref) => {
11
+ return /* @__PURE__ */ React21.createElement(
12
+ "input",
13
+ {
14
+ ref,
15
+ type,
16
+ "data-slot": "input",
17
+ className: cn(
18
+ // Core structure - no hardcoded colors, uses CSS variables
19
+ "flex h-9 w-full min-w-0 rounded-md border border-input",
20
+ "bg-transparent px-3 py-1 text-base shadow-sm",
21
+ "transition-colors outline-none md:text-sm",
22
+ // Focus state - uses ring-ring CSS variable (adapts to theme)
23
+ "focus-visible:ring-1 focus-visible:ring-ring",
24
+ // Error state - uses destructive CSS variables (adapts to theme)
25
+ "aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive",
26
+ // Disabled state - no color hardcoding
27
+ "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
28
+ // File input specific - inherits text color from parent
29
+ "file:inline-flex file:h-7 file:border-0 file:bg-transparent",
30
+ "file:text-sm file:font-medium",
31
+ // Autofill reset - prevents browser from overriding our dynamic colors
32
+ INPUT_AUTOFILL_RESET_CLASSES,
33
+ className
34
+ ),
35
+ ...props
36
+ }
37
+ );
38
+ }
39
+ );
40
+ Input.displayName = "Input";
41
+
42
+ // src/inputs/TextInput.tsx
43
+ function TextInput({
44
+ name,
45
+ value,
46
+ onChange,
47
+ onBlur,
48
+ placeholder,
49
+ disabled = false,
50
+ required = false,
51
+ error = false,
52
+ className = "",
53
+ type = "text",
54
+ id = "text",
55
+ ...props
56
+ }) {
57
+ const handleChange = (e) => {
58
+ onChange(e.target.value);
59
+ };
60
+ const handleBlur = () => {
61
+ onBlur?.();
62
+ };
63
+ const hasValue = String(value ?? "").trim().length > 0;
64
+ return /* @__PURE__ */ React21.createElement(
65
+ Input,
66
+ {
67
+ type,
68
+ id,
69
+ name,
70
+ value: value ?? "",
71
+ onChange: handleChange,
72
+ onBlur: handleBlur,
73
+ placeholder,
74
+ disabled,
75
+ required,
76
+ className: cn(
77
+ // Valid value indicator - ring-2 when has value and no error
78
+ !error && hasValue && "ring-2 ring-ring",
79
+ // Error state - handled by Input component via aria-invalid
80
+ className
81
+ ),
82
+ "aria-invalid": error || props["aria-invalid"],
83
+ "aria-describedby": props["aria-describedby"],
84
+ "aria-required": required || props["aria-required"],
85
+ ...props
86
+ }
87
+ );
88
+ }
89
+ TextInput.displayName = "TextInput";
90
+ function Textarea({ className, ...props }) {
91
+ return /* @__PURE__ */ React21.createElement(
92
+ "textarea",
93
+ {
94
+ "data-slot": "textarea",
95
+ className: cn(
96
+ // Core structure - uses CSS variables only
97
+ "flex field-sizing-content min-h-16 w-full rounded-md border border-input",
98
+ "bg-transparent px-3 py-2 text-base shadow-xs",
99
+ "transition-[color,box-shadow] outline-none md:text-sm",
100
+ // Focus state - uses ring-ring CSS variable
101
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
102
+ // Error state - uses destructive CSS variables
103
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20",
104
+ // Disabled state
105
+ "disabled:cursor-not-allowed disabled:opacity-50",
106
+ className
107
+ ),
108
+ ...props
109
+ }
110
+ );
111
+ }
112
+
113
+ // src/inputs/TextArea.tsx
114
+ function TextArea({
115
+ name,
116
+ value,
117
+ onChange,
118
+ onBlur,
119
+ placeholder,
120
+ disabled = false,
121
+ required = false,
122
+ error = false,
123
+ className = "",
124
+ rows = 3,
125
+ cols,
126
+ maxLength,
127
+ minLength,
128
+ wrap = "soft",
129
+ ...props
130
+ }) {
131
+ const handleChange = (e) => {
132
+ onChange(e.target.value);
133
+ };
134
+ const handleBlur = () => {
135
+ onBlur?.();
136
+ };
137
+ const hasValue = String(value ?? "").trim().length > 0;
138
+ return /* @__PURE__ */ React21.createElement(
139
+ Textarea,
140
+ {
141
+ name,
142
+ value: value ?? "",
143
+ onChange: handleChange,
144
+ onBlur: handleBlur,
145
+ placeholder,
146
+ disabled,
147
+ required,
148
+ className: cn(
149
+ // Valid value indicator - ring-2 when has value and no error
150
+ !error && hasValue && "ring-2 ring-ring",
151
+ // Error state - handled by Textarea component via aria-invalid
152
+ className
153
+ ),
154
+ rows,
155
+ cols,
156
+ maxLength,
157
+ minLength,
158
+ wrap,
159
+ "aria-invalid": error || props["aria-invalid"],
160
+ "aria-describedby": props["aria-describedby"],
161
+ "aria-required": required || props["aria-required"],
162
+ ...props
163
+ }
164
+ );
165
+ }
166
+ TextArea.displayName = "TextArea";
167
+ function Checkbox({
168
+ className,
169
+ ...props
170
+ }) {
171
+ return /* @__PURE__ */ React21.createElement(
172
+ Checkbox$1.Root,
173
+ {
174
+ "data-slot": "checkbox",
175
+ className: cn(
176
+ // Core structure - uses CSS variables
177
+ "peer size-4 shrink-0 rounded-[4px] border border-input bg-transparent shadow-xs",
178
+ "transition-shadow outline-none",
179
+ // Checked state - uses primary CSS variables
180
+ "data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
181
+ "data-[state=checked]:border-primary",
182
+ // Focus state - uses ring-ring CSS variable
183
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
184
+ // Error state - uses destructive CSS variables
185
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20",
186
+ // Disabled state
187
+ "disabled:cursor-not-allowed disabled:opacity-50",
188
+ className
189
+ ),
190
+ ...props
191
+ },
192
+ /* @__PURE__ */ React21.createElement(
193
+ Checkbox$1.Indicator,
194
+ {
195
+ "data-slot": "checkbox-indicator",
196
+ className: "grid place-content-center text-current transition-none"
197
+ },
198
+ /* @__PURE__ */ React21.createElement(
199
+ "svg",
200
+ {
201
+ className: "size-3.5",
202
+ viewBox: "0 0 24 24",
203
+ fill: "none",
204
+ stroke: "currentColor",
205
+ strokeWidth: "3",
206
+ strokeLinecap: "round",
207
+ strokeLinejoin: "round"
208
+ },
209
+ /* @__PURE__ */ React21.createElement("polyline", { points: "20 6 9 17 4 12" })
210
+ )
211
+ )
212
+ );
213
+ }
214
+
215
+ // src/inputs/Checkbox.tsx
216
+ function Checkbox2({
217
+ name,
218
+ value,
219
+ onChange,
220
+ onBlur,
221
+ disabled = false,
222
+ required = false,
223
+ error = false,
224
+ className = "",
225
+ label,
226
+ description,
227
+ useChoiceCard = false,
228
+ ...props
229
+ }) {
230
+ const checkboxId = props.id || `checkbox-${name}`;
231
+ const handleCheckedChange = (checked) => {
232
+ onChange(checked);
233
+ };
234
+ const handleBlur = () => {
235
+ onBlur?.();
236
+ };
237
+ const showChoiceCard = useChoiceCard || !!description;
238
+ const checkbox = /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(
239
+ "input",
240
+ {
241
+ type: "checkbox",
242
+ name,
243
+ checked: value,
244
+ onChange: () => {
245
+ },
246
+ disabled,
247
+ required,
248
+ tabIndex: -1,
249
+ "aria-hidden": "true",
250
+ style: {
251
+ position: "absolute",
252
+ width: "1px",
253
+ height: "1px",
254
+ padding: 0,
255
+ margin: "-1px",
256
+ overflow: "hidden",
257
+ clip: "rect(0, 0, 0, 0)",
258
+ whiteSpace: "nowrap",
259
+ border: 0
260
+ }
261
+ }
262
+ ), /* @__PURE__ */ React21.createElement(
263
+ Checkbox,
264
+ {
265
+ id: checkboxId,
266
+ checked: value,
267
+ onCheckedChange: handleCheckedChange,
268
+ onBlur: handleBlur,
269
+ disabled,
270
+ "aria-invalid": error || props["aria-invalid"],
271
+ "aria-describedby": description ? `${checkboxId}-description` : props["aria-describedby"],
272
+ "aria-required": required || props["aria-required"],
273
+ ...props
274
+ }
275
+ ));
276
+ if (!label) {
277
+ return /* @__PURE__ */ React21.createElement(Field, { className }, checkbox);
278
+ }
279
+ return /* @__PURE__ */ React21.createElement(Field, { className: "gap-0", invalid: Boolean(error) }, /* @__PURE__ */ React21.createElement(
280
+ FieldLabel,
281
+ {
282
+ htmlFor: checkboxId,
283
+ className: cn(
284
+ "flex gap-3 p-3 duration-200 select-auto font-normal leading-normal",
285
+ showChoiceCard && "border rounded-lg hover:ring-2 hover:ring-ring/50",
286
+ showChoiceCard && value && "ring-2 ring-ring",
287
+ showChoiceCard && error && "border-destructive",
288
+ disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
289
+ className
290
+ )
291
+ },
292
+ /* @__PURE__ */ React21.createElement(
293
+ "div",
294
+ {
295
+ className: cn(
296
+ "flex w-full gap-3",
297
+ showChoiceCard ? "items-start" : "items-center"
298
+ )
299
+ },
300
+ checkbox,
301
+ /* @__PURE__ */ React21.createElement(Field, { className: "flex-1 gap-1" }, /* @__PURE__ */ React21.createElement("span", { className: "text-sm font-medium leading-none" }, label), description && /* @__PURE__ */ React21.createElement(
302
+ FieldDescription,
303
+ {
304
+ id: `${checkboxId}-description`,
305
+ className: "leading-snug"
306
+ },
307
+ description
308
+ ))
309
+ )
310
+ ));
311
+ }
312
+ Checkbox2.displayName = "Checkbox";
313
+ function CheckboxGroup({
314
+ name,
315
+ value = [],
316
+ onChange,
317
+ onBlur,
318
+ disabled = false,
319
+ required = false,
320
+ error = false,
321
+ className = "",
322
+ layout = "stacked",
323
+ label,
324
+ description,
325
+ options,
326
+ showSelectAll = false,
327
+ selectAllLabel = "Select all",
328
+ minSelections,
329
+ maxSelections,
330
+ renderOption,
331
+ gridColumns = 2,
332
+ ...props
333
+ }) {
334
+ const enabledOptions = options.filter((opt) => !opt.disabled);
335
+ const enabledValues = enabledOptions.map((opt) => opt.value);
336
+ const selectedEnabledCount = value.filter(
337
+ (v) => enabledValues.includes(v)
338
+ ).length;
339
+ const allSelected = selectedEnabledCount === enabledOptions.length;
340
+ const someSelected = selectedEnabledCount > 0 && !allSelected;
341
+ const useChoiceCard = React21.useMemo(() => {
342
+ if (!options) return false;
343
+ return options?.some((opt) => opt.description);
344
+ }, [options]);
345
+ const countableValue = React21.useMemo(() => {
346
+ if (value?.length > 0) {
347
+ return value.length;
348
+ }
349
+ return 0;
350
+ }, [value]);
351
+ const handleChange = (optionValue, checked) => {
352
+ const newValues = checked ? [...value, optionValue] : value.filter((v) => v !== optionValue);
353
+ if (maxSelections && checked && newValues.length > maxSelections) {
354
+ return;
355
+ }
356
+ onChange(newValues);
357
+ };
358
+ const handleSelectAll = (checked) => {
359
+ if (checked) {
360
+ const allValues = enabledOptions.map((opt) => opt.value);
361
+ onChange(allValues);
362
+ } else {
363
+ onChange([]);
364
+ }
365
+ };
366
+ const handleBlur = () => {
367
+ onBlur?.();
368
+ };
369
+ const maxReached = Boolean(maxSelections && countableValue >= maxSelections);
370
+ const containerClass = React21.useMemo(() => {
371
+ return cn(
372
+ "w-full gap-3 grid grid-cols-1 border-0 m-0 p-0 min-w-0",
373
+ (layout === "grid" || layout === "inline") && "md:grid-cols-2",
374
+ className
375
+ );
376
+ }, [layout, className]);
377
+ const groupDescriptionId = description ? `${name}-description` : void 0;
378
+ const groupAriaDescribedBy = [props["aria-describedby"], groupDescriptionId].filter(Boolean).join(" ") || void 0;
379
+ return /* @__PURE__ */ React21.createElement(
380
+ "fieldset",
381
+ {
382
+ className: containerClass,
383
+ role: "group",
384
+ "aria-invalid": error || props["aria-invalid"],
385
+ "aria-describedby": groupAriaDescribedBy,
386
+ "aria-required": required || props["aria-required"],
387
+ "aria-label": typeof label === "string" ? label : props["aria-label"]
388
+ },
389
+ /* @__PURE__ */ React21.createElement(
390
+ LabelGroup,
391
+ {
392
+ labelHtmlFor: name,
393
+ required,
394
+ variant: "legend",
395
+ secondaryId: groupDescriptionId,
396
+ secondary: description,
397
+ primary: label
398
+ }
399
+ ),
400
+ showSelectAll && enabledOptions.length > 0 && /* @__PURE__ */ React21.createElement(
401
+ Checkbox2,
402
+ {
403
+ name: `${name}-select-all`,
404
+ id: `${name}-select-all`,
405
+ value: allSelected,
406
+ onChange: handleSelectAll,
407
+ onBlur: handleBlur,
408
+ indeterminate: someSelected,
409
+ label: selectAllLabel,
410
+ useChoiceCard,
411
+ disabled,
412
+ "aria-label": selectAllLabel
413
+ }
414
+ ),
415
+ options.map((option) => {
416
+ const isChecked = value.includes(option.value);
417
+ const isDisabled = disabled || option.disabled || maxReached && !isChecked;
418
+ return /* @__PURE__ */ React21.createElement(
419
+ Checkbox2,
420
+ {
421
+ key: option.value,
422
+ name,
423
+ id: `${name}-${option.value}`,
424
+ value: isChecked,
425
+ onChange: (checked) => handleChange(option.value, checked),
426
+ onBlur: handleBlur,
427
+ disabled: isDisabled,
428
+ required: required && minSelections ? value.length < minSelections : false,
429
+ error,
430
+ label: renderOption ? renderOption(option) : option.label,
431
+ description: renderOption ? void 0 : option.description,
432
+ useChoiceCard
433
+ }
434
+ );
435
+ }),
436
+ (minSelections || maxSelections) && /* @__PURE__ */ React21.createElement(
437
+ FieldDescription,
438
+ {
439
+ className: cn(
440
+ "p-2 rounded-lg border font-semibold mt-2 leading-snug",
441
+ minSelections && countableValue < minSelections ? "border-destructive bg-destructive/80 text-destructive-foreground" : "border-border bg-card text-card-foreground"
442
+ ),
443
+ "aria-live": "polite"
444
+ },
445
+ minSelections && countableValue < minSelections && /* @__PURE__ */ React21.createElement("span", null, "Select at least ", minSelections, " option", minSelections !== 1 ? "s" : ""),
446
+ maxSelections && /* @__PURE__ */ React21.createElement("span", null, countableValue, "/", maxSelections, " selected")
447
+ )
448
+ );
449
+ }
450
+ CheckboxGroup.displayName = "CheckboxGroup";
451
+ function RadioGroup({
452
+ className,
453
+ ...props
454
+ }) {
455
+ return /* @__PURE__ */ React21.createElement(
456
+ RadioGroup$1.Root,
457
+ {
458
+ "data-slot": "radio-group",
459
+ className: cn("grid gap-3", className),
460
+ ...props
461
+ }
462
+ );
463
+ }
464
+ function RadioGroupItem({
465
+ className,
466
+ ...props
467
+ }) {
468
+ return /* @__PURE__ */ React21.createElement(
469
+ RadioGroup$1.Item,
470
+ {
471
+ "data-slot": "radio-group-item",
472
+ className: cn(
473
+ // Core structure - uses CSS variables
474
+ "aspect-square size-4 shrink-0 rounded-full border border-input bg-transparent shadow-xs",
475
+ "text-primary transition-[color,box-shadow] outline-none",
476
+ // Focus state - uses ring-ring CSS variable
477
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
478
+ // Error state - uses destructive CSS variables
479
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20",
480
+ // Disabled state
481
+ "disabled:cursor-not-allowed disabled:opacity-50",
482
+ className
483
+ ),
484
+ ...props
485
+ },
486
+ /* @__PURE__ */ React21.createElement(
487
+ RadioGroup$1.Indicator,
488
+ {
489
+ "data-slot": "radio-group-indicator",
490
+ className: "relative flex items-center justify-center"
491
+ },
492
+ /* @__PURE__ */ React21.createElement(
493
+ "svg",
494
+ {
495
+ className: "fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2",
496
+ viewBox: "0 0 24 24"
497
+ },
498
+ /* @__PURE__ */ React21.createElement("circle", { cx: "12", cy: "12", r: "12" })
499
+ )
500
+ )
501
+ );
502
+ }
503
+
504
+ // src/inputs/Radio.tsx
505
+ function Radio({
506
+ name,
507
+ value,
508
+ onChange,
509
+ onBlur,
510
+ disabled = false,
511
+ required = false,
512
+ error = false,
513
+ className = "",
514
+ layout = "stacked",
515
+ label,
516
+ description,
517
+ options,
518
+ ...props
519
+ }) {
520
+ const handleValueChange = (selectedValue) => {
521
+ onChange(selectedValue);
522
+ };
523
+ const handleBlur = () => {
524
+ onBlur?.();
525
+ };
526
+ const useChoiceCard = React21.useMemo(() => {
527
+ return options.some((option) => option.description);
528
+ }, [options]);
529
+ const groupDescriptionId = description ? `${name}-description` : void 0;
530
+ return /* @__PURE__ */ React21.createElement(Field, { className: cn("w-full", className), invalid: Boolean(error) }, (label || description) && /* @__PURE__ */ React21.createElement(Field, { className: "mb-3 gap-1" }, label && /* @__PURE__ */ React21.createElement("div", { className: "text-base font-medium leading-none" }, label), description && /* @__PURE__ */ React21.createElement(
531
+ FieldDescription,
532
+ {
533
+ id: groupDescriptionId,
534
+ className: "leading-snug"
535
+ },
536
+ description
537
+ )), /* @__PURE__ */ React21.createElement(
538
+ RadioGroup,
539
+ {
540
+ name,
541
+ value,
542
+ onValueChange: handleValueChange,
543
+ onBlur: handleBlur,
544
+ disabled,
545
+ required,
546
+ className: cn(
547
+ "gap-3",
548
+ layout === "grid" && "grid grid-cols-1 md:grid-cols-2",
549
+ layout === "inline" && "flex flex-wrap"
550
+ ),
551
+ "aria-invalid": error || props["aria-invalid"],
552
+ "aria-describedby": groupDescriptionId || props["aria-describedby"],
553
+ "aria-required": required || props["aria-required"]
554
+ },
555
+ options.map((option) => {
556
+ const isSelected = value === option.value;
557
+ const isDisabled = disabled || option.disabled;
558
+ const radioId = `${name}-${option.value}`;
559
+ const hasDescription = !!option.description;
560
+ return /* @__PURE__ */ React21.createElement(
561
+ FieldLabel,
562
+ {
563
+ key: option.value,
564
+ htmlFor: radioId,
565
+ className: cn(
566
+ "flex gap-3 p-3 duration-200 select-auto font-normal leading-normal",
567
+ useChoiceCard && "border rounded-lg hover:ring-2 hover:ring-ring/50",
568
+ useChoiceCard && isSelected && "ring-2 ring-ring",
569
+ useChoiceCard && error && "border-destructive",
570
+ isDisabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
571
+ )
572
+ },
573
+ /* @__PURE__ */ React21.createElement(
574
+ Field,
575
+ {
576
+ orientation: "horizontal",
577
+ className: cn(
578
+ "flex w-full gap-3",
579
+ useChoiceCard ? "items-start" : "items-center"
580
+ )
581
+ },
582
+ /* @__PURE__ */ React21.createElement(
583
+ RadioGroupItem,
584
+ {
585
+ value: option.value,
586
+ id: radioId,
587
+ disabled: isDisabled,
588
+ className: "mt-0.5",
589
+ "aria-describedby": hasDescription ? `${radioId}-description` : void 0
590
+ }
591
+ ),
592
+ /* @__PURE__ */ React21.createElement(Field, { className: "flex-1 gap-1" }, /* @__PURE__ */ React21.createElement("span", { className: "text-sm font-medium leading-none" }, option.label), option.description && /* @__PURE__ */ React21.createElement(
593
+ FieldDescription,
594
+ {
595
+ id: `${radioId}-description`,
596
+ className: "leading-snug"
597
+ },
598
+ option.description
599
+ ))
600
+ )
601
+ );
602
+ })
603
+ ));
604
+ }
605
+ Radio.displayName = "Radio";
606
+ function Switch({
607
+ className,
608
+ size = "default",
609
+ ...props
610
+ }) {
611
+ return /* @__PURE__ */ React21.createElement(
612
+ Switch$1.Root,
613
+ {
614
+ "data-slot": "switch",
615
+ "data-size": size,
616
+ className: cn(
617
+ // Core structure - uses CSS variables
618
+ "peer group/switch inline-flex shrink-0 items-center rounded-full",
619
+ "border border-transparent shadow-xs transition-all outline-none",
620
+ // State-based backgrounds - use CSS variables
621
+ "data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
622
+ // Focus state
623
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
624
+ // Disabled state
625
+ "disabled:cursor-not-allowed disabled:opacity-50",
626
+ // Size variants
627
+ "data-[size=default]:h-[1.15rem] data-[size=default]:w-8",
628
+ "data-[size=sm]:h-3.5 data-[size=sm]:w-6",
629
+ className
630
+ ),
631
+ ...props
632
+ },
633
+ /* @__PURE__ */ React21.createElement(
634
+ Switch$1.Thumb,
635
+ {
636
+ "data-slot": "switch-thumb",
637
+ className: cn(
638
+ // Thumb appearance - inherits from parent theme
639
+ "bg-background pointer-events-none block rounded-full ring-0 transition-transform",
640
+ // Size variants
641
+ "group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3",
642
+ // Position based on state
643
+ "data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
644
+ )
645
+ }
646
+ )
647
+ );
648
+ }
649
+
650
+ // src/inputs/Switch.tsx
651
+ function Switch2({
652
+ name,
653
+ value,
654
+ onChange,
655
+ onBlur,
656
+ disabled = false,
657
+ required = false,
658
+ error = false,
659
+ className = "",
660
+ label,
661
+ description,
662
+ size = "default",
663
+ ...props
664
+ }) {
665
+ const switchId = props.id || `switch-${name}`;
666
+ const handleCheckedChange = (checked) => {
667
+ onChange(checked);
668
+ };
669
+ const handleBlur = () => {
670
+ onBlur?.();
671
+ };
672
+ const switchElement = /* @__PURE__ */ React21.createElement(
673
+ Switch,
674
+ {
675
+ id: switchId,
676
+ checked: value,
677
+ onCheckedChange: handleCheckedChange,
678
+ onBlur: handleBlur,
679
+ disabled,
680
+ size,
681
+ "aria-invalid": error || props["aria-invalid"],
682
+ "aria-describedby": description ? `${switchId}-description` : props["aria-describedby"],
683
+ "aria-required": required || props["aria-required"],
684
+ ...props
685
+ }
686
+ );
687
+ if (!label) {
688
+ return /* @__PURE__ */ React21.createElement(Field, { className }, switchElement);
689
+ }
690
+ return /* @__PURE__ */ React21.createElement(Field, { className: "gap-0", invalid: Boolean(error) }, /* @__PURE__ */ React21.createElement(
691
+ FieldLabel,
692
+ {
693
+ htmlFor: switchId,
694
+ className: cn(
695
+ "flex items-center gap-3 cursor-pointer select-auto font-normal leading-normal",
696
+ disabled && "opacity-50 cursor-not-allowed",
697
+ className
698
+ )
699
+ },
700
+ switchElement,
701
+ /* @__PURE__ */ React21.createElement(Field, { className: "gap-1" }, /* @__PURE__ */ React21.createElement("span", { className: "text-sm font-medium leading-none" }, label), description && /* @__PURE__ */ React21.createElement(
702
+ FieldDescription,
703
+ {
704
+ id: `${switchId}-description`,
705
+ className: "leading-snug"
706
+ },
707
+ description
708
+ ))
709
+ ));
710
+ }
711
+ Switch2.displayName = "Switch";
712
+ function Select({
713
+ ...props
714
+ }) {
715
+ return /* @__PURE__ */ React21.createElement(Select$1.Root, { "data-slot": "select", ...props });
716
+ }
717
+ function SelectGroup({
718
+ ...props
719
+ }) {
720
+ return /* @__PURE__ */ React21.createElement(Select$1.Group, { "data-slot": "select-group", ...props });
721
+ }
722
+ function SelectValue({
723
+ ...props
724
+ }) {
725
+ return /* @__PURE__ */ React21.createElement(Select$1.Value, { "data-slot": "select-value", ...props });
726
+ }
727
+ function SelectTrigger({
728
+ className,
729
+ size = "default",
730
+ children,
731
+ ...props
732
+ }) {
733
+ return /* @__PURE__ */ React21.createElement(
734
+ Select$1.Trigger,
735
+ {
736
+ "data-slot": "select-trigger",
737
+ "data-size": size,
738
+ className: cn(
739
+ // Core structure - uses CSS variables
740
+ "flex w-fit items-center justify-between gap-2 rounded-md border border-input",
741
+ "bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs",
742
+ "transition-[color,box-shadow] outline-none",
743
+ // Focus state
744
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
745
+ // Error state
746
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20",
747
+ // Disabled state
748
+ "disabled:cursor-not-allowed disabled:opacity-50",
749
+ // Size variants
750
+ "data-[size=default]:h-9 data-[size=sm]:h-8",
751
+ // Value styling
752
+ "*:data-[slot=select-value]:line-clamp-1",
753
+ "*:data-[slot=select-value]:flex",
754
+ "*:data-[slot=select-value]:items-center",
755
+ "*:data-[slot=select-value]:gap-2",
756
+ // SVG styling
757
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
758
+ className
759
+ ),
760
+ ...props
761
+ },
762
+ children,
763
+ /* @__PURE__ */ React21.createElement(Select$1.Icon, { asChild: true }, /* @__PURE__ */ React21.createElement(
764
+ "svg",
765
+ {
766
+ className: "size-4 opacity-50",
767
+ viewBox: "0 0 24 24",
768
+ fill: "none",
769
+ stroke: "currentColor",
770
+ strokeWidth: "2",
771
+ strokeLinecap: "round",
772
+ strokeLinejoin: "round"
773
+ },
774
+ /* @__PURE__ */ React21.createElement("polyline", { points: "6 9 12 15 18 9" })
775
+ ))
776
+ );
777
+ }
778
+ function SelectContent({
779
+ className,
780
+ children,
781
+ position = "item-aligned",
782
+ align = "center",
783
+ ...props
784
+ }) {
785
+ return /* @__PURE__ */ React21.createElement(Select$1.Portal, null, /* @__PURE__ */ React21.createElement(
786
+ Select$1.Content,
787
+ {
788
+ "data-slot": "select-content",
789
+ className: cn(
790
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 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 shadow-md",
791
+ 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",
792
+ className
793
+ ),
794
+ position,
795
+ align,
796
+ ...props
797
+ },
798
+ /* @__PURE__ */ React21.createElement(SelectScrollUpButton, null),
799
+ /* @__PURE__ */ React21.createElement(
800
+ Select$1.Viewport,
801
+ {
802
+ className: cn(
803
+ "p-1",
804
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
805
+ )
806
+ },
807
+ children
808
+ ),
809
+ /* @__PURE__ */ React21.createElement(SelectScrollDownButton, null)
810
+ ));
811
+ }
812
+ function SelectLabel({
813
+ className,
814
+ ...props
815
+ }) {
816
+ return /* @__PURE__ */ React21.createElement(
817
+ Select$1.Label,
818
+ {
819
+ "data-slot": "select-label",
820
+ className: cn("px-2 py-1.5 text-xs opacity-70", className),
821
+ ...props
822
+ }
823
+ );
824
+ }
825
+ function SelectItem({
826
+ className,
827
+ children,
828
+ ...props
829
+ }) {
830
+ return /* @__PURE__ */ React21.createElement(
831
+ Select$1.Item,
832
+ {
833
+ "data-slot": "select-item",
834
+ className: cn(
835
+ // Core structure - inherits text color
836
+ "relative flex w-full cursor-default items-center gap-2 rounded-sm",
837
+ "py-1.5 pr-8 pl-2 text-sm outline-hidden select-none",
838
+ // Focus state - uses accent CSS variable
839
+ "focus:bg-accent focus:text-accent-foreground",
840
+ // Disabled state
841
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
842
+ // SVG styling
843
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
844
+ // Span styling
845
+ "*:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
846
+ className
847
+ ),
848
+ ...props
849
+ },
850
+ /* @__PURE__ */ React21.createElement(
851
+ "span",
852
+ {
853
+ "data-slot": "select-item-indicator",
854
+ className: "absolute right-2 flex size-3.5 items-center justify-center"
855
+ },
856
+ /* @__PURE__ */ React21.createElement(Select$1.ItemIndicator, null, /* @__PURE__ */ React21.createElement(
857
+ "svg",
858
+ {
859
+ className: "size-4",
860
+ viewBox: "0 0 24 24",
861
+ fill: "none",
862
+ stroke: "currentColor",
863
+ strokeWidth: "3",
864
+ strokeLinecap: "round",
865
+ strokeLinejoin: "round"
866
+ },
867
+ /* @__PURE__ */ React21.createElement("polyline", { points: "20 6 9 17 4 12" })
868
+ ))
869
+ ),
870
+ /* @__PURE__ */ React21.createElement(Select$1.ItemText, null, children)
871
+ );
872
+ }
873
+ function SelectScrollUpButton({
874
+ className,
875
+ ...props
876
+ }) {
877
+ return /* @__PURE__ */ React21.createElement(
878
+ Select$1.ScrollUpButton,
879
+ {
880
+ "data-slot": "select-scroll-up-button",
881
+ className: cn(
882
+ "flex cursor-default items-center justify-center py-1",
883
+ className
884
+ ),
885
+ ...props
886
+ },
887
+ /* @__PURE__ */ React21.createElement(
888
+ "svg",
889
+ {
890
+ className: "size-4",
891
+ viewBox: "0 0 24 24",
892
+ fill: "none",
893
+ stroke: "currentColor",
894
+ strokeWidth: "2",
895
+ strokeLinecap: "round",
896
+ strokeLinejoin: "round"
897
+ },
898
+ /* @__PURE__ */ React21.createElement("polyline", { points: "18 15 12 9 6 15" })
899
+ )
900
+ );
901
+ }
902
+ function SelectScrollDownButton({
903
+ className,
904
+ ...props
905
+ }) {
906
+ return /* @__PURE__ */ React21.createElement(
907
+ Select$1.ScrollDownButton,
908
+ {
909
+ "data-slot": "select-scroll-down-button",
910
+ className: cn(
911
+ "flex cursor-default items-center justify-center py-1",
912
+ className
913
+ ),
914
+ ...props
915
+ },
916
+ /* @__PURE__ */ React21.createElement(
917
+ "svg",
918
+ {
919
+ className: "size-4",
920
+ viewBox: "0 0 24 24",
921
+ fill: "none",
922
+ stroke: "currentColor",
923
+ strokeWidth: "2",
924
+ strokeLinecap: "round",
925
+ strokeLinejoin: "round"
926
+ },
927
+ /* @__PURE__ */ React21.createElement("polyline", { points: "6 9 12 15 18 9" })
928
+ )
929
+ );
930
+ }
931
+
932
+ // src/inputs/Select.tsx
933
+ function Select2({
934
+ name,
935
+ value,
936
+ onChange,
937
+ onBlur,
938
+ onFocus,
939
+ disabled = false,
940
+ required = false,
941
+ error = false,
942
+ className = "",
943
+ placeholder = "Select...",
944
+ options = [],
945
+ optionGroups = [],
946
+ renderOption,
947
+ ...props
948
+ }) {
949
+ const [hasInteracted, setHasInteracted] = React21.useState(false);
950
+ const allOptions = React21.useMemo(() => {
951
+ if (optionGroups.length > 0) {
952
+ return optionGroups.flatMap((group) => group.options);
953
+ }
954
+ return options;
955
+ }, [options, optionGroups]);
956
+ const hasValue = Boolean(value);
957
+ const selectValue = value ? String(value) : void 0;
958
+ const handleValueChange = (newValue) => {
959
+ onChange(newValue);
960
+ };
961
+ const handleOpenChange = (open) => {
962
+ if (open) {
963
+ if (!hasInteracted) {
964
+ setHasInteracted(true);
965
+ }
966
+ onFocus?.();
967
+ } else if (hasInteracted) {
968
+ onBlur?.();
969
+ }
970
+ };
971
+ return /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(
972
+ "input",
973
+ {
974
+ type: "hidden",
975
+ name,
976
+ value: value ?? "",
977
+ disabled,
978
+ required,
979
+ tabIndex: -1,
980
+ "aria-hidden": "true",
981
+ style: {
982
+ position: "absolute",
983
+ width: "1px",
984
+ height: "1px",
985
+ padding: "0",
986
+ margin: "-1px",
987
+ overflow: "hidden",
988
+ clip: "rect(0, 0, 0, 0)",
989
+ whiteSpace: "nowrap",
990
+ border: "0"
991
+ }
992
+ }
993
+ ), /* @__PURE__ */ React21.createElement(
994
+ Select,
995
+ {
996
+ value: selectValue,
997
+ onValueChange: handleValueChange,
998
+ onOpenChange: handleOpenChange,
999
+ disabled
1000
+ },
1001
+ /* @__PURE__ */ React21.createElement(
1002
+ SelectTrigger,
1003
+ {
1004
+ className: cn(
1005
+ // Valid value indicator - ring-2 when has value and no error
1006
+ !error && hasValue && "ring-2 ring-ring",
1007
+ // Error state - handled by SelectTrigger via aria-invalid
1008
+ className
1009
+ ),
1010
+ "aria-invalid": error || props["aria-invalid"],
1011
+ "aria-describedby": props["aria-describedby"],
1012
+ "aria-required": required || props["aria-required"]
1013
+ },
1014
+ /* @__PURE__ */ React21.createElement(SelectValue, { placeholder })
1015
+ ),
1016
+ /* @__PURE__ */ React21.createElement(SelectContent, null, optionGroups.length > 0 ? (
1017
+ // Render grouped options
1018
+ optionGroups.map((group, groupIndex) => /* @__PURE__ */ React21.createElement(SelectGroup, { key: groupIndex }, /* @__PURE__ */ React21.createElement(SelectLabel, null, group.label), group.options.map((option) => /* @__PURE__ */ React21.createElement(
1019
+ SelectItem,
1020
+ {
1021
+ key: option.value,
1022
+ value: option.value,
1023
+ disabled: option.disabled
1024
+ },
1025
+ renderOption ? renderOption(option) : option.label
1026
+ ))))
1027
+ ) : (
1028
+ // Render flat options
1029
+ allOptions.map((option) => /* @__PURE__ */ React21.createElement(
1030
+ SelectItem,
1031
+ {
1032
+ key: option.value,
1033
+ value: option.value,
1034
+ disabled: option.disabled
1035
+ },
1036
+ renderOption ? renderOption(option) : option.label
1037
+ ))
1038
+ ))
1039
+ ));
1040
+ }
1041
+ Select2.displayName = "Select";
1042
+ function Dialog({
1043
+ ...props
1044
+ }) {
1045
+ return /* @__PURE__ */ React21.createElement(Dialog$1.Root, { "data-slot": "dialog", ...props });
1046
+ }
1047
+ function DialogPortal({
1048
+ ...props
1049
+ }) {
1050
+ return /* @__PURE__ */ React21.createElement(Dialog$1.Portal, { "data-slot": "dialog-portal", ...props });
1051
+ }
1052
+ function DialogClose({
1053
+ ...props
1054
+ }) {
1055
+ return /* @__PURE__ */ React21.createElement(Dialog$1.Close, { "data-slot": "dialog-close", ...props });
1056
+ }
1057
+ var DialogOverlay = React21.forwardRef(({ className, ...props }, ref) => {
1058
+ return /* @__PURE__ */ React21.createElement(
1059
+ Dialog$1.Overlay,
1060
+ {
1061
+ ref,
1062
+ "data-slot": "dialog-overlay",
1063
+ className: cn(
1064
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
1065
+ className
1066
+ ),
1067
+ ...props
1068
+ }
1069
+ );
1070
+ });
1071
+ DialogOverlay.displayName = Dialog$1.Overlay.displayName;
1072
+ var DialogContent = React21.forwardRef(({ className, children, showCloseButton = true, ...props }, ref) => {
1073
+ return /* @__PURE__ */ React21.createElement(DialogPortal, { "data-slot": "dialog-portal" }, /* @__PURE__ */ React21.createElement(DialogOverlay, null), /* @__PURE__ */ React21.createElement(
1074
+ Dialog$1.Content,
1075
+ {
1076
+ ref,
1077
+ "data-slot": "dialog-content",
1078
+ className: cn(
1079
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
1080
+ className
1081
+ ),
1082
+ ...props
1083
+ },
1084
+ children,
1085
+ showCloseButton && /* @__PURE__ */ React21.createElement(
1086
+ Dialog$1.Close,
1087
+ {
1088
+ "data-slot": "dialog-close",
1089
+ className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
1090
+ },
1091
+ /* @__PURE__ */ React21.createElement(
1092
+ "svg",
1093
+ {
1094
+ className: "size-4",
1095
+ viewBox: "0 0 24 24",
1096
+ fill: "none",
1097
+ stroke: "currentColor",
1098
+ strokeWidth: "2",
1099
+ strokeLinecap: "round",
1100
+ strokeLinejoin: "round"
1101
+ },
1102
+ /* @__PURE__ */ React21.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1103
+ /* @__PURE__ */ React21.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1104
+ ),
1105
+ /* @__PURE__ */ React21.createElement("span", { className: "sr-only" }, "Close")
1106
+ )
1107
+ ));
1108
+ });
1109
+ DialogContent.displayName = Dialog$1.Content.displayName;
1110
+ function DialogHeader({ className, ...props }) {
1111
+ return /* @__PURE__ */ React21.createElement(
1112
+ "div",
1113
+ {
1114
+ "data-slot": "dialog-header",
1115
+ className: cn("flex flex-col gap-2 text-center sm:text-left", className),
1116
+ ...props
1117
+ }
1118
+ );
1119
+ }
1120
+ function DialogTitle({
1121
+ className,
1122
+ ...props
1123
+ }) {
1124
+ return /* @__PURE__ */ React21.createElement(
1125
+ Dialog$1.Title,
1126
+ {
1127
+ "data-slot": "dialog-title",
1128
+ className: cn("text-lg leading-none font-semibold", className),
1129
+ ...props
1130
+ }
1131
+ );
1132
+ }
1133
+
1134
+ // src/components/ui/command.tsx
1135
+ function Command({
1136
+ className,
1137
+ ...props
1138
+ }) {
1139
+ return /* @__PURE__ */ React21.createElement(
1140
+ Command$1,
1141
+ {
1142
+ "data-slot": "command",
1143
+ className: cn(
1144
+ "bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
1145
+ className
1146
+ ),
1147
+ ...props
1148
+ }
1149
+ );
1150
+ }
1151
+ function CommandInput({
1152
+ className,
1153
+ ...props
1154
+ }) {
1155
+ return /* @__PURE__ */ React21.createElement(
1156
+ "div",
1157
+ {
1158
+ "data-slot": "command-input-wrapper",
1159
+ className: "flex h-9 items-center gap-2 border-b px-3"
1160
+ },
1161
+ /* @__PURE__ */ React21.createElement(
1162
+ "svg",
1163
+ {
1164
+ className: "size-4 shrink-0 opacity-50",
1165
+ viewBox: "0 0 24 24",
1166
+ fill: "none",
1167
+ stroke: "currentColor",
1168
+ strokeWidth: "2",
1169
+ strokeLinecap: "round",
1170
+ strokeLinejoin: "round"
1171
+ },
1172
+ /* @__PURE__ */ React21.createElement("circle", { cx: "11", cy: "11", r: "8" }),
1173
+ /* @__PURE__ */ React21.createElement("path", { d: "m21 21-4.3-4.3" })
1174
+ ),
1175
+ /* @__PURE__ */ React21.createElement(
1176
+ Command$1.Input,
1177
+ {
1178
+ "data-slot": "command-input",
1179
+ className: cn(
1180
+ "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
1181
+ className
1182
+ ),
1183
+ ...props
1184
+ }
1185
+ )
1186
+ );
1187
+ }
1188
+ function CommandList({
1189
+ className,
1190
+ ...props
1191
+ }) {
1192
+ return /* @__PURE__ */ React21.createElement(
1193
+ Command$1.List,
1194
+ {
1195
+ "data-slot": "command-list",
1196
+ className: cn(
1197
+ "max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
1198
+ className
1199
+ ),
1200
+ ...props
1201
+ }
1202
+ );
1203
+ }
1204
+ function CommandEmpty({
1205
+ ...props
1206
+ }) {
1207
+ return /* @__PURE__ */ React21.createElement(
1208
+ Command$1.Empty,
1209
+ {
1210
+ "data-slot": "command-empty",
1211
+ className: "py-6 text-center text-sm",
1212
+ ...props
1213
+ }
1214
+ );
1215
+ }
1216
+ function CommandGroup({
1217
+ className,
1218
+ ...props
1219
+ }) {
1220
+ return /* @__PURE__ */ React21.createElement(
1221
+ Command$1.Group,
1222
+ {
1223
+ "data-slot": "command-group",
1224
+ className: cn(
1225
+ "overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:opacity-70",
1226
+ className
1227
+ ),
1228
+ ...props
1229
+ }
1230
+ );
1231
+ }
1232
+ function Popover({
1233
+ ...props
1234
+ }) {
1235
+ return /* @__PURE__ */ React21.createElement(Popover$1.Root, { "data-slot": "popover", ...props });
1236
+ }
1237
+ function PopoverTrigger({
1238
+ ...props
1239
+ }) {
1240
+ return /* @__PURE__ */ React21.createElement(Popover$1.Trigger, { "data-slot": "popover-trigger", ...props });
1241
+ }
1242
+ function PopoverContent({
1243
+ className,
1244
+ align = "center",
1245
+ sideOffset = 4,
1246
+ ...props
1247
+ }) {
1248
+ return /* @__PURE__ */ React21.createElement(Popover$1.Portal, null, /* @__PURE__ */ React21.createElement(
1249
+ Popover$1.Content,
1250
+ {
1251
+ "data-slot": "popover-content",
1252
+ align,
1253
+ sideOffset,
1254
+ className: cn(
1255
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
1256
+ className
1257
+ ),
1258
+ ...props
1259
+ }
1260
+ ));
1261
+ }
1262
+
1263
+ // src/inputs/MultiSelect.tsx
1264
+ function ensureResizeObserver() {
1265
+ if (typeof window === "undefined") return;
1266
+ const windowWithResizeObserver = window;
1267
+ if (windowWithResizeObserver.ResizeObserver) return;
1268
+ windowWithResizeObserver.ResizeObserver = class ResizeObserverMock {
1269
+ observe() {
1270
+ }
1271
+ unobserve() {
1272
+ }
1273
+ disconnect() {
1274
+ }
1275
+ };
1276
+ if (typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype.scrollIntoView !== "function") {
1277
+ HTMLElement.prototype.scrollIntoView = () => {
1278
+ };
1279
+ }
1280
+ }
1281
+ function optionLabelText(option) {
1282
+ if (typeof option.label === "string") {
1283
+ return option.label;
1284
+ }
1285
+ return String(option.label);
1286
+ }
1287
+ function MultiSelect({
1288
+ name,
1289
+ value = [],
1290
+ onChange,
1291
+ onBlur,
1292
+ onFocus,
1293
+ disabled = false,
1294
+ required = false,
1295
+ error = false,
1296
+ className = "",
1297
+ placeholder = "Select...",
1298
+ searchable = true,
1299
+ clearable = true,
1300
+ loading = false,
1301
+ maxSelections,
1302
+ showSelectAll = false,
1303
+ options = [],
1304
+ optionGroups = [],
1305
+ renderOption,
1306
+ renderValue,
1307
+ ...props
1308
+ }) {
1309
+ const [isOpen, setIsOpen] = React21.useState(false);
1310
+ const [searchQuery, setSearchQuery] = React21.useState("");
1311
+ const [focusedIndex, setFocusedIndex] = React21.useState(-1);
1312
+ const [hasInteracted, setHasInteracted] = React21.useState(false);
1313
+ const triggerRef = React21.useRef(null);
1314
+ const dropdownId = `${name}-dropdown`;
1315
+ const searchInputId = `${name}-search`;
1316
+ ensureResizeObserver();
1317
+ const allOptions = React21.useMemo(() => {
1318
+ if (optionGroups.length > 0) {
1319
+ return optionGroups.flatMap((group) => group.options);
1320
+ }
1321
+ return options;
1322
+ }, [options, optionGroups]);
1323
+ const filteredOptions = React21.useMemo(() => {
1324
+ if (!searchQuery.trim()) {
1325
+ return allOptions;
1326
+ }
1327
+ const query = searchQuery.toLowerCase();
1328
+ return allOptions.filter(
1329
+ (option) => optionLabelText(option).toLowerCase().includes(query)
1330
+ );
1331
+ }, [allOptions, searchQuery]);
1332
+ const selectedOptions = React21.useMemo(() => {
1333
+ return allOptions.filter((option) => value.includes(option.value));
1334
+ }, [allOptions, value]);
1335
+ const hasValue = value.length > 0;
1336
+ const isMaxReached = React21.useMemo(() => {
1337
+ return maxSelections !== void 0 && value.length >= maxSelections;
1338
+ }, [maxSelections, value.length]);
1339
+ const getEnabledOptions = React21.useCallback(() => {
1340
+ return filteredOptions.filter(
1341
+ (option) => !option.disabled && (!isMaxReached || value.includes(option.value))
1342
+ );
1343
+ }, [filteredOptions, isMaxReached, value]);
1344
+ React21.useEffect(() => {
1345
+ if (!isOpen) return;
1346
+ if (!searchable) return;
1347
+ const id = window.setTimeout(() => {
1348
+ const searchInput = document.getElementById(
1349
+ searchInputId
1350
+ );
1351
+ searchInput?.focus();
1352
+ }, 0);
1353
+ return () => {
1354
+ window.clearTimeout(id);
1355
+ };
1356
+ }, [isOpen, searchable, searchInputId]);
1357
+ const handleToggleOption = React21.useCallback(
1358
+ (optionValue) => {
1359
+ const isSelected = value.includes(optionValue);
1360
+ if (isSelected) {
1361
+ onChange(value.filter((entry) => entry !== optionValue));
1362
+ } else if (!isMaxReached) {
1363
+ onChange([...value, optionValue]);
1364
+ }
1365
+ setSearchQuery("");
1366
+ },
1367
+ [isMaxReached, onChange, value]
1368
+ );
1369
+ const handleSelectAll = React21.useCallback(() => {
1370
+ const enabledOptions = filteredOptions.filter((option) => !option.disabled);
1371
+ onChange(enabledOptions.map((option) => option.value));
1372
+ setSearchQuery("");
1373
+ }, [filteredOptions, onChange]);
1374
+ const handleClearAll = React21.useCallback(
1375
+ (e) => {
1376
+ e.stopPropagation();
1377
+ onChange([]);
1378
+ setSearchQuery("");
1379
+ setFocusedIndex(-1);
1380
+ },
1381
+ [onChange]
1382
+ );
1383
+ const handleRemoveValue = React21.useCallback(
1384
+ (optionValue, e) => {
1385
+ e.stopPropagation();
1386
+ onChange(value.filter((entry) => entry !== optionValue));
1387
+ },
1388
+ [onChange, value]
1389
+ );
1390
+ const handleOpenChange = React21.useCallback(
1391
+ (nextOpen) => {
1392
+ if (disabled) {
1393
+ setIsOpen(false);
1394
+ return;
1395
+ }
1396
+ if (nextOpen) {
1397
+ if (!hasInteracted) {
1398
+ setHasInteracted(true);
1399
+ }
1400
+ setIsOpen(true);
1401
+ onFocus?.();
1402
+ return;
1403
+ }
1404
+ if (isOpen && hasInteracted) {
1405
+ onBlur?.();
1406
+ }
1407
+ setIsOpen(false);
1408
+ setSearchQuery("");
1409
+ setFocusedIndex(-1);
1410
+ },
1411
+ [disabled, hasInteracted, isOpen, onBlur, onFocus]
1412
+ );
1413
+ const handleTriggerBlur = React21.useCallback(() => {
1414
+ if (!isOpen) {
1415
+ onBlur?.();
1416
+ }
1417
+ }, [isOpen, onBlur]);
1418
+ const handleKeyDown = React21.useCallback(
1419
+ (event) => {
1420
+ if (disabled) return;
1421
+ const enabledOptions = getEnabledOptions();
1422
+ switch (event.key) {
1423
+ case "ArrowDown": {
1424
+ event.preventDefault();
1425
+ if (!isOpen) {
1426
+ setHasInteracted(true);
1427
+ setIsOpen(true);
1428
+ onFocus?.();
1429
+ if (enabledOptions.length > 0) {
1430
+ setFocusedIndex(filteredOptions.indexOf(enabledOptions[0]));
1431
+ }
1432
+ return;
1433
+ }
1434
+ if (enabledOptions.length === 0) return;
1435
+ const currentOption = filteredOptions[focusedIndex];
1436
+ const currentEnabledIndex = enabledOptions.findIndex(
1437
+ (option) => option === currentOption
1438
+ );
1439
+ const nextEnabledIndex = currentEnabledIndex === -1 ? 0 : (currentEnabledIndex + 1) % enabledOptions.length;
1440
+ setFocusedIndex(filteredOptions.indexOf(enabledOptions[nextEnabledIndex]));
1441
+ break;
1442
+ }
1443
+ case "ArrowUp": {
1444
+ event.preventDefault();
1445
+ if (!isOpen || enabledOptions.length === 0) return;
1446
+ const currentOption = filteredOptions[focusedIndex];
1447
+ const currentEnabledIndex = enabledOptions.findIndex(
1448
+ (option) => option === currentOption
1449
+ );
1450
+ const previousEnabledIndex = currentEnabledIndex === -1 ? enabledOptions.length - 1 : (currentEnabledIndex - 1 + enabledOptions.length) % enabledOptions.length;
1451
+ setFocusedIndex(
1452
+ filteredOptions.indexOf(enabledOptions[previousEnabledIndex])
1453
+ );
1454
+ break;
1455
+ }
1456
+ case "Enter": {
1457
+ event.preventDefault();
1458
+ if (isOpen && focusedIndex >= 0 && focusedIndex < filteredOptions.length) {
1459
+ const focusedOption = filteredOptions[focusedIndex];
1460
+ const optionDisabled = focusedOption.disabled || isMaxReached && !value.includes(focusedOption.value);
1461
+ if (!optionDisabled) {
1462
+ handleToggleOption(focusedOption.value);
1463
+ }
1464
+ return;
1465
+ }
1466
+ if (!isOpen) {
1467
+ setHasInteracted(true);
1468
+ setIsOpen(true);
1469
+ onFocus?.();
1470
+ }
1471
+ break;
1472
+ }
1473
+ case "Escape": {
1474
+ if (!isOpen) return;
1475
+ event.preventDefault();
1476
+ setIsOpen(false);
1477
+ setSearchQuery("");
1478
+ setFocusedIndex(-1);
1479
+ break;
1480
+ }
1481
+ case " ": {
1482
+ if (isOpen && focusedIndex >= 0 && focusedIndex < filteredOptions.length) {
1483
+ event.preventDefault();
1484
+ const focusedOption = filteredOptions[focusedIndex];
1485
+ const optionDisabled = focusedOption.disabled || isMaxReached && !value.includes(focusedOption.value);
1486
+ if (!optionDisabled) {
1487
+ handleToggleOption(focusedOption.value);
1488
+ }
1489
+ return;
1490
+ }
1491
+ if (!isOpen && !searchable) {
1492
+ event.preventDefault();
1493
+ setHasInteracted(true);
1494
+ setIsOpen(true);
1495
+ onFocus?.();
1496
+ }
1497
+ break;
1498
+ }
1499
+ }
1500
+ },
1501
+ [
1502
+ disabled,
1503
+ filteredOptions,
1504
+ focusedIndex,
1505
+ getEnabledOptions,
1506
+ handleToggleOption,
1507
+ isMaxReached,
1508
+ isOpen,
1509
+ onFocus,
1510
+ searchable,
1511
+ value
1512
+ ]
1513
+ );
1514
+ const combinedClassName = cn("relative w-full", className);
1515
+ return /* @__PURE__ */ React21.createElement("div", { className: combinedClassName }, /* @__PURE__ */ React21.createElement(
1516
+ "select",
1517
+ {
1518
+ name,
1519
+ value,
1520
+ onChange: () => {
1521
+ },
1522
+ disabled,
1523
+ required,
1524
+ "aria-hidden": "true",
1525
+ tabIndex: -1,
1526
+ style: { display: "none" },
1527
+ multiple: true
1528
+ },
1529
+ /* @__PURE__ */ React21.createElement("option", { value: "" }, "Select..."),
1530
+ allOptions.map((option) => /* @__PURE__ */ React21.createElement("option", { key: option.value, value: option.value }, optionLabelText(option)))
1531
+ ), /* @__PURE__ */ React21.createElement(Popover, { open: isOpen, onOpenChange: handleOpenChange }, /* @__PURE__ */ React21.createElement(PopoverTrigger, { asChild: true }, /* @__PURE__ */ React21.createElement(
1532
+ "div",
1533
+ {
1534
+ ref: triggerRef,
1535
+ className: cn(
1536
+ "flex min-h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm",
1537
+ "cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
1538
+ !error && hasValue && "ring-2 ring-ring",
1539
+ disabled && "cursor-not-allowed opacity-50 pointer-events-none",
1540
+ error && "border-destructive ring-1 ring-destructive"
1541
+ ),
1542
+ onKeyDown: handleKeyDown,
1543
+ onBlur: handleTriggerBlur,
1544
+ role: "combobox",
1545
+ "aria-expanded": isOpen,
1546
+ "aria-controls": dropdownId,
1547
+ "aria-invalid": error || props["aria-invalid"],
1548
+ "aria-describedby": props["aria-describedby"],
1549
+ "aria-required": required || props["aria-required"],
1550
+ "aria-disabled": disabled,
1551
+ tabIndex: disabled ? -1 : 0
1552
+ },
1553
+ /* @__PURE__ */ React21.createElement("div", { className: "flex flex-1 items-center overflow-hidden" }, selectedOptions.length > 0 ? /* @__PURE__ */ React21.createElement("div", { className: "flex flex-wrap gap-1" }, selectedOptions.map((option) => /* @__PURE__ */ React21.createElement(
1554
+ "span",
1555
+ {
1556
+ key: option.value,
1557
+ className: "inline-flex items-center gap-1 rounded px-2 py-0.5 text-xs font-medium"
1558
+ },
1559
+ renderValue ? renderValue(option) : /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement("span", { className: "max-w-40 overflow-hidden text-ellipsis whitespace-nowrap" }, option.label), !disabled && /* @__PURE__ */ React21.createElement(
1560
+ "button",
1561
+ {
1562
+ type: "button",
1563
+ className: "flex h-3.5 w-3.5 items-center justify-center rounded-sm border-none bg-transparent p-0 text-[0.625rem] transition-opacity hover:opacity-70",
1564
+ onClick: (e) => handleRemoveValue(option.value, e),
1565
+ "aria-label": `Remove ${optionLabelText(option)}`,
1566
+ tabIndex: -1
1567
+ },
1568
+ "\u2715"
1569
+ ))
1570
+ ))) : /* @__PURE__ */ React21.createElement("span", { className: "relative" }, placeholder)),
1571
+ /* @__PURE__ */ React21.createElement("div", { className: "ml-2 flex items-center gap-1" }, loading && /* @__PURE__ */ React21.createElement("span", { className: "text-xs" }, "\u23F3"), clearable && value.length > 0 && !disabled && !loading && /* @__PURE__ */ React21.createElement(
1572
+ "button",
1573
+ {
1574
+ type: "button",
1575
+ className: "flex h-4 w-4 items-center justify-center rounded-sm border-none bg-transparent p-0 text-xs transition-opacity hover:opacity-70",
1576
+ onClick: handleClearAll,
1577
+ "aria-label": "Clear all selections",
1578
+ tabIndex: -1
1579
+ },
1580
+ "\u2715"
1581
+ ), /* @__PURE__ */ React21.createElement("span", { className: "text-xs leading-none", "aria-hidden": "true" }, isOpen ? "\u25B2" : "\u25BC"))
1582
+ )), isOpen && /* @__PURE__ */ React21.createElement(
1583
+ PopoverContent,
1584
+ {
1585
+ id: dropdownId,
1586
+ align: "start",
1587
+ sideOffset: 4,
1588
+ className: "w-full min-w-[var(--radix-popover-trigger-width)] p-0",
1589
+ onOpenAutoFocus: (event) => {
1590
+ event.preventDefault();
1591
+ }
1592
+ },
1593
+ /* @__PURE__ */ React21.createElement(
1594
+ Command,
1595
+ {
1596
+ shouldFilter: false,
1597
+ className: "max-h-80",
1598
+ onKeyDown: handleKeyDown
1599
+ },
1600
+ searchable && /* @__PURE__ */ React21.createElement(
1601
+ CommandInput,
1602
+ {
1603
+ id: searchInputId,
1604
+ className: cn(INPUT_AUTOFILL_RESET_CLASSES),
1605
+ placeholder: "Search...",
1606
+ value: searchQuery,
1607
+ onValueChange: (nextValue) => {
1608
+ setSearchQuery(nextValue);
1609
+ setFocusedIndex(0);
1610
+ },
1611
+ "aria-label": "Search options"
1612
+ }
1613
+ ),
1614
+ showSelectAll && filteredOptions.length > 0 && /* @__PURE__ */ React21.createElement("div", { className: "flex gap-2 border-b border-input p-2" }, /* @__PURE__ */ React21.createElement(
1615
+ "button",
1616
+ {
1617
+ type: "button",
1618
+ className: "flex-1 rounded border border-input bg-transparent px-3 py-1.5 text-xs font-medium transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50",
1619
+ onClick: handleSelectAll,
1620
+ disabled
1621
+ },
1622
+ "Select All"
1623
+ ), value.length > 0 && /* @__PURE__ */ React21.createElement(
1624
+ "button",
1625
+ {
1626
+ type: "button",
1627
+ className: "flex-1 rounded border border-input bg-transparent px-3 py-1.5 text-xs font-medium transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50",
1628
+ onClick: handleClearAll,
1629
+ disabled
1630
+ },
1631
+ "Clear All"
1632
+ )),
1633
+ isMaxReached && /* @__PURE__ */ React21.createElement("div", { className: "border-b border-destructive bg-destructive/80 px-2 py-1 text-xs font-medium text-destructive-foreground" }, "Maximum ", maxSelections, " selection", maxSelections !== 1 ? "s" : "", " ", "reached"),
1634
+ /* @__PURE__ */ React21.createElement(CommandList, { role: "listbox", "aria-multiselectable": "true" }, /* @__PURE__ */ React21.createElement(CommandEmpty, null, "No options found"), optionGroups.length > 0 ? optionGroups.map((group, groupIndex) => {
1635
+ const groupOptions = group.options.filter(
1636
+ (option) => filteredOptions.includes(option)
1637
+ );
1638
+ if (groupOptions.length === 0) return null;
1639
+ return /* @__PURE__ */ React21.createElement(
1640
+ CommandGroup,
1641
+ {
1642
+ key: `${group.label}-${groupIndex}`,
1643
+ heading: group.label
1644
+ },
1645
+ groupOptions.map((option) => {
1646
+ const globalIndex = filteredOptions.indexOf(option);
1647
+ const isSelected = value.includes(option.value);
1648
+ const isFocused = globalIndex === focusedIndex;
1649
+ const optionDisabled = option.disabled || isMaxReached && !isSelected;
1650
+ return /* @__PURE__ */ React21.createElement(
1651
+ "div",
1652
+ {
1653
+ key: option.value,
1654
+ role: "option",
1655
+ "aria-selected": isSelected,
1656
+ "aria-disabled": optionDisabled,
1657
+ onMouseEnter: () => {
1658
+ setFocusedIndex(globalIndex);
1659
+ },
1660
+ onClick: () => {
1661
+ if (!optionDisabled) {
1662
+ handleToggleOption(option.value);
1663
+ }
1664
+ },
1665
+ className: cn(
1666
+ "relative flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent",
1667
+ isFocused && "bg-accent",
1668
+ isSelected && "bg-accent font-medium",
1669
+ optionDisabled && "pointer-events-none opacity-50"
1670
+ )
1671
+ },
1672
+ /* @__PURE__ */ React21.createElement("span", { className: "text-base leading-none" }, isSelected ? "\u2611" : "\u2610"),
1673
+ /* @__PURE__ */ React21.createElement("span", { className: "flex-1" }, renderOption ? renderOption(option) : option.label)
1674
+ );
1675
+ })
1676
+ );
1677
+ }) : filteredOptions.map((option, index) => {
1678
+ const isSelected = value.includes(option.value);
1679
+ const isFocused = index === focusedIndex;
1680
+ const optionDisabled = option.disabled || isMaxReached && !isSelected;
1681
+ return /* @__PURE__ */ React21.createElement(
1682
+ "div",
1683
+ {
1684
+ key: option.value,
1685
+ role: "option",
1686
+ "aria-selected": isSelected,
1687
+ "aria-disabled": optionDisabled,
1688
+ onMouseEnter: () => {
1689
+ setFocusedIndex(index);
1690
+ },
1691
+ onClick: () => {
1692
+ if (!optionDisabled) {
1693
+ handleToggleOption(option.value);
1694
+ }
1695
+ },
1696
+ className: cn(
1697
+ "relative flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent",
1698
+ isFocused && "bg-accent",
1699
+ isSelected && "bg-accent font-medium",
1700
+ optionDisabled && "pointer-events-none opacity-50"
1701
+ )
1702
+ },
1703
+ /* @__PURE__ */ React21.createElement("span", { className: "text-base leading-none" }, isSelected ? "\u2611" : "\u2610"),
1704
+ /* @__PURE__ */ React21.createElement("span", { className: "flex-1" }, renderOption ? renderOption(option) : option.label)
1705
+ );
1706
+ }))
1707
+ )
1708
+ )));
1709
+ }
1710
+ MultiSelect.displayName = "MultiSelect";
1711
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React21.useLayoutEffect : React21.useEffect;
1712
+
1713
+ // src/hooks/use-as-ref.ts
1714
+ function useAsRef(props) {
1715
+ const ref = React21.useRef(props);
1716
+ useIsomorphicLayoutEffect(() => {
1717
+ ref.current = props;
1718
+ });
1719
+ return ref;
1720
+ }
1721
+ function useLazyRef(fn) {
1722
+ const ref = React21.useRef(null);
1723
+ if (ref.current === null) {
1724
+ ref.current = fn();
1725
+ }
1726
+ return ref;
1727
+ }
1728
+
1729
+ // src/components/ui/file-upload.tsx
1730
+ var ROOT_NAME = "FileUpload";
1731
+ var DROPZONE_NAME = "FileUploadDropzone";
1732
+ var LIST_NAME = "FileUploadList";
1733
+ var ITEM_NAME = "FileUploadItem";
1734
+ var ITEM_PREVIEW_NAME = "FileUploadItemPreview";
1735
+ var ITEM_METADATA_NAME = "FileUploadItemMetadata";
1736
+ var ITEM_DELETE_NAME = "FileUploadItemDelete";
1737
+ function BaseFileIcon({
1738
+ children,
1739
+ className
1740
+ }) {
1741
+ return /* @__PURE__ */ React21.createElement(
1742
+ "svg",
1743
+ {
1744
+ viewBox: "0 0 24 24",
1745
+ fill: "none",
1746
+ stroke: "currentColor",
1747
+ strokeWidth: "2",
1748
+ strokeLinecap: "round",
1749
+ strokeLinejoin: "round",
1750
+ className: cn("size-5", className),
1751
+ "aria-hidden": "true"
1752
+ },
1753
+ children
1754
+ );
1755
+ }
1756
+ function FileVideoIcon() {
1757
+ return /* @__PURE__ */ React21.createElement(BaseFileIcon, null, /* @__PURE__ */ React21.createElement("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), /* @__PURE__ */ React21.createElement("polyline", { points: "14 2 14 8 20 8" }), /* @__PURE__ */ React21.createElement("rect", { x: "8", y: "12", width: "6", height: "4", rx: "1" }), /* @__PURE__ */ React21.createElement("path", { d: "m14 13 3-1.5v5L14 15" }));
1758
+ }
1759
+ function FileAudioIcon() {
1760
+ return /* @__PURE__ */ React21.createElement(BaseFileIcon, null, /* @__PURE__ */ React21.createElement("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), /* @__PURE__ */ React21.createElement("polyline", { points: "14 2 14 8 20 8" }), /* @__PURE__ */ React21.createElement("path", { d: "M10 16a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z" }), /* @__PURE__ */ React21.createElement("path", { d: "M13 17V11l3-1" }));
1761
+ }
1762
+ function FileTextIcon() {
1763
+ return /* @__PURE__ */ React21.createElement(BaseFileIcon, null, /* @__PURE__ */ React21.createElement("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), /* @__PURE__ */ React21.createElement("polyline", { points: "14 2 14 8 20 8" }), /* @__PURE__ */ React21.createElement("line", { x1: "8", y1: "13", x2: "16", y2: "13" }), /* @__PURE__ */ React21.createElement("line", { x1: "8", y1: "17", x2: "14", y2: "17" }));
1764
+ }
1765
+ function FileCodeIcon() {
1766
+ return /* @__PURE__ */ React21.createElement(BaseFileIcon, null, /* @__PURE__ */ React21.createElement("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), /* @__PURE__ */ React21.createElement("polyline", { points: "14 2 14 8 20 8" }), /* @__PURE__ */ React21.createElement("polyline", { points: "11 14 9 16 11 18" }), /* @__PURE__ */ React21.createElement("polyline", { points: "13 14 15 16 13 18" }));
1767
+ }
1768
+ function FileArchiveIcon() {
1769
+ return /* @__PURE__ */ React21.createElement(BaseFileIcon, null, /* @__PURE__ */ React21.createElement("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), /* @__PURE__ */ React21.createElement("polyline", { points: "14 2 14 8 20 8" }), /* @__PURE__ */ React21.createElement("rect", { x: "9", y: "11", width: "6", height: "2" }), /* @__PURE__ */ React21.createElement("path", { d: "M12 13v5" }));
1770
+ }
1771
+ function FileCogIcon() {
1772
+ return /* @__PURE__ */ React21.createElement(BaseFileIcon, null, /* @__PURE__ */ React21.createElement("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), /* @__PURE__ */ React21.createElement("polyline", { points: "14 2 14 8 20 8" }), /* @__PURE__ */ React21.createElement("circle", { cx: "12", cy: "16", r: "2" }), /* @__PURE__ */ React21.createElement("path", { d: "m12 12 .4.9m2.7 1.1 .9.4m-.9 2.7-.9.4m-2.7 1.1-.4.9m-2.3-.9-.4-.9m-2.7-1.1-.9-.4m.9-2.7.9-.4m2.7-1.1.4-.9" }));
1773
+ }
1774
+ function FileIcon() {
1775
+ return /* @__PURE__ */ React21.createElement(BaseFileIcon, null, /* @__PURE__ */ React21.createElement("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), /* @__PURE__ */ React21.createElement("polyline", { points: "14 2 14 8 20 8" }));
1776
+ }
1777
+ function formatBytes(bytes) {
1778
+ if (bytes === 0) return "0 B";
1779
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
1780
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
1781
+ return `${(bytes / 1024 ** i).toFixed(i ? 1 : 0)} ${sizes[i]}`;
1782
+ }
1783
+ function getFileIcon(file) {
1784
+ const type = file.type;
1785
+ const extension = file.name.split(".").pop()?.toLowerCase() ?? "";
1786
+ if (type.startsWith("video/")) {
1787
+ return /* @__PURE__ */ React21.createElement(FileVideoIcon, null);
1788
+ }
1789
+ if (type.startsWith("audio/")) {
1790
+ return /* @__PURE__ */ React21.createElement(FileAudioIcon, null);
1791
+ }
1792
+ if (type.startsWith("text/") || ["txt", "md", "rtf", "pdf"].includes(extension)) {
1793
+ return /* @__PURE__ */ React21.createElement(FileTextIcon, null);
1794
+ }
1795
+ if ([
1796
+ "html",
1797
+ "css",
1798
+ "js",
1799
+ "jsx",
1800
+ "ts",
1801
+ "tsx",
1802
+ "json",
1803
+ "xml",
1804
+ "php",
1805
+ "py",
1806
+ "rb",
1807
+ "java",
1808
+ "c",
1809
+ "cpp",
1810
+ "cs"
1811
+ ].includes(extension)) {
1812
+ return /* @__PURE__ */ React21.createElement(FileCodeIcon, null);
1813
+ }
1814
+ if (["zip", "rar", "7z", "tar", "gz", "bz2"].includes(extension)) {
1815
+ return /* @__PURE__ */ React21.createElement(FileArchiveIcon, null);
1816
+ }
1817
+ if (["exe", "msi", "app", "apk", "deb", "rpm"].includes(extension) || type.startsWith("application/")) {
1818
+ return /* @__PURE__ */ React21.createElement(FileCogIcon, null);
1819
+ }
1820
+ return /* @__PURE__ */ React21.createElement(FileIcon, null);
1821
+ }
1822
+ var StoreContext = React21.createContext(null);
1823
+ function useStoreContext(consumerName) {
1824
+ const context = React21.useContext(StoreContext);
1825
+ if (!context) {
1826
+ throw new Error(`\`${consumerName}\` must be used within \`${ROOT_NAME}\``);
1827
+ }
1828
+ return context;
1829
+ }
1830
+ function useStore(selector) {
1831
+ const store = useStoreContext("useStore");
1832
+ const lastValueRef = useLazyRef(
1833
+ () => null
1834
+ );
1835
+ const getSnapshot = React21.useCallback(() => {
1836
+ const state = store.getState();
1837
+ const prevValue = lastValueRef.current;
1838
+ if (prevValue && prevValue.state === state) {
1839
+ return prevValue.value;
1840
+ }
1841
+ const nextValue = selector(state);
1842
+ lastValueRef.current = { value: nextValue, state };
1843
+ return nextValue;
1844
+ }, [store, selector, lastValueRef]);
1845
+ return React21.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
1846
+ }
1847
+ var FileUploadContext = React21.createContext(
1848
+ null
1849
+ );
1850
+ function useFileUploadContext(consumerName) {
1851
+ const context = React21.useContext(FileUploadContext);
1852
+ if (!context) {
1853
+ throw new Error(`\`${consumerName}\` must be used within \`${ROOT_NAME}\``);
1854
+ }
1855
+ return context;
1856
+ }
1857
+ function FileUpload(props) {
1858
+ const {
1859
+ value,
1860
+ defaultValue,
1861
+ onValueChange,
1862
+ onAccept,
1863
+ onFileAccept,
1864
+ onFileReject,
1865
+ onFileValidate,
1866
+ onUpload,
1867
+ accept,
1868
+ maxFiles,
1869
+ maxSize,
1870
+ dir: dirProp,
1871
+ label,
1872
+ name,
1873
+ asChild,
1874
+ disabled = false,
1875
+ invalid = false,
1876
+ multiple = false,
1877
+ required = false,
1878
+ inputProps,
1879
+ children,
1880
+ className,
1881
+ ...rootProps
1882
+ } = props;
1883
+ const inputId = React21.useId();
1884
+ const dropzoneId = React21.useId();
1885
+ const listId = React21.useId();
1886
+ const labelId = React21.useId();
1887
+ const dir = useDirection(dirProp);
1888
+ const listeners = useLazyRef(() => /* @__PURE__ */ new Set()).current;
1889
+ const files = useLazyRef(() => /* @__PURE__ */ new Map()).current;
1890
+ const urlCache = useLazyRef(() => /* @__PURE__ */ new WeakMap()).current;
1891
+ const inputRef = React21.useRef(null);
1892
+ const isControlled = value !== void 0;
1893
+ const propsRef = useAsRef({
1894
+ onValueChange,
1895
+ onAccept,
1896
+ onFileAccept,
1897
+ onFileReject,
1898
+ onFileValidate,
1899
+ onUpload
1900
+ });
1901
+ const store = React21.useMemo(() => {
1902
+ let state = {
1903
+ files,
1904
+ dragOver: false,
1905
+ invalid
1906
+ };
1907
+ function reducer(state2, action) {
1908
+ switch (action.type) {
1909
+ case "ADD_FILES": {
1910
+ for (const file of action.files) {
1911
+ files.set(file, {
1912
+ file,
1913
+ progress: 0,
1914
+ status: "idle"
1915
+ });
1916
+ }
1917
+ if (propsRef.current.onValueChange) {
1918
+ const fileList = Array.from(files.values()).map(
1919
+ (fileState) => fileState.file
1920
+ );
1921
+ propsRef.current.onValueChange(fileList);
1922
+ }
1923
+ return { ...state2, files };
1924
+ }
1925
+ case "SET_FILES": {
1926
+ const newFileSet = new Set(action.files);
1927
+ for (const existingFile of files.keys()) {
1928
+ if (!newFileSet.has(existingFile)) {
1929
+ files.delete(existingFile);
1930
+ }
1931
+ }
1932
+ for (const file of action.files) {
1933
+ const existingState = files.get(file);
1934
+ if (!existingState) {
1935
+ files.set(file, {
1936
+ file,
1937
+ progress: 0,
1938
+ status: "idle"
1939
+ });
1940
+ }
1941
+ }
1942
+ return { ...state2, files };
1943
+ }
1944
+ case "SET_PROGRESS": {
1945
+ const fileState = files.get(action.file);
1946
+ if (fileState) {
1947
+ files.set(action.file, {
1948
+ ...fileState,
1949
+ progress: action.progress,
1950
+ status: "uploading"
1951
+ });
1952
+ }
1953
+ return { ...state2, files };
1954
+ }
1955
+ case "SET_SUCCESS": {
1956
+ const fileState = files.get(action.file);
1957
+ if (fileState) {
1958
+ files.set(action.file, {
1959
+ ...fileState,
1960
+ progress: 100,
1961
+ status: "success"
1962
+ });
1963
+ }
1964
+ return { ...state2, files };
1965
+ }
1966
+ case "SET_ERROR": {
1967
+ const fileState = files.get(action.file);
1968
+ if (fileState) {
1969
+ files.set(action.file, {
1970
+ ...fileState,
1971
+ error: action.error,
1972
+ status: "error"
1973
+ });
1974
+ }
1975
+ return { ...state2, files };
1976
+ }
1977
+ case "REMOVE_FILE": {
1978
+ const cachedUrl = urlCache.get(action.file);
1979
+ if (cachedUrl) {
1980
+ URL.revokeObjectURL(cachedUrl);
1981
+ urlCache.delete(action.file);
1982
+ }
1983
+ files.delete(action.file);
1984
+ if (propsRef.current.onValueChange) {
1985
+ const fileList = Array.from(files.values()).map(
1986
+ (fileState) => fileState.file
1987
+ );
1988
+ propsRef.current.onValueChange(fileList);
1989
+ }
1990
+ return { ...state2, files };
1991
+ }
1992
+ case "SET_DRAG_OVER": {
1993
+ return { ...state2, dragOver: action.dragOver };
1994
+ }
1995
+ case "SET_INVALID": {
1996
+ return { ...state2, invalid: action.invalid };
1997
+ }
1998
+ case "CLEAR": {
1999
+ for (const file of files.keys()) {
2000
+ const cachedUrl = urlCache.get(file);
2001
+ if (cachedUrl) {
2002
+ URL.revokeObjectURL(cachedUrl);
2003
+ urlCache.delete(file);
2004
+ }
2005
+ }
2006
+ files.clear();
2007
+ if (propsRef.current.onValueChange) {
2008
+ propsRef.current.onValueChange([]);
2009
+ }
2010
+ return { ...state2, files, invalid: false };
2011
+ }
2012
+ default:
2013
+ return state2;
2014
+ }
2015
+ }
2016
+ return {
2017
+ getState: () => state,
2018
+ dispatch: (action) => {
2019
+ state = reducer(state, action);
2020
+ for (const listener of listeners) {
2021
+ listener();
2022
+ }
2023
+ },
2024
+ subscribe: (listener) => {
2025
+ listeners.add(listener);
2026
+ return () => listeners.delete(listener);
2027
+ }
2028
+ };
2029
+ }, [listeners, files, invalid, propsRef, urlCache]);
2030
+ const acceptTypes = React21.useMemo(
2031
+ () => accept?.split(",").map((t) => t.trim()) ?? null,
2032
+ [accept]
2033
+ );
2034
+ const onProgress = useLazyRef(() => {
2035
+ let frame = 0;
2036
+ return (file, progress) => {
2037
+ if (frame) return;
2038
+ frame = requestAnimationFrame(() => {
2039
+ frame = 0;
2040
+ store.dispatch({
2041
+ type: "SET_PROGRESS",
2042
+ file,
2043
+ progress: Math.min(Math.max(0, progress), 100)
2044
+ });
2045
+ });
2046
+ };
2047
+ }).current;
2048
+ React21.useEffect(() => {
2049
+ if (isControlled) {
2050
+ store.dispatch({ type: "SET_FILES", files: value });
2051
+ } else if (defaultValue && defaultValue.length > 0 && !store.getState().files.size) {
2052
+ store.dispatch({ type: "SET_FILES", files: defaultValue });
2053
+ }
2054
+ }, [value, defaultValue, isControlled, store]);
2055
+ React21.useEffect(() => {
2056
+ return () => {
2057
+ for (const file of files.keys()) {
2058
+ const cachedUrl = urlCache.get(file);
2059
+ if (cachedUrl) {
2060
+ URL.revokeObjectURL(cachedUrl);
2061
+ }
2062
+ }
2063
+ };
2064
+ }, [files, urlCache]);
2065
+ const onFilesUpload = React21.useCallback(
2066
+ async (files2) => {
2067
+ try {
2068
+ for (const file of files2) {
2069
+ store.dispatch({ type: "SET_PROGRESS", file, progress: 0 });
2070
+ }
2071
+ if (propsRef.current.onUpload) {
2072
+ await propsRef.current.onUpload(files2, {
2073
+ onProgress,
2074
+ onSuccess: (file) => {
2075
+ store.dispatch({ type: "SET_SUCCESS", file });
2076
+ },
2077
+ onError: (file, error) => {
2078
+ store.dispatch({
2079
+ type: "SET_ERROR",
2080
+ file,
2081
+ error: error.message ?? "Upload failed"
2082
+ });
2083
+ }
2084
+ });
2085
+ } else {
2086
+ for (const file of files2) {
2087
+ store.dispatch({ type: "SET_SUCCESS", file });
2088
+ }
2089
+ }
2090
+ } catch (error) {
2091
+ const errorMessage = error instanceof Error ? error.message : "Upload failed";
2092
+ for (const file of files2) {
2093
+ store.dispatch({
2094
+ type: "SET_ERROR",
2095
+ file,
2096
+ error: errorMessage
2097
+ });
2098
+ }
2099
+ }
2100
+ },
2101
+ [store, propsRef, onProgress]
2102
+ );
2103
+ const onFilesChange = React21.useCallback(
2104
+ (originalFiles) => {
2105
+ if (disabled) return;
2106
+ let filesToProcess = [...originalFiles];
2107
+ let invalid2 = false;
2108
+ if (maxFiles) {
2109
+ const currentCount = store.getState().files.size;
2110
+ const remainingSlotCount = Math.max(0, maxFiles - currentCount);
2111
+ if (remainingSlotCount < filesToProcess.length) {
2112
+ const rejectedFiles2 = filesToProcess.slice(remainingSlotCount);
2113
+ invalid2 = true;
2114
+ filesToProcess = filesToProcess.slice(0, remainingSlotCount);
2115
+ for (const file of rejectedFiles2) {
2116
+ let rejectionMessage = `Maximum ${maxFiles} files allowed`;
2117
+ if (propsRef.current.onFileValidate) {
2118
+ const validationMessage = propsRef.current.onFileValidate(file);
2119
+ if (validationMessage) {
2120
+ rejectionMessage = validationMessage;
2121
+ }
2122
+ }
2123
+ propsRef.current.onFileReject?.(file, rejectionMessage);
2124
+ }
2125
+ }
2126
+ }
2127
+ const acceptedFiles = [];
2128
+ for (const file of filesToProcess) {
2129
+ let rejected = false;
2130
+ let rejectionMessage = "";
2131
+ if (propsRef.current.onFileValidate) {
2132
+ const validationMessage = propsRef.current.onFileValidate(file);
2133
+ if (validationMessage) {
2134
+ rejectionMessage = validationMessage;
2135
+ propsRef.current.onFileReject?.(file, rejectionMessage);
2136
+ rejected = true;
2137
+ invalid2 = true;
2138
+ continue;
2139
+ }
2140
+ }
2141
+ if (acceptTypes) {
2142
+ const fileType = file.type;
2143
+ const fileExtension = `.${file.name.split(".").pop()}`;
2144
+ if (!acceptTypes.some(
2145
+ (type) => type === fileType || type === fileExtension || type.includes("/*") && fileType.startsWith(type.replace("/*", "/"))
2146
+ )) {
2147
+ rejectionMessage = "File type not accepted";
2148
+ propsRef.current.onFileReject?.(file, rejectionMessage);
2149
+ rejected = true;
2150
+ invalid2 = true;
2151
+ }
2152
+ }
2153
+ if (maxSize && file.size > maxSize) {
2154
+ rejectionMessage = "File too large";
2155
+ propsRef.current.onFileReject?.(file, rejectionMessage);
2156
+ rejected = true;
2157
+ invalid2 = true;
2158
+ }
2159
+ if (!rejected) {
2160
+ acceptedFiles.push(file);
2161
+ }
2162
+ }
2163
+ if (invalid2) {
2164
+ store.dispatch({ type: "SET_INVALID", invalid: invalid2 });
2165
+ setTimeout(() => {
2166
+ store.dispatch({ type: "SET_INVALID", invalid: false });
2167
+ }, 2e3);
2168
+ }
2169
+ if (acceptedFiles.length > 0) {
2170
+ store.dispatch({ type: "ADD_FILES", files: acceptedFiles });
2171
+ if (isControlled && propsRef.current.onValueChange) {
2172
+ const currentFiles = Array.from(store.getState().files.values()).map(
2173
+ (f) => f.file
2174
+ );
2175
+ propsRef.current.onValueChange([...currentFiles]);
2176
+ }
2177
+ if (propsRef.current.onAccept) {
2178
+ propsRef.current.onAccept(acceptedFiles);
2179
+ }
2180
+ for (const file of acceptedFiles) {
2181
+ propsRef.current.onFileAccept?.(file);
2182
+ }
2183
+ if (propsRef.current.onUpload) {
2184
+ requestAnimationFrame(() => {
2185
+ onFilesUpload(acceptedFiles);
2186
+ });
2187
+ }
2188
+ }
2189
+ },
2190
+ [
2191
+ store,
2192
+ isControlled,
2193
+ propsRef,
2194
+ onFilesUpload,
2195
+ maxFiles,
2196
+ acceptTypes,
2197
+ maxSize,
2198
+ disabled
2199
+ ]
2200
+ );
2201
+ const onInputChange = React21.useCallback(
2202
+ (event) => {
2203
+ const files2 = Array.from(event.target.files ?? []);
2204
+ onFilesChange(files2);
2205
+ event.target.value = "";
2206
+ },
2207
+ [onFilesChange]
2208
+ );
2209
+ const contextValue = React21.useMemo(
2210
+ () => ({
2211
+ dropzoneId,
2212
+ inputId,
2213
+ listId,
2214
+ labelId,
2215
+ dir,
2216
+ disabled,
2217
+ inputRef,
2218
+ urlCache
2219
+ }),
2220
+ [dropzoneId, inputId, listId, labelId, dir, disabled, urlCache]
2221
+ );
2222
+ const RootPrimitive = asChild ? Slot : "div";
2223
+ const inputAriaDescribedBy = [
2224
+ contextValue.dropzoneId,
2225
+ inputProps?.["aria-describedby"]
2226
+ ].filter(Boolean).join(" ").trim();
2227
+ return /* @__PURE__ */ React21.createElement(StoreContext.Provider, { value: store }, /* @__PURE__ */ React21.createElement(FileUploadContext.Provider, { value: contextValue }, /* @__PURE__ */ React21.createElement(
2228
+ RootPrimitive,
2229
+ {
2230
+ "data-disabled": disabled ? "" : void 0,
2231
+ "data-slot": "file-upload",
2232
+ dir,
2233
+ ...rootProps,
2234
+ className: cn("relative flex flex-col gap-2", className)
2235
+ },
2236
+ children,
2237
+ /* @__PURE__ */ React21.createElement(
2238
+ "input",
2239
+ {
2240
+ type: "file",
2241
+ id: inputId,
2242
+ "aria-labelledby": inputProps?.["aria-labelledby"] ? `${labelId} ${inputProps["aria-labelledby"]}` : labelId,
2243
+ "aria-describedby": inputAriaDescribedBy || void 0,
2244
+ ref: inputRef,
2245
+ tabIndex: -1,
2246
+ accept,
2247
+ name,
2248
+ className: "sr-only",
2249
+ disabled,
2250
+ multiple,
2251
+ required,
2252
+ onChange: onInputChange,
2253
+ ...inputProps
2254
+ }
2255
+ ),
2256
+ /* @__PURE__ */ React21.createElement("div", { id: labelId, className: "sr-only" }, label ?? "File upload")
2257
+ )));
2258
+ }
2259
+ function FileUploadDropzone(props) {
2260
+ const {
2261
+ asChild,
2262
+ className,
2263
+ onClick: onClickProp,
2264
+ onDragOver: onDragOverProp,
2265
+ onDragEnter: onDragEnterProp,
2266
+ onDragLeave: onDragLeaveProp,
2267
+ onDrop: onDropProp,
2268
+ onPaste: onPasteProp,
2269
+ onKeyDown: onKeyDownProp,
2270
+ ...dropzoneProps
2271
+ } = props;
2272
+ const context = useFileUploadContext(DROPZONE_NAME);
2273
+ const store = useStoreContext(DROPZONE_NAME);
2274
+ const dragOver = useStore((state) => state.dragOver);
2275
+ const invalid = useStore((state) => state.invalid);
2276
+ const propsRef = useAsRef({
2277
+ onClick: onClickProp,
2278
+ onDragOver: onDragOverProp,
2279
+ onDragEnter: onDragEnterProp,
2280
+ onDragLeave: onDragLeaveProp,
2281
+ onDrop: onDropProp,
2282
+ onPaste: onPasteProp,
2283
+ onKeyDown: onKeyDownProp
2284
+ });
2285
+ const onClick = React21.useCallback(
2286
+ (event) => {
2287
+ propsRef.current.onClick?.(event);
2288
+ if (event.defaultPrevented) return;
2289
+ const target = event.target;
2290
+ const isFromTrigger = target instanceof HTMLElement && target.closest('[data-slot="file-upload-trigger"]');
2291
+ if (!isFromTrigger) {
2292
+ context.inputRef.current?.click();
2293
+ }
2294
+ },
2295
+ [context.inputRef, propsRef]
2296
+ );
2297
+ const onDragOver = React21.useCallback(
2298
+ (event) => {
2299
+ propsRef.current.onDragOver?.(event);
2300
+ if (event.defaultPrevented) return;
2301
+ event.preventDefault();
2302
+ store.dispatch({ type: "SET_DRAG_OVER", dragOver: true });
2303
+ },
2304
+ [store, propsRef]
2305
+ );
2306
+ const onDragEnter = React21.useCallback(
2307
+ (event) => {
2308
+ propsRef.current.onDragEnter?.(event);
2309
+ if (event.defaultPrevented) return;
2310
+ event.preventDefault();
2311
+ store.dispatch({ type: "SET_DRAG_OVER", dragOver: true });
2312
+ },
2313
+ [store, propsRef]
2314
+ );
2315
+ const onDragLeave = React21.useCallback(
2316
+ (event) => {
2317
+ propsRef.current.onDragLeave?.(event);
2318
+ if (event.defaultPrevented) return;
2319
+ const relatedTarget = event.relatedTarget;
2320
+ if (relatedTarget && relatedTarget instanceof Node && event.currentTarget.contains(relatedTarget)) {
2321
+ return;
2322
+ }
2323
+ event.preventDefault();
2324
+ store.dispatch({ type: "SET_DRAG_OVER", dragOver: false });
2325
+ },
2326
+ [store, propsRef]
2327
+ );
2328
+ const onDrop = React21.useCallback(
2329
+ (event) => {
2330
+ propsRef.current.onDrop?.(event);
2331
+ if (event.defaultPrevented) return;
2332
+ if (context.disabled) return;
2333
+ event.preventDefault();
2334
+ store.dispatch({ type: "SET_DRAG_OVER", dragOver: false });
2335
+ const files = Array.from(event.dataTransfer.files);
2336
+ const inputElement = context.inputRef.current;
2337
+ if (!inputElement) return;
2338
+ if (typeof DataTransfer === "undefined") return;
2339
+ const dataTransfer = new DataTransfer();
2340
+ for (const file of files) {
2341
+ dataTransfer.items.add(file);
2342
+ }
2343
+ inputElement.files = dataTransfer.files;
2344
+ inputElement.dispatchEvent(new Event("change", { bubbles: true }));
2345
+ },
2346
+ [store, context.inputRef, propsRef]
2347
+ );
2348
+ const onPaste = React21.useCallback(
2349
+ (event) => {
2350
+ propsRef.current.onPaste?.(event);
2351
+ if (event.defaultPrevented) return;
2352
+ if (context.disabled) return;
2353
+ event.preventDefault();
2354
+ store.dispatch({ type: "SET_DRAG_OVER", dragOver: false });
2355
+ const items = event.clipboardData?.items;
2356
+ if (!items) return;
2357
+ const files = [];
2358
+ for (let i = 0; i < items.length; i++) {
2359
+ const item = items[i];
2360
+ if (item?.kind === "file") {
2361
+ const file = item.getAsFile();
2362
+ if (file) {
2363
+ files.push(file);
2364
+ }
2365
+ }
2366
+ }
2367
+ if (files.length === 0) return;
2368
+ const inputElement = context.inputRef.current;
2369
+ if (!inputElement) return;
2370
+ if (typeof DataTransfer === "undefined") return;
2371
+ const dataTransfer = new DataTransfer();
2372
+ for (const file of files) {
2373
+ dataTransfer.items.add(file);
2374
+ }
2375
+ inputElement.files = dataTransfer.files;
2376
+ inputElement.dispatchEvent(new Event("change", { bubbles: true }));
2377
+ },
2378
+ [store, context.inputRef, propsRef]
2379
+ );
2380
+ const onKeyDown = React21.useCallback(
2381
+ (event) => {
2382
+ propsRef.current.onKeyDown?.(event);
2383
+ if (!event.defaultPrevented && (event.key === "Enter" || event.key === " ")) {
2384
+ event.preventDefault();
2385
+ context.inputRef.current?.click();
2386
+ }
2387
+ },
2388
+ [context.inputRef, propsRef]
2389
+ );
2390
+ const DropzonePrimitive = asChild ? Slot : "div";
2391
+ return /* @__PURE__ */ React21.createElement(
2392
+ DropzonePrimitive,
2393
+ {
2394
+ role: "region",
2395
+ id: context.dropzoneId,
2396
+ "aria-controls": `${context.inputId} ${context.listId}`,
2397
+ "aria-disabled": context.disabled,
2398
+ "aria-invalid": invalid,
2399
+ "data-disabled": context.disabled ? "" : void 0,
2400
+ "data-dragging": dragOver ? "" : void 0,
2401
+ "data-invalid": invalid ? "" : void 0,
2402
+ "data-slot": "file-upload-dropzone",
2403
+ dir: context.dir,
2404
+ tabIndex: context.disabled ? -1 : 0,
2405
+ ...dropzoneProps,
2406
+ className: cn(
2407
+ "relative flex select-none flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed p-6 outline-none transition-colors hover:bg-accent/30 focus-visible:border-ring/50 data-disabled:pointer-events-none data-dragging:border-primary/30 data-dragging:bg-accent/30 data-invalid:ring-destructive/20",
2408
+ className
2409
+ ),
2410
+ onClick,
2411
+ onDragEnter,
2412
+ onDragLeave,
2413
+ onDragOver,
2414
+ onDrop,
2415
+ onKeyDown,
2416
+ onPaste
2417
+ }
2418
+ );
2419
+ }
2420
+ function FileUploadList(props) {
2421
+ const {
2422
+ className,
2423
+ orientation = "vertical",
2424
+ asChild,
2425
+ forceMount,
2426
+ ...listProps
2427
+ } = props;
2428
+ const context = useFileUploadContext(LIST_NAME);
2429
+ const fileCount = useStore((state) => state.files.size);
2430
+ const shouldRender = forceMount || fileCount > 0;
2431
+ if (!shouldRender) return null;
2432
+ const ListPrimitive = asChild ? Slot : "div";
2433
+ return /* @__PURE__ */ React21.createElement(
2434
+ ListPrimitive,
2435
+ {
2436
+ role: "list",
2437
+ id: context.listId,
2438
+ "aria-orientation": orientation,
2439
+ "data-orientation": orientation,
2440
+ "data-slot": "file-upload-list",
2441
+ "data-state": shouldRender ? "active" : "inactive",
2442
+ dir: context.dir,
2443
+ ...listProps,
2444
+ className: cn(
2445
+ "data-[state=inactive]:fade-out-0 data-[state=active]:fade-in-0 data-[state=inactive]:slide-out-to-top-2 data-[state=active]:slide-in-from-top-2 flex flex-col gap-2 data-[state=active]:animate-in data-[state=inactive]:animate-out",
2446
+ orientation === "horizontal" && "flex-row overflow-x-auto p-1.5",
2447
+ className
2448
+ )
2449
+ }
2450
+ );
2451
+ }
2452
+ var FileUploadItemContext = React21.createContext(null);
2453
+ function useFileUploadItemContext(consumerName) {
2454
+ const context = React21.useContext(FileUploadItemContext);
2455
+ if (!context) {
2456
+ throw new Error(`\`${consumerName}\` must be used within \`${ITEM_NAME}\``);
2457
+ }
2458
+ return context;
2459
+ }
2460
+ function FileUploadItem(props) {
2461
+ const { value, asChild, className, ...itemProps } = props;
2462
+ const id = React21.useId();
2463
+ const statusId = `${id}-status`;
2464
+ const nameId = `${id}-name`;
2465
+ const sizeId = `${id}-size`;
2466
+ const messageId = `${id}-message`;
2467
+ const context = useFileUploadContext(ITEM_NAME);
2468
+ const fileState = useStore((state) => state.files.get(value));
2469
+ const fileCount = useStore((state) => state.files.size);
2470
+ const fileIndex = useStore((state) => {
2471
+ const files = Array.from(state.files.keys());
2472
+ return files.indexOf(value) + 1;
2473
+ });
2474
+ const itemContext = React21.useMemo(
2475
+ () => ({
2476
+ id,
2477
+ fileState,
2478
+ nameId,
2479
+ sizeId,
2480
+ statusId,
2481
+ messageId
2482
+ }),
2483
+ [id, fileState, statusId, nameId, sizeId, messageId]
2484
+ );
2485
+ if (!fileState) return null;
2486
+ const statusText = fileState.error ? `Error: ${fileState.error}` : fileState.status === "uploading" ? `Uploading: ${fileState.progress}% complete` : fileState.status === "success" ? "Upload complete" : "Ready to upload";
2487
+ const ItemPrimitive = asChild ? Slot : "div";
2488
+ return /* @__PURE__ */ React21.createElement(FileUploadItemContext.Provider, { value: itemContext }, /* @__PURE__ */ React21.createElement(
2489
+ ItemPrimitive,
2490
+ {
2491
+ role: "listitem",
2492
+ id,
2493
+ "aria-setsize": fileCount,
2494
+ "aria-posinset": fileIndex,
2495
+ "aria-describedby": `${nameId} ${sizeId} ${statusId} ${fileState.error ? messageId : ""}`,
2496
+ "aria-labelledby": nameId,
2497
+ "data-slot": "file-upload-item",
2498
+ dir: context.dir,
2499
+ ...itemProps,
2500
+ className: cn(
2501
+ "relative flex items-center gap-2.5 rounded-md border p-3",
2502
+ className
2503
+ )
2504
+ },
2505
+ props.children,
2506
+ /* @__PURE__ */ React21.createElement("span", { id: statusId, className: "sr-only" }, statusText)
2507
+ ));
2508
+ }
2509
+ function FileUploadItemPreview(props) {
2510
+ const { render, asChild, children, className, ...previewProps } = props;
2511
+ const itemContext = useFileUploadItemContext(ITEM_PREVIEW_NAME);
2512
+ const context = useFileUploadContext(ITEM_PREVIEW_NAME);
2513
+ const getDefaultRender = React21.useCallback(
2514
+ (file) => {
2515
+ if (itemContext.fileState?.file.type.startsWith("image/")) {
2516
+ let url = context.urlCache.get(file);
2517
+ if (!url) {
2518
+ url = URL.createObjectURL(file);
2519
+ context.urlCache.set(file, url);
2520
+ }
2521
+ return (
2522
+ // biome-ignore lint/performance/noImgElement: dynamic file URLs from user uploads don't work well with Next.js Image optimization
2523
+ /* @__PURE__ */ React21.createElement("img", { src: url, alt: file.name, className: "size-full object-cover" })
2524
+ );
2525
+ }
2526
+ return getFileIcon(file);
2527
+ },
2528
+ [itemContext.fileState?.file.type, context.urlCache]
2529
+ );
2530
+ const onPreviewRender = React21.useCallback(
2531
+ (file) => {
2532
+ if (render) {
2533
+ return render(file, () => getDefaultRender(file));
2534
+ }
2535
+ return getDefaultRender(file);
2536
+ },
2537
+ [render, getDefaultRender]
2538
+ );
2539
+ if (!itemContext.fileState) return null;
2540
+ const ItemPreviewPrimitive = asChild ? Slot : "div";
2541
+ return /* @__PURE__ */ React21.createElement(
2542
+ ItemPreviewPrimitive,
2543
+ {
2544
+ "aria-labelledby": itemContext.nameId,
2545
+ "data-slot": "file-upload-preview",
2546
+ ...previewProps,
2547
+ className: cn(
2548
+ "relative flex size-10 shrink-0 items-center justify-center overflow-hidden rounded border bg-accent/50 [&>svg]:size-10",
2549
+ className
2550
+ )
2551
+ },
2552
+ onPreviewRender(itemContext.fileState.file),
2553
+ children
2554
+ );
2555
+ }
2556
+ function FileUploadItemMetadata(props) {
2557
+ const {
2558
+ asChild,
2559
+ size = "default",
2560
+ children,
2561
+ className,
2562
+ ...metadataProps
2563
+ } = props;
2564
+ const context = useFileUploadContext(ITEM_METADATA_NAME);
2565
+ const itemContext = useFileUploadItemContext(ITEM_METADATA_NAME);
2566
+ if (!itemContext.fileState) return null;
2567
+ const ItemMetadataPrimitive = asChild ? Slot : "div";
2568
+ return /* @__PURE__ */ React21.createElement(
2569
+ ItemMetadataPrimitive,
2570
+ {
2571
+ "data-slot": "file-upload-metadata",
2572
+ dir: context.dir,
2573
+ ...metadataProps,
2574
+ className: cn("flex min-w-0 flex-1 flex-col", className)
2575
+ },
2576
+ children ?? /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(
2577
+ "span",
2578
+ {
2579
+ id: itemContext.nameId,
2580
+ className: cn(
2581
+ "truncate font-medium text-sm",
2582
+ size === "sm" && "font-normal text-[13px] leading-snug"
2583
+ )
2584
+ },
2585
+ itemContext.fileState.file.name
2586
+ ), /* @__PURE__ */ React21.createElement(
2587
+ "span",
2588
+ {
2589
+ id: itemContext.sizeId,
2590
+ className: cn(
2591
+ "truncate text-xs opacity-70",
2592
+ size === "sm" && "text-[11px] leading-snug"
2593
+ )
2594
+ },
2595
+ formatBytes(itemContext.fileState.file.size)
2596
+ ), itemContext.fileState.error && /* @__PURE__ */ React21.createElement(
2597
+ "span",
2598
+ {
2599
+ id: itemContext.messageId,
2600
+ className: "text-destructive text-xs"
2601
+ },
2602
+ itemContext.fileState.error
2603
+ ))
2604
+ );
2605
+ }
2606
+ function FileUploadItemDelete(props) {
2607
+ const { asChild, onClick: onClickProp, ...deleteProps } = props;
2608
+ const store = useStoreContext(ITEM_DELETE_NAME);
2609
+ const itemContext = useFileUploadItemContext(ITEM_DELETE_NAME);
2610
+ const onClick = React21.useCallback(
2611
+ (event) => {
2612
+ onClickProp?.(event);
2613
+ if (!itemContext.fileState || event.defaultPrevented) return;
2614
+ store.dispatch({
2615
+ type: "REMOVE_FILE",
2616
+ file: itemContext.fileState.file
2617
+ });
2618
+ },
2619
+ [store, itemContext.fileState, onClickProp]
2620
+ );
2621
+ if (!itemContext.fileState) return null;
2622
+ const ItemDeletePrimitive = asChild ? Slot : "button";
2623
+ return /* @__PURE__ */ React21.createElement(
2624
+ ItemDeletePrimitive,
2625
+ {
2626
+ type: "button",
2627
+ "aria-controls": itemContext.id,
2628
+ "aria-describedby": itemContext.nameId,
2629
+ "data-slot": "file-upload-item-delete",
2630
+ ...deleteProps,
2631
+ onClick
2632
+ }
2633
+ );
2634
+ }
2635
+
2636
+ // src/inputs/FileInput.tsx
2637
+ function FileInput({
2638
+ name,
2639
+ value = [],
2640
+ onChange,
2641
+ onBlur,
2642
+ placeholder = "Choose file...",
2643
+ disabled = false,
2644
+ required = false,
2645
+ error = false,
2646
+ className = "",
2647
+ accept,
2648
+ maxSize = 5 * 1024 * 1024,
2649
+ // 5MB default
2650
+ maxFiles = 1,
2651
+ multiple = false,
2652
+ showPreview = true,
2653
+ showProgress = true,
2654
+ uploadProgress = {},
2655
+ enableCropping = false,
2656
+ cropAspectRatio,
2657
+ onCropComplete,
2658
+ onValidationError,
2659
+ onFileRemove,
2660
+ ...props
2661
+ }) {
2662
+ const normalizedValue = React21.useMemo(() => {
2663
+ const safeValue = Array.isArray(value) ? value : [];
2664
+ return multiple ? safeValue : safeValue.slice(0, 1);
2665
+ }, [multiple, value]);
2666
+ const [cropperOpen, setCropperOpen] = React21.useState(false);
2667
+ const [imageToCrop, setImageToCrop] = React21.useState(null);
2668
+ const [crop, setCrop] = React21.useState({ x: 0, y: 0 });
2669
+ const [zoom, setZoom] = React21.useState(1);
2670
+ const [croppedAreaPixels, setCroppedAreaPixels] = React21.useState(null);
2671
+ const validateFile = React21.useCallback(
2672
+ (file) => {
2673
+ if (accept) {
2674
+ const acceptedTypes = accept.split(",").map((type) => type.trim());
2675
+ const isValidType = acceptedTypes.some((type) => {
2676
+ if (type.startsWith(".")) {
2677
+ return file.name.toLowerCase().endsWith(type.toLowerCase());
2678
+ }
2679
+ if (type.endsWith("/*")) {
2680
+ const baseType = type.split("/")[0];
2681
+ return file.type.startsWith(`${baseType}/`);
2682
+ }
2683
+ return file.type === type;
2684
+ });
2685
+ if (!isValidType) {
2686
+ return {
2687
+ file,
2688
+ error: "type",
2689
+ message: `File type "${file.type}" is not accepted. Accepted types: ${accept}`
2690
+ };
2691
+ }
2692
+ }
2693
+ if (file.size > maxSize) {
2694
+ const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2);
2695
+ const fileSizeMB = (file.size / (1024 * 1024)).toFixed(2);
2696
+ return {
2697
+ file,
2698
+ error: "size",
2699
+ message: `File size ${fileSizeMB}MB exceeds maximum ${maxSizeMB}MB`
2700
+ };
2701
+ }
2702
+ return null;
2703
+ },
2704
+ [accept, maxSize]
2705
+ );
2706
+ const mapRejectedFileError = React21.useCallback(
2707
+ (file, message) => {
2708
+ const normalizedMessage = message.toLowerCase();
2709
+ if (normalizedMessage.includes("maximum") && normalizedMessage.includes("files")) {
2710
+ return { file, error: "count", message };
2711
+ }
2712
+ if (normalizedMessage.includes("size") || normalizedMessage.includes("large")) {
2713
+ return { file, error: "size", message };
2714
+ }
2715
+ if (normalizedMessage.includes("type") || normalizedMessage.includes("accept")) {
2716
+ return { file, error: "type", message };
2717
+ }
2718
+ if (file.size > maxSize) {
2719
+ return { file, error: "size", message };
2720
+ }
2721
+ return { file, error: "type", message };
2722
+ },
2723
+ [maxSize]
2724
+ );
2725
+ const handleFileValidate = React21.useCallback(
2726
+ (file) => {
2727
+ const validationError = validateFile(file);
2728
+ return validationError?.message ?? null;
2729
+ },
2730
+ [validateFile]
2731
+ );
2732
+ const handleFileReject = React21.useCallback(
2733
+ (file, message) => {
2734
+ const validationError = mapRejectedFileError(file, message);
2735
+ onValidationError?.([validationError]);
2736
+ },
2737
+ [mapRejectedFileError, onValidationError]
2738
+ );
2739
+ const handleBlur = React21.useCallback(() => {
2740
+ onBlur?.();
2741
+ }, [onBlur]);
2742
+ const fileIdentity = React21.useCallback((file) => {
2743
+ return `${file.name}-${file.size}-${file.lastModified}`;
2744
+ }, []);
2745
+ const handleValueChange = React21.useCallback(
2746
+ (incomingFiles) => {
2747
+ const nextFiles = multiple ? incomingFiles : incomingFiles.slice(-1);
2748
+ if (onFileRemove && nextFiles.length < normalizedValue.length) {
2749
+ const nextFileIds = new Set(nextFiles.map((file) => fileIdentity(file)));
2750
+ normalizedValue.forEach((file, index) => {
2751
+ if (!nextFileIds.has(fileIdentity(file))) {
2752
+ onFileRemove(file, index);
2753
+ }
2754
+ });
2755
+ }
2756
+ if (enableCropping && !multiple) {
2757
+ const nextImageFile = nextFiles[0];
2758
+ const previousFile = normalizedValue[0];
2759
+ const isNewSingleImage = Boolean(
2760
+ nextImageFile && nextImageFile.type.startsWith("image/") && nextImageFile !== previousFile
2761
+ );
2762
+ if (isNewSingleImage) {
2763
+ const previewUrl = URL.createObjectURL(nextImageFile);
2764
+ setImageToCrop({ file: nextImageFile, url: previewUrl });
2765
+ setCropperOpen(true);
2766
+ return;
2767
+ }
2768
+ }
2769
+ onChange(nextFiles);
2770
+ },
2771
+ [
2772
+ enableCropping,
2773
+ maxFiles,
2774
+ multiple,
2775
+ normalizedValue,
2776
+ onChange,
2777
+ onFileRemove,
2778
+ fileIdentity
2779
+ ]
2780
+ );
2781
+ const createCroppedImage = React21.useCallback(
2782
+ async (imageUrl, cropArea) => {
2783
+ return new Promise((resolve, reject) => {
2784
+ const image = new Image();
2785
+ image.onload = () => {
2786
+ const canvas = document.createElement("canvas");
2787
+ const ctx = canvas.getContext("2d");
2788
+ if (!ctx) {
2789
+ reject(new Error("Failed to get canvas context"));
2790
+ return;
2791
+ }
2792
+ canvas.width = cropArea.width;
2793
+ canvas.height = cropArea.height;
2794
+ ctx.drawImage(
2795
+ image,
2796
+ cropArea.x,
2797
+ cropArea.y,
2798
+ cropArea.width,
2799
+ cropArea.height,
2800
+ 0,
2801
+ 0,
2802
+ cropArea.width,
2803
+ cropArea.height
2804
+ );
2805
+ canvas.toBlob(
2806
+ (blob) => {
2807
+ if (blob) {
2808
+ resolve(blob);
2809
+ } else {
2810
+ reject(new Error("Failed to create blob from canvas"));
2811
+ }
2812
+ },
2813
+ "image/jpeg",
2814
+ 0.95
2815
+ );
2816
+ };
2817
+ image.onerror = () => {
2818
+ reject(new Error("Failed to load image"));
2819
+ };
2820
+ image.src = imageUrl;
2821
+ });
2822
+ },
2823
+ []
2824
+ );
2825
+ const handleCropSave = React21.useCallback(async () => {
2826
+ if (!imageToCrop || !croppedAreaPixels) return;
2827
+ try {
2828
+ const croppedBlob = await createCroppedImage(
2829
+ imageToCrop.url,
2830
+ croppedAreaPixels
2831
+ );
2832
+ if (onCropComplete) {
2833
+ onCropComplete(croppedBlob, imageToCrop.file);
2834
+ }
2835
+ const croppedFile = new File([croppedBlob], imageToCrop.file.name, {
2836
+ type: "image/jpeg"
2837
+ });
2838
+ let updatedFiles;
2839
+ if (!multiple) {
2840
+ updatedFiles = [croppedFile];
2841
+ } else {
2842
+ const existingIndex = normalizedValue.findIndex(
2843
+ (file) => file === imageToCrop.file
2844
+ );
2845
+ if (existingIndex === -1) {
2846
+ updatedFiles = [...normalizedValue, croppedFile].slice(0, maxFiles);
2847
+ } else {
2848
+ updatedFiles = normalizedValue.map(
2849
+ (file, index) => index === existingIndex ? croppedFile : file
2850
+ );
2851
+ }
2852
+ }
2853
+ onChange(updatedFiles);
2854
+ setCropperOpen(false);
2855
+ URL.revokeObjectURL(imageToCrop.url);
2856
+ setImageToCrop(null);
2857
+ setCrop({ x: 0, y: 0 });
2858
+ setZoom(1);
2859
+ setCroppedAreaPixels(null);
2860
+ } catch (cropError) {
2861
+ console.error("Failed to crop image:", cropError);
2862
+ }
2863
+ }, [
2864
+ createCroppedImage,
2865
+ croppedAreaPixels,
2866
+ imageToCrop,
2867
+ maxFiles,
2868
+ multiple,
2869
+ normalizedValue,
2870
+ onChange,
2871
+ onCropComplete
2872
+ ]);
2873
+ const handleCropCancel = React21.useCallback(() => {
2874
+ if (imageToCrop) {
2875
+ URL.revokeObjectURL(imageToCrop.url);
2876
+ }
2877
+ setCropperOpen(false);
2878
+ setImageToCrop(null);
2879
+ setCrop({ x: 0, y: 0 });
2880
+ setZoom(1);
2881
+ setCroppedAreaPixels(null);
2882
+ }, [imageToCrop]);
2883
+ const handleCrop = React21.useCallback((file) => {
2884
+ if (!file.type.startsWith("image/")) return;
2885
+ const previewUrl = URL.createObjectURL(file);
2886
+ setImageToCrop({ file, url: previewUrl });
2887
+ setCropperOpen(true);
2888
+ }, []);
2889
+ const onCropChange = React21.useCallback((nextCrop) => {
2890
+ setCrop(nextCrop);
2891
+ }, []);
2892
+ const onZoomChange = React21.useCallback((nextZoom) => {
2893
+ setZoom(nextZoom);
2894
+ }, []);
2895
+ const onCropCompleteInternal = React21.useCallback(
2896
+ (_, nextCroppedAreaPixels) => {
2897
+ setCroppedAreaPixels(nextCroppedAreaPixels);
2898
+ },
2899
+ []
2900
+ );
2901
+ const formatFileSize = React21.useCallback((bytes) => {
2902
+ if (bytes === 0) return "0 Bytes";
2903
+ const unit = 1024;
2904
+ const units = ["Bytes", "KB", "MB", "GB"];
2905
+ const index = Math.floor(Math.log(bytes) / Math.log(unit));
2906
+ return Math.round(bytes / Math.pow(unit, index) * 100) / 100 + " " + units[index];
2907
+ }, []);
2908
+ React21.useEffect(() => {
2909
+ return () => {
2910
+ if (imageToCrop) {
2911
+ URL.revokeObjectURL(imageToCrop.url);
2912
+ }
2913
+ };
2914
+ }, [imageToCrop]);
2915
+ const fileCountLabel = normalizedValue.length > 0 ? `${normalizedValue.length} file(s) selected` : placeholder;
2916
+ return /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(
2917
+ FileUpload,
2918
+ {
2919
+ name,
2920
+ value: normalizedValue,
2921
+ onValueChange: handleValueChange,
2922
+ onFileValidate: handleFileValidate,
2923
+ onFileReject: handleFileReject,
2924
+ accept,
2925
+ maxSize,
2926
+ maxFiles: multiple ? maxFiles : void 0,
2927
+ multiple,
2928
+ disabled,
2929
+ required: required && normalizedValue.length === 0,
2930
+ invalid: Boolean(error || props["aria-invalid"]),
2931
+ label: "File upload",
2932
+ className: cn(className),
2933
+ inputProps: {
2934
+ ...props,
2935
+ onBlur: handleBlur,
2936
+ style: { display: "none" },
2937
+ "aria-invalid": error || props["aria-invalid"],
2938
+ "aria-required": required || props["aria-required"],
2939
+ "aria-describedby": props["aria-describedby"]
2940
+ }
2941
+ },
2942
+ /* @__PURE__ */ React21.createElement(
2943
+ FileUploadDropzone,
2944
+ {
2945
+ role: "button",
2946
+ "aria-label": placeholder,
2947
+ className: cn(
2948
+ "flex min-h-32 w-full cursor-pointer items-center justify-center border-input bg-transparent p-6 transition-colors",
2949
+ "hover:bg-accent/50 hover:border-ring",
2950
+ "data-[dragging]:bg-accent data-[dragging]:border-ring",
2951
+ disabled && "cursor-not-allowed opacity-50",
2952
+ error && "border-destructive"
2953
+ )
2954
+ },
2955
+ /* @__PURE__ */ React21.createElement("div", { className: "flex flex-col items-center gap-2 text-center" }, /* @__PURE__ */ React21.createElement(
2956
+ "svg",
2957
+ {
2958
+ width: "48",
2959
+ height: "48",
2960
+ viewBox: "0 0 24 24",
2961
+ fill: "none",
2962
+ stroke: "currentColor",
2963
+ strokeWidth: "2",
2964
+ strokeLinecap: "round",
2965
+ strokeLinejoin: "round",
2966
+ "aria-hidden": "true"
2967
+ },
2968
+ /* @__PURE__ */ React21.createElement("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
2969
+ /* @__PURE__ */ React21.createElement("polyline", { points: "17 8 12 3 7 8" }),
2970
+ /* @__PURE__ */ React21.createElement("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
2971
+ ), /* @__PURE__ */ React21.createElement("p", { className: "text-sm font-medium" }, fileCountLabel), accept && /* @__PURE__ */ React21.createElement("p", { className: "text-xs" }, "Accepted: ", accept), /* @__PURE__ */ React21.createElement("p", { className: "text-xs" }, "Max size: ", formatFileSize(maxSize)))
2972
+ ),
2973
+ /* @__PURE__ */ React21.createElement(FileUploadList, { className: "mt-4" }, normalizedValue.map((file, index) => {
2974
+ const progressValue = uploadProgress[file.name];
2975
+ const hasProgress = showProgress && typeof progressValue === "number";
2976
+ return /* @__PURE__ */ React21.createElement(
2977
+ FileUploadItem,
2978
+ {
2979
+ key: `${file.name}-${index}`,
2980
+ value: file,
2981
+ className: "flex items-center gap-3 border-border bg-card text-card-foreground hover:bg-primary/50 transition-colors"
2982
+ },
2983
+ showPreview ? /* @__PURE__ */ React21.createElement(FileUploadItemPreview, { className: "h-12 w-12 rounded [&>img]:h-full [&>img]:w-full [&>img]:object-cover [&>svg]:size-6" }) : null,
2984
+ /* @__PURE__ */ React21.createElement("div", { className: "flex min-w-0 flex-1 flex-col" }, /* @__PURE__ */ React21.createElement(FileUploadItemMetadata, { className: "min-w-0" }), /* @__PURE__ */ React21.createElement("span", { className: "text-xs" }, formatFileSize(file.size)), hasProgress ? /* @__PURE__ */ React21.createElement("div", { className: "mt-1 flex items-center gap-2" }, /* @__PURE__ */ React21.createElement(
2985
+ "div",
2986
+ {
2987
+ className: "h-1.5 flex-1 overflow-hidden rounded-full bg-accent/40",
2988
+ role: "progressbar",
2989
+ "aria-valuenow": progressValue,
2990
+ "aria-valuemin": 0,
2991
+ "aria-valuemax": 100,
2992
+ "aria-label": `Upload progress: ${progressValue}%`
2993
+ },
2994
+ /* @__PURE__ */ React21.createElement(
2995
+ "div",
2996
+ {
2997
+ className: "h-full bg-primary transition-all",
2998
+ style: { width: `${progressValue}%` }
2999
+ }
3000
+ )
3001
+ ), /* @__PURE__ */ React21.createElement("span", { className: "text-xs" }, progressValue, "%")) : null),
3002
+ enableCropping && file.type.startsWith("image/") ? /* @__PURE__ */ React21.createElement(
3003
+ Button,
3004
+ {
3005
+ type: "button",
3006
+ variant: "ghost",
3007
+ size: "icon",
3008
+ onClick: (event) => {
3009
+ event.stopPropagation();
3010
+ handleCrop(file);
3011
+ },
3012
+ disabled,
3013
+ className: "h-8 w-8 p-0",
3014
+ "aria-label": `Crop ${file.name}`
3015
+ },
3016
+ /* @__PURE__ */ React21.createElement(
3017
+ "svg",
3018
+ {
3019
+ width: "20",
3020
+ height: "20",
3021
+ viewBox: "0 0 24 24",
3022
+ fill: "none",
3023
+ stroke: "currentColor",
3024
+ strokeWidth: "2",
3025
+ strokeLinecap: "round",
3026
+ strokeLinejoin: "round",
3027
+ "aria-hidden": "true"
3028
+ },
3029
+ /* @__PURE__ */ React21.createElement("path", { d: "M6.13 1L6 16a2 2 0 0 0 2 2h15" }),
3030
+ /* @__PURE__ */ React21.createElement("path", { d: "M1 6.13L16 6a2 2 0 0 1 2 2v15" })
3031
+ )
3032
+ ) : null,
3033
+ /* @__PURE__ */ React21.createElement(FileUploadItemDelete, { asChild: true }, /* @__PURE__ */ React21.createElement(
3034
+ Button,
3035
+ {
3036
+ type: "button",
3037
+ variant: "ghost",
3038
+ size: "icon",
3039
+ disabled,
3040
+ className: "h-8 w-8 p-0",
3041
+ "aria-label": `Remove ${file.name}`
3042
+ },
3043
+ /* @__PURE__ */ React21.createElement(
3044
+ "svg",
3045
+ {
3046
+ width: "20",
3047
+ height: "20",
3048
+ viewBox: "0 0 24 24",
3049
+ fill: "none",
3050
+ stroke: "currentColor",
3051
+ strokeWidth: "2",
3052
+ strokeLinecap: "round",
3053
+ strokeLinejoin: "round",
3054
+ "aria-hidden": "true"
3055
+ },
3056
+ /* @__PURE__ */ React21.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
3057
+ /* @__PURE__ */ React21.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
3058
+ )
3059
+ ))
3060
+ );
3061
+ }))
3062
+ ), /* @__PURE__ */ React21.createElement(
3063
+ Dialog,
3064
+ {
3065
+ open: cropperOpen && Boolean(imageToCrop),
3066
+ onOpenChange: (open) => {
3067
+ if (!open) {
3068
+ handleCropCancel();
3069
+ }
3070
+ }
3071
+ },
3072
+ imageToCrop ? /* @__PURE__ */ React21.createElement(
3073
+ DialogContent,
3074
+ {
3075
+ showCloseButton: false,
3076
+ className: "max-w-3xl gap-0 p-0",
3077
+ "aria-describedby": void 0
3078
+ },
3079
+ /* @__PURE__ */ React21.createElement(DialogHeader, { className: "flex-row items-center justify-between border-b border-border px-4 py-3" }, /* @__PURE__ */ React21.createElement(DialogTitle, null, "Crop Image"), /* @__PURE__ */ React21.createElement(DialogClose, { asChild: true }, /* @__PURE__ */ React21.createElement(
3080
+ Button,
3081
+ {
3082
+ type: "button",
3083
+ variant: "ghost",
3084
+ size: "icon",
3085
+ className: "h-8 w-8 p-0",
3086
+ "aria-label": "Close"
3087
+ },
3088
+ /* @__PURE__ */ React21.createElement(
3089
+ "svg",
3090
+ {
3091
+ width: "16",
3092
+ height: "16",
3093
+ viewBox: "0 0 24 24",
3094
+ fill: "none",
3095
+ stroke: "currentColor",
3096
+ strokeWidth: "2",
3097
+ strokeLinecap: "round",
3098
+ strokeLinejoin: "round",
3099
+ "aria-hidden": "true"
3100
+ },
3101
+ /* @__PURE__ */ React21.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
3102
+ /* @__PURE__ */ React21.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
3103
+ )
3104
+ ))),
3105
+ /* @__PURE__ */ React21.createElement("div", { className: "p-4" }, /* @__PURE__ */ React21.createElement(
3106
+ "div",
3107
+ {
3108
+ className: "relative h-96 w-full overflow-hidden rounded-md bg-accent/40",
3109
+ onMouseDown: (event) => {
3110
+ event.preventDefault();
3111
+ const startX = event.clientX - crop.x;
3112
+ const startY = event.clientY - crop.y;
3113
+ const handleMouseMove = (moveEvent) => {
3114
+ onCropChange({
3115
+ x: moveEvent.clientX - startX,
3116
+ y: moveEvent.clientY - startY
3117
+ });
3118
+ };
3119
+ const handleMouseUp = () => {
3120
+ document.removeEventListener("mousemove", handleMouseMove);
3121
+ document.removeEventListener("mouseup", handleMouseUp);
3122
+ };
3123
+ document.addEventListener("mousemove", handleMouseMove);
3124
+ document.addEventListener("mouseup", handleMouseUp);
3125
+ }
3126
+ },
3127
+ /* @__PURE__ */ React21.createElement(
3128
+ "img",
3129
+ {
3130
+ src: imageToCrop.url,
3131
+ alt: "Crop preview",
3132
+ className: "absolute inset-0 h-full w-full object-contain",
3133
+ style: {
3134
+ transform: `translate(${crop.x}px, ${crop.y}px) scale(${zoom})`
3135
+ },
3136
+ draggable: false,
3137
+ onLoad: (event) => {
3138
+ const image = event.currentTarget;
3139
+ const containerWidth = 600;
3140
+ const containerHeight = 400;
3141
+ const cropWidth = cropAspectRatio ? Math.min(
3142
+ containerWidth * 0.8,
3143
+ containerHeight * 0.8 * cropAspectRatio
3144
+ ) : containerWidth * 0.8;
3145
+ const cropHeight = cropAspectRatio ? cropWidth / cropAspectRatio : containerHeight * 0.8;
3146
+ const imageWidth = image.naturalWidth;
3147
+ const imageHeight = image.naturalHeight;
3148
+ const scale = zoom;
3149
+ const centerX = containerWidth / 2;
3150
+ const centerY = containerHeight / 2;
3151
+ const cropX = (centerX - crop.x - cropWidth / 2) / scale;
3152
+ const cropY = (centerY - crop.y - cropHeight / 2) / scale;
3153
+ onCropCompleteInternal(null, {
3154
+ x: Math.max(0, cropX),
3155
+ y: Math.max(0, cropY),
3156
+ width: Math.min(cropWidth / scale, imageWidth),
3157
+ height: Math.min(cropHeight / scale, imageHeight)
3158
+ });
3159
+ }
3160
+ }
3161
+ ),
3162
+ /* @__PURE__ */ React21.createElement(
3163
+ "div",
3164
+ {
3165
+ className: "pointer-events-none absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded border-2 border-primary",
3166
+ style: {
3167
+ width: cropAspectRatio ? `${Math.min(80, 80 * cropAspectRatio)}%` : "80%",
3168
+ aspectRatio: cropAspectRatio ? String(cropAspectRatio) : void 0
3169
+ }
3170
+ },
3171
+ /* @__PURE__ */ React21.createElement("div", { className: "absolute inset-0 grid grid-cols-3 grid-rows-3" }, /* @__PURE__ */ React21.createElement("div", { className: "border-r border-b border-primary/30" }), /* @__PURE__ */ React21.createElement("div", { className: "border-r border-b border-primary/30" }), /* @__PURE__ */ React21.createElement("div", { className: "border-b border-primary/30" }), /* @__PURE__ */ React21.createElement("div", { className: "border-r border-b border-primary/30" }), /* @__PURE__ */ React21.createElement("div", { className: "border-r border-b border-primary/30" }), /* @__PURE__ */ React21.createElement("div", { className: "border-b border-primary/30" }), /* @__PURE__ */ React21.createElement("div", { className: "border-r border-primary/30" }), /* @__PURE__ */ React21.createElement("div", { className: "border-r border-primary/30" }), /* @__PURE__ */ React21.createElement("div", null))
3172
+ )
3173
+ ), /* @__PURE__ */ React21.createElement("div", { className: "mt-4 flex items-center gap-3" }, /* @__PURE__ */ React21.createElement(
3174
+ "label",
3175
+ {
3176
+ htmlFor: "zoom-slider",
3177
+ className: "whitespace-nowrap text-sm font-medium"
3178
+ },
3179
+ "Zoom: ",
3180
+ zoom.toFixed(1),
3181
+ "x"
3182
+ ), /* @__PURE__ */ React21.createElement(
3183
+ "input",
3184
+ {
3185
+ id: "zoom-slider",
3186
+ type: "range",
3187
+ min: "1",
3188
+ max: "3",
3189
+ step: "0.1",
3190
+ value: zoom,
3191
+ onChange: (event) => onZoomChange(parseFloat(event.target.value)),
3192
+ className: "h-2 flex-1 cursor-pointer appearance-none rounded-lg bg-accent/60",
3193
+ "aria-label": "Zoom level"
3194
+ }
3195
+ ))),
3196
+ /* @__PURE__ */ React21.createElement("div", { className: "flex items-center justify-end gap-2 border-t border-border p-4" }, /* @__PURE__ */ React21.createElement(Button, { type: "button", variant: "outline", onClick: handleCropCancel }, "Cancel"), /* @__PURE__ */ React21.createElement(Button, { type: "button", onClick: handleCropSave }, "Save"))
3197
+ ) : null
3198
+ ));
3199
+ }
3200
+ FileInput.displayName = "FileInput";
3201
+ function Calendar({
3202
+ className,
3203
+ classNames,
3204
+ showOutsideDays = true,
3205
+ captionLayout = "label",
3206
+ buttonVariant = "ghost",
3207
+ formatters,
3208
+ components,
3209
+ ...props
3210
+ }) {
3211
+ const defaultClassNames = getDefaultClassNames();
3212
+ return /* @__PURE__ */ React21.createElement(
3213
+ DayPicker,
3214
+ {
3215
+ showOutsideDays,
3216
+ className: cn(
3217
+ "bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
3218
+ String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
3219
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
3220
+ className
3221
+ ),
3222
+ captionLayout,
3223
+ formatters: {
3224
+ formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }),
3225
+ ...formatters
3226
+ },
3227
+ classNames: {
3228
+ root: cn("w-fit", defaultClassNames.root),
3229
+ months: cn(
3230
+ "flex gap-4 flex-col md:flex-row relative",
3231
+ defaultClassNames.months
3232
+ ),
3233
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
3234
+ nav: cn(
3235
+ "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
3236
+ defaultClassNames.nav
3237
+ ),
3238
+ button_previous: cn(
3239
+ buttonVariants({ variant: buttonVariant }),
3240
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
3241
+ defaultClassNames.button_previous
3242
+ ),
3243
+ button_next: cn(
3244
+ buttonVariants({ variant: buttonVariant }),
3245
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
3246
+ defaultClassNames.button_next
3247
+ ),
3248
+ month_caption: cn(
3249
+ "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
3250
+ defaultClassNames.month_caption
3251
+ ),
3252
+ dropdowns: cn(
3253
+ "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
3254
+ defaultClassNames.dropdowns
3255
+ ),
3256
+ dropdown_root: cn(
3257
+ "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
3258
+ defaultClassNames.dropdown_root
3259
+ ),
3260
+ dropdown: cn(
3261
+ "absolute bg-popover inset-0 opacity-0",
3262
+ defaultClassNames.dropdown
3263
+ ),
3264
+ caption_label: cn(
3265
+ "select-none font-medium",
3266
+ captionLayout === "label" ? "text-sm" : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:opacity-70 [&>svg]:size-3.5",
3267
+ defaultClassNames.caption_label
3268
+ ),
3269
+ table: "w-full border-collapse",
3270
+ weekdays: cn("flex", defaultClassNames.weekdays),
3271
+ weekday: cn(
3272
+ "opacity-70 rounded-md flex-1 font-normal text-[0.8rem] select-none",
3273
+ defaultClassNames.weekday
3274
+ ),
3275
+ week: cn("flex w-full mt-2", defaultClassNames.week),
3276
+ week_number_header: cn(
3277
+ "select-none w-(--cell-size)",
3278
+ defaultClassNames.week_number_header
3279
+ ),
3280
+ week_number: cn(
3281
+ "text-[0.8rem] select-none opacity-70",
3282
+ defaultClassNames.week_number
3283
+ ),
3284
+ day: cn(
3285
+ "relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
3286
+ props.showWeekNumber ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md" : "[&:first-child[data-selected=true]_button]:rounded-l-md",
3287
+ defaultClassNames.day
3288
+ ),
3289
+ range_start: cn(
3290
+ "rounded-l-md bg-accent",
3291
+ defaultClassNames.range_start
3292
+ ),
3293
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
3294
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
3295
+ today: cn(
3296
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
3297
+ defaultClassNames.today
3298
+ ),
3299
+ outside: cn(
3300
+ "opacity-50",
3301
+ defaultClassNames.outside
3302
+ ),
3303
+ disabled: cn(
3304
+ "opacity-50",
3305
+ defaultClassNames.disabled
3306
+ ),
3307
+ hidden: cn("invisible", defaultClassNames.hidden),
3308
+ ...classNames
3309
+ },
3310
+ components: {
3311
+ Root: ({ className: className2, rootRef, ...props2 }) => {
3312
+ return /* @__PURE__ */ React21.createElement(
3313
+ "div",
3314
+ {
3315
+ "data-slot": "calendar",
3316
+ ref: rootRef,
3317
+ className: cn(className2),
3318
+ ...props2
3319
+ }
3320
+ );
3321
+ },
3322
+ Chevron: ({ className: className2, orientation, ...props2 }) => {
3323
+ if (orientation === "left") {
3324
+ return /* @__PURE__ */ React21.createElement(
3325
+ "svg",
3326
+ {
3327
+ className: cn("size-4", className2),
3328
+ viewBox: "0 0 24 24",
3329
+ fill: "none",
3330
+ stroke: "currentColor",
3331
+ strokeWidth: "2",
3332
+ strokeLinecap: "round",
3333
+ strokeLinejoin: "round",
3334
+ ...props2
3335
+ },
3336
+ /* @__PURE__ */ React21.createElement("polyline", { points: "15 18 9 12 15 6" })
3337
+ );
3338
+ }
3339
+ if (orientation === "right") {
3340
+ return /* @__PURE__ */ React21.createElement(
3341
+ "svg",
3342
+ {
3343
+ className: cn("size-4", className2),
3344
+ viewBox: "0 0 24 24",
3345
+ fill: "none",
3346
+ stroke: "currentColor",
3347
+ strokeWidth: "2",
3348
+ strokeLinecap: "round",
3349
+ strokeLinejoin: "round",
3350
+ ...props2
3351
+ },
3352
+ /* @__PURE__ */ React21.createElement("polyline", { points: "9 18 15 12 9 6" })
3353
+ );
3354
+ }
3355
+ return /* @__PURE__ */ React21.createElement(
3356
+ "svg",
3357
+ {
3358
+ className: cn("size-4", className2),
3359
+ viewBox: "0 0 24 24",
3360
+ fill: "none",
3361
+ stroke: "currentColor",
3362
+ strokeWidth: "2",
3363
+ strokeLinecap: "round",
3364
+ strokeLinejoin: "round",
3365
+ ...props2
3366
+ },
3367
+ /* @__PURE__ */ React21.createElement("polyline", { points: "6 9 12 15 18 9" })
3368
+ );
3369
+ },
3370
+ DayButton: CalendarDayButton,
3371
+ WeekNumber: ({ children, ...props2 }) => {
3372
+ return /* @__PURE__ */ React21.createElement("td", { ...props2 }, /* @__PURE__ */ React21.createElement("div", { className: "flex size-(--cell-size) items-center justify-center text-center" }, children));
3373
+ },
3374
+ ...components
3375
+ },
3376
+ ...props
3377
+ }
3378
+ );
3379
+ }
3380
+ function CalendarDayButton({
3381
+ className,
3382
+ day,
3383
+ modifiers,
3384
+ ...props
3385
+ }) {
3386
+ const defaultClassNames = getDefaultClassNames();
3387
+ const ref = React21.useRef(null);
3388
+ React21.useEffect(() => {
3389
+ if (modifiers.focused) ref.current?.focus();
3390
+ }, [modifiers.focused]);
3391
+ return /* @__PURE__ */ React21.createElement(
3392
+ Button,
3393
+ {
3394
+ ref,
3395
+ variant: "ghost",
3396
+ size: "icon",
3397
+ "data-day": day.date.toLocaleDateString(),
3398
+ "data-selected-single": modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle,
3399
+ "data-range-start": modifiers.range_start,
3400
+ "data-range-end": modifiers.range_end,
3401
+ "data-range-middle": modifiers.range_middle,
3402
+ className: cn(
3403
+ // Core structure
3404
+ "flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal",
3405
+ // Selected states - uses CSS variables
3406
+ "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground",
3407
+ "data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground",
3408
+ "data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground",
3409
+ "data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground",
3410
+ // Focus state
3411
+ "group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10",
3412
+ "group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 group-data-[focused=true]/day:ring-[3px]",
3413
+ // Rounding based on position
3414
+ "data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md",
3415
+ "data-[range-middle=true]:rounded-none",
3416
+ "data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md",
3417
+ // Nested span styling
3418
+ "[&>span]:text-xs [&>span]:opacity-70",
3419
+ defaultClassNames.day,
3420
+ className
3421
+ ),
3422
+ ...props
3423
+ }
3424
+ );
3425
+ }
3426
+
3427
+ // src/inputs/DatePicker.tsx
3428
+ function formatDate(date, format) {
3429
+ if (!date) return "";
3430
+ const d = new Date(date);
3431
+ const month = String(d.getMonth() + 1).padStart(2, "0");
3432
+ const day = String(d.getDate()).padStart(2, "0");
3433
+ const year = d.getFullYear();
3434
+ return format.replace("MM", month).replace("dd", day).replace("yyyy", String(year)).replace("yy", String(year).slice(2));
3435
+ }
3436
+ function DatePickerDayButton({
3437
+ day,
3438
+ modifiers,
3439
+ className,
3440
+ children,
3441
+ ...props
3442
+ }) {
3443
+ return /* @__PURE__ */ React21.createElement(
3444
+ "button",
3445
+ {
3446
+ type: "button",
3447
+ className: cn(
3448
+ "flex items-center justify-center h-8 w-8 rounded-md border-none bg-transparent cursor-pointer text-sm transition-colors",
3449
+ "hover:bg-accent",
3450
+ modifiers.selected && "bg-primary text-primary-foreground font-semibold",
3451
+ !modifiers.selected && modifiers.today && "border border-primary",
3452
+ modifiers.disabled && "cursor-not-allowed opacity-50 pointer-events-none",
3453
+ className
3454
+ ),
3455
+ ...props
3456
+ },
3457
+ children ?? day.date.getDate()
3458
+ );
3459
+ }
3460
+ function DatePicker({
3461
+ name,
3462
+ value,
3463
+ onChange,
3464
+ onBlur,
3465
+ disabled = false,
3466
+ required = false,
3467
+ error = false,
3468
+ className = "",
3469
+ placeholder = "Select date...",
3470
+ format = "MM/dd/yyyy",
3471
+ minDate,
3472
+ maxDate,
3473
+ disabledDates = [],
3474
+ isDateDisabled,
3475
+ clearable = true,
3476
+ showIcon = true,
3477
+ ...props
3478
+ }) {
3479
+ const [isOpen, setIsOpen] = React21.useState(false);
3480
+ const [hasInteracted, setHasInteracted] = React21.useState(false);
3481
+ const [selectedMonth, setSelectedMonth] = React21.useState(
3482
+ value || /* @__PURE__ */ new Date()
3483
+ );
3484
+ const inputRef = React21.useRef(null);
3485
+ React21.useEffect(() => {
3486
+ if (value) {
3487
+ setSelectedMonth(value);
3488
+ }
3489
+ }, [value]);
3490
+ const disabledMatchers = React21.useMemo(() => {
3491
+ const matchers = [];
3492
+ if (minDate) {
3493
+ matchers.push({ before: minDate });
3494
+ }
3495
+ if (maxDate) {
3496
+ matchers.push({ after: maxDate });
3497
+ }
3498
+ if (disabledDates.length > 0) {
3499
+ matchers.push(disabledDates);
3500
+ }
3501
+ if (isDateDisabled) {
3502
+ matchers.push(isDateDisabled);
3503
+ }
3504
+ return matchers;
3505
+ }, [disabledDates, isDateDisabled, maxDate, minDate]);
3506
+ const handleDateSelect = React21.useCallback(
3507
+ (date) => {
3508
+ if (!date) return;
3509
+ onChange(date);
3510
+ setSelectedMonth(date);
3511
+ setIsOpen(false);
3512
+ onBlur?.();
3513
+ },
3514
+ [onBlur, onChange]
3515
+ );
3516
+ const handleClear = React21.useCallback(
3517
+ (e) => {
3518
+ e.stopPropagation();
3519
+ onChange(null);
3520
+ setIsOpen(false);
3521
+ inputRef.current?.focus();
3522
+ },
3523
+ [onChange]
3524
+ );
3525
+ const handleOpenChange = React21.useCallback(
3526
+ (nextOpen) => {
3527
+ if (disabled) {
3528
+ setIsOpen(false);
3529
+ return;
3530
+ }
3531
+ if (nextOpen) {
3532
+ if (!hasInteracted) {
3533
+ setHasInteracted(true);
3534
+ }
3535
+ setIsOpen(true);
3536
+ return;
3537
+ }
3538
+ if (isOpen && hasInteracted) {
3539
+ onBlur?.();
3540
+ }
3541
+ setIsOpen(false);
3542
+ },
3543
+ [disabled, hasInteracted, isOpen, onBlur]
3544
+ );
3545
+ const handleInputBlur = React21.useCallback(() => {
3546
+ if (!isOpen) {
3547
+ onBlur?.();
3548
+ }
3549
+ }, [isOpen, onBlur]);
3550
+ const handleInputClick = React21.useCallback(() => {
3551
+ if (!hasInteracted) {
3552
+ setHasInteracted(true);
3553
+ }
3554
+ }, [hasInteracted]);
3555
+ const hasValue = Boolean(value);
3556
+ const displayValue = formatDate(value, format);
3557
+ const combinedClassName = cn("relative", className);
3558
+ return /* @__PURE__ */ React21.createElement("div", { className: combinedClassName }, /* @__PURE__ */ React21.createElement(
3559
+ "input",
3560
+ {
3561
+ type: "hidden",
3562
+ name,
3563
+ value: value ? value.toISOString() : ""
3564
+ }
3565
+ ), /* @__PURE__ */ React21.createElement(Popover, { open: isOpen, onOpenChange: handleOpenChange }, /* @__PURE__ */ React21.createElement("div", { className: "relative" }, showIcon && /* @__PURE__ */ React21.createElement(
3566
+ "span",
3567
+ {
3568
+ className: "absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none",
3569
+ "aria-hidden": "true"
3570
+ },
3571
+ /* @__PURE__ */ React21.createElement(
3572
+ "svg",
3573
+ {
3574
+ xmlns: "http://www.w3.org/2000/svg",
3575
+ width: "18",
3576
+ height: "18",
3577
+ viewBox: "0 0 24 24",
3578
+ fill: "none",
3579
+ stroke: "currentColor",
3580
+ strokeLinecap: "round",
3581
+ strokeLinejoin: "round",
3582
+ strokeWidth: "2"
3583
+ },
3584
+ /* @__PURE__ */ React21.createElement("path", { d: "M8 2v4m8-4v4m5 8V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8M3 10h18m-5 10l2 2l4-4" })
3585
+ )
3586
+ ), /* @__PURE__ */ React21.createElement(PopoverTrigger, { asChild: true }, /* @__PURE__ */ React21.createElement(
3587
+ "input",
3588
+ {
3589
+ ref: inputRef,
3590
+ id: props.id,
3591
+ type: "text",
3592
+ className: cn(
3593
+ "flex h-9 w-full rounded-md border border-input bg-transparent py-1 text-base shadow-sm transition-colors",
3594
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
3595
+ "disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
3596
+ INPUT_AUTOFILL_RESET_CLASSES,
3597
+ showIcon ? "pl-10" : "pl-3",
3598
+ clearable && value ? "pr-10" : "pr-3",
3599
+ !error && hasValue && "ring-2 ring-ring",
3600
+ error && "border-destructive ring-1 ring-destructive"
3601
+ ),
3602
+ value: displayValue,
3603
+ onClick: handleInputClick,
3604
+ onBlur: handleInputBlur,
3605
+ disabled,
3606
+ required,
3607
+ placeholder,
3608
+ "aria-invalid": error || props["aria-invalid"] ? "true" : "false",
3609
+ "aria-describedby": props["aria-describedby"],
3610
+ "aria-required": required || props["aria-required"],
3611
+ readOnly: true
3612
+ }
3613
+ )), clearable && value && !disabled && /* @__PURE__ */ React21.createElement(
3614
+ "button",
3615
+ {
3616
+ type: "button",
3617
+ className: "absolute right-3 top-1/2 -translate-y-1/2 transition-colors",
3618
+ onClick: handleClear,
3619
+ "aria-label": "Clear date",
3620
+ tabIndex: -1
3621
+ },
3622
+ "\u2715"
3623
+ )), !disabled && /* @__PURE__ */ React21.createElement(
3624
+ PopoverContent,
3625
+ {
3626
+ align: "start",
3627
+ sideOffset: 4,
3628
+ className: "w-auto p-0",
3629
+ onOpenAutoFocus: (event) => {
3630
+ event.preventDefault();
3631
+ }
3632
+ },
3633
+ /* @__PURE__ */ React21.createElement(
3634
+ Calendar,
3635
+ {
3636
+ mode: "single",
3637
+ selected: value ?? void 0,
3638
+ onSelect: handleDateSelect,
3639
+ month: selectedMonth,
3640
+ onMonthChange: setSelectedMonth,
3641
+ disabled: disabledMatchers,
3642
+ showOutsideDays: true,
3643
+ labels: {
3644
+ labelGrid: () => "Calendar",
3645
+ labelDayButton: (date) => formatDate(date, format),
3646
+ labelPrevious: () => "Previous month",
3647
+ labelNext: () => "Next month"
3648
+ },
3649
+ components: {
3650
+ DayButton: DatePickerDayButton
3651
+ },
3652
+ classNames: {
3653
+ today: "border border-primary rounded-md bg-transparent"
3654
+ }
3655
+ }
3656
+ )
3657
+ )));
3658
+ }
3659
+ DatePicker.displayName = "DatePicker";
3660
+ function normalizeToNativeTime(value) {
3661
+ if (!value) return "";
3662
+ const twelveHourMatch = value.match(
3663
+ /^(\d{1,2}):(\d{2})(?::(\d{2}))?\s*(AM|PM)$/i
3664
+ );
3665
+ if (twelveHourMatch) {
3666
+ const rawHour = parseInt(twelveHourMatch[1], 10);
3667
+ const minute = parseInt(twelveHourMatch[2], 10);
3668
+ const period = twelveHourMatch[4].toUpperCase();
3669
+ if (Number.isNaN(rawHour) || Number.isNaN(minute) || rawHour < 1 || rawHour > 12 || minute < 0 || minute > 59) {
3670
+ return "";
3671
+ }
3672
+ const normalizedHour = period === "PM" ? rawHour === 12 ? 12 : rawHour + 12 : rawHour === 12 ? 0 : rawHour;
3673
+ return `${String(normalizedHour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
3674
+ }
3675
+ const twentyFourHourMatch = value.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
3676
+ if (twentyFourHourMatch) {
3677
+ const hour = parseInt(twentyFourHourMatch[1], 10);
3678
+ const minute = parseInt(twentyFourHourMatch[2], 10);
3679
+ if (Number.isNaN(hour) || Number.isNaN(minute) || hour < 0 || hour > 23 || minute < 0 || minute > 59) {
3680
+ return "";
3681
+ }
3682
+ return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
3683
+ }
3684
+ return "";
3685
+ }
3686
+ function formatFromNativeTime(nativeValue, use24Hour) {
3687
+ if (!nativeValue) return "";
3688
+ const [hourValue, minuteValue] = nativeValue.split(":");
3689
+ const hour = parseInt(hourValue, 10);
3690
+ const minute = parseInt(minuteValue, 10);
3691
+ if (Number.isNaN(hour) || Number.isNaN(minute)) {
3692
+ return "";
3693
+ }
3694
+ if (use24Hour) {
3695
+ return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
3696
+ }
3697
+ const period = hour >= 12 ? "PM" : "AM";
3698
+ const hour12 = hour % 12 || 12;
3699
+ return `${hour12}:${String(minute).padStart(2, "0")} ${period}`;
3700
+ }
3701
+ function TimePicker({
3702
+ name,
3703
+ value,
3704
+ onChange,
3705
+ onBlur,
3706
+ disabled = false,
3707
+ required = false,
3708
+ error = false,
3709
+ className = "",
3710
+ placeholder = "Select time...",
3711
+ use24Hour = false,
3712
+ minuteStep = 1,
3713
+ clearable = true,
3714
+ showIcon = true,
3715
+ ...props
3716
+ }) {
3717
+ const inputRef = React21.useRef(null);
3718
+ const [nativeValue, setNativeValue] = React21.useState(
3719
+ normalizeToNativeTime(value)
3720
+ );
3721
+ React21.useEffect(() => {
3722
+ setNativeValue(normalizeToNativeTime(value));
3723
+ }, [value]);
3724
+ const handleChange = (e) => {
3725
+ const nextNativeValue = e.target.value;
3726
+ setNativeValue(nextNativeValue);
3727
+ onChange(formatFromNativeTime(nextNativeValue, use24Hour));
3728
+ };
3729
+ const handleClear = (e) => {
3730
+ e.stopPropagation();
3731
+ setNativeValue("");
3732
+ onChange("");
3733
+ inputRef.current?.focus();
3734
+ };
3735
+ const hasValue = Boolean(value);
3736
+ const stepInSeconds = Math.max(1, minuteStep * 60);
3737
+ return /* @__PURE__ */ React21.createElement("div", { className: cn("relative", className) }, /* @__PURE__ */ React21.createElement("input", { type: "hidden", name, value }), /* @__PURE__ */ React21.createElement("div", { className: "relative" }, showIcon && /* @__PURE__ */ React21.createElement(
3738
+ "span",
3739
+ {
3740
+ className: "absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none",
3741
+ "aria-hidden": "true"
3742
+ },
3743
+ /* @__PURE__ */ React21.createElement(
3744
+ "svg",
3745
+ {
3746
+ xmlns: "http://www.w3.org/2000/svg",
3747
+ width: "18",
3748
+ height: "18",
3749
+ viewBox: "0 0 24 24",
3750
+ fill: "none",
3751
+ stroke: "currentColor",
3752
+ strokeLinecap: "round",
3753
+ strokeLinejoin: "round",
3754
+ strokeWidth: "2"
3755
+ },
3756
+ /* @__PURE__ */ React21.createElement("circle", { cx: "12", cy: "12", r: "10" }),
3757
+ /* @__PURE__ */ React21.createElement("path", { d: "M12 6v6l4 2" })
3758
+ )
3759
+ ), /* @__PURE__ */ React21.createElement(
3760
+ Input,
3761
+ {
3762
+ ref: inputRef,
3763
+ type: "time",
3764
+ className: cn(
3765
+ "appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none",
3766
+ INPUT_AUTOFILL_RESET_CLASSES,
3767
+ showIcon ? "pl-10" : "pl-3",
3768
+ clearable && value ? "pr-10" : "pr-3",
3769
+ !error && hasValue && "ring-2 ring-ring",
3770
+ error && "border-destructive ring-1 ring-destructive"
3771
+ ),
3772
+ value: nativeValue,
3773
+ onChange: handleChange,
3774
+ onBlur,
3775
+ disabled,
3776
+ required,
3777
+ step: stepInSeconds,
3778
+ placeholder,
3779
+ "aria-invalid": error || props["aria-invalid"] ? "true" : "false",
3780
+ "aria-describedby": props["aria-describedby"],
3781
+ "aria-required": required || props["aria-required"],
3782
+ ...props
3783
+ }
3784
+ ), clearable && value && !disabled && /* @__PURE__ */ React21.createElement(
3785
+ Button,
3786
+ {
3787
+ type: "button",
3788
+ variant: "ghost",
3789
+ size: "icon",
3790
+ className: "absolute right-1.5 top-1/2 h-7 w-7 -translate-y-1/2 p-0",
3791
+ onClick: handleClear,
3792
+ "aria-label": "Clear time",
3793
+ tabIndex: -1
3794
+ },
3795
+ /* @__PURE__ */ React21.createElement(
3796
+ "svg",
3797
+ {
3798
+ width: "14",
3799
+ height: "14",
3800
+ viewBox: "0 0 24 24",
3801
+ fill: "none",
3802
+ stroke: "currentColor",
3803
+ strokeWidth: "2",
3804
+ strokeLinecap: "round",
3805
+ strokeLinejoin: "round",
3806
+ "aria-hidden": "true"
3807
+ },
3808
+ /* @__PURE__ */ React21.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
3809
+ /* @__PURE__ */ React21.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
3810
+ )
3811
+ )));
3812
+ }
3813
+ TimePicker.displayName = "TimePicker";
3814
+ function formatDate2(date, format) {
3815
+ if (!date) return "";
3816
+ const d = new Date(date);
3817
+ const month = String(d.getMonth() + 1).padStart(2, "0");
3818
+ const day = String(d.getDate()).padStart(2, "0");
3819
+ const year = d.getFullYear();
3820
+ return format.replace("MM", month).replace("dd", day).replace("yyyy", String(year)).replace("yy", String(year).slice(2));
3821
+ }
3822
+ function toDayTimestamp(date) {
3823
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
3824
+ }
3825
+ function isSameDay(date, target) {
3826
+ if (!target) return false;
3827
+ return toDayTimestamp(date) === toDayTimestamp(target);
3828
+ }
3829
+ function isDateInRange(date, start, end) {
3830
+ if (!start || !end) return false;
3831
+ const value = toDayTimestamp(date);
3832
+ const startTs = toDayTimestamp(start);
3833
+ const endTs = toDayTimestamp(end);
3834
+ return value >= Math.min(startTs, endTs) && value <= Math.max(startTs, endTs);
3835
+ }
3836
+ function DateRangePicker({
3837
+ name,
3838
+ value = { start: null, end: null },
3839
+ onChange,
3840
+ onBlur,
3841
+ disabled = false,
3842
+ required = false,
3843
+ error = false,
3844
+ className = "",
3845
+ placeholder = "Select date range...",
3846
+ format = "MM/dd/yyyy",
3847
+ minDate,
3848
+ maxDate,
3849
+ disabledDates = [],
3850
+ isDateDisabled,
3851
+ clearable = true,
3852
+ showIcon = true,
3853
+ separator = " - ",
3854
+ ...props
3855
+ }) {
3856
+ const [isOpen, setIsOpen] = React21.useState(false);
3857
+ const [hasInteracted, setHasInteracted] = React21.useState(false);
3858
+ const [selectedMonth, setSelectedMonth] = React21.useState(
3859
+ value.start || /* @__PURE__ */ new Date()
3860
+ );
3861
+ const [rangeStart, setRangeStart] = React21.useState(value.start);
3862
+ const [rangeEnd, setRangeEnd] = React21.useState(value.end);
3863
+ const [hoverDate, setHoverDate] = React21.useState(null);
3864
+ const inputRef = React21.useRef(null);
3865
+ React21.useEffect(() => {
3866
+ setRangeStart(value.start);
3867
+ setRangeEnd(value.end);
3868
+ if (value.start) {
3869
+ setSelectedMonth(value.start);
3870
+ }
3871
+ }, [value]);
3872
+ const disabledMatchers = React21.useMemo(() => {
3873
+ const matchers = [];
3874
+ if (minDate) {
3875
+ matchers.push({ before: minDate });
3876
+ }
3877
+ if (maxDate) {
3878
+ matchers.push({ after: maxDate });
3879
+ }
3880
+ if (disabledDates.length > 0) {
3881
+ matchers.push(disabledDates);
3882
+ }
3883
+ if (isDateDisabled) {
3884
+ matchers.push(isDateDisabled);
3885
+ }
3886
+ return matchers;
3887
+ }, [disabledDates, isDateDisabled, maxDate, minDate]);
3888
+ const handleDateSelect = React21.useCallback(
3889
+ (date) => {
3890
+ if (!rangeStart || rangeEnd) {
3891
+ setRangeStart(date);
3892
+ setRangeEnd(null);
3893
+ setHoverDate(null);
3894
+ setSelectedMonth(date);
3895
+ onChange({ start: date, end: null });
3896
+ onBlur?.();
3897
+ return;
3898
+ }
3899
+ if (toDayTimestamp(date) < toDayTimestamp(rangeStart)) {
3900
+ setRangeStart(date);
3901
+ setRangeEnd(rangeStart);
3902
+ setHoverDate(null);
3903
+ setSelectedMonth(date);
3904
+ onChange({ start: date, end: rangeStart });
3905
+ setIsOpen(false);
3906
+ onBlur?.();
3907
+ return;
3908
+ }
3909
+ setRangeEnd(date);
3910
+ setHoverDate(null);
3911
+ setSelectedMonth(date);
3912
+ onChange({ start: rangeStart, end: date });
3913
+ setIsOpen(false);
3914
+ onBlur?.();
3915
+ },
3916
+ [onBlur, onChange, rangeEnd, rangeStart]
3917
+ );
3918
+ const handleClear = React21.useCallback(
3919
+ (e) => {
3920
+ e.stopPropagation();
3921
+ setRangeStart(null);
3922
+ setRangeEnd(null);
3923
+ setHoverDate(null);
3924
+ setIsOpen(false);
3925
+ onChange({ start: null, end: null });
3926
+ inputRef.current?.focus();
3927
+ },
3928
+ [onChange]
3929
+ );
3930
+ const handleOpenChange = React21.useCallback(
3931
+ (nextOpen) => {
3932
+ if (disabled) {
3933
+ setIsOpen(false);
3934
+ return;
3935
+ }
3936
+ if (nextOpen) {
3937
+ if (!hasInteracted) {
3938
+ setHasInteracted(true);
3939
+ }
3940
+ setIsOpen(true);
3941
+ return;
3942
+ }
3943
+ if (isOpen && hasInteracted) {
3944
+ onBlur?.();
3945
+ }
3946
+ setHoverDate(null);
3947
+ setIsOpen(false);
3948
+ },
3949
+ [disabled, hasInteracted, isOpen, onBlur]
3950
+ );
3951
+ const handleInputBlur = React21.useCallback(() => {
3952
+ if (!isOpen) {
3953
+ onBlur?.();
3954
+ }
3955
+ }, [isOpen, onBlur]);
3956
+ const handleInputClick = React21.useCallback(() => {
3957
+ if (!hasInteracted) {
3958
+ setHasInteracted(true);
3959
+ }
3960
+ }, [hasInteracted]);
3961
+ const RangeDayButton = React21.useCallback(
3962
+ ({
3963
+ day,
3964
+ modifiers,
3965
+ className: dayClassName,
3966
+ children,
3967
+ onClick,
3968
+ onMouseEnter,
3969
+ onMouseLeave,
3970
+ ...rest
3971
+ }) => {
3972
+ const date = day.date;
3973
+ const isStart = isSameDay(date, rangeStart);
3974
+ const isEnd = isSameDay(date, rangeEnd);
3975
+ const isRangeEndpoint = isStart || isEnd;
3976
+ const isInCommittedRange = isDateInRange(date, rangeStart, rangeEnd);
3977
+ const isInHoverRange = !!rangeStart && !rangeEnd && !!hoverDate && isDateInRange(date, rangeStart, hoverDate);
3978
+ const isRangeHighlight = (isInCommittedRange || isInHoverRange) && !isRangeEndpoint;
3979
+ const isToday = isSameDay(date, /* @__PURE__ */ new Date());
3980
+ return /* @__PURE__ */ React21.createElement(
3981
+ "button",
3982
+ {
3983
+ type: "button",
3984
+ ...rest,
3985
+ className: cn(
3986
+ "flex items-center justify-center h-8 w-8 rounded-md border-none bg-transparent cursor-pointer text-sm transition-colors",
3987
+ "hover:bg-accent",
3988
+ isRangeEndpoint && "bg-primary text-primary-foreground font-semibold",
3989
+ isRangeHighlight && "bg-accent",
3990
+ !isRangeEndpoint && !isRangeHighlight && isToday && "border border-primary",
3991
+ modifiers.disabled && "cursor-not-allowed opacity-50 pointer-events-none",
3992
+ dayClassName
3993
+ ),
3994
+ onClick: (event) => {
3995
+ onClick?.(event);
3996
+ if (modifiers.disabled) return;
3997
+ handleDateSelect(date);
3998
+ },
3999
+ onMouseEnter: (event) => {
4000
+ onMouseEnter?.(event);
4001
+ if (modifiers.disabled) {
4002
+ setHoverDate(null);
4003
+ return;
4004
+ }
4005
+ setHoverDate(date);
4006
+ },
4007
+ onMouseLeave: (event) => {
4008
+ onMouseLeave?.(event);
4009
+ setHoverDate(null);
4010
+ }
4011
+ },
4012
+ children ?? date.getDate()
4013
+ );
4014
+ },
4015
+ [handleDateSelect, hoverDate, rangeEnd, rangeStart]
4016
+ );
4017
+ const hasValue = Boolean(rangeStart || rangeEnd);
4018
+ const selectedRange = rangeStart || rangeEnd ? {
4019
+ from: rangeStart ?? void 0,
4020
+ to: rangeEnd ?? void 0
4021
+ } : void 0;
4022
+ const displayValue = rangeStart && rangeEnd ? `${formatDate2(rangeStart, format)}${separator}${formatDate2(rangeEnd, format)}` : rangeStart ? formatDate2(rangeStart, format) : "";
4023
+ const combinedClassName = cn("relative", className);
4024
+ return /* @__PURE__ */ React21.createElement("div", { className: combinedClassName }, /* @__PURE__ */ React21.createElement(
4025
+ "input",
4026
+ {
4027
+ type: "hidden",
4028
+ name: `${name}[start]`,
4029
+ value: rangeStart ? rangeStart.toISOString() : ""
4030
+ }
4031
+ ), /* @__PURE__ */ React21.createElement(
4032
+ "input",
4033
+ {
4034
+ type: "hidden",
4035
+ name: `${name}[end]`,
4036
+ value: rangeEnd ? rangeEnd.toISOString() : ""
4037
+ }
4038
+ ), /* @__PURE__ */ React21.createElement(Popover, { open: isOpen, onOpenChange: handleOpenChange }, /* @__PURE__ */ React21.createElement("div", { className: "relative" }, showIcon && /* @__PURE__ */ React21.createElement(
4039
+ "span",
4040
+ {
4041
+ className: "absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none",
4042
+ "aria-hidden": "true"
4043
+ },
4044
+ /* @__PURE__ */ React21.createElement(
4045
+ "svg",
4046
+ {
4047
+ xmlns: "http://www.w3.org/2000/svg",
4048
+ width: "18",
4049
+ height: "18",
4050
+ viewBox: "0 0 24 24",
4051
+ fill: "none",
4052
+ stroke: "currentColor",
4053
+ strokeLinecap: "round",
4054
+ strokeLinejoin: "round",
4055
+ strokeWidth: "2"
4056
+ },
4057
+ /* @__PURE__ */ React21.createElement("path", { d: "M8 2v4m8-4v4m5 8V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8M3 10h18m-5 10l2 2l4-4" })
4058
+ )
4059
+ ), /* @__PURE__ */ React21.createElement(PopoverTrigger, { asChild: true }, /* @__PURE__ */ React21.createElement(
4060
+ "input",
4061
+ {
4062
+ ref: inputRef,
4063
+ id: props.id,
4064
+ type: "text",
4065
+ className: cn(
4066
+ "flex h-9 w-full rounded-md border border-input bg-transparent py-1 text-base shadow-sm transition-colors",
4067
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
4068
+ "disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
4069
+ INPUT_AUTOFILL_RESET_CLASSES,
4070
+ showIcon ? "pl-10" : "pl-3",
4071
+ clearable && (rangeStart || rangeEnd) ? "pr-10" : "pr-3",
4072
+ !error && hasValue && "ring-2 ring-ring",
4073
+ error && "border-destructive ring-1 ring-destructive"
4074
+ ),
4075
+ value: displayValue,
4076
+ onClick: handleInputClick,
4077
+ onBlur: handleInputBlur,
4078
+ disabled,
4079
+ required,
4080
+ placeholder,
4081
+ "aria-invalid": error || props["aria-invalid"] ? "true" : "false",
4082
+ "aria-describedby": props["aria-describedby"],
4083
+ "aria-required": required || props["aria-required"],
4084
+ readOnly: true
4085
+ }
4086
+ )), clearable && (rangeStart || rangeEnd) && !disabled && /* @__PURE__ */ React21.createElement(
4087
+ "button",
4088
+ {
4089
+ type: "button",
4090
+ className: "absolute right-3 top-1/2 -translate-y-1/2 transition-colors",
4091
+ onClick: handleClear,
4092
+ "aria-label": "Clear date range",
4093
+ tabIndex: -1
4094
+ },
4095
+ "\u2715"
4096
+ )), !disabled && /* @__PURE__ */ React21.createElement(
4097
+ PopoverContent,
4098
+ {
4099
+ align: "start",
4100
+ sideOffset: 4,
4101
+ className: "w-auto p-0",
4102
+ onOpenAutoFocus: (event) => {
4103
+ event.preventDefault();
4104
+ }
4105
+ },
4106
+ /* @__PURE__ */ React21.createElement(
4107
+ Calendar,
4108
+ {
4109
+ mode: "range",
4110
+ selected: selectedRange,
4111
+ month: selectedMonth,
4112
+ onMonthChange: setSelectedMonth,
4113
+ disabled: disabledMatchers,
4114
+ labels: {
4115
+ labelGrid: () => "Calendar",
4116
+ labelDayButton: (date) => formatDate2(date, format),
4117
+ labelPrevious: () => "Previous month",
4118
+ labelNext: () => "Next month"
4119
+ },
4120
+ components: {
4121
+ DayButton: RangeDayButton
4122
+ },
4123
+ classNames: {
4124
+ today: "border border-primary rounded-md bg-transparent"
4125
+ },
4126
+ showOutsideDays: true
4127
+ }
4128
+ ),
4129
+ rangeStart && !rangeEnd && /* @__PURE__ */ React21.createElement("div", { className: "border-t border-input px-3 py-2 text-center text-xs opacity-70" }, "Select end date")
4130
+ )));
4131
+ }
4132
+ DateRangePicker.displayName = "DateRangePicker";
4133
+
4134
+ export { Checkbox2 as Checkbox, CheckboxGroup, DatePicker, DateRangePicker, FileInput, MultiSelect, Radio, Select2 as Select, Switch2 as Switch, TextArea, TextInput, TimePicker };
4135
+ //# sourceMappingURL=chunk-5NT5T5XY.js.map
4136
+ //# sourceMappingURL=chunk-5NT5T5XY.js.map