@page-speed/forms 0.5.2 → 0.5.4

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