react-file-upload-ui 0.1.0 → 0.1.2

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.mjs CHANGED
@@ -1,16 +1,843 @@
1
1
  // src/index.tsx
2
+ import { useRef } from "react";
3
+
4
+ // src/components/ErrorComponent.tsx
5
+ import { useEffect, useState } from "react";
2
6
  import { jsx, jsxs } from "react/jsx-runtime";
3
- var ReactFileUploaderUI = ({
7
+ function ErrorComponent({ error, setError }) {
8
+ const [isVisible, setIsVisible] = useState(true);
9
+ useEffect(() => {
10
+ setIsVisible(true);
11
+ const fadeTimer = setTimeout(() => {
12
+ setIsVisible(false);
13
+ }, 1e3);
14
+ const clearTimer = setTimeout(() => {
15
+ setError("");
16
+ }, 1e3);
17
+ return () => {
18
+ clearTimeout(fadeTimer);
19
+ clearTimeout(clearTimer);
20
+ };
21
+ }, [error, setError]);
22
+ return /* @__PURE__ */ jsxs(
23
+ "div",
24
+ {
25
+ className: `mt-3 absolute bottom-0 left-0 w-full bg-red-50 border border-red-200 rounded-lg p-3 flex items-start gap-2 transition-opacity duration-500 ${isVisible ? "opacity-100" : "opacity-0"}`,
26
+ children: [
27
+ /* @__PURE__ */ jsx(
28
+ "svg",
29
+ {
30
+ className: "w-4 h-4 text-red-500 mt-0.5 flex-shrink-0",
31
+ fill: "none",
32
+ stroke: "currentColor",
33
+ viewBox: "0 0 24 24",
34
+ children: /* @__PURE__ */ jsx(
35
+ "path",
36
+ {
37
+ strokeLinecap: "round",
38
+ strokeLinejoin: "round",
39
+ strokeWidth: 2,
40
+ d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
41
+ }
42
+ )
43
+ }
44
+ ),
45
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-red-700 flex-1", children: error }),
46
+ /* @__PURE__ */ jsx(
47
+ "button",
48
+ {
49
+ onClick: () => setError(""),
50
+ className: "text-red-500 hover:text-red-700",
51
+ type: "button",
52
+ children: /* @__PURE__ */ jsx(
53
+ "svg",
54
+ {
55
+ className: "w-4 h-4",
56
+ fill: "none",
57
+ stroke: "currentColor",
58
+ viewBox: "0 0 24 24",
59
+ children: /* @__PURE__ */ jsx(
60
+ "path",
61
+ {
62
+ strokeLinecap: "round",
63
+ strokeLinejoin: "round",
64
+ strokeWidth: 2,
65
+ d: "M6 18L18 6M6 6l12 12"
66
+ }
67
+ )
68
+ }
69
+ )
70
+ }
71
+ )
72
+ ]
73
+ }
74
+ );
75
+ }
76
+ var ErrorComponent_default = ErrorComponent;
77
+
78
+ // src/components/ProgressBarComponent.tsx
79
+ import { jsx as jsx2 } from "react/jsx-runtime";
80
+ function ProgressBarComponent({ uploadProgress }) {
81
+ return /* @__PURE__ */ jsx2(
82
+ "div",
83
+ {
84
+ className: "bg-blue-600 h-1 -mt-1 rounded-full transition-all duration-300",
85
+ style: { width: `${uploadProgress}%` }
86
+ }
87
+ );
88
+ }
89
+ var ProgressBarComponent_default = ProgressBarComponent;
90
+
91
+ // src/components/FileList/index.tsx
92
+ import { useState as useState2, useEffect as useEffect2 } from "react";
93
+
94
+ // src/components/FileList/MainPreview.tsx
95
+ import { jsx as jsx3 } from "react/jsx-runtime";
96
+ function MainPreview({ selectedFile }) {
97
+ if (!selectedFile) {
98
+ return null;
99
+ }
100
+ return /* @__PURE__ */ jsx3("div", { className: "w-[180px] h-[180px] p-1.5 border border-gray-100 rounded-xl", children: /* @__PURE__ */ jsx3(
101
+ "img",
102
+ {
103
+ src: selectedFile.preview,
104
+ alt: selectedFile.name,
105
+ className: "w-full h-full object-cover rounded-lg"
106
+ }
107
+ ) });
108
+ }
109
+ var MainPreview_default = MainPreview;
110
+
111
+ // src/components/svg/CrossIcon.tsx
112
+ import { jsx as jsx4 } from "react/jsx-runtime";
113
+ function CrossIcon({ ...props }) {
114
+ return /* @__PURE__ */ jsx4(
115
+ "svg",
116
+ {
117
+ className: "w-3 h-3",
118
+ fill: "none",
119
+ stroke: "currentColor",
120
+ viewBox: "0 0 24 24",
121
+ ...props,
122
+ children: /* @__PURE__ */ jsx4(
123
+ "path",
124
+ {
125
+ strokeLinecap: "round",
126
+ strokeLinejoin: "round",
127
+ strokeWidth: 2,
128
+ d: "M6 18L18 6M6 6l12 12"
129
+ }
130
+ )
131
+ }
132
+ );
133
+ }
134
+ var CrossIcon_default = CrossIcon;
135
+
136
+ // src/components/svg/PlusIcon.tsx
137
+ import { jsx as jsx5 } from "react/jsx-runtime";
138
+ function PlusIcon({ ...props }) {
139
+ return /* @__PURE__ */ jsx5(
140
+ "svg",
141
+ {
142
+ className: "w-8 h-8",
143
+ fill: "none",
144
+ stroke: "currentColor",
145
+ viewBox: "0 0 24 24",
146
+ ...props,
147
+ children: /* @__PURE__ */ jsx5(
148
+ "path",
149
+ {
150
+ strokeLinecap: "round",
151
+ strokeLinejoin: "round",
152
+ strokeWidth: 2,
153
+ d: "M12 4v16m8-8H4"
154
+ }
155
+ )
156
+ }
157
+ );
158
+ }
159
+ var PlusIcon_default = PlusIcon;
160
+
161
+ // src/components/FileList/AddFileButton.tsx
162
+ import { jsx as jsx6 } from "react/jsx-runtime";
163
+ function AddFileButton({ onAddFile }) {
164
+ return /* @__PURE__ */ jsx6(
165
+ "button",
166
+ {
167
+ onClick: onAddFile,
168
+ className: "shrink-0 cursor-pointer w-16 h-16 rounded-lg border-2 border-dashed border-gray-300 flex items-center justify-center text-gray-400 hover:text-blue-500 hover:border-blue-500 hover:bg-blue-50 transition-all",
169
+ type: "button",
170
+ children: /* @__PURE__ */ jsx6(PlusIcon_default, {})
171
+ }
172
+ );
173
+ }
174
+ var AddFileButton_default = AddFileButton;
175
+
176
+ // src/components/svg/CheckIcon.tsx
177
+ import { jsx as jsx7 } from "react/jsx-runtime";
178
+ function CheckIcon({ ...props }) {
179
+ return /* @__PURE__ */ jsx7(
180
+ "svg",
181
+ {
182
+ className: "w-6 h-6",
183
+ fill: "none",
184
+ stroke: "currentColor",
185
+ viewBox: "0 0 24 24",
186
+ ...props,
187
+ children: /* @__PURE__ */ jsx7(
188
+ "path",
189
+ {
190
+ strokeLinecap: "round",
191
+ strokeLinejoin: "round",
192
+ strokeWidth: 2,
193
+ d: "M5 13l4 4L19 7"
194
+ }
195
+ )
196
+ }
197
+ );
198
+ }
199
+ var CheckIcon_default = CheckIcon;
200
+
201
+ // src/components/svg/StarIcon.tsx
202
+ import { jsx as jsx8 } from "react/jsx-runtime";
203
+ function StarIcon({ ...props }) {
204
+ return /* @__PURE__ */ jsx8("svg", { className: "w-6 h-6", fill: "currentColor", viewBox: "0 0 20 20", ...props, children: /* @__PURE__ */ jsx8("path", { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" }) });
205
+ }
206
+ var StarIcon_default = StarIcon;
207
+
208
+ // src/components/FileList/SetDefaultButton.tsx
209
+ import { jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
210
+ function SetDefaultButton({
211
+ selectedFile,
212
+ onSetDefault
213
+ }) {
214
+ const isDefault = selectedFile.isDefault;
215
+ return /* @__PURE__ */ jsxs2(
216
+ "button",
217
+ {
218
+ onClick: () => onSetDefault(selectedFile.id),
219
+ className: `w-40 flex items-center justify-center cursor-pointer gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 ${isDefault ? "bg-green-100 text-green-700 border border-green-200 hover:bg-green-200" : "bg-white border border-gray-200 text-gray-600 hover:border-blue-500 hover:text-blue-600 hover:bg-blue-50"}`,
220
+ type: "button",
221
+ children: [
222
+ isDefault ? /* @__PURE__ */ jsx9(CheckIcon_default, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx9(StarIcon_default, { className: "w-4 h-4 opacity-60" }),
223
+ /* @__PURE__ */ jsx9("span", { children: isDefault ? "Default Image" : "Set as Default" })
224
+ ]
225
+ }
226
+ );
227
+ }
228
+ var SetDefaultButton_default = SetDefaultButton;
229
+
230
+ // src/components/svg/TrashIcon.tsx
231
+ import { jsx as jsx10 } from "react/jsx-runtime";
232
+ function TrashIcon({ ...props }) {
233
+ return /* @__PURE__ */ jsx10(
234
+ "svg",
235
+ {
236
+ className: "w-6 h-6",
237
+ fill: "none",
238
+ stroke: "currentColor",
239
+ viewBox: "0 0 24 24",
240
+ ...props,
241
+ children: /* @__PURE__ */ jsx10(
242
+ "path",
243
+ {
244
+ strokeLinecap: "round",
245
+ strokeLinejoin: "round",
246
+ strokeWidth: 2,
247
+ d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
248
+ }
249
+ )
250
+ }
251
+ );
252
+ }
253
+ var TrashIcon_default = TrashIcon;
254
+
255
+ // src/components/FileList/ClearAllFile.tsx
256
+ import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
257
+ function ClearAllFile({ onClearAll }) {
258
+ return /* @__PURE__ */ jsxs3(
259
+ "button",
260
+ {
261
+ onClick: onClearAll,
262
+ className: "flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium bg-white border border-gray-200 text-gray-600 hover:border-red-500 hover:text-red-600 hover:bg-red-50 transition-all duration-200 w-full justify-center",
263
+ title: "Clear all files",
264
+ children: [
265
+ /* @__PURE__ */ jsx11(TrashIcon_default, { className: "w-4 h-4" }),
266
+ /* @__PURE__ */ jsx11("span", { children: "Clear All" })
267
+ ]
268
+ }
269
+ );
270
+ }
271
+ var ClearAllFile_default = ClearAllFile;
272
+
273
+ // src/components/FileList/index.tsx
274
+ import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
275
+ var FileList = ({
276
+ files,
277
+ removeFile,
278
+ clearAll,
279
+ setAsDefault,
280
+ onAddFile
281
+ }) => {
282
+ const [selectedFileId, setSelectedFileId] = useState2(null);
283
+ useEffect2(() => {
284
+ if (files.length > 0) {
285
+ if (!selectedFileId || !files.find((f) => f.id === selectedFileId)) {
286
+ setSelectedFileId(files[0].id);
287
+ }
288
+ } else {
289
+ setSelectedFileId(null);
290
+ }
291
+ }, [files, selectedFileId]);
292
+ if (files.length === 0) {
293
+ return null;
294
+ }
295
+ const selectedFile = files.find((f) => f.id === selectedFileId) || files[0];
296
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-4 p-3", children: [
297
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-center gap-6", children: [
298
+ /* @__PURE__ */ jsx12(MainPreview_default, { selectedFile }),
299
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", children: [
300
+ /* @__PURE__ */ jsx12(
301
+ SetDefaultButton_default,
302
+ {
303
+ selectedFile,
304
+ onSetDefault: setAsDefault
305
+ }
306
+ ),
307
+ /* @__PURE__ */ jsx12(ClearAllFile_default, { onClearAll: clearAll })
308
+ ] })
309
+ ] }),
310
+ /* @__PURE__ */ jsxs4("div", { className: "w-full flex flex-wrap items-center gap-1", children: [
311
+ files.map((file) => /* @__PURE__ */ jsxs4("div", { className: "relative group/thumb", children: [
312
+ /* @__PURE__ */ jsxs4(
313
+ "button",
314
+ {
315
+ onClick: () => setSelectedFileId(file.id),
316
+ className: `relative cursor-pointer w-16 h-16 p-0.5 rounded-lg overflow-hidden border transition-all ${selectedFileId === file.id ? "border-gray-300" : "border-transparent hover:border-gray-100"}`,
317
+ children: [
318
+ file.preview ? /* @__PURE__ */ jsx12(
319
+ "img",
320
+ {
321
+ src: file.preview,
322
+ alt: file.name,
323
+ className: "w-full h-full object-cover"
324
+ }
325
+ ) : /* @__PURE__ */ jsx12("div", { className: "w-full h-full bg-gray-100 flex items-center justify-center", children: /* @__PURE__ */ jsx12(
326
+ "svg",
327
+ {
328
+ className: "w-6 h-6 text-gray-400",
329
+ fill: "none",
330
+ stroke: "currentColor",
331
+ viewBox: "0 0 24 24",
332
+ children: /* @__PURE__ */ jsx12(
333
+ "path",
334
+ {
335
+ strokeLinecap: "round",
336
+ strokeLinejoin: "round",
337
+ strokeWidth: 2,
338
+ d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
339
+ }
340
+ )
341
+ }
342
+ ) }),
343
+ file.isDefault && /* @__PURE__ */ jsx12("div", { className: "absolute top-1 left-1 p-0.5 bg-yellow-400 rounded-full", children: /* @__PURE__ */ jsx12(
344
+ "svg",
345
+ {
346
+ className: "w-3 h-3 text-white",
347
+ fill: "currentColor",
348
+ viewBox: "0 0 20 20",
349
+ children: /* @__PURE__ */ jsx12("path", { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" })
350
+ }
351
+ ) })
352
+ ]
353
+ }
354
+ ),
355
+ /* @__PURE__ */ jsx12(
356
+ "button",
357
+ {
358
+ onClick: (e) => {
359
+ e.stopPropagation();
360
+ removeFile(file.id);
361
+ },
362
+ className: "absolute cursor-pointer -top-2 -right-2 w-5 h-5 bg-red-500 text-white rounded-full flex items-center justify-center opacity-0 group-hover/thumb:opacity-100 transition-opacity shadow-sm hover:bg-red-600 z-10",
363
+ title: "Remove file",
364
+ children: /* @__PURE__ */ jsx12(CrossIcon_default, {})
365
+ }
366
+ )
367
+ ] }, file.id)),
368
+ /* @__PURE__ */ jsx12(AddFileButton_default, { onAddFile })
369
+ ] })
370
+ ] });
371
+ };
372
+ var FileList_default = FileList;
373
+
374
+ // src/hooks/useDragging.ts
375
+ import { useState as useState3 } from "react";
376
+ function useDragging({ disabled }) {
377
+ const [isDragging, setIsDragging] = useState3(false);
378
+ const handleDragEnter = (e) => {
379
+ e.preventDefault();
380
+ e.stopPropagation();
381
+ if (!disabled) {
382
+ setIsDragging(true);
383
+ }
384
+ };
385
+ const handleDragLeave = (e) => {
386
+ e.preventDefault();
387
+ e.stopPropagation();
388
+ const rect = e.currentTarget.getBoundingClientRect();
389
+ const x = e.clientX;
390
+ const y = e.clientY;
391
+ if (x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom) {
392
+ setIsDragging(false);
393
+ }
394
+ };
395
+ const handleDragOver = (e) => {
396
+ e.preventDefault();
397
+ e.stopPropagation();
398
+ };
399
+ return {
400
+ isDragging,
401
+ setIsDragging,
402
+ handleDragEnter,
403
+ handleDragLeave,
404
+ handleDragOver
405
+ };
406
+ }
407
+ var useDragging_default = useDragging;
408
+
409
+ // src/hooks/useFileHandler.ts
410
+ import { useState as useState4, useCallback, useEffect as useEffect3 } from "react";
411
+
412
+ // src/utils/format.ts
413
+ function formatFileSize(bytes) {
414
+ if (bytes === 0) {
415
+ return "0 Bytes";
416
+ }
417
+ const k = 1024;
418
+ const sizes = ["Bytes", "KB", "MB", "GB"];
419
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
420
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
421
+ }
422
+
423
+ // src/utils/validation.ts
424
+ var validateFiles = (newFiles, currentFiles, maxFiles, maxSize, accept) => {
425
+ let validFiles = newFiles;
426
+ const errors = [];
427
+ if (currentFiles.length + validFiles.length > maxFiles) {
428
+ errors.push(`Maximum ${maxFiles} files allowed`);
429
+ validFiles = validFiles.slice(0, maxFiles - currentFiles.length);
430
+ }
431
+ const oversizedFiles = validFiles.filter((file) => file.size > maxSize);
432
+ if (oversizedFiles.length > 0) {
433
+ errors.push(
434
+ `${oversizedFiles.length} file(s) exceed ${formatFileSize(maxSize)}`
435
+ );
436
+ validFiles = validFiles.filter((file) => file.size <= maxSize);
437
+ }
438
+ if (accept) {
439
+ const acceptedTypes = accept.split(",").map((type) => type.trim());
440
+ const invalidFiles = validFiles.filter((file) => {
441
+ return !acceptedTypes.some((type) => {
442
+ if (type.startsWith(".")) {
443
+ return file.name.toLowerCase().endsWith(type.toLowerCase());
444
+ }
445
+ if (type.endsWith("/*")) {
446
+ const mainType = type.split("/")[0];
447
+ return file.type.startsWith(mainType);
448
+ }
449
+ return file.type === type;
450
+ });
451
+ });
452
+ if (invalidFiles.length > 0) {
453
+ errors.push(`${invalidFiles.length} file(s) have invalid type`);
454
+ }
455
+ validFiles = validFiles.filter((file) => {
456
+ return acceptedTypes.some((type) => {
457
+ if (type.startsWith(".")) {
458
+ return file.name.toLowerCase().endsWith(type.toLowerCase());
459
+ }
460
+ if (type.endsWith("/*")) {
461
+ const mainType = type.split("/")[0];
462
+ return file.type.startsWith(mainType);
463
+ }
464
+ return file.type === type;
465
+ });
466
+ });
467
+ }
468
+ return { validFiles, errors };
469
+ };
470
+
471
+ // src/hooks/useFileHandler.ts
472
+ var useFileHandler = ({
473
+ maxFiles,
474
+ maxSize,
4
475
  accept,
5
- multiple
476
+ multiple,
477
+ onFilesSelected,
478
+ onError,
479
+ initialFiles
6
480
  }) => {
7
- return /* @__PURE__ */ jsxs("div", { children: [
8
- /* @__PURE__ */ jsx("input", { type: "file", accept, multiple }),
9
- "Test Package"
481
+ const transformToFileData = (files2) => {
482
+ return files2.map((file) => ({
483
+ id: file.id,
484
+ isDefault: file.isDefault || false,
485
+ file: file.rawFile,
486
+ url: file.preview,
487
+ name: file.name
488
+ }));
489
+ };
490
+ const extractNameFromUrl = (url) => {
491
+ try {
492
+ const pathname = new URL(url).pathname;
493
+ const parts = pathname.split("/").filter(Boolean);
494
+ return parts[parts.length - 1] || url;
495
+ } catch (e) {
496
+ return url;
497
+ }
498
+ };
499
+ const [files, setFiles] = useState4(() => {
500
+ if (!initialFiles || initialFiles.length === 0) return [];
501
+ return initialFiles.map((f) => {
502
+ var _a, _b, _c;
503
+ return {
504
+ id: (_a = f.id) != null ? _a : `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
505
+ name: (_b = f.name) != null ? _b : extractNameFromUrl(f.url),
506
+ size: 0,
507
+ type: (_c = f.type) != null ? _c : "",
508
+ preview: f.url,
509
+ isDefault: !!f.isDefault,
510
+ rawFile: void 0
511
+ };
512
+ });
513
+ });
514
+ const [uploadProgress, setUploadProgress] = useState4(0);
515
+ const [error, setError] = useState4("");
516
+ useEffect3(() => {
517
+ if (files.length > 0 && onFilesSelected) {
518
+ onFilesSelected(transformToFileData(files));
519
+ }
520
+ }, []);
521
+ const simulateUpload = useCallback(() => {
522
+ setUploadProgress(0);
523
+ const interval = setInterval(() => {
524
+ setUploadProgress((prev) => {
525
+ if (prev >= 100) {
526
+ clearInterval(interval);
527
+ return 100;
528
+ }
529
+ return prev + 10;
530
+ });
531
+ }, 150);
532
+ }, []);
533
+ const handleFiles = useCallback(
534
+ (newFiles) => {
535
+ setError("");
536
+ const { validFiles, errors } = validateFiles(
537
+ newFiles,
538
+ files,
539
+ maxFiles,
540
+ maxSize,
541
+ accept
542
+ );
543
+ if (errors.length > 0) {
544
+ const errorMessage = errors.join(". ");
545
+ setError(errorMessage);
546
+ onError == null ? void 0 : onError(errorMessage);
547
+ }
548
+ if (validFiles.length === 0) {
549
+ return;
550
+ }
551
+ const filesWithMeta = validFiles.map((file) => {
552
+ var _a;
553
+ const id = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
554
+ const isImage = (_a = file.type) == null ? void 0 : _a.startsWith("image/");
555
+ const preview = isImage ? URL.createObjectURL(file) : void 0;
556
+ const fileWithMeta = {
557
+ id,
558
+ name: file.name,
559
+ size: file.size,
560
+ type: file.type,
561
+ preview,
562
+ isDefault: false,
563
+ rawFile: file
564
+ };
565
+ return fileWithMeta;
566
+ });
567
+ const updatedFiles = multiple ? [...files, ...filesWithMeta] : filesWithMeta;
568
+ setFiles(updatedFiles);
569
+ onFilesSelected == null ? void 0 : onFilesSelected(transformToFileData(updatedFiles));
570
+ if (filesWithMeta.length > 0) {
571
+ simulateUpload();
572
+ }
573
+ },
574
+ [
575
+ files,
576
+ maxFiles,
577
+ maxSize,
578
+ accept,
579
+ multiple,
580
+ onFilesSelected,
581
+ onError,
582
+ simulateUpload
583
+ ]
584
+ );
585
+ const handleFileInput = useCallback(
586
+ (e) => {
587
+ if (e.target.files) {
588
+ const selectedFiles = Array.from(e.target.files);
589
+ handleFiles(selectedFiles);
590
+ }
591
+ },
592
+ [handleFiles]
593
+ );
594
+ const removeFile = useCallback(
595
+ (id) => {
596
+ const fileToRemove = files.find((f) => f.id === id);
597
+ if ((fileToRemove == null ? void 0 : fileToRemove.preview) && fileToRemove.rawFile) {
598
+ URL.revokeObjectURL(fileToRemove.preview);
599
+ }
600
+ const updatedFiles = files.filter((f) => f.id !== id);
601
+ setFiles(updatedFiles);
602
+ onFilesSelected == null ? void 0 : onFilesSelected(transformToFileData(updatedFiles));
603
+ setError("");
604
+ },
605
+ [files, onFilesSelected]
606
+ );
607
+ const clearAll = useCallback(() => {
608
+ files.forEach((file) => {
609
+ if (file.preview && file.rawFile) {
610
+ URL.revokeObjectURL(file.preview);
611
+ }
612
+ });
613
+ setFiles([]);
614
+ onFilesSelected == null ? void 0 : onFilesSelected(transformToFileData([]));
615
+ setError("");
616
+ setUploadProgress(0);
617
+ }, [files, onFilesSelected]);
618
+ const setAsDefault = useCallback(
619
+ (id) => {
620
+ const updatedFiles = files.map((file) => {
621
+ if (file.id === id && file.isDefault) {
622
+ file.isDefault = false;
623
+ } else {
624
+ file.isDefault = file.id === id;
625
+ }
626
+ return file;
627
+ });
628
+ setFiles([...updatedFiles]);
629
+ onFilesSelected == null ? void 0 : onFilesSelected(transformToFileData(updatedFiles));
630
+ },
631
+ [files, onFilesSelected]
632
+ );
633
+ return {
634
+ files,
635
+ uploadProgress,
636
+ error,
637
+ setError,
638
+ handleFiles,
639
+ handleFileInput,
640
+ removeFile,
641
+ clearAll,
642
+ setAsDefault
643
+ };
644
+ };
645
+ var useFileHandler_default = useFileHandler;
646
+
647
+ // src/utils/input-handler.ts
648
+ var handleDrop = (e, setIsDragging, handleFiles, disabled) => {
649
+ e.preventDefault();
650
+ e.stopPropagation();
651
+ setIsDragging(false);
652
+ if (disabled) {
653
+ return;
654
+ }
655
+ const droppedFiles = Array.from(e.dataTransfer.files);
656
+ handleFiles(droppedFiles);
657
+ };
658
+
659
+ // src/components/svg/UploadIcon.tsx
660
+ import { jsx as jsx13 } from "react/jsx-runtime";
661
+ function UploadIcon({ ...props }) {
662
+ return /* @__PURE__ */ jsx13(
663
+ "svg",
664
+ {
665
+ fill: "none",
666
+ stroke: "currentColor",
667
+ viewBox: "0 0 24 24",
668
+ ...props,
669
+ children: /* @__PURE__ */ jsx13(
670
+ "path",
671
+ {
672
+ strokeLinecap: "round",
673
+ strokeLinejoin: "round",
674
+ strokeWidth: 2,
675
+ d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
676
+ }
677
+ )
678
+ }
679
+ );
680
+ }
681
+ var UploadIcon_default = UploadIcon;
682
+
683
+ // src/components/LabelUI.tsx
684
+ import { jsx as jsx14, jsxs as jsxs5 } from "react/jsx-runtime";
685
+ function LabelUI({
686
+ accept,
687
+ isDragging,
688
+ maxSize = 10 * 1024 * 1024,
689
+ maxFiles = 10,
690
+ disabled = false
691
+ }) {
692
+ return /* @__PURE__ */ jsxs5("div", { className: "p-6", children: [
693
+ /* @__PURE__ */ jsx14("div", { className: "mb-3 flex justify-center", children: /* @__PURE__ */ jsx14("div", { children: /* @__PURE__ */ jsx14(
694
+ UploadIcon_default,
695
+ {
696
+ className: `w-8 h-8 transition-colors duration-200 ${isDragging && !disabled ? "text-blue-500" : "text-gray-400"}`
697
+ }
698
+ ) }) }),
699
+ /* @__PURE__ */ jsx14("h3", { className: "text-lg font-semibold text-gray-700 mb-1", children: isDragging && !disabled ? "Drop files here" : "Drag & drop files" }),
700
+ /* @__PURE__ */ jsx14("p", { className: "text-sm text-gray-500 mb-3", children: "or click to browse" }),
701
+ /* @__PURE__ */ jsxs5("div", { className: "text-xs text-gray-400 space-y-1", children: [
702
+ accept && /* @__PURE__ */ jsxs5("p", { children: [
703
+ "Accepted: ",
704
+ accept
705
+ ] }),
706
+ /* @__PURE__ */ jsxs5("p", { children: [
707
+ "Max size: ",
708
+ formatFileSize(maxSize),
709
+ " \u2022 Max files: ",
710
+ maxFiles
711
+ ] })
712
+ ] })
10
713
  ] });
714
+ }
715
+ var LabelUI_default = LabelUI;
716
+
717
+ // src/index.tsx
718
+ import { jsx as jsx15, jsxs as jsxs6 } from "react/jsx-runtime";
719
+ var ReactFileUploaderUI = ({
720
+ accept,
721
+ multiple = true,
722
+ maxSize = 10 * 1024 * 1024,
723
+ maxFiles = 10,
724
+ onFilesSelected,
725
+ onError,
726
+ disabled = false,
727
+ className = "",
728
+ defaultFiles
729
+ }) => {
730
+ const fileInputRef = useRef(null);
731
+ const {
732
+ files,
733
+ uploadProgress,
734
+ error,
735
+ setError,
736
+ handleFiles,
737
+ handleFileInput,
738
+ removeFile,
739
+ clearAll,
740
+ setAsDefault
741
+ } = useFileHandler_default({
742
+ maxFiles,
743
+ maxSize,
744
+ accept,
745
+ multiple,
746
+ onFilesSelected,
747
+ onError,
748
+ initialFiles: defaultFiles
749
+ });
750
+ const {
751
+ isDragging,
752
+ setIsDragging,
753
+ handleDragEnter,
754
+ handleDragLeave,
755
+ handleDragOver
756
+ } = useDragging_default({ disabled });
757
+ const onDrop = (e) => {
758
+ handleDrop(e, setIsDragging, handleFiles, disabled);
759
+ };
760
+ const hasFiles = files.length > 0;
761
+ return /* @__PURE__ */ jsxs6(
762
+ "div",
763
+ {
764
+ className: `w-full border-gray-200 border border-dashed rounded-xl relative ${className}`,
765
+ children: [
766
+ /* @__PURE__ */ jsxs6(
767
+ "div",
768
+ {
769
+ className: `
770
+ relative text-center
771
+ transition-all duration-200 ease-in-out
772
+ ${disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"}
773
+ ${isDragging && !disabled ? "border-blue-500 bg-blue-50 scale-[1.01]" : "border-gray-300 bg-white hover:border-blue-400 hover:bg-gray-50"}
774
+ `,
775
+ onDragEnter: handleDragEnter,
776
+ onDragOver: handleDragOver,
777
+ onDragLeave: handleDragLeave,
778
+ onDrop,
779
+ onClick: () => {
780
+ var _a;
781
+ return !disabled && ((_a = fileInputRef.current) == null ? void 0 : _a.click());
782
+ },
783
+ role: "button",
784
+ "aria-label": "File upload area",
785
+ tabIndex: disabled ? -1 : 0,
786
+ onKeyDown: (e) => {
787
+ var _a;
788
+ if ((e.key === "Enter" || e.key === " ") && !disabled) {
789
+ (_a = fileInputRef.current) == null ? void 0 : _a.click();
790
+ }
791
+ },
792
+ children: [
793
+ /* @__PURE__ */ jsx15(
794
+ "input",
795
+ {
796
+ ref: fileInputRef,
797
+ type: "file",
798
+ accept,
799
+ multiple,
800
+ onChange: handleFileInput,
801
+ className: "hidden",
802
+ disabled,
803
+ "aria-label": "File input"
804
+ }
805
+ ),
806
+ !hasFiles && /* @__PURE__ */ jsx15(
807
+ LabelUI_default,
808
+ {
809
+ accept,
810
+ multiple,
811
+ maxSize,
812
+ maxFiles,
813
+ disabled,
814
+ isDragging
815
+ }
816
+ )
817
+ ]
818
+ }
819
+ ),
820
+ /* @__PURE__ */ jsx15(
821
+ FileList_default,
822
+ {
823
+ files,
824
+ uploadProgress,
825
+ removeFile,
826
+ clearAll,
827
+ setAsDefault,
828
+ onAddFile: () => {
829
+ var _a;
830
+ return !disabled && ((_a = fileInputRef.current) == null ? void 0 : _a.click());
831
+ }
832
+ }
833
+ ),
834
+ files.length > 0 && uploadProgress < 100 && /* @__PURE__ */ jsx15(ProgressBarComponent_default, { uploadProgress }),
835
+ error && /* @__PURE__ */ jsx15(ErrorComponent_default, { error, setError })
836
+ ]
837
+ }
838
+ );
11
839
  };
12
840
  var index_default = ReactFileUploaderUI;
13
841
  export {
14
- ReactFileUploaderUI,
15
842
  index_default as default
16
843
  };