@szum-tech/design-system 3.13.0 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/chunk-4IGU5SVP.js +54 -0
  2. package/dist/{chunk-XJZOANXX.cjs → chunk-6HX7ETL3.cjs} +1 -1
  3. package/dist/chunk-B4M7Q5KX.cjs +25 -0
  4. package/dist/{chunk-S2BCU6WG.cjs → chunk-CAIAZGSW.cjs} +5 -4
  5. package/dist/chunk-EOTQVIA5.js +993 -0
  6. package/dist/chunk-EZCWHR56.cjs +2355 -0
  7. package/dist/chunk-G24PWQKG.js +20 -0
  8. package/dist/chunk-GEDBA3JU.cjs +49 -0
  9. package/dist/chunk-IZU3KULT.js +41 -0
  10. package/dist/{chunk-KQ6QE7BT.cjs → chunk-MNDQXDV4.cjs} +4 -4
  11. package/dist/chunk-N4TYSZSU.cjs +44 -0
  12. package/dist/chunk-OZSNSRLV.cjs +1027 -0
  13. package/dist/{chunk-DL54DIMD.js → chunk-Q2MJKFIE.js} +2 -1
  14. package/dist/chunk-S3ZUFD6U.js +23 -0
  15. package/dist/{chunk-WXZE35FK.js → chunk-VK2FJ65F.js} +1 -1
  16. package/dist/chunk-VL3TPVSB.js +2299 -0
  17. package/dist/{chunk-E5TYGWGE.js → chunk-XKCLVPUC.js} +1 -1
  18. package/dist/{chunk-XIQUR62A.cjs → chunk-XY3ZNUWR.cjs} +32 -20
  19. package/dist/color-picker.types-GI7dq2Ig.d.cts +42 -0
  20. package/dist/color-picker.types-GI7dq2Ig.d.ts +42 -0
  21. package/dist/components/badge-overflow/index.cjs +3 -2
  22. package/dist/components/badge-overflow/index.js +2 -1
  23. package/dist/components/button/index.cjs +17 -14
  24. package/dist/components/button/index.d.cts +1 -0
  25. package/dist/components/button/index.d.ts +1 -0
  26. package/dist/components/button/index.js +16 -13
  27. package/dist/components/carousel/index.cjs +18 -15
  28. package/dist/components/carousel/index.d.cts +1 -0
  29. package/dist/components/carousel/index.d.ts +1 -0
  30. package/dist/components/carousel/index.js +16 -13
  31. package/dist/components/color-picker/index.cjs +88 -0
  32. package/dist/components/color-picker/index.d.cts +71 -0
  33. package/dist/components/color-picker/index.d.ts +71 -0
  34. package/dist/components/color-picker/index.js +39 -0
  35. package/dist/components/dialog/index.d.cts +1 -0
  36. package/dist/components/dialog/index.d.ts +1 -0
  37. package/dist/components/field/index.cjs +12 -12
  38. package/dist/components/field/index.js +2 -2
  39. package/dist/components/file-upload/index.cjs +60 -0
  40. package/dist/components/file-upload/index.d.cts +131 -0
  41. package/dist/components/file-upload/index.d.ts +131 -0
  42. package/dist/components/file-upload/index.js +3 -0
  43. package/dist/components/index.cjs +221 -94
  44. package/dist/components/index.d.cts +7 -1
  45. package/dist/components/index.d.ts +7 -1
  46. package/dist/components/index.js +16 -13
  47. package/dist/components/input/index.cjs +2 -2
  48. package/dist/components/input/index.js +1 -1
  49. package/dist/components/item/index.d.cts +1 -0
  50. package/dist/components/item/index.d.ts +1 -0
  51. package/dist/components/masonry/index.cjs +5 -4
  52. package/dist/components/masonry/index.js +3 -2
  53. package/dist/components/popover/index.cjs +35 -0
  54. package/dist/components/popover/index.d.cts +18 -0
  55. package/dist/components/popover/index.d.ts +18 -0
  56. package/dist/components/popover/index.js +2 -0
  57. package/dist/components/select/index.cjs +7 -7
  58. package/dist/components/select/index.d.cts +8 -14
  59. package/dist/components/select/index.d.ts +8 -14
  60. package/dist/components/select/index.js +1 -1
  61. package/dist/components/sheet/index.d.cts +2 -1
  62. package/dist/components/sheet/index.d.ts +2 -1
  63. package/dist/components/stepper/index.cjs +35 -32
  64. package/dist/components/stepper/index.d.cts +1 -0
  65. package/dist/components/stepper/index.d.ts +1 -0
  66. package/dist/components/stepper/index.js +16 -13
  67. package/dist/components/timeline/index.cjs +12 -11
  68. package/dist/components/timeline/index.d.cts +1 -0
  69. package/dist/components/timeline/index.d.ts +1 -0
  70. package/dist/components/timeline/index.js +3 -2
  71. package/dist/components/toaster/index.cjs +18 -15
  72. package/dist/components/toaster/index.js +16 -13
  73. package/dist/components/typing-text/index.d.cts +1 -0
  74. package/dist/components/typing-text/index.d.ts +1 -0
  75. package/dist/components/word-rotate/index.d.cts +1 -0
  76. package/dist/components/word-rotate/index.d.ts +1 -0
  77. package/dist/hooks/index.cjs +17 -8
  78. package/dist/hooks/index.d.cts +15 -4
  79. package/dist/hooks/index.d.ts +15 -4
  80. package/dist/hooks/index.js +2 -1
  81. package/dist/popover-trigger-Cf4PPj0z.d.cts +14 -0
  82. package/dist/popover-trigger-Cf4PPj0z.d.ts +14 -0
  83. package/dist/select-Bf6XQtt-.d.cts +12 -0
  84. package/dist/select-Bf6XQtt-.d.ts +12 -0
  85. package/package.json +1 -1
  86. package/dist/chunk-5AA4IE2T.cjs +0 -27
  87. package/dist/chunk-IDOJLUDL.cjs +0 -1065
  88. package/dist/chunk-SB5UG7OC.js +0 -41
  89. package/dist/chunk-U2HEQZMY.js +0 -1020
  90. package/dist/chunk-UGSNASZM.js +0 -25
  91. package/dist/{chunk-DTYX7CYN.cjs → chunk-GAZLMZBH.cjs} +1 -1
  92. package/dist/{chunk-3MH6P44N.js → chunk-K5QSMTKJ.js} +1 -1
