@spark-ui/components 11.2.5 → 11.3.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.
- package/dist/chunk-TKAU6SMC.mjs +197 -0
- package/dist/chunk-TKAU6SMC.mjs.map +1 -0
- package/dist/docgen.json +1800 -6
- package/dist/file-upload/index.d.mts +283 -0
- package/dist/file-upload/index.d.ts +283 -0
- package/dist/file-upload/index.js +2042 -0
- package/dist/file-upload/index.js.map +1 -0
- package/dist/file-upload/index.mjs +828 -0
- package/dist/file-upload/index.mjs.map +1 -0
- package/dist/progress/index.d.mts +2 -2
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/index.mjs +4 -193
- package/dist/progress/index.mjs.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Progress
|
|
3
|
+
} from "../chunk-TKAU6SMC.mjs";
|
|
4
|
+
import {
|
|
5
|
+
IconButton
|
|
6
|
+
} from "../chunk-XYK6V3JF.mjs";
|
|
7
|
+
import {
|
|
8
|
+
Icon
|
|
9
|
+
} from "../chunk-UMUMFMFB.mjs";
|
|
10
|
+
import {
|
|
11
|
+
Button
|
|
12
|
+
} from "../chunk-HEKSVWYW.mjs";
|
|
13
|
+
import "../chunk-GAK4SC2F.mjs";
|
|
14
|
+
import "../chunk-KEGAAGJW.mjs";
|
|
15
|
+
import {
|
|
16
|
+
Slot
|
|
17
|
+
} from "../chunk-6QCEPQ3U.mjs";
|
|
18
|
+
|
|
19
|
+
// src/file-upload/FileUpload.tsx
|
|
20
|
+
import { useCombinedState } from "@spark-ui/hooks/use-combined-state";
|
|
21
|
+
import { createContext, useContext, useRef, useState } from "react";
|
|
22
|
+
|
|
23
|
+
// src/file-upload/utils.ts
|
|
24
|
+
function validateFileAccept(file, accept) {
|
|
25
|
+
if (!accept) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
const patterns = accept.split(",").map((pattern) => pattern.trim());
|
|
29
|
+
return patterns.some((pattern) => {
|
|
30
|
+
if (pattern.includes("/")) {
|
|
31
|
+
if (pattern.endsWith("/*")) {
|
|
32
|
+
const baseType = pattern.slice(0, -2);
|
|
33
|
+
return file.type.startsWith(baseType + "/");
|
|
34
|
+
}
|
|
35
|
+
return file.type === pattern;
|
|
36
|
+
}
|
|
37
|
+
if (pattern.startsWith(".")) {
|
|
38
|
+
const extension2 = pattern.toLowerCase();
|
|
39
|
+
const fileName2 = file.name.toLowerCase();
|
|
40
|
+
return fileName2.endsWith(extension2);
|
|
41
|
+
}
|
|
42
|
+
const extension = "." + pattern.toLowerCase();
|
|
43
|
+
const fileName = file.name.toLowerCase();
|
|
44
|
+
return fileName.endsWith(extension);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function validateFileSize(file, minFileSize, maxFileSize, locale) {
|
|
48
|
+
const defaultLocale = locale || getDefaultLocale();
|
|
49
|
+
if (minFileSize !== void 0 && file.size < minFileSize) {
|
|
50
|
+
const errorMessage = `File "${file.name}" is too small. Minimum size is ${formatFileSize(minFileSize, defaultLocale)}.`;
|
|
51
|
+
return {
|
|
52
|
+
valid: false,
|
|
53
|
+
error: errorMessage
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (maxFileSize !== void 0 && file.size > maxFileSize) {
|
|
57
|
+
const errorMessage = `File "${file.name}" is too large. Maximum size is ${formatFileSize(maxFileSize, defaultLocale)}.`;
|
|
58
|
+
return {
|
|
59
|
+
valid: false,
|
|
60
|
+
error: errorMessage
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return { valid: true };
|
|
64
|
+
}
|
|
65
|
+
function getDefaultLocale() {
|
|
66
|
+
if (typeof navigator !== "undefined" && navigator.language) {
|
|
67
|
+
return navigator.language;
|
|
68
|
+
}
|
|
69
|
+
return "en";
|
|
70
|
+
}
|
|
71
|
+
function formatFileSize(bytes, locale) {
|
|
72
|
+
const defaultLocale = locale || getDefaultLocale();
|
|
73
|
+
let normalizedLocale = defaultLocale;
|
|
74
|
+
if (defaultLocale.length === 2) {
|
|
75
|
+
normalizedLocale = defaultLocale === "fr" ? "fr-FR" : "en-US";
|
|
76
|
+
}
|
|
77
|
+
if (bytes === 0) {
|
|
78
|
+
const formatter2 = new Intl.NumberFormat(normalizedLocale, {
|
|
79
|
+
style: "unit",
|
|
80
|
+
unit: "byte",
|
|
81
|
+
unitDisplay: "long",
|
|
82
|
+
minimumFractionDigits: 0,
|
|
83
|
+
maximumFractionDigits: 0
|
|
84
|
+
});
|
|
85
|
+
return formatter2.format(0);
|
|
86
|
+
}
|
|
87
|
+
const k = 1024;
|
|
88
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
89
|
+
const units = ["byte", "kilobyte", "megabyte", "gigabyte"];
|
|
90
|
+
const unit = units[i] || "byte";
|
|
91
|
+
const size = bytes / Math.pow(k, i);
|
|
92
|
+
const unitDisplay = i === 0 ? "long" : "short";
|
|
93
|
+
const formatter = new Intl.NumberFormat(normalizedLocale, {
|
|
94
|
+
style: "unit",
|
|
95
|
+
unit,
|
|
96
|
+
unitDisplay,
|
|
97
|
+
minimumFractionDigits: 0,
|
|
98
|
+
maximumFractionDigits: 2
|
|
99
|
+
});
|
|
100
|
+
return formatter.format(size);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/file-upload/FileUpload.tsx
|
|
104
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
105
|
+
var FileUploadContext = createContext(null);
|
|
106
|
+
var FileUpload = ({
|
|
107
|
+
asChild: _asChild = false,
|
|
108
|
+
children,
|
|
109
|
+
defaultValue = [],
|
|
110
|
+
value: controlledValue,
|
|
111
|
+
onFilesChange,
|
|
112
|
+
multiple = true,
|
|
113
|
+
accept,
|
|
114
|
+
maxFiles,
|
|
115
|
+
onMaxFilesReached,
|
|
116
|
+
maxFileSize,
|
|
117
|
+
minFileSize,
|
|
118
|
+
onFileSizeError,
|
|
119
|
+
disabled = false,
|
|
120
|
+
readOnly = false,
|
|
121
|
+
locale
|
|
122
|
+
}) => {
|
|
123
|
+
const defaultLocale = locale || (typeof navigator !== "undefined" && navigator.language ? navigator.language : "en");
|
|
124
|
+
const inputRef = useRef(null);
|
|
125
|
+
const triggerRef = useRef(null);
|
|
126
|
+
const dropzoneRef = useRef(null);
|
|
127
|
+
const deleteButtonRefs = useRef([]);
|
|
128
|
+
const [filesState, setFilesState, ,] = useCombinedState(
|
|
129
|
+
controlledValue,
|
|
130
|
+
defaultValue,
|
|
131
|
+
onFilesChange
|
|
132
|
+
);
|
|
133
|
+
const files = filesState ?? [];
|
|
134
|
+
const setFiles = setFilesState;
|
|
135
|
+
const [rejectedFiles, setRejectedFiles] = useState([]);
|
|
136
|
+
const addFiles = (newFiles) => {
|
|
137
|
+
if (disabled || readOnly) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
setRejectedFiles([]);
|
|
141
|
+
const newRejectedFiles = [];
|
|
142
|
+
const fileExists = (file, existingFiles) => {
|
|
143
|
+
return existingFiles.some(
|
|
144
|
+
(existingFile) => existingFile.name === file.name && existingFile.size === file.size
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
const addRejectedFile = (file, error) => {
|
|
148
|
+
const existingRejection = newRejectedFiles.find(
|
|
149
|
+
(rejected) => rejected.file.name === file.name && rejected.file.size === file.size
|
|
150
|
+
);
|
|
151
|
+
if (existingRejection) {
|
|
152
|
+
if (!existingRejection.errors.includes(error)) {
|
|
153
|
+
existingRejection.errors.push(error);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
newRejectedFiles.push({
|
|
157
|
+
file,
|
|
158
|
+
errors: [error]
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (onFileSizeError) {
|
|
162
|
+
onFileSizeError(file, error);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
setFiles((prev) => {
|
|
166
|
+
const currentFiles = prev ?? [];
|
|
167
|
+
if (maxFiles !== void 0) {
|
|
168
|
+
const currentCount = currentFiles.length;
|
|
169
|
+
const remainingSlots = maxFiles - currentCount;
|
|
170
|
+
if (remainingSlots <= 0) {
|
|
171
|
+
newFiles.forEach((file) => {
|
|
172
|
+
addRejectedFile(file, "TOO_MANY_FILES");
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
let filteredFiles = newFiles;
|
|
177
|
+
if (accept) {
|
|
178
|
+
const rejectedByAccept = newFiles.filter((file) => !validateFileAccept(file, accept));
|
|
179
|
+
rejectedByAccept.forEach((file) => {
|
|
180
|
+
addRejectedFile(file, "FILE_INVALID_TYPE");
|
|
181
|
+
});
|
|
182
|
+
filteredFiles = newFiles.filter((file) => validateFileAccept(file, accept));
|
|
183
|
+
}
|
|
184
|
+
let validSizeFiles = filteredFiles;
|
|
185
|
+
if (minFileSize !== void 0 || maxFileSize !== void 0) {
|
|
186
|
+
validSizeFiles = filteredFiles.filter((file) => {
|
|
187
|
+
const validation = validateFileSize(file, minFileSize, maxFileSize, defaultLocale);
|
|
188
|
+
if (!validation.valid) {
|
|
189
|
+
if (maxFileSize !== void 0 && file.size > maxFileSize) {
|
|
190
|
+
addRejectedFile(file, "FILE_TOO_LARGE");
|
|
191
|
+
} else if (minFileSize !== void 0 && file.size < minFileSize) {
|
|
192
|
+
addRejectedFile(file, "FILE_TOO_SMALL");
|
|
193
|
+
} else {
|
|
194
|
+
addRejectedFile(file, "FILE_INVALID");
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const seenFiles = /* @__PURE__ */ new Map();
|
|
202
|
+
const duplicateFiles = [];
|
|
203
|
+
const uniqueFiles = validSizeFiles.filter((file) => {
|
|
204
|
+
const fileKey = `${file.name}-${file.size}`;
|
|
205
|
+
const existsInPrev = fileExists(file, currentFiles);
|
|
206
|
+
if (existsInPrev) {
|
|
207
|
+
duplicateFiles.push(file);
|
|
208
|
+
addRejectedFile(file, "FILE_EXISTS");
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
if (seenFiles.has(fileKey)) {
|
|
212
|
+
duplicateFiles.push(file);
|
|
213
|
+
addRejectedFile(file, "FILE_EXISTS");
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
seenFiles.set(fileKey, file);
|
|
217
|
+
return true;
|
|
218
|
+
});
|
|
219
|
+
let filesToAdd = multiple ? uniqueFiles : uniqueFiles.slice(0, 1);
|
|
220
|
+
if (maxFiles !== void 0) {
|
|
221
|
+
const currentCount = currentFiles.length;
|
|
222
|
+
const remainingSlots = maxFiles - currentCount;
|
|
223
|
+
if (remainingSlots <= 0) {
|
|
224
|
+
filesToAdd.forEach((file) => {
|
|
225
|
+
addRejectedFile(file, "TOO_MANY_FILES");
|
|
226
|
+
});
|
|
227
|
+
onMaxFilesReached?.(maxFiles, filesToAdd.length);
|
|
228
|
+
filesToAdd = [];
|
|
229
|
+
} else if (filesToAdd.length > remainingSlots) {
|
|
230
|
+
filesToAdd.forEach((file) => {
|
|
231
|
+
addRejectedFile(file, "TOO_MANY_FILES");
|
|
232
|
+
});
|
|
233
|
+
onMaxFilesReached?.(maxFiles, filesToAdd.length);
|
|
234
|
+
filesToAdd = [];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const updated = multiple ? [...currentFiles, ...filesToAdd] : filesToAdd;
|
|
238
|
+
const rejectedFilesToAdd = [...newRejectedFiles];
|
|
239
|
+
setRejectedFiles(rejectedFilesToAdd);
|
|
240
|
+
return updated;
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
const removeFile = (index) => {
|
|
244
|
+
if (disabled || readOnly) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
setFiles((prev) => {
|
|
248
|
+
const currentFiles = prev ?? [];
|
|
249
|
+
const updated = currentFiles.filter((_, i) => i !== index);
|
|
250
|
+
if (maxFiles !== void 0 && updated.length < maxFiles) {
|
|
251
|
+
setRejectedFiles(
|
|
252
|
+
(prevRejected) => prevRejected.filter((rejected) => !rejected.errors.includes("TOO_MANY_FILES"))
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return updated;
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
const clearFiles = () => {
|
|
259
|
+
if (disabled || readOnly) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
setFiles([]);
|
|
263
|
+
setRejectedFiles([]);
|
|
264
|
+
deleteButtonRefs.current = [];
|
|
265
|
+
};
|
|
266
|
+
const removeRejectedFile = (index) => {
|
|
267
|
+
if (disabled || readOnly) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
setRejectedFiles((prev) => prev.filter((_, i) => i !== index));
|
|
271
|
+
};
|
|
272
|
+
const clearRejectedFiles = () => {
|
|
273
|
+
setRejectedFiles([]);
|
|
274
|
+
};
|
|
275
|
+
const maxFilesReached = maxFiles !== void 0 && files.length >= maxFiles;
|
|
276
|
+
return /* @__PURE__ */ jsx(
|
|
277
|
+
FileUploadContext.Provider,
|
|
278
|
+
{
|
|
279
|
+
value: {
|
|
280
|
+
inputRef,
|
|
281
|
+
files,
|
|
282
|
+
rejectedFiles,
|
|
283
|
+
addFiles,
|
|
284
|
+
removeFile,
|
|
285
|
+
removeRejectedFile,
|
|
286
|
+
clearFiles,
|
|
287
|
+
clearRejectedFiles,
|
|
288
|
+
triggerRef,
|
|
289
|
+
dropzoneRef,
|
|
290
|
+
deleteButtonRefs,
|
|
291
|
+
multiple,
|
|
292
|
+
maxFiles,
|
|
293
|
+
maxFilesReached,
|
|
294
|
+
disabled,
|
|
295
|
+
readOnly,
|
|
296
|
+
locale: defaultLocale
|
|
297
|
+
},
|
|
298
|
+
children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
299
|
+
children,
|
|
300
|
+
/* @__PURE__ */ jsx(
|
|
301
|
+
"input",
|
|
302
|
+
{
|
|
303
|
+
ref: inputRef,
|
|
304
|
+
type: "file",
|
|
305
|
+
tabIndex: -1,
|
|
306
|
+
id: "image_uploads",
|
|
307
|
+
multiple,
|
|
308
|
+
name: "image_uploads",
|
|
309
|
+
accept,
|
|
310
|
+
disabled,
|
|
311
|
+
readOnly: readOnly && !disabled,
|
|
312
|
+
className: "sr-only",
|
|
313
|
+
onChange: (e) => {
|
|
314
|
+
if (e.target.files && !disabled && !readOnly) {
|
|
315
|
+
addFiles(Array.from(e.target.files));
|
|
316
|
+
try {
|
|
317
|
+
e.target.value = "";
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
)
|
|
324
|
+
] })
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
};
|
|
328
|
+
FileUpload.displayName = "FileUpload";
|
|
329
|
+
var useFileUploadContext = () => {
|
|
330
|
+
const context = useContext(FileUploadContext);
|
|
331
|
+
if (!context) {
|
|
332
|
+
throw Error("useFileUploadContext must be used within a FileUpload provider");
|
|
333
|
+
}
|
|
334
|
+
return context;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// src/file-upload/FileUploadAcceptedFile.tsx
|
|
338
|
+
import { CvOutline } from "@spark-ui/icons/CvOutline";
|
|
339
|
+
|
|
340
|
+
// src/file-upload/FileUploadItem.tsx
|
|
341
|
+
import { cx } from "class-variance-authority";
|
|
342
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
343
|
+
var Item = ({
|
|
344
|
+
asChild: _asChild = false,
|
|
345
|
+
className,
|
|
346
|
+
children,
|
|
347
|
+
...props
|
|
348
|
+
}) => {
|
|
349
|
+
return /* @__PURE__ */ jsx2(
|
|
350
|
+
"li",
|
|
351
|
+
{
|
|
352
|
+
"data-spark-component": "file-upload-item",
|
|
353
|
+
className: cx(
|
|
354
|
+
"relative",
|
|
355
|
+
"default:bg-surface default:border-sm default:border-outline default:p-md default:rounded-md",
|
|
356
|
+
"gap-md flex items-center justify-between default:w-full",
|
|
357
|
+
className
|
|
358
|
+
),
|
|
359
|
+
...props,
|
|
360
|
+
children
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
};
|
|
364
|
+
Item.displayName = "FileUpload.Item";
|
|
365
|
+
|
|
366
|
+
// src/file-upload/FileUploadItemDeleteTrigger.tsx
|
|
367
|
+
import { Close } from "@spark-ui/icons/Close";
|
|
368
|
+
import { cx as cx2 } from "class-variance-authority";
|
|
369
|
+
import { useRef as useRef2 } from "react";
|
|
370
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
371
|
+
var ItemDeleteTrigger = ({
|
|
372
|
+
className,
|
|
373
|
+
fileIndex,
|
|
374
|
+
onClick,
|
|
375
|
+
...props
|
|
376
|
+
}) => {
|
|
377
|
+
const { removeFile, triggerRef, dropzoneRef, deleteButtonRefs, disabled, readOnly } = useFileUploadContext();
|
|
378
|
+
const buttonRef = useRef2(null);
|
|
379
|
+
const handleClick = (e) => {
|
|
380
|
+
if (disabled || readOnly) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
removeFile(fileIndex);
|
|
384
|
+
setTimeout(() => {
|
|
385
|
+
const remainingButtons = deleteButtonRefs.current.filter(Boolean);
|
|
386
|
+
if (remainingButtons.length > 0) {
|
|
387
|
+
const targetIndex = Math.min(fileIndex, remainingButtons.length - 1);
|
|
388
|
+
const nextButton = remainingButtons[targetIndex];
|
|
389
|
+
if (nextButton) {
|
|
390
|
+
nextButton.focus();
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
const focusTarget = triggerRef.current || dropzoneRef.current;
|
|
394
|
+
if (focusTarget) {
|
|
395
|
+
focusTarget.focus();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}, 0);
|
|
399
|
+
onClick?.(e);
|
|
400
|
+
};
|
|
401
|
+
const setRef = (node) => {
|
|
402
|
+
buttonRef.current = node;
|
|
403
|
+
if (node) {
|
|
404
|
+
while (deleteButtonRefs.current.length <= fileIndex) {
|
|
405
|
+
deleteButtonRefs.current.push(null);
|
|
406
|
+
}
|
|
407
|
+
deleteButtonRefs.current[fileIndex] = node;
|
|
408
|
+
} else {
|
|
409
|
+
if (deleteButtonRefs.current[fileIndex]) {
|
|
410
|
+
deleteButtonRefs.current[fileIndex] = null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
return /* @__PURE__ */ jsx3(
|
|
415
|
+
IconButton,
|
|
416
|
+
{
|
|
417
|
+
ref: setRef,
|
|
418
|
+
"data-spark-component": "file-upload-item-delete-trigger",
|
|
419
|
+
className: cx2(className),
|
|
420
|
+
onClick: handleClick,
|
|
421
|
+
disabled: disabled || readOnly,
|
|
422
|
+
size: "sm",
|
|
423
|
+
design: "contrast",
|
|
424
|
+
intent: "surface",
|
|
425
|
+
...props,
|
|
426
|
+
children: /* @__PURE__ */ jsx3(Icon, { size: "sm", children: /* @__PURE__ */ jsx3(Close, {}) })
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
};
|
|
430
|
+
ItemDeleteTrigger.displayName = "FileUpload.ItemDeleteTrigger";
|
|
431
|
+
|
|
432
|
+
// src/file-upload/FileUploadItemFileName.tsx
|
|
433
|
+
import { cx as cx3 } from "class-variance-authority";
|
|
434
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
435
|
+
var ItemFileName = ({
|
|
436
|
+
asChild: _asChild = false,
|
|
437
|
+
className,
|
|
438
|
+
children,
|
|
439
|
+
...props
|
|
440
|
+
}) => {
|
|
441
|
+
return /* @__PURE__ */ jsx4(
|
|
442
|
+
"p",
|
|
443
|
+
{
|
|
444
|
+
"data-spark-component": "file-upload-item-file-name",
|
|
445
|
+
className: cx3("text-body-2 truncate font-medium", className),
|
|
446
|
+
...props,
|
|
447
|
+
children
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
};
|
|
451
|
+
ItemFileName.displayName = "FileUpload.ItemFileName";
|
|
452
|
+
|
|
453
|
+
// src/file-upload/FileUploadItemSizeText.tsx
|
|
454
|
+
import { cx as cx4 } from "class-variance-authority";
|
|
455
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
456
|
+
var ItemSizeText = ({
|
|
457
|
+
asChild: _asChild = false,
|
|
458
|
+
className,
|
|
459
|
+
children,
|
|
460
|
+
...props
|
|
461
|
+
}) => {
|
|
462
|
+
return /* @__PURE__ */ jsx5(
|
|
463
|
+
"p",
|
|
464
|
+
{
|
|
465
|
+
"data-spark-component": "file-upload-item-size-text",
|
|
466
|
+
className: cx4("text-caption", className),
|
|
467
|
+
...props,
|
|
468
|
+
children
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
};
|
|
472
|
+
ItemSizeText.displayName = "FileUpload.ItemSizeText";
|
|
473
|
+
|
|
474
|
+
// src/file-upload/FileUploadAcceptedFile.tsx
|
|
475
|
+
import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
476
|
+
var AcceptedFile = ({
|
|
477
|
+
asChild: _asChild = false,
|
|
478
|
+
className,
|
|
479
|
+
file,
|
|
480
|
+
fileIndex,
|
|
481
|
+
uploadProgress,
|
|
482
|
+
...props
|
|
483
|
+
}) => {
|
|
484
|
+
const { locale } = useFileUploadContext();
|
|
485
|
+
return /* @__PURE__ */ jsxs2(Item, { className, ...props, children: [
|
|
486
|
+
/* @__PURE__ */ jsx6("div", { className: "size-sz-40 bg-support-container flex items-center justify-center rounded-md", children: /* @__PURE__ */ jsx6(Icon, { size: "md", children: /* @__PURE__ */ jsx6(CvOutline, {}) }) }),
|
|
487
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
|
|
488
|
+
/* @__PURE__ */ jsxs2("div", { className: "gap-md flex flex-row items-center justify-between", children: [
|
|
489
|
+
/* @__PURE__ */ jsx6(ItemFileName, { children: file.name }),
|
|
490
|
+
/* @__PURE__ */ jsx6(ItemSizeText, { className: "opacity-dim-1", children: formatFileSize(file.size, locale) })
|
|
491
|
+
] }),
|
|
492
|
+
uploadProgress !== void 0 && /* @__PURE__ */ jsx6("div", { className: "mt-md", children: /* @__PURE__ */ jsx6(
|
|
493
|
+
Progress,
|
|
494
|
+
{
|
|
495
|
+
value: uploadProgress,
|
|
496
|
+
max: 100,
|
|
497
|
+
"aria-label": `Upload progress: ${uploadProgress}%`
|
|
498
|
+
}
|
|
499
|
+
) })
|
|
500
|
+
] }),
|
|
501
|
+
/* @__PURE__ */ jsx6(ItemDeleteTrigger, { "aria-label": "Delete file", fileIndex })
|
|
502
|
+
] });
|
|
503
|
+
};
|
|
504
|
+
AcceptedFile.displayName = "FileUpload.AcceptedFile";
|
|
505
|
+
|
|
506
|
+
// src/file-upload/FileUploadContext.tsx
|
|
507
|
+
import { Fragment, jsx as jsx7 } from "react/jsx-runtime";
|
|
508
|
+
var Context = ({ children }) => {
|
|
509
|
+
const { files = [], rejectedFiles = [], locale } = useFileUploadContext();
|
|
510
|
+
return /* @__PURE__ */ jsx7(Fragment, { children: children({
|
|
511
|
+
acceptedFiles: files,
|
|
512
|
+
rejectedFiles,
|
|
513
|
+
formatFileSize,
|
|
514
|
+
locale
|
|
515
|
+
}) });
|
|
516
|
+
};
|
|
517
|
+
Context.displayName = "FileUpload.Context";
|
|
518
|
+
|
|
519
|
+
// src/file-upload/FileUploadDropzone.tsx
|
|
520
|
+
import { cx as cx5 } from "class-variance-authority";
|
|
521
|
+
import { useRef as useRef3 } from "react";
|
|
522
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
523
|
+
function Dropzone({
|
|
524
|
+
children,
|
|
525
|
+
onFiles,
|
|
526
|
+
className,
|
|
527
|
+
unstyled = false
|
|
528
|
+
}) {
|
|
529
|
+
const ctx = useFileUploadContext();
|
|
530
|
+
const dropzoneRef = useRef3(null);
|
|
531
|
+
if (!ctx) throw new Error("FileUploadDropzone must be used inside <FileUpload>");
|
|
532
|
+
const handleDrop = (e) => {
|
|
533
|
+
e.preventDefault();
|
|
534
|
+
e.stopPropagation();
|
|
535
|
+
e.currentTarget.setAttribute("data-drag-over", "false");
|
|
536
|
+
if (ctx.disabled || ctx.readOnly) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const files = e.dataTransfer.files;
|
|
540
|
+
onFiles?.(files);
|
|
541
|
+
let filesArray = [];
|
|
542
|
+
if (files) {
|
|
543
|
+
filesArray = Array.isArray(files) ? [...files] : Array.from(files);
|
|
544
|
+
}
|
|
545
|
+
if (filesArray.length > 0) {
|
|
546
|
+
ctx.addFiles(filesArray);
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
const handleClick = () => {
|
|
550
|
+
if (!ctx.disabled && !ctx.readOnly) {
|
|
551
|
+
ctx.inputRef.current?.click();
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
const handleKeyDown = (e) => {
|
|
555
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
556
|
+
e.preventDefault();
|
|
557
|
+
if (!ctx.disabled && !ctx.readOnly) {
|
|
558
|
+
ctx.inputRef.current?.click();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
const isDisabled = ctx.disabled || ctx.readOnly;
|
|
563
|
+
return /* @__PURE__ */ jsx8(
|
|
564
|
+
"div",
|
|
565
|
+
{
|
|
566
|
+
ref: (node) => {
|
|
567
|
+
dropzoneRef.current = node;
|
|
568
|
+
if (ctx.dropzoneRef) {
|
|
569
|
+
ctx.dropzoneRef.current = node;
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
role: "button",
|
|
573
|
+
tabIndex: isDisabled ? -1 : 0,
|
|
574
|
+
"aria-disabled": ctx.disabled ? true : void 0,
|
|
575
|
+
onClick: handleClick,
|
|
576
|
+
onKeyDown: handleKeyDown,
|
|
577
|
+
onDrop: handleDrop,
|
|
578
|
+
onDragOver: (e) => {
|
|
579
|
+
e.preventDefault();
|
|
580
|
+
},
|
|
581
|
+
className: unstyled ? className : cx5(
|
|
582
|
+
"default:bg-surface default:border-sm default:border-outline default:rounded-lg default:border-dashed",
|
|
583
|
+
"gap-lg flex flex-col items-center justify-center text-center",
|
|
584
|
+
"default:p-xl",
|
|
585
|
+
"transition-colors duration-200",
|
|
586
|
+
!isDisabled && "hover:bg-surface-hovered",
|
|
587
|
+
"data-[drag-over=true]:border-outline-high data-[drag-over=true]:bg-surface-hovered data-[drag-over=true]:border-solid",
|
|
588
|
+
// Disabled: more visually disabled (opacity + cursor)
|
|
589
|
+
ctx.disabled && "cursor-not-allowed opacity-50",
|
|
590
|
+
// ReadOnly: less visually disabled (just cursor, no opacity)
|
|
591
|
+
ctx.readOnly && !ctx.disabled && "cursor-default",
|
|
592
|
+
className
|
|
593
|
+
),
|
|
594
|
+
onDragEnter: (e) => {
|
|
595
|
+
if (!isDisabled) {
|
|
596
|
+
e.currentTarget.setAttribute("data-drag-over", "true");
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
onDragLeave: (e) => {
|
|
600
|
+
e.currentTarget.setAttribute("data-drag-over", "false");
|
|
601
|
+
},
|
|
602
|
+
children
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
Dropzone.displayName = "FileUploadDropzone";
|
|
607
|
+
|
|
608
|
+
// src/file-upload/FileUploadPreviewImage.tsx
|
|
609
|
+
import { cx as cx6 } from "class-variance-authority";
|
|
610
|
+
import { useEffect, useState as useState2 } from "react";
|
|
611
|
+
import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
612
|
+
var PreviewImage = ({
|
|
613
|
+
asChild: _asChild = false,
|
|
614
|
+
className,
|
|
615
|
+
file,
|
|
616
|
+
fallback = "\u{1F4C4}",
|
|
617
|
+
...props
|
|
618
|
+
}) => {
|
|
619
|
+
const [imageError, setImageError] = useState2(false);
|
|
620
|
+
const [imageLoaded, setImageLoaded] = useState2(false);
|
|
621
|
+
const isImage = file.type.startsWith("image/");
|
|
622
|
+
const imageUrl = isImage ? URL.createObjectURL(file) : null;
|
|
623
|
+
useEffect(() => {
|
|
624
|
+
return () => {
|
|
625
|
+
if (imageUrl) {
|
|
626
|
+
URL.revokeObjectURL(imageUrl);
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
}, [imageUrl]);
|
|
630
|
+
if (!isImage || imageError) {
|
|
631
|
+
return /* @__PURE__ */ jsx9(
|
|
632
|
+
"div",
|
|
633
|
+
{
|
|
634
|
+
"data-spark-component": "file-upload-preview-image",
|
|
635
|
+
className: cx6(
|
|
636
|
+
"bg-neutral-container flex items-center justify-center rounded-md",
|
|
637
|
+
className
|
|
638
|
+
),
|
|
639
|
+
...props,
|
|
640
|
+
children: fallback
|
|
641
|
+
}
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
return /* @__PURE__ */ jsxs3(
|
|
645
|
+
"div",
|
|
646
|
+
{
|
|
647
|
+
"data-spark-component": "file-upload-preview-image",
|
|
648
|
+
className: cx6("bg-neutral-container overflow-hidden", className),
|
|
649
|
+
...props,
|
|
650
|
+
children: [
|
|
651
|
+
/* @__PURE__ */ jsx9(
|
|
652
|
+
"img",
|
|
653
|
+
{
|
|
654
|
+
src: imageUrl,
|
|
655
|
+
alt: file.name,
|
|
656
|
+
className: cx6("size-full object-cover", !imageLoaded && "opacity-0"),
|
|
657
|
+
onLoad: () => setImageLoaded(true),
|
|
658
|
+
onError: () => setImageError(true)
|
|
659
|
+
}
|
|
660
|
+
),
|
|
661
|
+
!imageLoaded && /* @__PURE__ */ jsx9("div", { className: "absolute inset-0 flex items-center justify-center", children: fallback })
|
|
662
|
+
]
|
|
663
|
+
}
|
|
664
|
+
);
|
|
665
|
+
};
|
|
666
|
+
PreviewImage.displayName = "FileUpload.PreviewImage";
|
|
667
|
+
|
|
668
|
+
// src/file-upload/FileUploadRejectedFile.tsx
|
|
669
|
+
import { WarningOutline } from "@spark-ui/icons/WarningOutline";
|
|
670
|
+
import { cx as cx8 } from "class-variance-authority";
|
|
671
|
+
|
|
672
|
+
// src/file-upload/FileUploadRejectedFileDeleteTrigger.tsx
|
|
673
|
+
import { Close as Close2 } from "@spark-ui/icons/Close";
|
|
674
|
+
import { cx as cx7 } from "class-variance-authority";
|
|
675
|
+
import { useRef as useRef4 } from "react";
|
|
676
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
677
|
+
var RejectedFileDeleteTrigger = ({
|
|
678
|
+
className,
|
|
679
|
+
rejectedFileIndex,
|
|
680
|
+
onClick,
|
|
681
|
+
...props
|
|
682
|
+
}) => {
|
|
683
|
+
const { removeRejectedFile, triggerRef, dropzoneRef, disabled, readOnly } = useFileUploadContext();
|
|
684
|
+
const buttonRef = useRef4(null);
|
|
685
|
+
const handleClick = (e) => {
|
|
686
|
+
if (disabled || readOnly) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
removeRejectedFile(rejectedFileIndex);
|
|
690
|
+
setTimeout(() => {
|
|
691
|
+
const focusTarget = triggerRef.current || dropzoneRef.current;
|
|
692
|
+
if (focusTarget) {
|
|
693
|
+
focusTarget.focus();
|
|
694
|
+
}
|
|
695
|
+
}, 0);
|
|
696
|
+
onClick?.(e);
|
|
697
|
+
};
|
|
698
|
+
return /* @__PURE__ */ jsx10(
|
|
699
|
+
IconButton,
|
|
700
|
+
{
|
|
701
|
+
ref: buttonRef,
|
|
702
|
+
"data-spark-component": "file-upload-rejected-file-delete-trigger",
|
|
703
|
+
className: cx7(className),
|
|
704
|
+
onClick: handleClick,
|
|
705
|
+
disabled: disabled || readOnly,
|
|
706
|
+
size: "sm",
|
|
707
|
+
design: "contrast",
|
|
708
|
+
intent: "surface",
|
|
709
|
+
...props,
|
|
710
|
+
children: /* @__PURE__ */ jsx10(Icon, { size: "sm", children: /* @__PURE__ */ jsx10(Close2, {}) })
|
|
711
|
+
}
|
|
712
|
+
);
|
|
713
|
+
};
|
|
714
|
+
RejectedFileDeleteTrigger.displayName = "FileUpload.RejectedFileDeleteTrigger";
|
|
715
|
+
|
|
716
|
+
// src/file-upload/FileUploadRejectedFile.tsx
|
|
717
|
+
import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
718
|
+
var RejectedFile = ({
|
|
719
|
+
asChild: _asChild = false,
|
|
720
|
+
className,
|
|
721
|
+
rejectedFile,
|
|
722
|
+
rejectedFileIndex,
|
|
723
|
+
renderError,
|
|
724
|
+
...props
|
|
725
|
+
}) => {
|
|
726
|
+
const { locale } = useFileUploadContext();
|
|
727
|
+
return /* @__PURE__ */ jsxs4(Item, { className: cx8("border-error border-md", className), ...props, children: [
|
|
728
|
+
/* @__PURE__ */ jsx11("div", { className: "size-sz-40 bg-error-container flex items-center justify-center rounded-md", children: /* @__PURE__ */ jsx11(Icon, { size: "md", className: "text-error", children: /* @__PURE__ */ jsx11(WarningOutline, {}) }) }),
|
|
729
|
+
/* @__PURE__ */ jsx11("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxs4("div", { className: "gap-md flex flex-col", children: [
|
|
730
|
+
/* @__PURE__ */ jsxs4("div", { className: "gap-md flex flex-row items-center justify-between", children: [
|
|
731
|
+
/* @__PURE__ */ jsx11(ItemFileName, { children: rejectedFile.file.name }),
|
|
732
|
+
/* @__PURE__ */ jsx11(ItemSizeText, { className: "opacity-dim-1", children: formatFileSize(rejectedFile.file.size, locale) })
|
|
733
|
+
] }),
|
|
734
|
+
/* @__PURE__ */ jsx11("div", { className: "gap-xs flex flex-col", children: rejectedFile.errors.map((error, errorIndex) => /* @__PURE__ */ jsx11("div", { className: "text-caption text-error", "data-error-code": error, children: renderError(error) }, errorIndex)) })
|
|
735
|
+
] }) }),
|
|
736
|
+
/* @__PURE__ */ jsx11(
|
|
737
|
+
RejectedFileDeleteTrigger,
|
|
738
|
+
{
|
|
739
|
+
"aria-label": `Remove ${rejectedFile.file.name} error`,
|
|
740
|
+
rejectedFileIndex
|
|
741
|
+
}
|
|
742
|
+
)
|
|
743
|
+
] });
|
|
744
|
+
};
|
|
745
|
+
RejectedFile.displayName = "FileUpload.RejectedFile";
|
|
746
|
+
|
|
747
|
+
// src/file-upload/FileUploadTrigger.tsx
|
|
748
|
+
import { cx as cx9 } from "class-variance-authority";
|
|
749
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
750
|
+
var Trigger = ({
|
|
751
|
+
className,
|
|
752
|
+
children,
|
|
753
|
+
asChild = false,
|
|
754
|
+
unstyled = false,
|
|
755
|
+
design = "filled",
|
|
756
|
+
intent = "basic",
|
|
757
|
+
ref,
|
|
758
|
+
...props
|
|
759
|
+
}) => {
|
|
760
|
+
const { inputRef, triggerRef, disabled, readOnly } = useFileUploadContext();
|
|
761
|
+
const handleClick = (e) => {
|
|
762
|
+
e.stopPropagation();
|
|
763
|
+
e.preventDefault();
|
|
764
|
+
if (!disabled && !readOnly) {
|
|
765
|
+
inputRef.current?.click();
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
const buttonComponent = unstyled ? "button" : Button;
|
|
769
|
+
const Comp = asChild ? Slot : buttonComponent;
|
|
770
|
+
return /* @__PURE__ */ jsx12(
|
|
771
|
+
Comp,
|
|
772
|
+
{
|
|
773
|
+
type: "button",
|
|
774
|
+
ref: (node) => {
|
|
775
|
+
if (triggerRef) {
|
|
776
|
+
triggerRef.current = node;
|
|
777
|
+
}
|
|
778
|
+
if (ref) {
|
|
779
|
+
if (typeof ref === "function") {
|
|
780
|
+
ref(node);
|
|
781
|
+
} else {
|
|
782
|
+
ref.current = node;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
},
|
|
786
|
+
design,
|
|
787
|
+
intent,
|
|
788
|
+
"data-spark-component": "file-upload-trigger",
|
|
789
|
+
className: cx9(className),
|
|
790
|
+
disabled: disabled || readOnly,
|
|
791
|
+
onClick: handleClick,
|
|
792
|
+
...props,
|
|
793
|
+
children
|
|
794
|
+
}
|
|
795
|
+
);
|
|
796
|
+
};
|
|
797
|
+
Trigger.displayName = "FileUpload.Trigger";
|
|
798
|
+
|
|
799
|
+
// src/file-upload/index.ts
|
|
800
|
+
var FileUpload2 = Object.assign(FileUpload, {
|
|
801
|
+
Trigger,
|
|
802
|
+
Dropzone,
|
|
803
|
+
Context,
|
|
804
|
+
Item,
|
|
805
|
+
ItemFileName,
|
|
806
|
+
ItemSizeText,
|
|
807
|
+
ItemDeleteTrigger,
|
|
808
|
+
PreviewImage,
|
|
809
|
+
AcceptedFile,
|
|
810
|
+
RejectedFile,
|
|
811
|
+
RejectedFileDeleteTrigger
|
|
812
|
+
});
|
|
813
|
+
FileUpload2.displayName = "FileUpload";
|
|
814
|
+
Trigger.displayName = "FileUpload.Trigger";
|
|
815
|
+
Dropzone.displayName = "FileUpload.Dropzone";
|
|
816
|
+
Context.displayName = "FileUpload.Context";
|
|
817
|
+
Item.displayName = "FileUpload.Item";
|
|
818
|
+
ItemFileName.displayName = "FileUpload.ItemFileName";
|
|
819
|
+
ItemSizeText.displayName = "FileUpload.ItemSizeText";
|
|
820
|
+
ItemDeleteTrigger.displayName = "FileUpload.ItemDeleteTrigger";
|
|
821
|
+
PreviewImage.displayName = "FileUpload.PreviewImage";
|
|
822
|
+
AcceptedFile.displayName = "FileUpload.AcceptedFile";
|
|
823
|
+
RejectedFile.displayName = "FileUpload.RejectedFile";
|
|
824
|
+
RejectedFileDeleteTrigger.displayName = "FileUpload.RejectedFileDeleteTrigger";
|
|
825
|
+
export {
|
|
826
|
+
FileUpload2 as FileUpload
|
|
827
|
+
};
|
|
828
|
+
//# sourceMappingURL=index.mjs.map
|