@k3-tech/react-kit 0.0.52 → 0.0.54
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.js
CHANGED
|
@@ -33577,7 +33577,9 @@ function FileUploader({
|
|
|
33577
33577
|
const preparedFile = isBlobLike(transformed) ? await normalizeTransformedFile(preparedSource, transformed) : transformed ?? preparedSource;
|
|
33578
33578
|
setFilesAndEmit((prev) => {
|
|
33579
33579
|
var _a2;
|
|
33580
|
-
const index2 = prev.findIndex(
|
|
33580
|
+
const index2 = prev.findIndex(
|
|
33581
|
+
(file) => getUploadToken(file) === token
|
|
33582
|
+
);
|
|
33581
33583
|
if (index2 < 0) return prev;
|
|
33582
33584
|
const n2 = [...prev];
|
|
33583
33585
|
n2[index2] = {
|
|
@@ -33594,7 +33596,9 @@ function FileUploader({
|
|
|
33594
33596
|
});
|
|
33595
33597
|
const result = await uploader(preparedFile, (pct) => {
|
|
33596
33598
|
setFilesAndEmit((prev) => {
|
|
33597
|
-
const index2 = prev.findIndex(
|
|
33599
|
+
const index2 = prev.findIndex(
|
|
33600
|
+
(file) => getUploadToken(file) === token
|
|
33601
|
+
);
|
|
33598
33602
|
if (index2 < 0) return prev;
|
|
33599
33603
|
const n2 = [...prev];
|
|
33600
33604
|
n2[index2] = {
|
|
@@ -33606,7 +33610,9 @@ function FileUploader({
|
|
|
33606
33610
|
});
|
|
33607
33611
|
});
|
|
33608
33612
|
setFilesAndEmit((prev) => {
|
|
33609
|
-
const index2 = prev.findIndex(
|
|
33613
|
+
const index2 = prev.findIndex(
|
|
33614
|
+
(file) => getUploadToken(file) === token
|
|
33615
|
+
);
|
|
33610
33616
|
if (index2 < 0) return prev;
|
|
33611
33617
|
const n2 = [...prev];
|
|
33612
33618
|
n2[index2] = {
|
|
@@ -33623,7 +33629,9 @@ function FileUploader({
|
|
|
33623
33629
|
if (uploaded) onUploadSuccess == null ? void 0 : onUploadSuccess(uploaded);
|
|
33624
33630
|
} catch (err) {
|
|
33625
33631
|
setFilesAndEmit((prev) => {
|
|
33626
|
-
const index2 = prev.findIndex(
|
|
33632
|
+
const index2 = prev.findIndex(
|
|
33633
|
+
(file) => getUploadToken(file) === token
|
|
33634
|
+
);
|
|
33627
33635
|
if (index2 < 0) return prev;
|
|
33628
33636
|
const n2 = [...prev];
|
|
33629
33637
|
const msg = err && typeof err === "object" && "message" in err ? String(err.message) : "Upload failed";
|
|
@@ -33699,7 +33707,10 @@ function FileUploader({
|
|
|
33699
33707
|
return next;
|
|
33700
33708
|
});
|
|
33701
33709
|
if (uploader) {
|
|
33702
|
-
const capacity = typeof maxFiles === "number" ? Math.max(
|
|
33710
|
+
const capacity = typeof maxFiles === "number" ? Math.max(
|
|
33711
|
+
0,
|
|
33712
|
+
maxFiles - (((_a2 = isControlled ? value : files) == null ? void 0 : _a2.length) ?? 0)
|
|
33713
|
+
) : acceptedFiles.length;
|
|
33703
33714
|
uploadQueue.slice(0, capacity).forEach(({ file, token }) => {
|
|
33704
33715
|
void startUpload(token, file);
|
|
33705
33716
|
});
|
|
@@ -33858,6 +33869,7 @@ function FileUploader({
|
|
|
33858
33869
|
}
|
|
33859
33870
|
},
|
|
33860
33871
|
"aria-label": "Download",
|
|
33872
|
+
type: "button",
|
|
33861
33873
|
children: /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" })
|
|
33862
33874
|
}
|
|
33863
33875
|
) }),
|
|
@@ -33871,6 +33883,7 @@ function FileUploader({
|
|
|
33871
33883
|
variant: "ghost",
|
|
33872
33884
|
onClick: () => onRemove2(idx),
|
|
33873
33885
|
"aria-label": "Remove",
|
|
33886
|
+
type: "button",
|
|
33874
33887
|
children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
|
|
33875
33888
|
}
|
|
33876
33889
|
) }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUploader.d.ts","sourceRoot":"","sources":["../../../../src/kit/components/fileuploader/FileUploader.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"FileUploader.d.ts","sourceRoot":"","sources":["../../../../src/kit/components/fileuploader/FileUploader.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAmK7D,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,eAAe,EACf,aAAa,EACb,QAAQ,EACR,SAAgB,EAChB,OAAO,EACP,UAAU,EACV,QAAe,EACf,QAAQ,EACR,MAAM,EACN,MAAe,EACf,QAAQ,EACR,uBAA+B,EAC/B,+BAAuC,EACvC,YAAmB,EACnB,gBAAwB,EACxB,WAA4D,EAC5D,SAAS,GACV,EAAE,iBAAiB,2CAmpBnB;AAED,eAAe,YAAY,CAAC"}
|
package/package.json
CHANGED
|
@@ -13,92 +13,92 @@ import {
|
|
|
13
13
|
UploadCloud,
|
|
14
14
|
Video,
|
|
15
15
|
XCircle,
|
|
16
|
-
} from
|
|
17
|
-
import { memo, useCallback, useEffect, useRef, useState } from
|
|
18
|
-
import { type Accept, useDropzone } from
|
|
19
|
-
import { cn } from
|
|
20
|
-
import { Button } from
|
|
21
|
-
import { Checkbox } from
|
|
22
|
-
import { Dialog, DialogContent } from
|
|
23
|
-
import { Progress } from
|
|
16
|
+
} from "lucide-react";
|
|
17
|
+
import { memo, useCallback, useEffect, useRef, useState } from "react";
|
|
18
|
+
import { type Accept, useDropzone } from "react-dropzone";
|
|
19
|
+
import { cn } from "../../../shadcn/lib/utils";
|
|
20
|
+
import { Button } from "../../../shadcn/ui/button";
|
|
21
|
+
import { Checkbox } from "../../../shadcn/ui/checkbox";
|
|
22
|
+
import { Dialog, DialogContent } from "../../../shadcn/ui/dialog";
|
|
23
|
+
import { Progress } from "../../../shadcn/ui/progress";
|
|
24
24
|
import {
|
|
25
25
|
Tooltip,
|
|
26
26
|
TooltipContent,
|
|
27
27
|
TooltipProvider,
|
|
28
28
|
TooltipTrigger,
|
|
29
|
-
} from
|
|
30
|
-
import { createImagePreprocessTransform } from
|
|
31
|
-
import type { FileRecord, FileUploaderProps } from
|
|
29
|
+
} from "../../../shadcn/ui/tooltip";
|
|
30
|
+
import { createImagePreprocessTransform } from "./image-preprocess";
|
|
31
|
+
import type { FileRecord, FileUploaderProps } from "./types";
|
|
32
32
|
|
|
33
33
|
// Cache preview URLs per Blob instance without mutating the file object
|
|
34
34
|
const previewUrlMap: WeakMap<Blob, string> = new WeakMap();
|
|
35
35
|
|
|
36
36
|
function isBlobLike(value: unknown): value is Blob {
|
|
37
37
|
return (
|
|
38
|
-
typeof value ===
|
|
38
|
+
typeof value === "object" &&
|
|
39
39
|
value !== null &&
|
|
40
|
-
|
|
41
|
-
typeof (value as { size?: unknown }).size ===
|
|
42
|
-
|
|
43
|
-
typeof (value as { type?: unknown }).type ===
|
|
40
|
+
"size" in value &&
|
|
41
|
+
typeof (value as { size?: unknown }).size === "number" &&
|
|
42
|
+
"type" in value &&
|
|
43
|
+
typeof (value as { type?: unknown }).type === "string"
|
|
44
44
|
);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function formatBytes(bytes?: number) {
|
|
48
|
-
if (!bytes && bytes !== 0) return
|
|
49
|
-
const sizes = [
|
|
50
|
-
if (bytes === 0) return
|
|
48
|
+
if (!bytes && bytes !== 0) return "";
|
|
49
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
50
|
+
if (bytes === 0) return "0 Byte";
|
|
51
51
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
52
52
|
return `${(bytes / 1024 ** i).toFixed(2)} ${sizes[i]}`;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function isImageType(type?: string, name?: string) {
|
|
56
56
|
if (!type && name) {
|
|
57
|
-
const ext = name.toLowerCase().split(
|
|
57
|
+
const ext = name.toLowerCase().split(".").pop();
|
|
58
58
|
if (!ext) return false;
|
|
59
59
|
return [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
"png",
|
|
61
|
+
"jpg",
|
|
62
|
+
"jpeg",
|
|
63
|
+
"webp",
|
|
64
|
+
"gif",
|
|
65
|
+
"bmp",
|
|
66
|
+
"svg",
|
|
67
|
+
"heic",
|
|
68
|
+
"heif",
|
|
69
69
|
].includes(ext);
|
|
70
70
|
}
|
|
71
|
-
return !!type?.startsWith(
|
|
71
|
+
return !!type?.startsWith("image/");
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function pickIconByType(type?: string, name?: string) {
|
|
75
|
-
const t = (type ||
|
|
76
|
-
const ext = (name?.split(
|
|
75
|
+
const t = (type || "").toLowerCase();
|
|
76
|
+
const ext = (name?.split(".").pop() || "").toLowerCase();
|
|
77
77
|
if (
|
|
78
|
-
t.startsWith(
|
|
78
|
+
t.startsWith("image/") ||
|
|
79
79
|
[
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
"png",
|
|
81
|
+
"jpg",
|
|
82
|
+
"jpeg",
|
|
83
|
+
"webp",
|
|
84
|
+
"gif",
|
|
85
|
+
"bmp",
|
|
86
|
+
"svg",
|
|
87
|
+
"heic",
|
|
88
|
+
"heif",
|
|
89
89
|
].includes(ext)
|
|
90
90
|
)
|
|
91
91
|
return <ImageIcon className="h-8 w-8" />;
|
|
92
|
-
if (t.startsWith(
|
|
92
|
+
if (t.startsWith("video/") || ["mp4", "mov", "webm", "mkv"].includes(ext))
|
|
93
93
|
return <Video className="h-8 w-8" />;
|
|
94
|
-
if (t.startsWith(
|
|
94
|
+
if (t.startsWith("audio/") || ["mp3", "wav", "aac", "flac"].includes(ext))
|
|
95
95
|
return <Music className="h-8 w-8" />;
|
|
96
|
-
if ([
|
|
96
|
+
if (["zip", "rar", "7z", "tar", "gz"].includes(ext))
|
|
97
97
|
return <Archive className="h-8 w-8" />;
|
|
98
|
-
if ([
|
|
98
|
+
if (["txt", "md", "rtf"].includes(ext))
|
|
99
99
|
return <FileText className="h-8 w-8" />;
|
|
100
100
|
if (
|
|
101
|
-
[
|
|
101
|
+
["js", "ts", "tsx", "json", "yml", "yaml", "xml", "html", "css"].includes(
|
|
102
102
|
ext,
|
|
103
103
|
)
|
|
104
104
|
)
|
|
@@ -110,15 +110,15 @@ async function normalizeTransformedFile(
|
|
|
110
110
|
file: File | (Blob & { name?: string; lastModified?: number }),
|
|
111
111
|
transformed: File | Blob,
|
|
112
112
|
) {
|
|
113
|
-
const fileName = getFileLabel(file,
|
|
113
|
+
const fileName = getFileLabel(file, "image");
|
|
114
114
|
const lastModified =
|
|
115
|
-
|
|
115
|
+
"name" in file && typeof file.lastModified === "number"
|
|
116
116
|
? file.lastModified
|
|
117
117
|
: Date.now();
|
|
118
118
|
const fileType =
|
|
119
|
-
|
|
119
|
+
"type" in file && typeof file.type === "string" ? file.type : "";
|
|
120
120
|
try {
|
|
121
|
-
if (typeof File ===
|
|
121
|
+
if (typeof File === "function") {
|
|
122
122
|
return new File([transformed], fileName, {
|
|
123
123
|
type: transformed.type || fileType,
|
|
124
124
|
lastModified,
|
|
@@ -151,8 +151,8 @@ async function normalizeTransformedFile(
|
|
|
151
151
|
return fallback as File;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
function getFileLabel(file: File | Blob, fallback =
|
|
155
|
-
if (
|
|
154
|
+
function getFileLabel(file: File | Blob, fallback = "image"): string {
|
|
155
|
+
if ("name" in file && typeof file.name === "string" && file.name) {
|
|
156
156
|
return file.name;
|
|
157
157
|
}
|
|
158
158
|
return fallback;
|
|
@@ -176,7 +176,10 @@ function getPreviewUrl(file: FileRecord) {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
function createUploadToken() {
|
|
179
|
-
if (
|
|
179
|
+
if (
|
|
180
|
+
typeof crypto !== "undefined" &&
|
|
181
|
+
typeof crypto.randomUUID === "function"
|
|
182
|
+
) {
|
|
180
183
|
return crypto.randomUUID();
|
|
181
184
|
}
|
|
182
185
|
|
|
@@ -185,7 +188,7 @@ function createUploadToken() {
|
|
|
185
188
|
|
|
186
189
|
function getUploadToken(file: FileRecord) {
|
|
187
190
|
const token = file.meta?.uploadToken;
|
|
188
|
-
return typeof token ===
|
|
191
|
+
return typeof token === "string" ? token : undefined;
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
export function FileUploader({
|
|
@@ -203,13 +206,13 @@ export function FileUploader({
|
|
|
203
206
|
multiple = true,
|
|
204
207
|
maxFiles,
|
|
205
208
|
accept,
|
|
206
|
-
layout =
|
|
209
|
+
layout = "grid",
|
|
207
210
|
disabled,
|
|
208
211
|
enableBackgroundRemoval = false,
|
|
209
212
|
defaultBackgroundRemovalEnabled = false,
|
|
210
213
|
withDownload = true,
|
|
211
214
|
withImagePreview = false,
|
|
212
|
-
placeholder =
|
|
215
|
+
placeholder = "Drag and drop files here, or click to select",
|
|
213
216
|
className,
|
|
214
217
|
}: FileUploaderProps) {
|
|
215
218
|
const isControlled = value !== undefined;
|
|
@@ -221,7 +224,7 @@ export function FileUploader({
|
|
|
221
224
|
const [previewItem, setPreviewItem] = useState<{
|
|
222
225
|
src: string;
|
|
223
226
|
name: string;
|
|
224
|
-
}>({ src:
|
|
227
|
+
}>({ src: "", name: "" });
|
|
225
228
|
|
|
226
229
|
const prevUrlsRef = useRef<Set<string>>(new Set());
|
|
227
230
|
useEffect(() => {
|
|
@@ -237,7 +240,7 @@ export function FileUploader({
|
|
|
237
240
|
(updater: FileRecord[] | ((prev: FileRecord[]) => FileRecord[])) => {
|
|
238
241
|
setFiles((prev) => {
|
|
239
242
|
let next: FileRecord[];
|
|
240
|
-
if (typeof updater ===
|
|
243
|
+
if (typeof updater === "function") {
|
|
241
244
|
const fn = updater as (p: FileRecord[]) => FileRecord[];
|
|
242
245
|
next = fn(prev);
|
|
243
246
|
} else {
|
|
@@ -264,7 +267,7 @@ export function FileUploader({
|
|
|
264
267
|
const next = prev.map((f) => {
|
|
265
268
|
if ((f.url || f.thumbnailUrl) && !f.status) {
|
|
266
269
|
changed = true;
|
|
267
|
-
return { ...f, status:
|
|
270
|
+
return { ...f, status: "success" as const, progress: 100 };
|
|
268
271
|
}
|
|
269
272
|
return f;
|
|
270
273
|
});
|
|
@@ -330,7 +333,9 @@ export function FileUploader({
|
|
|
330
333
|
? await normalizeTransformedFile(preparedSource, transformed)
|
|
331
334
|
: (transformed ?? preparedSource);
|
|
332
335
|
setFilesAndEmit((prev) => {
|
|
333
|
-
const index = prev.findIndex(
|
|
336
|
+
const index = prev.findIndex(
|
|
337
|
+
(file) => getUploadToken(file) === token,
|
|
338
|
+
);
|
|
334
339
|
if (index < 0) return prev;
|
|
335
340
|
const n = [...prev];
|
|
336
341
|
n[index] = {
|
|
@@ -339,7 +344,7 @@ export function FileUploader({
|
|
|
339
344
|
name: getFileLabel(preparedFile, n[index]?.name || f.name),
|
|
340
345
|
size: preparedFile.size,
|
|
341
346
|
type: preparedFile.type,
|
|
342
|
-
status:
|
|
347
|
+
status: "uploading",
|
|
343
348
|
progress: 0,
|
|
344
349
|
errorMessage: undefined,
|
|
345
350
|
};
|
|
@@ -347,25 +352,29 @@ export function FileUploader({
|
|
|
347
352
|
});
|
|
348
353
|
const result = await uploader(preparedFile as File, (pct) => {
|
|
349
354
|
setFilesAndEmit((prev) => {
|
|
350
|
-
const index = prev.findIndex(
|
|
355
|
+
const index = prev.findIndex(
|
|
356
|
+
(file) => getUploadToken(file) === token,
|
|
357
|
+
);
|
|
351
358
|
if (index < 0) return prev;
|
|
352
359
|
const n = [...prev];
|
|
353
360
|
n[index] = {
|
|
354
361
|
...n[index],
|
|
355
362
|
progress: Math.min(100, Math.max(0, Math.round(pct))),
|
|
356
|
-
status:
|
|
363
|
+
status: "uploading",
|
|
357
364
|
};
|
|
358
365
|
return n;
|
|
359
366
|
});
|
|
360
367
|
});
|
|
361
368
|
setFilesAndEmit((prev) => {
|
|
362
|
-
const index = prev.findIndex(
|
|
369
|
+
const index = prev.findIndex(
|
|
370
|
+
(file) => getUploadToken(file) === token,
|
|
371
|
+
);
|
|
363
372
|
if (index < 0) return prev;
|
|
364
373
|
const n = [...prev];
|
|
365
374
|
n[index] = {
|
|
366
375
|
...n[index],
|
|
367
376
|
...result,
|
|
368
|
-
status:
|
|
377
|
+
status: "success",
|
|
369
378
|
progress: 100,
|
|
370
379
|
};
|
|
371
380
|
return n;
|
|
@@ -376,16 +385,18 @@ export function FileUploader({
|
|
|
376
385
|
if (uploaded) onUploadSuccess?.(uploaded);
|
|
377
386
|
} catch (err) {
|
|
378
387
|
setFilesAndEmit((prev) => {
|
|
379
|
-
const index = prev.findIndex(
|
|
388
|
+
const index = prev.findIndex(
|
|
389
|
+
(file) => getUploadToken(file) === token,
|
|
390
|
+
);
|
|
380
391
|
if (index < 0) return prev;
|
|
381
392
|
const n = [...prev];
|
|
382
393
|
const msg =
|
|
383
394
|
err &&
|
|
384
|
-
typeof err ===
|
|
385
|
-
|
|
395
|
+
typeof err === "object" &&
|
|
396
|
+
"message" in (err as Record<string, unknown>)
|
|
386
397
|
? String((err as { message?: unknown }).message)
|
|
387
|
-
|
|
388
|
-
n[index] = { ...n[index], status:
|
|
398
|
+
: "Upload failed";
|
|
399
|
+
n[index] = { ...n[index], status: "error", errorMessage: msg };
|
|
389
400
|
return n;
|
|
390
401
|
});
|
|
391
402
|
const failed = [...(isControlled ? value : files)].find(
|
|
@@ -421,7 +432,7 @@ export function FileUploader({
|
|
|
421
432
|
|
|
422
433
|
const handleRetryAll = useCallback(() => {
|
|
423
434
|
if (!uploader) return;
|
|
424
|
-
const failedFiles = files.filter((f) => f.status ===
|
|
435
|
+
const failedFiles = files.filter((f) => f.status === "error" && !!f.file);
|
|
425
436
|
if (failedFiles.length === 0) return;
|
|
426
437
|
onRetryAll?.(failedFiles);
|
|
427
438
|
failedFiles.forEach((fr) => {
|
|
@@ -443,7 +454,7 @@ export function FileUploader({
|
|
|
443
454
|
setFilesAndEmit((prev) => {
|
|
444
455
|
const existing = [...prev];
|
|
445
456
|
const capacity =
|
|
446
|
-
typeof maxFiles ===
|
|
457
|
+
typeof maxFiles === "number"
|
|
447
458
|
? Math.max(0, maxFiles - existing.length)
|
|
448
459
|
: acceptedFiles.length;
|
|
449
460
|
const incoming = uploadQueue
|
|
@@ -456,7 +467,7 @@ export function FileUploader({
|
|
|
456
467
|
name: file.name,
|
|
457
468
|
size: file.size,
|
|
458
469
|
type: file.type,
|
|
459
|
-
status: uploader ?
|
|
470
|
+
status: uploader ? "uploading" : "idle",
|
|
460
471
|
progress: uploader ? 0 : undefined,
|
|
461
472
|
meta: { uploadToken: token },
|
|
462
473
|
}));
|
|
@@ -468,8 +479,11 @@ export function FileUploader({
|
|
|
468
479
|
|
|
469
480
|
if (uploader) {
|
|
470
481
|
const capacity =
|
|
471
|
-
typeof maxFiles ===
|
|
472
|
-
? Math.max(
|
|
482
|
+
typeof maxFiles === "number"
|
|
483
|
+
? Math.max(
|
|
484
|
+
0,
|
|
485
|
+
maxFiles - ((isControlled ? value : files)?.length ?? 0),
|
|
486
|
+
)
|
|
473
487
|
: acceptedFiles.length;
|
|
474
488
|
uploadQueue.slice(0, capacity).forEach(({ file, token }) => {
|
|
475
489
|
void startUpload(token, file);
|
|
@@ -514,11 +528,11 @@ export function FileUploader({
|
|
|
514
528
|
}, [files]);
|
|
515
529
|
|
|
516
530
|
const rootClasses = cn(
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
isDragActive &&
|
|
520
|
-
isDragReject &&
|
|
521
|
-
(disabled || disabledBecauseFull) &&
|
|
531
|
+
"w-full border border-dashed rounded-md p-4 text-sm transition-colors bg-background",
|
|
532
|
+
"hover:border-foreground/50",
|
|
533
|
+
isDragActive && "border-primary",
|
|
534
|
+
isDragReject && "border-destructive",
|
|
535
|
+
(disabled || disabledBecauseFull) && "opacity-50 pointer-events-none",
|
|
522
536
|
);
|
|
523
537
|
|
|
524
538
|
// (renderThumb removed; inlined into FileItem for better memoization)
|
|
@@ -542,7 +556,7 @@ export function FileUploader({
|
|
|
542
556
|
type FileItemProps = {
|
|
543
557
|
fr: FileRecord;
|
|
544
558
|
idx: number;
|
|
545
|
-
layout:
|
|
559
|
+
layout: "grid" | "list";
|
|
546
560
|
withDownload: boolean;
|
|
547
561
|
withImagePreview: boolean;
|
|
548
562
|
uploaderPresent: boolean;
|
|
@@ -565,20 +579,20 @@ export function FileUploader({
|
|
|
565
579
|
}: FileItemProps) {
|
|
566
580
|
const name = fr.name;
|
|
567
581
|
const size = formatBytes(fr.size);
|
|
568
|
-
const error = fr.status ===
|
|
582
|
+
const error = fr.status === "error" ? fr.errorMessage : undefined;
|
|
569
583
|
const preview = getPreviewUrl(fr);
|
|
570
584
|
return (
|
|
571
585
|
<div
|
|
572
586
|
className={cn(
|
|
573
|
-
|
|
574
|
-
layout ===
|
|
587
|
+
"flex items-center gap-3 border rounded-md p-2 bg-card",
|
|
588
|
+
layout === "grid" ? "flex-col items-stretch" : "flex-row",
|
|
575
589
|
)}
|
|
576
590
|
>
|
|
577
|
-
<div className={cn(layout ===
|
|
591
|
+
<div className={cn(layout === "grid" ? "self-center" : "")}>
|
|
578
592
|
<div
|
|
579
593
|
className={cn(
|
|
580
|
-
|
|
581
|
-
layout ===
|
|
594
|
+
"relative overflow-hidden bg-muted/40 border rounded-md flex items-center justify-center",
|
|
595
|
+
layout === "grid" ? "h-28 w-28" : "h-16 w-16",
|
|
582
596
|
)}
|
|
583
597
|
>
|
|
584
598
|
{preview ? (
|
|
@@ -607,17 +621,17 @@ export function FileUploader({
|
|
|
607
621
|
{pickIconByType(fr.type, fr.name)}
|
|
608
622
|
</div>
|
|
609
623
|
)}
|
|
610
|
-
{fr.status ===
|
|
624
|
+
{fr.status === "uploading" ? (
|
|
611
625
|
<div className="absolute inset-0 flex items-center justify-center bg-black/40">
|
|
612
626
|
<Loader2 className="h-6 w-6 text-white animate-spin" />
|
|
613
627
|
</div>
|
|
614
628
|
) : null}
|
|
615
|
-
{fr.status ===
|
|
629
|
+
{fr.status === "success" ? (
|
|
616
630
|
<div className="absolute top-1 right-1 text-green-500">
|
|
617
631
|
<CheckCircle2 className="h-5 w-5 drop-shadow" />
|
|
618
632
|
</div>
|
|
619
633
|
) : null}
|
|
620
|
-
{fr.status ===
|
|
634
|
+
{fr.status === "error" ? (
|
|
621
635
|
<div className="absolute top-1 right-1 text-red-500">
|
|
622
636
|
<XCircle className="h-5 w-5 drop-shadow" />
|
|
623
637
|
</div>
|
|
@@ -625,7 +639,7 @@ export function FileUploader({
|
|
|
625
639
|
</div>
|
|
626
640
|
</div>
|
|
627
641
|
<div
|
|
628
|
-
className={cn(
|
|
642
|
+
className={cn("min-w-0 flex-1", layout === "grid" ? "mt-2" : "")}
|
|
629
643
|
>
|
|
630
644
|
<div className="flex items-center justify-between gap-2">
|
|
631
645
|
<div className="min-w-0">
|
|
@@ -635,7 +649,7 @@ export function FileUploader({
|
|
|
635
649
|
<div className="text-xs text-muted-foreground">{size}</div>
|
|
636
650
|
</div>
|
|
637
651
|
<div className="flex items-center gap-1">
|
|
638
|
-
{fr.status ===
|
|
652
|
+
{fr.status === "error" ? (
|
|
639
653
|
<Tooltip>
|
|
640
654
|
<TooltipTrigger asChild>
|
|
641
655
|
<Button
|
|
@@ -660,10 +674,11 @@ export function FileUploader({
|
|
|
660
674
|
onClick={() => {
|
|
661
675
|
const url = fr.url ?? fr.thumbnailUrl;
|
|
662
676
|
if (url) {
|
|
663
|
-
window.open(url,
|
|
677
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
664
678
|
}
|
|
665
679
|
}}
|
|
666
680
|
aria-label="Download"
|
|
681
|
+
type="button"
|
|
667
682
|
>
|
|
668
683
|
<Download className="h-4 w-4" />
|
|
669
684
|
</Button>
|
|
@@ -678,6 +693,7 @@ export function FileUploader({
|
|
|
678
693
|
variant="ghost"
|
|
679
694
|
onClick={() => onRemove(idx)}
|
|
680
695
|
aria-label="Remove"
|
|
696
|
+
type="button"
|
|
681
697
|
>
|
|
682
698
|
<Trash2 className="h-4 w-4" />
|
|
683
699
|
</Button>
|
|
@@ -686,7 +702,7 @@ export function FileUploader({
|
|
|
686
702
|
</Tooltip>
|
|
687
703
|
</div>
|
|
688
704
|
</div>
|
|
689
|
-
{fr.status ===
|
|
705
|
+
{fr.status === "uploading" ? (
|
|
690
706
|
<div className="mt-2">
|
|
691
707
|
<Progress value={fr.progress ?? 0} />
|
|
692
708
|
</div>
|
|
@@ -707,7 +723,7 @@ export function FileUploader({
|
|
|
707
723
|
);
|
|
708
724
|
|
|
709
725
|
return (
|
|
710
|
-
<div className={cn(
|
|
726
|
+
<div className={cn("space-y-3", className)}>
|
|
711
727
|
{enableBackgroundRemoval ? (
|
|
712
728
|
<div className="flex items-start gap-3 rounded-md border bg-muted/20 p-3">
|
|
713
729
|
<Checkbox
|
|
@@ -733,7 +749,7 @@ export function FileUploader({
|
|
|
733
749
|
<input
|
|
734
750
|
{...getInputProps({
|
|
735
751
|
onClick: (e) => {
|
|
736
|
-
(e.target as HTMLInputElement).value =
|
|
752
|
+
(e.target as HTMLInputElement).value = "";
|
|
737
753
|
},
|
|
738
754
|
})}
|
|
739
755
|
/>
|
|
@@ -755,16 +771,16 @@ export function FileUploader({
|
|
|
755
771
|
<div>
|
|
756
772
|
<div className="font-medium">
|
|
757
773
|
{disabledBecauseFull
|
|
758
|
-
?
|
|
774
|
+
? "File limit reached"
|
|
759
775
|
: isDragActive
|
|
760
|
-
?
|
|
776
|
+
? "Drop the files here"
|
|
761
777
|
: placeholder}
|
|
762
778
|
</div>
|
|
763
779
|
<div className="text-xs">
|
|
764
|
-
{accept ?
|
|
765
|
-
{typeof maxFiles ===
|
|
766
|
-
? ` • Up to ${maxFiles} file${maxFiles > 1 ?
|
|
767
|
-
:
|
|
780
|
+
{accept ? "Specific file types only" : "Any file type"}
|
|
781
|
+
{typeof maxFiles === "number"
|
|
782
|
+
? ` • Up to ${maxFiles} file${maxFiles > 1 ? "s" : ""}`
|
|
783
|
+
: ""}
|
|
768
784
|
</div>
|
|
769
785
|
</div>
|
|
770
786
|
</div>
|
|
@@ -777,7 +793,7 @@ export function FileUploader({
|
|
|
777
793
|
disabled={
|
|
778
794
|
!!disabled ||
|
|
779
795
|
!uploader ||
|
|
780
|
-
files.every((f) => f.status !==
|
|
796
|
+
files.every((f) => f.status !== "error" || !f.file)
|
|
781
797
|
}
|
|
782
798
|
>
|
|
783
799
|
<RotateCcw className="mr-1 h-4 w-4" /> Retry failed
|
|
@@ -790,7 +806,7 @@ export function FileUploader({
|
|
|
790
806
|
<TooltipProvider>
|
|
791
807
|
<div
|
|
792
808
|
className={cn(
|
|
793
|
-
layout ===
|
|
809
|
+
layout === "grid" ? "flex flex-wrap gap-3" : "flex flex-col gap-2",
|
|
794
810
|
)}
|
|
795
811
|
>
|
|
796
812
|
{files.length === 0 ? (
|
|
@@ -819,7 +835,7 @@ export function FileUploader({
|
|
|
819
835
|
onOpenChange={(open) => {
|
|
820
836
|
setPreviewOpen(open);
|
|
821
837
|
if (!open) {
|
|
822
|
-
setPreviewItem({ src:
|
|
838
|
+
setPreviewItem({ src: "", name: "" });
|
|
823
839
|
}
|
|
824
840
|
}}
|
|
825
841
|
>
|