@@ -0,0 +1,993 @@
1
+ import { useLazyRef, useAsRef } from './chunk-G24PWQKG.js';
2
+ import { cn } from './chunk-ZD2QRAOX.js';
3
+ import * as React3 from 'react';
4
+ import { Direction, Slot } from 'radix-ui';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+ import { cva } from 'class-variance-authority';
7
+ import { FileVideoIcon, FileAudioIcon, FileTextIcon, FileCodeIcon, FileArchiveIcon, FileCogIcon, FileIcon } from 'lucide-react';
8
+
9
+ // src/components/file-upload/file-upload.constants.ts
10
+ var ROOT_NAME = "FileUpload";
11
+ var DROPZONE_NAME = "FileUploadDropzone";
12
+ var TRIGGER_NAME = "FileUploadTrigger";
13
+ var LIST_NAME = "FileUploadList";
14
+ var ITEM_NAME = "FileUploadItem";
15
+ var ITEM_PREVIEW_NAME = "FileUploadItemPreview";
16
+ var ITEM_METADATA_NAME = "FileUploadItemMetadata";
17
+ var ITEM_PROGRESS_NAME = "FileUploadItemProgress";
18
+ var ITEM_DELETE_NAME = "FileUploadItemDelete";
19
+ var CLEAR_NAME = "FileUploadClear";
20
+
21
+ // src/components/file-upload/file-upload.context.tsx
22
+ var FileUploadContext = React3.createContext(null);
23
+ function useFileUploadContext(consumerName) {
24
+ const context = React3.useContext(FileUploadContext);
25
+ if (!context) {
26
+ throw new Error(`\`${consumerName}\` must be used within \`${ROOT_NAME}\``);
27
+ }
28
+ return context;
29
+ }
30
+ var FileUploadStoreContext = React3.createContext(null);
31
+ function useFileUploadStoreContext(consumerName) {
32
+ const context = React3.useContext(FileUploadStoreContext);
33
+ if (!context) {
34
+ throw new Error(`\`${consumerName}\` must be used within \`${ROOT_NAME}\``);
35
+ }
36
+ return context;
37
+ }
38
+ function useFileUploadStore(selector) {
39
+ const store = useFileUploadStoreContext("useFileUploadStore");
40
+ const lastValueRef = useLazyRef(() => null);
41
+ const getSnapshot = React3.useCallback(() => {
42
+ const state = store.getState();
43
+ const prevValue = lastValueRef.current;
44
+ if (prevValue && prevValue.state === state) {
45
+ return prevValue.value;
46
+ }
47
+ const nextValue = selector(state);
48
+ lastValueRef.current = { value: nextValue, state };
49
+ return nextValue;
50
+ }, [store, selector, lastValueRef]);
51
+ return React3.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
52
+ }
53
+ function createFileUploadStore(listeners, files, urlCache, propsRef, initialInvalid) {
54
+ let state = {
55
+ files,
56
+ dragOver: false,
57
+ invalid: initialInvalid
58
+ };
59
+ function reducer(state2, action) {
60
+ switch (action.type) {
61
+ case "ADD_FILES": {
62
+ for (const file of action.files) {
63
+ files.set(file, {
64
+ file,
65
+ progress: 0,
66
+ status: "idle"
67
+ });
68
+ }
69
+ if (propsRef.current.onValueChange) {
70
+ const fileList = Array.from(files.values()).map((fileState) => fileState.file);
71
+ propsRef.current.onValueChange(fileList);
72
+ }
73
+ return { ...state2, files };
74
+ }
75
+ case "SET_FILES": {
76
+ const newFileSet = new Set(action.files);
77
+ for (const existingFile of files.keys()) {
78
+ if (!newFileSet.has(existingFile)) {
79
+ files.delete(existingFile);
80
+ }
81
+ }
82
+ for (const file of action.files) {
83
+ const existingState = files.get(file);
84
+ if (!existingState) {
85
+ files.set(file, {
86
+ file,
87
+ progress: 0,
88
+ status: "idle"
89
+ });
90
+ }
91
+ }
92
+ return { ...state2, files };
93
+ }
94
+ case "SET_PROGRESS": {
95
+ const fileState = files.get(action.file);
96
+ if (fileState) {
97
+ files.set(action.file, {
98
+ ...fileState,
99
+ progress: action.progress,
100
+ status: "uploading"
101
+ });
102
+ }
103
+ return { ...state2, files };
104
+ }
105
+ case "SET_SUCCESS": {
106
+ const fileState = files.get(action.file);
107
+ if (fileState) {
108
+ files.set(action.file, {
109
+ ...fileState,
110
+ progress: 100,
111
+ status: "success"
112
+ });
113
+ }
114
+ return { ...state2, files };
115
+ }
116
+ case "SET_ERROR": {
117
+ const fileState = files.get(action.file);
118
+ if (fileState) {
119
+ files.set(action.file, {
120
+ ...fileState,
121
+ error: action.error,
122
+ status: "error"
123
+ });
124
+ }
125
+ return { ...state2, files };
126
+ }
127
+ case "REMOVE_FILE": {
128
+ const cachedUrl = urlCache.get(action.file);
129
+ if (cachedUrl) {
130
+ URL.revokeObjectURL(cachedUrl);
131
+ urlCache.delete(action.file);
132
+ }
133
+ files.delete(action.file);
134
+ if (propsRef.current.onValueChange) {
135
+ const fileList = Array.from(files.values()).map((fileState) => fileState.file);
136
+ propsRef.current.onValueChange(fileList);
137
+ }
138
+ return { ...state2, files };
139
+ }
140
+ case "SET_DRAG_OVER": {
141
+ return { ...state2, dragOver: action.dragOver };
142
+ }
143
+ case "SET_INVALID": {
144
+ return { ...state2, invalid: action.invalid };
145
+ }
146
+ case "CLEAR": {
147
+ for (const file of files.keys()) {
148
+ const cachedUrl = urlCache.get(file);
149
+ if (cachedUrl) {
150
+ URL.revokeObjectURL(cachedUrl);
151
+ urlCache.delete(file);
152
+ }
153
+ }
154
+ files.clear();
155
+ if (propsRef.current.onValueChange) {
156
+ propsRef.current.onValueChange([]);
157
+ }
158
+ return { ...state2, files, invalid: false };
159
+ }
160
+ default:
161
+ return state2;
162
+ }
163
+ }
164
+ return {
165
+ getState: () => state,
166
+ dispatch: (action) => {
167
+ state = reducer(state, action);
168
+ for (const listener of listeners) {
169
+ listener();
170
+ }
171
+ },
172
+ subscribe: (listener) => {
173
+ listeners.add(listener);
174
+ return () => listeners.delete(listener);
175
+ }
176
+ };
177
+ }
178
+ function FileUpload(props) {
179
+ const {
180
+ value,
181
+ defaultValue,
182
+ onValueChange,
183
+ onAccept,
184
+ onFileAccept,
185
+ onFileReject,
186
+ onFileValidate,
187
+ onUpload,
188
+ accept,
189
+ maxFiles,
190
+ maxSize,
191
+ dir: dirProp,
192
+ label,
193
+ name,
194
+ asChild,
195
+ disabled = false,
196
+ invalid = false,
197
+ multiple = false,
198
+ required = false,
199
+ children,
200
+ className,
201
+ ...rootProps
202
+ } = props;
203
+ const inputId = React3.useId();
204
+ const dropzoneId = React3.useId();
205
+ const listId = React3.useId();
206
+ const labelId = React3.useId();
207
+ const dir = Direction.useDirection(dirProp);
208
+ const listeners = useLazyRef(() => /* @__PURE__ */ new Set()).current;
209
+ const files = useLazyRef(() => /* @__PURE__ */ new Map()).current;
210
+ const urlCache = useLazyRef(() => /* @__PURE__ */ new WeakMap()).current;
211
+ const inputRef = React3.useRef(null);
212
+ const isControlled = value !== void 0;
213
+ const propsRef = useAsRef({
214
+ onValueChange,
215
+ onAccept,
216
+ onFileAccept,
217
+ onFileReject,
218
+ onFileValidate,
219
+ onUpload
220
+ });
221
+ const store = React3.useMemo(
222
+ () => createFileUploadStore(listeners, files, urlCache, propsRef, invalid),
223
+ // eslint-disable-next-line react-hooks/exhaustive-deps
224
+ [listeners, files, urlCache, propsRef]
225
+ );
226
+ React3.useEffect(() => {
227
+ store.dispatch({ type: "SET_INVALID", invalid });
228
+ }, [store, invalid]);
229
+ const acceptTypes = React3.useMemo(() => accept?.split(",").map((t) => t.trim()) ?? null, [accept]);
230
+ const onProgress = useLazyRef(() => {
231
+ let frame = 0;
232
+ return (file, progress) => {
233
+ if (frame) return;
234
+ frame = requestAnimationFrame(() => {
235
+ frame = 0;
236
+ store.dispatch({
237
+ type: "SET_PROGRESS",
238
+ file,
239
+ progress: Math.min(Math.max(0, progress), 100)
240
+ });
241
+ });
242
+ };
243
+ }).current;
244
+ React3.useEffect(() => {
245
+ if (isControlled) {
246
+ store.dispatch({ type: "SET_FILES", files: value });
247
+ } else if (defaultValue && defaultValue.length > 0 && !store.getState().files.size) {
248
+ store.dispatch({ type: "SET_FILES", files: defaultValue });
249
+ }
250
+ }, [value, defaultValue, isControlled, store]);
251
+ React3.useEffect(() => {
252
+ return () => {
253
+ for (const file of files.keys()) {
254
+ const cachedUrl = urlCache.get(file);
255
+ if (cachedUrl) {
256
+ URL.revokeObjectURL(cachedUrl);
257
+ }
258
+ }
259
+ };
260
+ }, [files, urlCache]);
261
+ const onFilesUpload = React3.useCallback(
262
+ async (filesToUpload) => {
263
+ try {
264
+ for (const file of filesToUpload) {
265
+ store.dispatch({ type: "SET_PROGRESS", file, progress: 0 });
266
+ }
267
+ if (propsRef.current.onUpload) {
268
+ await propsRef.current.onUpload(filesToUpload, {
269
+ onProgress,
270
+ onSuccess: (file) => {
271
+ store.dispatch({ type: "SET_SUCCESS", file });
272
+ },
273
+ onError: (file, error) => {
274
+ store.dispatch({
275
+ type: "SET_ERROR",
276
+ file,
277
+ error: error.message ?? "Upload failed"
278
+ });
279
+ }
280
+ });
281
+ } else {
282
+ for (const file of filesToUpload) {
283
+ store.dispatch({ type: "SET_SUCCESS", file });
284
+ }
285
+ }
286
+ } catch (error) {
287
+ const errorMessage = error instanceof Error ? error.message : "Upload failed";
288
+ for (const file of filesToUpload) {
289
+ store.dispatch({
290
+ type: "SET_ERROR",
291
+ file,
292
+ error: errorMessage
293
+ });
294
+ }
295
+ }
296
+ },
297
+ [store, propsRef, onProgress]
298
+ );
299
+ const onFilesChange = React3.useCallback(
300
+ (originalFiles) => {
301
+ if (disabled) return;
302
+ let filesToProcess = [...originalFiles];
303
+ let hasInvalid = false;
304
+ if (maxFiles) {
305
+ const currentCount = store.getState().files.size;
306
+ const remainingSlotCount = Math.max(0, maxFiles - currentCount);
307
+ if (remainingSlotCount < filesToProcess.length) {
308
+ const rejectedFiles = filesToProcess.slice(remainingSlotCount);
309
+ hasInvalid = true;
310
+ filesToProcess = filesToProcess.slice(0, remainingSlotCount);
311
+ for (const file of rejectedFiles) {
312
+ let rejectionMessage = `Maximum ${maxFiles} files allowed`;
313
+ if (propsRef.current.onFileValidate) {
314
+ const validationMessage = propsRef.current.onFileValidate(file);
315
+ if (validationMessage) {
316
+ rejectionMessage = validationMessage;
317
+ }
318
+ }
319
+ propsRef.current.onFileReject?.(file, rejectionMessage);
320
+ }
321
+ }
322
+ }
323
+ const acceptedFiles = [];
324
+ for (const file of filesToProcess) {
325
+ let rejected = false;
326
+ let rejectionMessage = "";
327
+ if (propsRef.current.onFileValidate) {
328
+ const validationMessage = propsRef.current.onFileValidate(file);
329
+ if (validationMessage) {
330
+ rejectionMessage = validationMessage;
331
+ propsRef.current.onFileReject?.(file, rejectionMessage);
332
+ rejected = true;
333
+ hasInvalid = true;
334
+ continue;
335
+ }
336
+ }
337
+ if (acceptTypes) {
338
+ const fileType = file.type;
339
+ const fileExtension = `.${file.name.split(".").pop()}`;
340
+ if (!acceptTypes.some(
341
+ (type) => type === fileType || type === fileExtension || type.includes("/*") && fileType.startsWith(type.replace("/*", "/"))
342
+ )) {
343
+ rejectionMessage = "File type not accepted";
344
+ propsRef.current.onFileReject?.(file, rejectionMessage);
345
+ rejected = true;
346
+ hasInvalid = true;
347
+ }
348
+ }
349
+ if (maxSize && file.size > maxSize) {
350
+ rejectionMessage = "File too large";
351
+ propsRef.current.onFileReject?.(file, rejectionMessage);
352
+ rejected = true;
353
+ hasInvalid = true;
354
+ }
355
+ if (!rejected) {
356
+ acceptedFiles.push(file);
357
+ }
358
+ }
359
+ if (hasInvalid) {
360
+ store.dispatch({ type: "SET_INVALID", invalid: true });
361
+ setTimeout(() => {
362
+ store.dispatch({ type: "SET_INVALID", invalid: false });
363
+ }, 2e3);
364
+ }
365
+ if (acceptedFiles.length > 0) {
366
+ store.dispatch({ type: "ADD_FILES", files: acceptedFiles });
367
+ if (isControlled && propsRef.current.onValueChange) {
368
+ const currentFiles = Array.from(store.getState().files.values()).map((f) => f.file);
369
+ propsRef.current.onValueChange([...currentFiles]);
370
+ }
371
+ if (propsRef.current.onAccept) {
372
+ propsRef.current.onAccept(acceptedFiles);
373
+ }
374
+ for (const file of acceptedFiles) {
375
+ propsRef.current.onFileAccept?.(file);
376
+ }
377
+ if (propsRef.current.onUpload) {
378
+ requestAnimationFrame(() => {
379
+ void onFilesUpload(acceptedFiles);
380
+ });
381
+ }
382
+ }
383
+ },
384
+ [store, isControlled, propsRef, onFilesUpload, maxFiles, acceptTypes, maxSize, disabled]
385
+ );
386
+ const onInputChange = React3.useCallback(
387
+ (event) => {
388
+ const changedFiles = Array.from(event.target.files ?? []);
389
+ onFilesChange(changedFiles);
390
+ event.target.value = "";
391
+ },
392
+ [onFilesChange]
393
+ );
394
+ const contextValue = React3.useMemo(
395
+ () => ({
396
+ dropzoneId,
397
+ inputId,
398
+ listId,
399
+ labelId,
400
+ dir,
401
+ disabled,
402
+ inputRef,
403
+ urlCache
404
+ }),
405
+ [dropzoneId, inputId, listId, labelId, dir, disabled, urlCache]
406
+ );
407
+ const RootPrimitive = asChild ? Slot.Slot : "div";
408
+ return /* @__PURE__ */ jsx(FileUploadStoreContext.Provider, { value: store, children: /* @__PURE__ */ jsx(FileUploadContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
409
+ RootPrimitive,
410
+ {
411
+ "data-disabled": disabled ? "" : void 0,
412
+ "data-slot": "file-upload",
413
+ dir,
414
+ ...rootProps,
415
+ className: cn("relative flex flex-col gap-2", className),
416
+ children: [
417
+ children,
418
+ /* @__PURE__ */ jsx(
419
+ "input",
420
+ {
421
+ type: "file",
422
+ id: inputId,
423
+ "aria-labelledby": labelId,
424
+ "aria-describedby": dropzoneId,
425
+ ref: inputRef,
426
+ tabIndex: -1,
427
+ accept,
428
+ name,
429
+ className: "sr-only",
430
+ disabled,
431
+ multiple,
432
+ required,
433
+ onChange: onInputChange
434
+ }
435
+ ),
436
+ /* @__PURE__ */ jsx("div", { id: labelId, className: "sr-only", children: label ?? "File upload" })
437
+ ]
438
+ }
439
+ ) }) });
440
+ }
441
+ function FileUploadDropzone(props) {
442
+ const {
443
+ asChild,
444
+ className,
445
+ onClick: onClickProp,
446
+ onDragOver: onDragOverProp,
447
+ onDragEnter: onDragEnterProp,
448
+ onDragLeave: onDragLeaveProp,
449
+ onDrop: onDropProp,
450
+ onPaste: onPasteProp,
451
+ onKeyDown: onKeyDownProp,
452
+ ...dropzoneProps
453
+ } = props;
454
+ const context = useFileUploadContext(DROPZONE_NAME);
455
+ const store = useFileUploadStoreContext(DROPZONE_NAME);
456
+ const dragOver = useFileUploadStore((state) => state.dragOver);
457
+ const invalid = useFileUploadStore((state) => state.invalid);
458
+ const propsRef = useAsRef({
459
+ onClick: onClickProp,
460
+ onDragOver: onDragOverProp,
461
+ onDragEnter: onDragEnterProp,
462
+ onDragLeave: onDragLeaveProp,
463
+ onDrop: onDropProp,
464
+ onPaste: onPasteProp,
465
+ onKeyDown: onKeyDownProp
466
+ });
467
+ const onClick = React3.useCallback(
468
+ (event) => {
469
+ propsRef.current.onClick?.(event);
470
+ if (event.defaultPrevented) return;
471
+ const target = event.target;
472
+ const isFromTrigger = target instanceof HTMLElement && target.closest('[data-slot="file-upload-trigger"]');
473
+ if (!isFromTrigger) {
474
+ context.inputRef.current?.click();
475
+ }
476
+ },
477
+ [context.inputRef, propsRef]
478
+ );
479
+ const onDragOver = React3.useCallback(
480
+ (event) => {
481
+ propsRef.current.onDragOver?.(event);
482
+ if (event.defaultPrevented) return;
483
+ event.preventDefault();
484
+ store.dispatch({ type: "SET_DRAG_OVER", dragOver: true });
485
+ },
486
+ [store, propsRef]
487
+ );
488
+ const onDragEnter = React3.useCallback(
489
+ (event) => {
490
+ propsRef.current.onDragEnter?.(event);
491
+ if (event.defaultPrevented) return;
492
+ event.preventDefault();
493
+ store.dispatch({ type: "SET_DRAG_OVER", dragOver: true });
494
+ },
495
+ [store, propsRef]
496
+ );
497
+ const onDragLeave = React3.useCallback(
498
+ (event) => {
499
+ propsRef.current.onDragLeave?.(event);
500
+ if (event.defaultPrevented) return;
501
+ const relatedTarget = event.relatedTarget;
502
+ if (relatedTarget && relatedTarget instanceof Node && event.currentTarget.contains(relatedTarget)) {
503
+ return;
504
+ }
505
+ event.preventDefault();
506
+ store.dispatch({ type: "SET_DRAG_OVER", dragOver: false });
507
+ },
508
+ [store, propsRef]
509
+ );
510
+ const onDrop = React3.useCallback(
511
+ (event) => {
512
+ propsRef.current.onDrop?.(event);
513
+ if (event.defaultPrevented) return;
514
+ event.preventDefault();
515
+ store.dispatch({ type: "SET_DRAG_OVER", dragOver: false });
516
+ const droppedFiles = Array.from(event.dataTransfer.files);
517
+ const inputElement = context.inputRef.current;
518
+ if (!inputElement) return;
519
+ const dataTransfer = new DataTransfer();
520
+ for (const file of droppedFiles) {
521
+ dataTransfer.items.add(file);
522
+ }
523
+ inputElement.files = dataTransfer.files;
524
+ inputElement.dispatchEvent(new Event("change", { bubbles: true }));
525
+ },
526
+ [store, context.inputRef, propsRef]
527
+ );
528
+ const onPaste = React3.useCallback(
529
+ (event) => {
530
+ propsRef.current.onPaste?.(event);
531
+ if (event.defaultPrevented) return;
532
+ event.preventDefault();
533
+ store.dispatch({ type: "SET_DRAG_OVER", dragOver: false });
534
+ const items = event.clipboardData?.items;
535
+ if (!items) return;
536
+ const pastedFiles = [];
537
+ for (let i = 0; i < items.length; i++) {
538
+ const item = items[i];
539
+ if (item?.kind === "file") {
540
+ const file = item.getAsFile();
541
+ if (file) {
542
+ pastedFiles.push(file);
543
+ }
544
+ }
545
+ }
546
+ if (pastedFiles.length === 0) return;
547
+ const inputElement = context.inputRef.current;
548
+ if (!inputElement) return;
549
+ const dataTransfer = new DataTransfer();
550
+ for (const file of pastedFiles) {
551
+ dataTransfer.items.add(file);
552
+ }
553
+ inputElement.files = dataTransfer.files;
554
+ inputElement.dispatchEvent(new Event("change", { bubbles: true }));
555
+ },
556
+ [store, context.inputRef, propsRef]
557
+ );
558
+ const onKeyDown = React3.useCallback(
559
+ (event) => {
560
+ propsRef.current.onKeyDown?.(event);
561
+ if (!event.defaultPrevented && (event.key === "Enter" || event.key === " ")) {
562
+ event.preventDefault();
563
+ context.inputRef.current?.click();
564
+ }
565
+ },
566
+ [context.inputRef, propsRef]
567
+ );
568
+ const DropzonePrimitive = asChild ? Slot.Slot : "div";
569
+ return /* @__PURE__ */ jsx(
570
+ DropzonePrimitive,
571
+ {
572
+ role: "region",
573
+ id: context.dropzoneId,
574
+ "aria-controls": `${context.inputId} ${context.listId}`,
575
+ "aria-disabled": context.disabled,
576
+ "aria-invalid": invalid,
577
+ "data-disabled": context.disabled ? "" : void 0,
578
+ "data-dragging": dragOver ? "" : void 0,
579
+ "data-invalid": invalid ? "" : void 0,
580
+ "data-slot": "file-upload-dropzone",
581
+ dir: context.dir,
582
+ tabIndex: context.disabled ? void 0 : 0,
583
+ ...dropzoneProps,
584
+ className: cn(
585
+ "hover:bg-accent/30 focus-visible:border-ring/50 data-dragging:border-primary/30 data-invalid:border-error data-dragging:bg-accent/30 data-invalid:ring-error/20 relative flex flex-col items-center justify-center gap-2 rounded border-2 border-dashed p-6 transition-colors outline-none select-none data-disabled:pointer-events-none",
586
+ className
587
+ ),
588
+ onClick,
589
+ onDragEnter,
590
+ onDragLeave,
591
+ onDragOver,
592
+ onDrop,
593
+ onKeyDown,
594
+ onPaste
595
+ }
596
+ );
597
+ }
598
+ function FileUploadTrigger(props) {
599
+ const { asChild, onClick: onClickProp, ...triggerProps } = props;
600
+ const context = useFileUploadContext(TRIGGER_NAME);
601
+ const propsRef = useAsRef({
602
+ onClick: onClickProp
603
+ });
604
+ const onClick = React3.useCallback(
605
+ (event) => {
606
+ propsRef.current.onClick?.(event);
607
+ if (event.defaultPrevented) return;
608
+ context.inputRef.current?.click();
609
+ },
610
+ [context.inputRef, propsRef]
611
+ );
612
+ const TriggerPrimitive = asChild ? Slot.Slot : "button";
613
+ return /* @__PURE__ */ jsx(
614
+ TriggerPrimitive,
615
+ {
616
+ type: "button",
617
+ "aria-controls": context.inputId,
618
+ "data-disabled": context.disabled ? "" : void 0,
619
+ "data-slot": "file-upload-trigger",
620
+ ...triggerProps,
621
+ disabled: context.disabled,
622
+ onClick
623
+ }
624
+ );
625
+ }
626
+ var fileUploadListVariants = cva(
627
+ "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 data-[state=active]:animate-in data-[state=inactive]:animate-out flex flex-col gap-2",
628
+ {
629
+ variants: {
630
+ orientation: {
631
+ vertical: "",
632
+ horizontal: "flex-row overflow-x-auto p-1.5"
633
+ }
634
+ },
635
+ defaultVariants: {
636
+ orientation: "vertical"
637
+ }
638
+ }
639
+ );
640
+ function FileUploadList(props) {
641
+ const { className, orientation = "vertical", asChild, forceMount, ...listProps } = props;
642
+ const context = useFileUploadContext(LIST_NAME);
643
+ const fileCount = useFileUploadStore((state) => state.files.size);
644
+ const shouldRender = forceMount || fileCount > 0;
645
+ if (!shouldRender) return null;
646
+ const ListPrimitive = asChild ? Slot.Slot : "div";
647
+ return /* @__PURE__ */ jsx(
648
+ ListPrimitive,
649
+ {
650
+ role: "list",
651
+ id: context.listId,
652
+ "aria-orientation": orientation,
653
+ "data-orientation": orientation,
654
+ "data-slot": "file-upload-list",
655
+ "data-state": shouldRender ? "active" : "inactive",
656
+ dir: context.dir,
657
+ ...listProps,
658
+ className: cn(fileUploadListVariants({ orientation }), className)
659
+ }
660
+ );
661
+ }
662
+ var FileUploadItemContext = React3.createContext(null);
663
+ function useFileUploadItemContext(consumerName) {
664
+ const context = React3.useContext(FileUploadItemContext);
665
+ if (!context) {
666
+ throw new Error(`\`${consumerName}\` must be used within \`${ITEM_NAME}\``);
667
+ }
668
+ return context;
669
+ }
670
+ function FileUploadItem(props) {
671
+ const { value, asChild, className, ...itemProps } = props;
672
+ const id = React3.useId();
673
+ const statusId = `${id}-status`;
674
+ const nameId = `${id}-name`;
675
+ const sizeId = `${id}-size`;
676
+ const messageId = `${id}-message`;
677
+ const context = useFileUploadContext(ITEM_NAME);
678
+ const fileState = useFileUploadStore((state) => state.files.get(value));
679
+ const fileCount = useFileUploadStore((state) => state.files.size);
680
+ const fileIndex = useFileUploadStore((state) => {
681
+ const keys = Array.from(state.files.keys());
682
+ return keys.indexOf(value) + 1;
683
+ });
684
+ const itemContext = React3.useMemo(
685
+ () => ({
686
+ id,
687
+ fileState,
688
+ nameId,
689
+ sizeId,
690
+ statusId,
691
+ messageId
692
+ }),
693
+ [id, fileState, statusId, nameId, sizeId, messageId]
694
+ );
695
+ if (!fileState) return null;
696
+ const statusText = fileState.error ? `Error: ${fileState.error}` : fileState.status === "uploading" ? `Uploading: ${fileState.progress}% complete` : fileState.status === "success" ? "Upload complete" : "Ready to upload";
697
+ const ItemPrimitive = asChild ? Slot.Slot : "div";
698
+ return /* @__PURE__ */ jsx(FileUploadItemContext.Provider, { value: itemContext, children: /* @__PURE__ */ jsxs(
699
+ ItemPrimitive,
700
+ {
701
+ role: "listitem",
702
+ id,
703
+ "aria-setsize": fileCount,
704
+ "aria-posinset": fileIndex,
705
+ "aria-describedby": `${nameId} ${sizeId} ${statusId} ${fileState.error ? messageId : ""}`,
706
+ "aria-labelledby": nameId,
707
+ "data-slot": "file-upload-item",
708
+ dir: context.dir,
709
+ ...itemProps,
710
+ className: cn("relative flex items-center gap-2.5 rounded border p-3", className),
711
+ children: [
712
+ props.children,
713
+ /* @__PURE__ */ jsx("span", { id: statusId, className: "sr-only", children: statusText })
714
+ ]
715
+ }
716
+ ) });
717
+ }
718
+ function formatBytes(bytes) {
719
+ if (bytes === 0) return "0 B";
720
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
721
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
722
+ return `${(bytes / 1024 ** i).toFixed(i ? 1 : 0)} ${sizes[i]}`;
723
+ }
724
+ function getFileIcon(file) {
725
+ const type = file.type;
726
+ const extension = file.name.split(".").pop()?.toLowerCase() ?? "";
727
+ if (type.startsWith("video/")) {
728
+ return /* @__PURE__ */ jsx(FileVideoIcon, {});
729
+ }
730
+ if (type.startsWith("audio/")) {
731
+ return /* @__PURE__ */ jsx(FileAudioIcon, {});
732
+ }
733
+ if (type.startsWith("text/") || ["txt", "md", "rtf", "pdf"].includes(extension)) {
734
+ return /* @__PURE__ */ jsx(FileTextIcon, {});
735
+ }
736
+ if (["html", "css", "js", "jsx", "ts", "tsx", "json", "xml", "php", "py", "rb", "java", "c", "cpp", "cs"].includes(
737
+ extension
738
+ )) {
739
+ return /* @__PURE__ */ jsx(FileCodeIcon, {});
740
+ }
741
+ if (["zip", "rar", "7z", "tar", "gz", "bz2"].includes(extension)) {
742
+ return /* @__PURE__ */ jsx(FileArchiveIcon, {});
743
+ }
744
+ if (["exe", "msi", "app", "apk", "deb", "rpm"].includes(extension) || type.startsWith("application/")) {
745
+ return /* @__PURE__ */ jsx(FileCogIcon, {});
746
+ }
747
+ return /* @__PURE__ */ jsx(FileIcon, {});
748
+ }
749
+ function FileUploadItemPreview(props) {
750
+ const { render, asChild, children, className, ...previewProps } = props;
751
+ const itemContext = useFileUploadItemContext(ITEM_PREVIEW_NAME);
752
+ const context = useFileUploadContext(ITEM_PREVIEW_NAME);
753
+ const getDefaultRender = React3.useCallback(
754
+ (file) => {
755
+ if (itemContext.fileState?.file.type.startsWith("image/")) {
756
+ let url = context.urlCache.get(file);
757
+ if (!url) {
758
+ url = URL.createObjectURL(file);
759
+ context.urlCache.set(file, url);
760
+ }
761
+ return (
762
+ // biome-ignore lint/performance/noImgElement: dynamic file URLs from user uploads don't work well with Next.js Image optimization
763
+ /* @__PURE__ */ jsx("img", { src: url, alt: file.name, className: "size-full object-cover" })
764
+ );
765
+ }
766
+ return getFileIcon(file);
767
+ },
768
+ [itemContext.fileState?.file.type, context.urlCache]
769
+ );
770
+ const onPreviewRender = React3.useCallback(
771
+ (file) => {
772
+ if (render) {
773
+ return render(file, () => getDefaultRender(file));
774
+ }
775
+ return getDefaultRender(file);
776
+ },
777
+ [render, getDefaultRender]
778
+ );
779
+ if (!itemContext.fileState) return null;
780
+ const ItemPreviewPrimitive = asChild ? Slot.Slot : "div";
781
+ return /* @__PURE__ */ jsxs(
782
+ ItemPreviewPrimitive,
783
+ {
784
+ "aria-labelledby": itemContext.nameId,
785
+ "data-slot": "file-upload-preview",
786
+ ...previewProps,
787
+ className: cn(
788
+ "bg-accent/50 relative flex size-10 shrink-0 items-center justify-center overflow-hidden rounded border [&>svg]:size-10",
789
+ className
790
+ ),
791
+ children: [
792
+ onPreviewRender(itemContext.fileState.file),
793
+ children
794
+ ]
795
+ }
796
+ );
797
+ }
798
+ function FileUploadItemMetadata(props) {
799
+ const { asChild, size = "default", children, className, ...metadataProps } = props;
800
+ const context = useFileUploadContext(ITEM_METADATA_NAME);
801
+ const itemContext = useFileUploadItemContext(ITEM_METADATA_NAME);
802
+ if (!itemContext.fileState) return null;
803
+ const ItemMetadataPrimitive = asChild ? Slot.Slot : "div";
804
+ return /* @__PURE__ */ jsx(
805
+ ItemMetadataPrimitive,
806
+ {
807
+ "data-slot": "file-upload-metadata",
808
+ dir: context.dir,
809
+ ...metadataProps,
810
+ className: cn("flex min-w-0 flex-1 flex-col", className),
811
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
812
+ /* @__PURE__ */ jsx(
813
+ "span",
814
+ {
815
+ id: itemContext.nameId,
816
+ className: cn("truncate text-sm font-medium", size === "sm" && "text-[13px] leading-snug font-normal"),
817
+ children: itemContext.fileState.file.name
818
+ }
819
+ ),
820
+ /* @__PURE__ */ jsx(
821
+ "span",
822
+ {
823
+ id: itemContext.sizeId,
824
+ className: cn("text-muted-foreground truncate text-xs", size === "sm" && "text-[11px] leading-snug"),
825
+ children: formatBytes(itemContext.fileState.file.size)
826
+ }
827
+ ),
828
+ itemContext.fileState.error && /* @__PURE__ */ jsx("span", { id: itemContext.messageId, className: "text-destructive text-xs", children: itemContext.fileState.error })
829
+ ] })
830
+ }
831
+ );
832
+ }
833
+ function FileUploadItemProgress(props) {
834
+ const { variant = "linear", size = 40, asChild, forceMount, className, ...progressProps } = props;
835
+ const itemContext = useFileUploadItemContext(ITEM_PROGRESS_NAME);
836
+ if (!itemContext.fileState) return null;
837
+ const shouldRender = forceMount || itemContext.fileState.progress !== 100;
838
+ if (!shouldRender) return null;
839
+ const ItemProgressPrimitive = asChild ? Slot.Slot : "div";
840
+ switch (variant) {
841
+ case "circular": {
842
+ const circumference = 2 * Math.PI * ((size - 4) / 2);
843
+ const strokeDashoffset = circumference - itemContext.fileState.progress / 100 * circumference;
844
+ return /* @__PURE__ */ jsx(
845
+ ItemProgressPrimitive,
846
+ {
847
+ role: "progressbar",
848
+ "aria-valuemin": 0,
849
+ "aria-valuemax": 100,
850
+ "aria-valuenow": itemContext.fileState.progress,
851
+ "aria-valuetext": `${itemContext.fileState.progress}%`,
852
+ "aria-labelledby": itemContext.nameId,
853
+ "data-slot": "file-upload-progress",
854
+ ...progressProps,
855
+ className: cn("absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2", className),
856
+ children: /* @__PURE__ */ jsxs(
857
+ "svg",
858
+ {
859
+ className: "-rotate-90 transform",
860
+ width: size,
861
+ height: size,
862
+ viewBox: `0 0 ${size} ${size}`,
863
+ fill: "none",
864
+ stroke: "currentColor",
865
+ children: [
866
+ /* @__PURE__ */ jsx("circle", { className: "text-primary/20", strokeWidth: "2", cx: size / 2, cy: size / 2, r: (size - 4) / 2 }),
867
+ /* @__PURE__ */ jsx(
868
+ "circle",
869
+ {
870
+ className: "text-primary transition-[stroke-dashoffset] duration-300 ease-linear",
871
+ strokeWidth: "2",
872
+ strokeLinecap: "round",
873
+ strokeDasharray: circumference,
874
+ strokeDashoffset,
875
+ cx: size / 2,
876
+ cy: size / 2,
877
+ r: (size - 4) / 2
878
+ }
879
+ )
880
+ ]
881
+ }
882
+ )
883
+ }
884
+ );
885
+ }
886
+ case "fill": {
887
+ const progressPercentage = itemContext.fileState.progress;
888
+ const topInset = 100 - progressPercentage;
889
+ return /* @__PURE__ */ jsx(
890
+ ItemProgressPrimitive,
891
+ {
892
+ role: "progressbar",
893
+ "aria-valuemin": 0,
894
+ "aria-valuemax": 100,
895
+ "aria-valuenow": progressPercentage,
896
+ "aria-valuetext": `${progressPercentage}%`,
897
+ "aria-labelledby": itemContext.nameId,
898
+ "data-slot": "file-upload-progress",
899
+ ...progressProps,
900
+ className: cn("bg-primary/50 absolute inset-0 transition-[clip-path] duration-300 ease-linear", className),
901
+ style: {
902
+ clipPath: `inset(${topInset}% 0% 0% 0%)`
903
+ }
904
+ }
905
+ );
906
+ }
907
+ default:
908
+ return /* @__PURE__ */ jsx(
909
+ ItemProgressPrimitive,
910
+ {
911
+ role: "progressbar",
912
+ "aria-valuemin": 0,
913
+ "aria-valuemax": 100,
914
+ "aria-valuenow": itemContext.fileState.progress,
915
+ "aria-valuetext": `${itemContext.fileState.progress}%`,
916
+ "aria-labelledby": itemContext.nameId,
917
+ "data-slot": "file-upload-progress",
918
+ ...progressProps,
919
+ className: cn("bg-primary/20 relative h-1.5 w-full overflow-hidden rounded-full", className),
920
+ children: /* @__PURE__ */ jsx(
921
+ "div",
922
+ {
923
+ className: "bg-primary h-full w-full flex-1 transition-transform duration-300 ease-linear",
924
+ style: {
925
+ transform: `translateX(-${100 - itemContext.fileState.progress}%)`
926
+ }
927
+ }
928
+ )
929
+ }
930
+ );
931
+ }
932
+ }
933
+ function FileUploadItemDelete(props) {
934
+ const { asChild, onClick: onClickProp, ...deleteProps } = props;
935
+ const store = useFileUploadStoreContext(ITEM_DELETE_NAME);
936
+ const itemContext = useFileUploadItemContext(ITEM_DELETE_NAME);
937
+ const onClick = React3.useCallback(
938
+ (event) => {
939
+ onClickProp?.(event);
940
+ if (!itemContext.fileState || event.defaultPrevented) return;
941
+ store.dispatch({
942
+ type: "REMOVE_FILE",
943
+ file: itemContext.fileState.file
944
+ });
945
+ },
946
+ [store, itemContext.fileState, onClickProp]
947
+ );
948
+ if (!itemContext.fileState) return null;
949
+ const ItemDeletePrimitive = asChild ? Slot.Slot : "button";
950
+ return /* @__PURE__ */ jsx(
951
+ ItemDeletePrimitive,
952
+ {
953
+ type: "button",
954
+ "aria-controls": itemContext.id,
955
+ "aria-describedby": itemContext.nameId,
956
+ "data-slot": "file-upload-item-delete",
957
+ ...deleteProps,
958
+ onClick
959
+ }
960
+ );
961
+ }
962
+ function FileUploadClear(props) {
963
+ const { asChild, forceMount, disabled, onClick: onClickProp, ...clearProps } = props;
964
+ const context = useFileUploadContext(CLEAR_NAME);
965
+ const store = useFileUploadStoreContext(CLEAR_NAME);
966
+ const fileCount = useFileUploadStore((state) => state.files.size);
967
+ const isDisabled = disabled || context.disabled;
968
+ const onClick = React3.useCallback(
969
+ (event) => {
970
+ onClickProp?.(event);
971
+ if (event.defaultPrevented) return;
972
+ store.dispatch({ type: "CLEAR" });
973
+ },
974
+ [store, onClickProp]
975
+ );
976
+ const shouldRender = forceMount || fileCount > 0;
977
+ if (!shouldRender) return null;
978
+ const ClearPrimitive = asChild ? Slot.Slot : "button";
979
+ return /* @__PURE__ */ jsx(
980
+ ClearPrimitive,
981
+ {
982
+ type: "button",
983
+ "aria-controls": context.listId,
984
+ "data-slot": "file-upload-clear",
985
+ "data-disabled": isDisabled ? "" : void 0,
986
+ ...clearProps,
987
+ disabled: isDisabled,
988
+ onClick
989
+ }
990
+ );
991
+ }
992
+
993
+ export { FileUpload, FileUploadClear, FileUploadDropzone, FileUploadItem, FileUploadItemDelete, FileUploadItemMetadata, FileUploadItemPreview, FileUploadItemProgress, FileUploadList, FileUploadTrigger, useFileUploadContext, useFileUploadItemContext, useFileUploadStore };