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