@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((file) => getUploadToken(file) === token);
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((file) => getUploadToken(file) === token);
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((file) => getUploadToken(file) === token);
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((file) => getUploadToken(file) === token);
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(0, maxFiles - (((_a2 = isControlled ? value : files) == null ? void 0 : _a2.length) ?? 0)) : acceptedFiles.length;
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;AAgK7D,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,2CAsoBnB;AAED,eAAe,YAAY,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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k3-tech/react-kit",
3
- "version": "0.0.52",
3
+ "version": "0.0.54",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -13,92 +13,92 @@ import {
13
13
  UploadCloud,
14
14
  Video,
15
15
  XCircle,
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';
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 '../../../shadcn/ui/tooltip';
30
- import { createImagePreprocessTransform } from './image-preprocess';
31
- import type { FileRecord, FileUploaderProps } from './types';
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 === 'object' &&
38
+ typeof value === "object" &&
39
39
  value !== null &&
40
- 'size' in value &&
41
- typeof (value as { size?: unknown }).size === 'number' &&
42
- 'type' in value &&
43
- typeof (value as { type?: unknown }).type === 'string'
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 = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
50
- if (bytes === 0) return '0 Byte';
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('.').pop();
57
+ const ext = name.toLowerCase().split(".").pop();
58
58
  if (!ext) return false;
59
59
  return [
60
- 'png',
61
- 'jpg',
62
- 'jpeg',
63
- 'webp',
64
- 'gif',
65
- 'bmp',
66
- 'svg',
67
- 'heic',
68
- 'heif',
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('image/');
71
+ return !!type?.startsWith("image/");
72
72
  }
73
73
 
74
74
  function pickIconByType(type?: string, name?: string) {
75
- const t = (type || '').toLowerCase();
76
- const ext = (name?.split('.').pop() || '').toLowerCase();
75
+ const t = (type || "").toLowerCase();
76
+ const ext = (name?.split(".").pop() || "").toLowerCase();
77
77
  if (
78
- t.startsWith('image/') ||
78
+ t.startsWith("image/") ||
79
79
  [
80
- 'png',
81
- 'jpg',
82
- 'jpeg',
83
- 'webp',
84
- 'gif',
85
- 'bmp',
86
- 'svg',
87
- 'heic',
88
- 'heif',
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('video/') || ['mp4', 'mov', 'webm', 'mkv'].includes(ext))
92
+ if (t.startsWith("video/") || ["mp4", "mov", "webm", "mkv"].includes(ext))
93
93
  return <Video className="h-8 w-8" />;
94
- if (t.startsWith('audio/') || ['mp3', 'wav', 'aac', 'flac'].includes(ext))
94
+ if (t.startsWith("audio/") || ["mp3", "wav", "aac", "flac"].includes(ext))
95
95
  return <Music className="h-8 w-8" />;
96
- if (['zip', 'rar', '7z', 'tar', 'gz'].includes(ext))
96
+ if (["zip", "rar", "7z", "tar", "gz"].includes(ext))
97
97
  return <Archive className="h-8 w-8" />;
98
- if (['txt', 'md', 'rtf'].includes(ext))
98
+ if (["txt", "md", "rtf"].includes(ext))
99
99
  return <FileText className="h-8 w-8" />;
100
100
  if (
101
- ['js', 'ts', 'tsx', 'json', 'yml', 'yaml', 'xml', 'html', 'css'].includes(
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, 'image');
113
+ const fileName = getFileLabel(file, "image");
114
114
  const lastModified =
115
- 'name' in file && typeof file.lastModified === 'number'
115
+ "name" in file && typeof file.lastModified === "number"
116
116
  ? file.lastModified
117
117
  : Date.now();
118
118
  const fileType =
119
- 'type' in file && typeof file.type === 'string' ? file.type : '';
119
+ "type" in file && typeof file.type === "string" ? file.type : "";
120
120
  try {
121
- if (typeof File === 'function') {
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 = 'image'): string {
155
- if ('name' in file && typeof file.name === 'string' && file.name) {
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 (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
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 === 'string' ? token : undefined;
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 = 'grid',
209
+ layout = "grid",
207
210
  disabled,
208
211
  enableBackgroundRemoval = false,
209
212
  defaultBackgroundRemovalEnabled = false,
210
213
  withDownload = true,
211
214
  withImagePreview = false,
212
- placeholder = 'Drag and drop files here, or click to select',
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: '', name: '' });
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 === 'function') {
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: 'success' as const, progress: 100 };
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((file) => getUploadToken(file) === token);
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: 'uploading',
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((file) => getUploadToken(file) === token);
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: 'uploading',
363
+ status: "uploading",
357
364
  };
358
365
  return n;
359
366
  });
360
367
  });
361
368
  setFilesAndEmit((prev) => {
362
- const index = prev.findIndex((file) => getUploadToken(file) === token);
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: 'success',
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((file) => getUploadToken(file) === token);
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 === 'object' &&
385
- 'message' in (err as Record<string, unknown>)
395
+ typeof err === "object" &&
396
+ "message" in (err as Record<string, unknown>)
386
397
  ? String((err as { message?: unknown }).message)
387
- : 'Upload failed';
388
- n[index] = { ...n[index], status: 'error', errorMessage: msg };
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 === 'error' && !!f.file);
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 === 'number'
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 ? 'uploading' : 'idle',
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 === 'number'
472
- ? Math.max(0, maxFiles - ((isControlled ? value : files)?.length ?? 0))
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
- 'w-full border border-dashed rounded-md p-4 text-sm transition-colors bg-background',
518
- 'hover:border-foreground/50',
519
- isDragActive && 'border-primary',
520
- isDragReject && 'border-destructive',
521
- (disabled || disabledBecauseFull) && 'opacity-50 pointer-events-none',
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: 'grid' | 'list';
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 === 'error' ? fr.errorMessage : undefined;
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
- 'flex items-center gap-3 border rounded-md p-2 bg-card',
574
- layout === 'grid' ? 'flex-col items-stretch' : 'flex-row',
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 === 'grid' ? 'self-center' : '')}>
591
+ <div className={cn(layout === "grid" ? "self-center" : "")}>
578
592
  <div
579
593
  className={cn(
580
- 'relative overflow-hidden bg-muted/40 border rounded-md flex items-center justify-center',
581
- layout === 'grid' ? 'h-28 w-28' : 'h-16 w-16',
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 === 'uploading' ? (
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 === 'success' ? (
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 === 'error' ? (
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('min-w-0 flex-1', layout === 'grid' ? 'mt-2' : '')}
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 === 'error' ? (
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, '_blank', 'noopener,noreferrer');
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 === 'uploading' ? (
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('space-y-3', className)}>
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
- ? 'File limit reached'
774
+ ? "File limit reached"
759
775
  : isDragActive
760
- ? 'Drop the files here'
776
+ ? "Drop the files here"
761
777
  : placeholder}
762
778
  </div>
763
779
  <div className="text-xs">
764
- {accept ? 'Specific file types only' : 'Any file type'}
765
- {typeof maxFiles === 'number'
766
- ? ` • Up to ${maxFiles} file${maxFiles > 1 ? 's' : ''}`
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 !== 'error' || !f.file)
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 === 'grid' ? 'flex flex-wrap gap-3' : 'flex flex-col gap-2',
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: '', name: '' });
838
+ setPreviewItem({ src: "", name: "" });
823
839
  }
824
840
  }}
825
841
  >