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