@moontra/moonui-pro 2.10.0 → 2.11.1
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.d.ts +20 -0
- package/dist/index.mjs +93 -54
- package/package.json +1 -1
- package/src/components/file-upload/index.tsx +133 -57
package/dist/index.d.ts
CHANGED
|
@@ -1774,6 +1774,26 @@ interface FileUploadProProps {
|
|
|
1774
1774
|
uploadStrategy?: 'direct' | 'presigned' | 'multipart';
|
|
1775
1775
|
endpoint?: string;
|
|
1776
1776
|
headers?: Record<string, string>;
|
|
1777
|
+
serviceConfig?: {
|
|
1778
|
+
type: 'aws-s3' | 'cloudinary' | 'firebase' | 'custom';
|
|
1779
|
+
awsConfig?: {
|
|
1780
|
+
region: string;
|
|
1781
|
+
bucket: string;
|
|
1782
|
+
accessKeyId?: string;
|
|
1783
|
+
secretAccessKey?: string;
|
|
1784
|
+
sessionToken?: string;
|
|
1785
|
+
};
|
|
1786
|
+
cloudinaryConfig?: {
|
|
1787
|
+
cloudName: string;
|
|
1788
|
+
uploadPreset: string;
|
|
1789
|
+
apiKey?: string;
|
|
1790
|
+
apiSecret?: string;
|
|
1791
|
+
};
|
|
1792
|
+
firebaseConfig?: {
|
|
1793
|
+
storageBucket: string;
|
|
1794
|
+
folder?: string;
|
|
1795
|
+
};
|
|
1796
|
+
};
|
|
1777
1797
|
onUpload?: (files: FileUploadItem[]) => Promise<void>;
|
|
1778
1798
|
onProgress?: (fileId: string, progress: number) => void;
|
|
1779
1799
|
onComplete?: (fileId: string, result: any) => void;
|
package/dist/index.mjs
CHANGED
|
@@ -58771,23 +58771,54 @@ var generateFileHash = async (file) => {
|
|
|
58771
58771
|
};
|
|
58772
58772
|
var createImagePreview = (file) => {
|
|
58773
58773
|
return new Promise((resolve) => {
|
|
58774
|
-
const
|
|
58775
|
-
|
|
58776
|
-
|
|
58777
|
-
|
|
58778
|
-
|
|
58779
|
-
|
|
58780
|
-
|
|
58781
|
-
|
|
58782
|
-
|
|
58774
|
+
const reader = new FileReader();
|
|
58775
|
+
reader.onload = (e) => {
|
|
58776
|
+
const result = e.target?.result;
|
|
58777
|
+
const img = new Image();
|
|
58778
|
+
img.onload = () => {
|
|
58779
|
+
const canvas = document.createElement("canvas");
|
|
58780
|
+
const ctx = canvas.getContext("2d");
|
|
58781
|
+
const maxThumbSize = 400;
|
|
58782
|
+
let thumbWidth = img.width;
|
|
58783
|
+
let thumbHeight = img.height;
|
|
58784
|
+
if (thumbWidth > maxThumbSize || thumbHeight > maxThumbSize) {
|
|
58785
|
+
const aspectRatio = img.width / img.height;
|
|
58786
|
+
if (aspectRatio > 1) {
|
|
58787
|
+
thumbWidth = maxThumbSize;
|
|
58788
|
+
thumbHeight = maxThumbSize / aspectRatio;
|
|
58789
|
+
} else {
|
|
58790
|
+
thumbHeight = maxThumbSize;
|
|
58791
|
+
thumbWidth = maxThumbSize * aspectRatio;
|
|
58792
|
+
}
|
|
58793
|
+
}
|
|
58794
|
+
canvas.width = thumbWidth;
|
|
58795
|
+
canvas.height = thumbHeight;
|
|
58796
|
+
ctx.drawImage(img, 0, 0, thumbWidth, thumbHeight);
|
|
58797
|
+
const thumbnail = canvas.toDataURL("image/jpeg", 0.9);
|
|
58798
|
+
resolve({
|
|
58799
|
+
type: "image",
|
|
58800
|
+
url: result,
|
|
58801
|
+
thumbnail,
|
|
58802
|
+
dimensions: { width: img.width, height: img.height }
|
|
58803
|
+
});
|
|
58804
|
+
};
|
|
58805
|
+
img.onerror = () => {
|
|
58806
|
+
resolve({
|
|
58807
|
+
type: "image",
|
|
58808
|
+
url: result,
|
|
58809
|
+
thumbnail: result
|
|
58810
|
+
});
|
|
58811
|
+
};
|
|
58812
|
+
img.src = result;
|
|
58783
58813
|
};
|
|
58784
|
-
|
|
58814
|
+
reader.onerror = () => {
|
|
58815
|
+
const url = URL.createObjectURL(file);
|
|
58785
58816
|
resolve({
|
|
58786
58817
|
type: "image",
|
|
58787
58818
|
url
|
|
58788
58819
|
});
|
|
58789
58820
|
};
|
|
58790
|
-
|
|
58821
|
+
reader.readAsDataURL(file);
|
|
58791
58822
|
});
|
|
58792
58823
|
};
|
|
58793
58824
|
var createVideoPreview = (file) => {
|
|
@@ -58839,16 +58870,16 @@ var BulkActions = ({
|
|
|
58839
58870
|
children: [
|
|
58840
58871
|
/* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
|
|
58841
58872
|
selectedIds.length,
|
|
58842
|
-
"
|
|
58873
|
+
" files selected"
|
|
58843
58874
|
] }),
|
|
58844
58875
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-1 ml-auto", children: [
|
|
58845
58876
|
/* @__PURE__ */ jsxs(MoonUIButtonPro, { variant: "ghost", size: "sm", onClick: onBulkDownload, children: [
|
|
58846
58877
|
/* @__PURE__ */ jsx(Download, { className: "h-4 w-4 mr-1" }),
|
|
58847
|
-
"
|
|
58878
|
+
"Download"
|
|
58848
58879
|
] }),
|
|
58849
58880
|
/* @__PURE__ */ jsxs(MoonUIButtonPro, { variant: "ghost", size: "sm", onClick: onBulkRemove, children: [
|
|
58850
58881
|
/* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 mr-1" }),
|
|
58851
|
-
"
|
|
58882
|
+
"Delete"
|
|
58852
58883
|
] }),
|
|
58853
58884
|
/* @__PURE__ */ jsx(MoonUIButtonPro, { variant: "ghost", size: "sm", onClick: onClearSelection, children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
|
|
58854
58885
|
] })
|
|
@@ -58870,37 +58901,45 @@ var FilePreviewModal = ({
|
|
|
58870
58901
|
/* @__PURE__ */ jsx(MoonUIBadgePro, { variant: "secondary", children: formatFileSize(file.file.size) })
|
|
58871
58902
|
] }) }),
|
|
58872
58903
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center p-4 bg-muted/20 rounded-lg", children: [
|
|
58873
|
-
file.preview.type === "image" && /* @__PURE__ */
|
|
58874
|
-
|
|
58875
|
-
|
|
58876
|
-
|
|
58877
|
-
|
|
58878
|
-
|
|
58879
|
-
|
|
58880
|
-
|
|
58881
|
-
|
|
58904
|
+
file.preview.type === "image" && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
58905
|
+
/* @__PURE__ */ jsx(
|
|
58906
|
+
"img",
|
|
58907
|
+
{
|
|
58908
|
+
src: file.preview.url,
|
|
58909
|
+
alt: file.file.name,
|
|
58910
|
+
className: "max-w-full max-h-[60vh] object-contain rounded shadow-lg",
|
|
58911
|
+
loading: "eager"
|
|
58912
|
+
}
|
|
58913
|
+
),
|
|
58914
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/80 rounded opacity-0 pointer-events-none transition-opacity duration-300", children: /* @__PURE__ */ jsx(Loader2, { className: "h-8 w-8 animate-spin text-muted-foreground" }) })
|
|
58915
|
+
] }),
|
|
58916
|
+
file.preview.type === "video" && /* @__PURE__ */ jsx("div", { className: "relative w-full max-w-4xl", children: /* @__PURE__ */ jsx(
|
|
58882
58917
|
"video",
|
|
58883
58918
|
{
|
|
58884
58919
|
src: file.preview.url,
|
|
58885
58920
|
controls: true,
|
|
58886
|
-
className: "
|
|
58921
|
+
className: "w-full max-h-[60vh] rounded shadow-lg",
|
|
58922
|
+
poster: file.preview.thumbnail
|
|
58887
58923
|
}
|
|
58888
|
-
),
|
|
58889
|
-
file.preview.type === "audio" && /* @__PURE__ */ jsx("div", { className: "w-full max-w-md", children: /* @__PURE__ */
|
|
58924
|
+
) }),
|
|
58925
|
+
file.preview.type === "audio" && /* @__PURE__ */ jsx("div", { className: "w-full max-w-md space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "p-8 bg-gradient-to-br from-primary/10 to-primary/5 rounded-lg", children: [
|
|
58926
|
+
/* @__PURE__ */ jsx(Music, { className: "h-16 w-16 mx-auto text-primary mb-4" }),
|
|
58927
|
+
/* @__PURE__ */ jsx("audio", { src: file.preview.url, controls: true, className: "w-full" })
|
|
58928
|
+
] }) }),
|
|
58890
58929
|
!["image", "video", "audio"].includes(file.preview.type) && /* @__PURE__ */ jsxs("div", { className: "text-center py-8", children: [
|
|
58891
58930
|
getFileIcon(file.file.type, "lg"),
|
|
58892
|
-
/* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "
|
|
58931
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "Preview not available" })
|
|
58893
58932
|
] })
|
|
58894
58933
|
] }),
|
|
58895
58934
|
file.preview.dimensions && /* @__PURE__ */ jsxs("div", { className: "flex gap-4 text-sm text-muted-foreground", children: [
|
|
58896
58935
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
58897
|
-
"
|
|
58936
|
+
"Dimensions: ",
|
|
58898
58937
|
file.preview.dimensions.width,
|
|
58899
58938
|
" \xD7 ",
|
|
58900
58939
|
file.preview.dimensions.height
|
|
58901
58940
|
] }),
|
|
58902
58941
|
file.preview.duration && /* @__PURE__ */ jsxs("span", { children: [
|
|
58903
|
-
"
|
|
58942
|
+
"Duration: ",
|
|
58904
58943
|
formatTime(file.preview.duration)
|
|
58905
58944
|
] })
|
|
58906
58945
|
] })
|
|
@@ -58952,21 +58991,21 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
58952
58991
|
return /* @__PURE__ */ jsx(MoonUICardPro, { className: cn("w-full", className), children: /* @__PURE__ */ jsx(MoonUICardContentPro, { className: "py-12 text-center", children: /* @__PURE__ */ jsxs("div", { className: "max-w-md mx-auto space-y-4", children: [
|
|
58953
58992
|
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto", children: /* @__PURE__ */ jsx(Lock, { className: "h-6 w-6 text-purple-600 dark:text-purple-400" }) }),
|
|
58954
58993
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
58955
|
-
/* @__PURE__ */ jsx("h3", { className: "font-semibold text-lg mb-2", children: "Pro
|
|
58956
|
-
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm mb-4", children: "
|
|
58994
|
+
/* @__PURE__ */ jsx("h3", { className: "font-semibold text-lg mb-2", children: "Pro Feature" }),
|
|
58995
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm mb-4", children: "Advanced File Upload is exclusive to MoonUI Pro subscribers." }),
|
|
58957
58996
|
/* @__PURE__ */ jsx("div", { className: "flex gap-3 justify-center", children: /* @__PURE__ */ jsx("a", { href: "/pricing", children: /* @__PURE__ */ jsxs(MoonUIButtonPro, { size: "sm", children: [
|
|
58958
58997
|
/* @__PURE__ */ jsx(Sparkles, { className: "mr-2 h-4 w-4" }),
|
|
58959
|
-
"Pro
|
|
58998
|
+
"Upgrade to Pro"
|
|
58960
58999
|
] }) }) })
|
|
58961
59000
|
] })
|
|
58962
59001
|
] }) }) });
|
|
58963
59002
|
}
|
|
58964
59003
|
const validateFile = useCallback(async (file) => {
|
|
58965
59004
|
if (file.size > maxSize) {
|
|
58966
|
-
return `
|
|
59005
|
+
return `File size exceeds ${formatFileSize(maxSize)} limit`;
|
|
58967
59006
|
}
|
|
58968
59007
|
if (allowedMimeTypes.length > 0 && !allowedMimeTypes.includes(file.type)) {
|
|
58969
|
-
return `
|
|
59008
|
+
return `File type ${file.type} is not supported`;
|
|
58970
59009
|
}
|
|
58971
59010
|
if (accept !== "*") {
|
|
58972
59011
|
const acceptTypes = accept.split(",").map((t2) => t2.trim());
|
|
@@ -58977,7 +59016,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
58977
59016
|
return file.type === acceptType;
|
|
58978
59017
|
});
|
|
58979
59018
|
if (!isAccepted) {
|
|
58980
|
-
return `
|
|
59019
|
+
return `File type not accepted`;
|
|
58981
59020
|
}
|
|
58982
59021
|
}
|
|
58983
59022
|
if (customValidation) {
|
|
@@ -59012,7 +59051,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59012
59051
|
};
|
|
59013
59052
|
}
|
|
59014
59053
|
} catch (error2) {
|
|
59015
|
-
console.warn("
|
|
59054
|
+
console.warn("Failed to create preview:", error2);
|
|
59016
59055
|
}
|
|
59017
59056
|
return void 0;
|
|
59018
59057
|
}, [showPreview, previewTypes]);
|
|
@@ -59090,14 +59129,14 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59090
59129
|
const fileArray = Array.from(fileList);
|
|
59091
59130
|
setError(null);
|
|
59092
59131
|
if (files.length + fileArray.length > maxFiles) {
|
|
59093
|
-
setError(`
|
|
59132
|
+
setError(`Maximum ${maxFiles} files allowed`);
|
|
59094
59133
|
return;
|
|
59095
59134
|
}
|
|
59096
59135
|
if (maxTotalSize) {
|
|
59097
59136
|
const currentSize = files.reduce((sum, f) => sum + f.file.size, 0);
|
|
59098
59137
|
const newSize = fileArray.reduce((sum, f) => sum + f.size, 0);
|
|
59099
59138
|
if (currentSize + newSize > maxTotalSize) {
|
|
59100
|
-
setError(`
|
|
59139
|
+
setError(`Total file size exceeds ${formatFileSize(maxTotalSize)} limit`);
|
|
59101
59140
|
return;
|
|
59102
59141
|
}
|
|
59103
59142
|
}
|
|
@@ -59111,7 +59150,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59111
59150
|
}
|
|
59112
59151
|
const isDuplicate = await checkDuplicate(file);
|
|
59113
59152
|
if (isDuplicate) {
|
|
59114
|
-
errors.push(`${file.name}:
|
|
59153
|
+
errors.push(`${file.name}: File already exists`);
|
|
59115
59154
|
continue;
|
|
59116
59155
|
}
|
|
59117
59156
|
validFiles.push(file);
|
|
@@ -59298,28 +59337,28 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59298
59337
|
}
|
|
59299
59338
|
),
|
|
59300
59339
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
59301
|
-
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: isDragOver ? "
|
|
59302
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "
|
|
59340
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: isDragOver ? "Drop files here" : "Upload Files" }),
|
|
59341
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Drag and drop files or click to select" }),
|
|
59303
59342
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-4 mt-3 text-xs text-muted-foreground", children: [
|
|
59304
59343
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
59305
|
-
"
|
|
59344
|
+
"Max ",
|
|
59306
59345
|
maxFiles,
|
|
59307
|
-
"
|
|
59346
|
+
" files"
|
|
59308
59347
|
] }),
|
|
59309
59348
|
/* @__PURE__ */ jsx("span", { children: "\u2022" }),
|
|
59310
59349
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
59311
59350
|
formatFileSize(maxSize),
|
|
59312
|
-
"
|
|
59351
|
+
" per file"
|
|
59313
59352
|
] }),
|
|
59314
59353
|
resumable && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
59315
59354
|
/* @__PURE__ */ jsx("span", { children: "\u2022" }),
|
|
59316
|
-
/* @__PURE__ */ jsx("span", { children: "
|
|
59355
|
+
/* @__PURE__ */ jsx("span", { children: "Resumable" })
|
|
59317
59356
|
] })
|
|
59318
59357
|
] })
|
|
59319
59358
|
] }),
|
|
59320
59359
|
/* @__PURE__ */ jsxs(MoonUIButtonPro, { variant: "outline", disabled, type: "button", children: [
|
|
59321
59360
|
/* @__PURE__ */ jsx(Upload, { className: "mr-2 h-4 w-4" }),
|
|
59322
|
-
"
|
|
59361
|
+
"Select Files"
|
|
59323
59362
|
] })
|
|
59324
59363
|
] })
|
|
59325
59364
|
]
|
|
@@ -59358,7 +59397,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59358
59397
|
children: [
|
|
59359
59398
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
59360
59399
|
/* @__PURE__ */ jsxs("h4", { className: "text-sm font-medium", children: [
|
|
59361
|
-
"
|
|
59400
|
+
"Uploaded Files (",
|
|
59362
59401
|
files.length,
|
|
59363
59402
|
")"
|
|
59364
59403
|
] }),
|
|
@@ -59368,7 +59407,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59368
59407
|
variant: "ghost",
|
|
59369
59408
|
size: "sm",
|
|
59370
59409
|
onClick: selectedIds.length === files.length ? handleClearSelection : handleSelectAll,
|
|
59371
|
-
children: selectedIds.length === files.length ? "
|
|
59410
|
+
children: selectedIds.length === files.length ? "Clear Selection" : "Select All"
|
|
59372
59411
|
}
|
|
59373
59412
|
)
|
|
59374
59413
|
] }),
|
|
@@ -59522,7 +59561,7 @@ var FileUploadItem = ({
|
|
|
59522
59561
|
file.status === "success" && /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3 w-3 mr-1" }),
|
|
59523
59562
|
file.status === "error" && /* @__PURE__ */ jsx(AlertCircle, { className: "h-3 w-3 mr-1" }),
|
|
59524
59563
|
file.status === "paused" && /* @__PURE__ */ jsx(Pause, { className: "h-3 w-3 mr-1" }),
|
|
59525
|
-
file.status === "pending" ? "
|
|
59564
|
+
file.status === "pending" ? "Pending" : file.status === "uploading" ? "Uploading" : file.status === "paused" ? "Paused" : file.status === "success" ? "Completed" : file.status === "error" ? "Error" : "Cancelled"
|
|
59526
59565
|
]
|
|
59527
59566
|
}
|
|
59528
59567
|
)
|
|
@@ -59539,7 +59578,7 @@ var FileUploadItem = ({
|
|
|
59539
59578
|
] }),
|
|
59540
59579
|
file.estimatedTime && file.estimatedTime > 0 && /* @__PURE__ */ jsxs("span", { children: [
|
|
59541
59580
|
formatTime(file.estimatedTime),
|
|
59542
|
-
"
|
|
59581
|
+
" remaining"
|
|
59543
59582
|
] })
|
|
59544
59583
|
] }),
|
|
59545
59584
|
/* @__PURE__ */ jsx(MoonUIProgressPro, { value: file.progress, className: "h-1" })
|
|
@@ -59583,19 +59622,19 @@ var FileUploadItem = ({
|
|
|
59583
59622
|
/* @__PURE__ */ jsxs(MoonUIDropdownMenuContentPro, { align: "end", children: [
|
|
59584
59623
|
canPreview && /* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { onClick: onPreview, children: [
|
|
59585
59624
|
/* @__PURE__ */ jsx(Eye, { className: "mr-2 h-4 w-4" }),
|
|
59586
|
-
"
|
|
59625
|
+
"Preview"
|
|
59587
59626
|
] }),
|
|
59588
59627
|
/* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { children: [
|
|
59589
59628
|
/* @__PURE__ */ jsx(Copy, { className: "mr-2 h-4 w-4" }),
|
|
59590
|
-
"
|
|
59629
|
+
"Copy Link"
|
|
59591
59630
|
] }),
|
|
59592
59631
|
/* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { children: [
|
|
59593
59632
|
/* @__PURE__ */ jsx(Share, { className: "mr-2 h-4 w-4" }),
|
|
59594
|
-
"
|
|
59633
|
+
"Share"
|
|
59595
59634
|
] }),
|
|
59596
59635
|
/* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { onClick: onRemove, className: "text-destructive", children: [
|
|
59597
59636
|
/* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-4 w-4" }),
|
|
59598
|
-
"
|
|
59637
|
+
"Delete"
|
|
59599
59638
|
] })
|
|
59600
59639
|
] })
|
|
59601
59640
|
] })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.1",
|
|
4
4
|
"description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -78,6 +78,28 @@ export interface FileUploadProProps {
|
|
|
78
78
|
endpoint?: string
|
|
79
79
|
headers?: Record<string, string>
|
|
80
80
|
|
|
81
|
+
// Service integrations
|
|
82
|
+
serviceConfig?: {
|
|
83
|
+
type: 'aws-s3' | 'cloudinary' | 'firebase' | 'custom'
|
|
84
|
+
awsConfig?: {
|
|
85
|
+
region: string
|
|
86
|
+
bucket: string
|
|
87
|
+
accessKeyId?: string
|
|
88
|
+
secretAccessKey?: string
|
|
89
|
+
sessionToken?: string
|
|
90
|
+
}
|
|
91
|
+
cloudinaryConfig?: {
|
|
92
|
+
cloudName: string
|
|
93
|
+
uploadPreset: string
|
|
94
|
+
apiKey?: string
|
|
95
|
+
apiSecret?: string
|
|
96
|
+
}
|
|
97
|
+
firebaseConfig?: {
|
|
98
|
+
storageBucket: string
|
|
99
|
+
folder?: string
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
81
103
|
// Callbacks
|
|
82
104
|
onUpload?: (files: FileUploadItem[]) => Promise<void>
|
|
83
105
|
onProgress?: (fileId: string, progress: number) => void
|
|
@@ -200,26 +222,67 @@ const generateFileHash = async (file: File): Promise<string> => {
|
|
|
200
222
|
|
|
201
223
|
const createImagePreview = (file: File): Promise<FilePreview> => {
|
|
202
224
|
return new Promise((resolve) => {
|
|
203
|
-
const
|
|
204
|
-
const url = URL.createObjectURL(file)
|
|
225
|
+
const reader = new FileReader()
|
|
205
226
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
227
|
+
reader.onload = (e) => {
|
|
228
|
+
const result = e.target?.result as string
|
|
229
|
+
const img = new Image()
|
|
230
|
+
|
|
231
|
+
img.onload = () => {
|
|
232
|
+
// Create high-quality thumbnail
|
|
233
|
+
const canvas = document.createElement('canvas')
|
|
234
|
+
const ctx = canvas.getContext('2d')!
|
|
235
|
+
|
|
236
|
+
// Calculate thumbnail dimensions (max 400px)
|
|
237
|
+
const maxThumbSize = 400
|
|
238
|
+
let thumbWidth = img.width
|
|
239
|
+
let thumbHeight = img.height
|
|
240
|
+
|
|
241
|
+
if (thumbWidth > maxThumbSize || thumbHeight > maxThumbSize) {
|
|
242
|
+
const aspectRatio = img.width / img.height
|
|
243
|
+
if (aspectRatio > 1) {
|
|
244
|
+
thumbWidth = maxThumbSize
|
|
245
|
+
thumbHeight = maxThumbSize / aspectRatio
|
|
246
|
+
} else {
|
|
247
|
+
thumbHeight = maxThumbSize
|
|
248
|
+
thumbWidth = maxThumbSize * aspectRatio
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
canvas.width = thumbWidth
|
|
253
|
+
canvas.height = thumbHeight
|
|
254
|
+
ctx.drawImage(img, 0, 0, thumbWidth, thumbHeight)
|
|
255
|
+
|
|
256
|
+
const thumbnail = canvas.toDataURL('image/jpeg', 0.9)
|
|
257
|
+
|
|
258
|
+
resolve({
|
|
259
|
+
type: 'image',
|
|
260
|
+
url: result,
|
|
261
|
+
thumbnail,
|
|
262
|
+
dimensions: { width: img.width, height: img.height }
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
img.onerror = () => {
|
|
267
|
+
resolve({
|
|
268
|
+
type: 'image',
|
|
269
|
+
url: result,
|
|
270
|
+
thumbnail: result
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
img.src = result
|
|
213
275
|
}
|
|
214
276
|
|
|
215
|
-
|
|
277
|
+
reader.onerror = () => {
|
|
278
|
+
const url = URL.createObjectURL(file)
|
|
216
279
|
resolve({
|
|
217
280
|
type: 'image',
|
|
218
281
|
url
|
|
219
282
|
})
|
|
220
283
|
}
|
|
221
284
|
|
|
222
|
-
|
|
285
|
+
reader.readAsDataURL(file)
|
|
223
286
|
})
|
|
224
287
|
}
|
|
225
288
|
|
|
@@ -284,15 +347,15 @@ const BulkActions = ({
|
|
|
284
347
|
exit={{ opacity: 0, y: -10 }}
|
|
285
348
|
className="flex items-center gap-2 p-3 bg-primary/5 rounded-lg border"
|
|
286
349
|
>
|
|
287
|
-
<span className="text-sm font-medium">{selectedIds.length}
|
|
350
|
+
<span className="text-sm font-medium">{selectedIds.length} files selected</span>
|
|
288
351
|
<div className="flex gap-1 ml-auto">
|
|
289
352
|
<Button variant="ghost" size="sm" onClick={onBulkDownload}>
|
|
290
353
|
<Download className="h-4 w-4 mr-1" />
|
|
291
|
-
|
|
354
|
+
Download
|
|
292
355
|
</Button>
|
|
293
356
|
<Button variant="ghost" size="sm" onClick={onBulkRemove}>
|
|
294
357
|
<Trash2 className="h-4 w-4 mr-1" />
|
|
295
|
-
|
|
358
|
+
Delete
|
|
296
359
|
</Button>
|
|
297
360
|
<Button variant="ghost" size="sm" onClick={onClearSelection}>
|
|
298
361
|
<X className="h-4 w-4" />
|
|
@@ -329,40 +392,53 @@ const FilePreviewModal = ({
|
|
|
329
392
|
|
|
330
393
|
<div className="flex items-center justify-center p-4 bg-muted/20 rounded-lg">
|
|
331
394
|
{file.preview.type === 'image' && (
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
395
|
+
<div className="relative">
|
|
396
|
+
<img
|
|
397
|
+
src={file.preview.url}
|
|
398
|
+
alt={file.file.name}
|
|
399
|
+
className="max-w-full max-h-[60vh] object-contain rounded shadow-lg"
|
|
400
|
+
loading="eager"
|
|
401
|
+
/>
|
|
402
|
+
{/* Image loading indicator */}
|
|
403
|
+
<div className="absolute inset-0 flex items-center justify-center bg-background/80 rounded opacity-0 pointer-events-none transition-opacity duration-300">
|
|
404
|
+
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
337
407
|
)}
|
|
338
408
|
|
|
339
409
|
{file.preview.type === 'video' && (
|
|
340
|
-
<
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
410
|
+
<div className="relative w-full max-w-4xl">
|
|
411
|
+
<video
|
|
412
|
+
src={file.preview.url}
|
|
413
|
+
controls
|
|
414
|
+
className="w-full max-h-[60vh] rounded shadow-lg"
|
|
415
|
+
poster={file.preview.thumbnail}
|
|
416
|
+
/>
|
|
417
|
+
</div>
|
|
345
418
|
)}
|
|
346
419
|
|
|
347
420
|
{file.preview.type === 'audio' && (
|
|
348
|
-
<div className="w-full max-w-md">
|
|
349
|
-
<
|
|
421
|
+
<div className="w-full max-w-md space-y-4">
|
|
422
|
+
<div className="p-8 bg-gradient-to-br from-primary/10 to-primary/5 rounded-lg">
|
|
423
|
+
<Music className="h-16 w-16 mx-auto text-primary mb-4" />
|
|
424
|
+
<audio src={file.preview.url} controls className="w-full" />
|
|
425
|
+
</div>
|
|
350
426
|
</div>
|
|
351
427
|
)}
|
|
352
428
|
|
|
353
429
|
{!['image', 'video', 'audio'].includes(file.preview.type) && (
|
|
354
430
|
<div className="text-center py-8">
|
|
355
431
|
{getFileIcon(file.file.type, 'lg')}
|
|
356
|
-
<p className="mt-2 text-muted-foreground"
|
|
432
|
+
<p className="mt-2 text-muted-foreground">Preview not available</p>
|
|
357
433
|
</div>
|
|
358
434
|
)}
|
|
359
435
|
</div>
|
|
360
436
|
|
|
361
437
|
{file.preview.dimensions && (
|
|
362
438
|
<div className="flex gap-4 text-sm text-muted-foreground">
|
|
363
|
-
<span>
|
|
439
|
+
<span>Dimensions: {file.preview.dimensions.width} × {file.preview.dimensions.height}</span>
|
|
364
440
|
{file.preview.duration && (
|
|
365
|
-
<span>
|
|
441
|
+
<span>Duration: {formatTime(file.preview.duration)}</span>
|
|
366
442
|
)}
|
|
367
443
|
</div>
|
|
368
444
|
)}
|
|
@@ -428,15 +504,15 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
428
504
|
<Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
|
429
505
|
</div>
|
|
430
506
|
<div>
|
|
431
|
-
<h3 className="font-semibold text-lg mb-2">Pro
|
|
507
|
+
<h3 className="font-semibold text-lg mb-2">Pro Feature</h3>
|
|
432
508
|
<p className="text-muted-foreground text-sm mb-4">
|
|
433
|
-
|
|
509
|
+
Advanced File Upload is exclusive to MoonUI Pro subscribers.
|
|
434
510
|
</p>
|
|
435
511
|
<div className="flex gap-3 justify-center">
|
|
436
512
|
<a href="/pricing">
|
|
437
513
|
<Button size="sm">
|
|
438
514
|
<Sparkles className="mr-2 h-4 w-4" />
|
|
439
|
-
Pro
|
|
515
|
+
Upgrade to Pro
|
|
440
516
|
</Button>
|
|
441
517
|
</a>
|
|
442
518
|
</div>
|
|
@@ -451,12 +527,12 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
451
527
|
const validateFile = useCallback(async (file: File): Promise<string | null> => {
|
|
452
528
|
// Boyut kontrolü
|
|
453
529
|
if (file.size > maxSize) {
|
|
454
|
-
return `
|
|
530
|
+
return `File size exceeds ${formatFileSize(maxSize)} limit`
|
|
455
531
|
}
|
|
456
532
|
|
|
457
533
|
// MIME type kontrolü
|
|
458
534
|
if (allowedMimeTypes.length > 0 && !allowedMimeTypes.includes(file.type)) {
|
|
459
|
-
return `
|
|
535
|
+
return `File type ${file.type} is not supported`
|
|
460
536
|
}
|
|
461
537
|
|
|
462
538
|
// Accept kontrolü
|
|
@@ -470,7 +546,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
470
546
|
})
|
|
471
547
|
|
|
472
548
|
if (!isAccepted) {
|
|
473
|
-
return `
|
|
549
|
+
return `File type not accepted`
|
|
474
550
|
}
|
|
475
551
|
}
|
|
476
552
|
|
|
@@ -512,7 +588,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
512
588
|
}
|
|
513
589
|
}
|
|
514
590
|
} catch (error) {
|
|
515
|
-
console.warn('
|
|
591
|
+
console.warn('Failed to create preview:', error)
|
|
516
592
|
}
|
|
517
593
|
|
|
518
594
|
return undefined
|
|
@@ -630,7 +706,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
630
706
|
|
|
631
707
|
// Toplam dosya sayısı kontrolü
|
|
632
708
|
if (files.length + fileArray.length > maxFiles) {
|
|
633
|
-
setError(`
|
|
709
|
+
setError(`Maximum ${maxFiles} files allowed`)
|
|
634
710
|
return
|
|
635
711
|
}
|
|
636
712
|
|
|
@@ -640,7 +716,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
640
716
|
const newSize = fileArray.reduce((sum, f) => sum + f.size, 0)
|
|
641
717
|
|
|
642
718
|
if (currentSize + newSize > maxTotalSize) {
|
|
643
|
-
setError(`
|
|
719
|
+
setError(`Total file size exceeds ${formatFileSize(maxTotalSize)} limit`)
|
|
644
720
|
return
|
|
645
721
|
}
|
|
646
722
|
}
|
|
@@ -658,7 +734,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
658
734
|
|
|
659
735
|
const isDuplicate = await checkDuplicate(file)
|
|
660
736
|
if (isDuplicate) {
|
|
661
|
-
errors.push(`${file.name}:
|
|
737
|
+
errors.push(`${file.name}: File already exists`)
|
|
662
738
|
continue
|
|
663
739
|
}
|
|
664
740
|
|
|
@@ -886,19 +962,19 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
886
962
|
|
|
887
963
|
<div>
|
|
888
964
|
<h3 className="text-lg font-semibold">
|
|
889
|
-
{isDragOver ? '
|
|
965
|
+
{isDragOver ? 'Drop files here' : 'Upload Files'}
|
|
890
966
|
</h3>
|
|
891
967
|
<p className="text-sm text-muted-foreground mt-1">
|
|
892
|
-
|
|
968
|
+
Drag and drop files or click to select
|
|
893
969
|
</p>
|
|
894
970
|
<div className="flex items-center justify-center gap-4 mt-3 text-xs text-muted-foreground">
|
|
895
|
-
<span>
|
|
971
|
+
<span>Max {maxFiles} files</span>
|
|
896
972
|
<span>•</span>
|
|
897
|
-
<span>{formatFileSize(maxSize)}
|
|
973
|
+
<span>{formatFileSize(maxSize)} per file</span>
|
|
898
974
|
{resumable && (
|
|
899
975
|
<>
|
|
900
976
|
<span>•</span>
|
|
901
|
-
<span>
|
|
977
|
+
<span>Resumable</span>
|
|
902
978
|
</>
|
|
903
979
|
)}
|
|
904
980
|
</div>
|
|
@@ -906,7 +982,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
906
982
|
|
|
907
983
|
<Button variant="outline" disabled={disabled} type="button">
|
|
908
984
|
<Upload className="mr-2 h-4 w-4" />
|
|
909
|
-
|
|
985
|
+
Select Files
|
|
910
986
|
</Button>
|
|
911
987
|
</div>
|
|
912
988
|
</motion.div>
|
|
@@ -946,7 +1022,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
946
1022
|
{/* List Header */}
|
|
947
1023
|
<div className="flex items-center justify-between">
|
|
948
1024
|
<h4 className="text-sm font-medium">
|
|
949
|
-
|
|
1025
|
+
Uploaded Files ({files.length})
|
|
950
1026
|
</h4>
|
|
951
1027
|
{allowBulkOperations && files.length > 1 && (
|
|
952
1028
|
<Button
|
|
@@ -954,7 +1030,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
954
1030
|
size="sm"
|
|
955
1031
|
onClick={selectedIds.length === files.length ? handleClearSelection : handleSelectAll}
|
|
956
1032
|
>
|
|
957
|
-
{selectedIds.length === files.length ? '
|
|
1033
|
+
{selectedIds.length === files.length ? 'Clear Selection' : 'Select All'}
|
|
958
1034
|
</Button>
|
|
959
1035
|
)}
|
|
960
1036
|
</div>
|
|
@@ -1152,11 +1228,11 @@ const FileUploadItem = ({
|
|
|
1152
1228
|
{file.status === 'success' && <CheckCircle2 className="h-3 w-3 mr-1" />}
|
|
1153
1229
|
{file.status === 'error' && <AlertCircle className="h-3 w-3 mr-1" />}
|
|
1154
1230
|
{file.status === 'paused' && <Pause className="h-3 w-3 mr-1" />}
|
|
1155
|
-
{file.status === 'pending' ? '
|
|
1156
|
-
file.status === 'uploading' ? '
|
|
1157
|
-
file.status === 'paused' ? '
|
|
1158
|
-
file.status === 'success' ? '
|
|
1159
|
-
file.status === 'error' ? '
|
|
1231
|
+
{file.status === 'pending' ? 'Pending' :
|
|
1232
|
+
file.status === 'uploading' ? 'Uploading' :
|
|
1233
|
+
file.status === 'paused' ? 'Paused' :
|
|
1234
|
+
file.status === 'success' ? 'Completed' :
|
|
1235
|
+
file.status === 'error' ? 'Error' : 'Cancelled'}
|
|
1160
1236
|
</Badge>
|
|
1161
1237
|
</div>
|
|
1162
1238
|
|
|
@@ -1169,7 +1245,7 @@ const FileUploadItem = ({
|
|
|
1169
1245
|
<span>{formatFileSize(file.speed)}/s</span>
|
|
1170
1246
|
)}
|
|
1171
1247
|
{file.estimatedTime && file.estimatedTime > 0 && (
|
|
1172
|
-
<span>{formatTime(file.estimatedTime)}
|
|
1248
|
+
<span>{formatTime(file.estimatedTime)} remaining</span>
|
|
1173
1249
|
)}
|
|
1174
1250
|
</div>
|
|
1175
1251
|
<Progress value={file.progress} className="h-1" />
|
|
@@ -1232,20 +1308,20 @@ const FileUploadItem = ({
|
|
|
1232
1308
|
{canPreview && (
|
|
1233
1309
|
<DropdownMenuItem onClick={onPreview}>
|
|
1234
1310
|
<Eye className="mr-2 h-4 w-4" />
|
|
1235
|
-
|
|
1311
|
+
Preview
|
|
1236
1312
|
</DropdownMenuItem>
|
|
1237
1313
|
)}
|
|
1238
1314
|
<DropdownMenuItem>
|
|
1239
1315
|
<Copy className="mr-2 h-4 w-4" />
|
|
1240
|
-
|
|
1316
|
+
Copy Link
|
|
1241
1317
|
</DropdownMenuItem>
|
|
1242
1318
|
<DropdownMenuItem>
|
|
1243
1319
|
<Share className="mr-2 h-4 w-4" />
|
|
1244
|
-
|
|
1320
|
+
Share
|
|
1245
1321
|
</DropdownMenuItem>
|
|
1246
1322
|
<DropdownMenuItem onClick={onRemove} className="text-destructive">
|
|
1247
1323
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
1248
|
-
|
|
1324
|
+
Delete
|
|
1249
1325
|
</DropdownMenuItem>
|
|
1250
1326
|
</DropdownMenuContent>
|
|
1251
1327
|
</DropdownMenu>
|