@appcorp/shadcn 1.2.0 → 2.0.0
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/components/app-sidebar.js +16 -23
- package/components/audio.js +11 -19
- package/components/dashboard.js +27 -65
- package/components/drawer-generic.js +20 -28
- package/components/enhanced-checkbox.js +10 -57
- package/components/enhanced-combobox.js +99 -143
- package/components/enhanced-drawer-footer.js +22 -31
- package/components/enhanced-drawer-header.js +6 -14
- package/components/enhanced-dropzone-impl.d.ts +32 -0
- package/components/enhanced-dropzone-impl.js +218 -0
- package/components/enhanced-dropzone.d.ts +2 -63
- package/components/enhanced-dropzone.js +6 -294
- package/components/enhanced-input.js +10 -57
- package/components/enhanced-label.js +6 -52
- package/components/enhanced-radio.js +12 -20
- package/components/enhanced-select.js +13 -61
- package/components/enhanced-slider.js +11 -58
- package/components/enhanced-switch.js +11 -58
- package/components/enhanced-table-footer-action.js +11 -19
- package/components/enhanced-table-footer-page.js +16 -24
- package/components/enhanced-table-footer-pagination.js +11 -21
- package/components/enhanced-table-header-action.js +22 -30
- package/components/enhanced-table-header-search.js +9 -17
- package/components/enhanced-table.js +125 -153
- package/components/enhanced-textarea.js +10 -57
- package/components/field.d.ts +1 -1
- package/components/field.js +31 -88
- package/components/image-resize-dialog.js +61 -159
- package/components/nav-main.js +32 -78
- package/components/nav-projects.js +37 -48
- package/components/nav-user.js +41 -51
- package/components/show-toast.js +5 -9
- package/components/sidebar.d.ts +1 -1
- package/components/sidebar.js +115 -184
- package/components/team-switcher.js +36 -47
- package/components/theme-provider.js +36 -84
- package/components/timeline.js +12 -20
- package/components/tooltip.js +10 -59
- package/components/ui/accordion.js +17 -67
- package/components/ui/alert-dialog.js +32 -89
- package/components/ui/alert.d.ts +1 -1
- package/components/ui/alert.js +14 -63
- package/components/ui/area-chart-v1-impl.d.ts +19 -0
- package/components/ui/area-chart-v1-impl.js +75 -0
- package/components/ui/area-chart-v1.d.ts +2 -19
- package/components/ui/area-chart-v1.js +4 -113
- package/components/ui/aspect-ratio.js +3 -39
- package/components/ui/avatar.js +13 -62
- package/components/ui/badge.d.ts +2 -2
- package/components/ui/badge.js +7 -55
- package/components/ui/breadcrumb.js +27 -80
- package/components/ui/button.d.ts +2 -2
- package/components/ui/button.js +10 -58
- package/components/ui/calendar-impl.d.ts +8 -0
- package/components/ui/calendar-impl.js +55 -0
- package/components/ui/calendar.d.ts +2 -8
- package/components/ui/calendar.js +5 -108
- package/components/ui/card.js +21 -73
- package/components/ui/carousel-impl.d.ts +18 -0
- package/components/ui/carousel-impl.js +117 -0
- package/components/ui/carousel.d.ts +6 -18
- package/components/ui/carousel.js +8 -170
- package/components/ui/chart-impl.d.ts +62 -0
- package/components/ui/chart-impl.js +162 -0
- package/components/ui/chart.d.ts +7 -62
- package/components/ui/chart.js +9 -219
- package/components/ui/checkbox.js +10 -57
- package/components/ui/collapsible.js +5 -43
- package/components/ui/combobox.js +22 -58
- package/components/ui/command-impl.d.ts +80 -0
- package/components/ui/command-impl.js +63 -0
- package/components/ui/command.d.ts +9 -80
- package/components/ui/command.js +12 -117
- package/components/ui/context-menu.js +41 -102
- package/components/ui/dialog.js +28 -84
- package/components/ui/drawer-impl.d.ts +22 -0
- package/components/ui/drawer-impl.js +56 -0
- package/components/ui/drawer.d.ts +10 -22
- package/components/ui/drawer.js +13 -111
- package/components/ui/dropdown-menu.js +41 -102
- package/components/ui/form.d.ts +4 -2
- package/components/ui/form.js +41 -95
- package/components/ui/hover-card.js +9 -58
- package/components/ui/input-otp-impl.d.ts +34 -0
- package/components/ui/input-otp-impl.js +43 -0
- package/components/ui/input-otp.d.ts +4 -34
- package/components/ui/input-otp.js +7 -92
- package/components/ui/input.js +6 -53
- package/components/ui/label.d.ts +1 -1
- package/components/ui/label.js +9 -56
- package/components/ui/language-selector.js +17 -55
- package/components/ui/menubar.js +46 -108
- package/components/ui/navigation-menu.d.ts +1 -1
- package/components/ui/navigation-menu.js +29 -84
- package/components/ui/pagination.js +30 -83
- package/components/ui/popover.js +10 -60
- package/components/ui/progress.js +8 -55
- package/components/ui/radio-group.js +12 -60
- package/components/ui/resizable-impl.d.ts +24 -0
- package/components/ui/resizable-impl.js +27 -0
- package/components/ui/resizable.d.ts +3 -24
- package/components/ui/resizable.js +6 -78
- package/components/ui/scroll-area.js +10 -58
- package/components/ui/select.js +34 -90
- package/components/ui/separator.js +7 -54
- package/components/ui/sheet.d.ts +1 -1
- package/components/ui/sheet.js +30 -86
- package/components/ui/skeleton.js +5 -21
- package/components/ui/slider.js +10 -57
- package/components/ui/sonner.js +7 -24
- package/components/ui/switch.js +8 -55
- package/components/ui/table.js +27 -81
- package/components/ui/tabs.js +14 -64
- package/components/ui/textarea.js +6 -53
- package/components/ui/theme-switcher.js +41 -91
- package/components/ui/toggle.d.ts +2 -2
- package/components/ui/toggle.js +9 -57
- package/data/admin-dashboard.js +16 -19
- package/hooks/use-enhanced-combobox.js +47 -124
- package/hooks/use-mobile.js +8 -44
- package/hooks/use-outside-click.js +6 -10
- package/lib/themes.js +1 -4
- package/lib/toast-utils.js +78 -120
- package/lib/utils.js +4 -11
- package/package.json +9 -90
- package/utils/transform-breadcrumbs.js +4 -8
- package/utils/transform-navitems.js +7 -22
- package/components/aceternity-ui/apple-cards-carousel.d.ts +0 -35
- package/components/aceternity-ui/apple-cards-carousel.js +0 -263
- package/components/shadcn-example/ai-chat/chat-interface.d.ts +0 -11
- package/components/shadcn-example/ai-chat/chat-interface.js +0 -70
- package/components/shadcn-example/ai-chat/index.d.ts +0 -38
- package/components/shadcn-example/ai-chat/index.js +0 -66
- package/components/shadcn-example/ai-chat/sidebar.d.ts +0 -15
- package/components/shadcn-example/ai-chat/sidebar.js +0 -68
- package/components/shadcn-example/ai-chat/welcome-section.d.ts +0 -12
- package/components/shadcn-example/ai-chat/welcome-section.js +0 -26
- package/components/shadcn-example/authentication/index.d.ts +0 -24
- package/components/shadcn-example/authentication/index.js +0 -59
- package/components/shadcn-example/authentication/user-auth-form.d.ts +0 -11
- package/components/shadcn-example/authentication/user-auth-form.js +0 -54
- package/components/shadcn-example/blog-detail-page/comment-section.d.ts +0 -15
- package/components/shadcn-example/blog-detail-page/comment-section.js +0 -83
- package/components/shadcn-example/blog-detail-page/content.d.ts +0 -7
- package/components/shadcn-example/blog-detail-page/content.js +0 -15
- package/components/shadcn-example/blog-detail-page/header.d.ts +0 -11
- package/components/shadcn-example/blog-detail-page/header.js +0 -36
- package/components/shadcn-example/blog-detail-page/index.d.ts +0 -25
- package/components/shadcn-example/blog-detail-page/index.js +0 -74
- package/components/shadcn-example/blog-page/blog-post-card.d.ts +0 -13
- package/components/shadcn-example/blog-page/blog-post-card.js +0 -30
- package/components/shadcn-example/blog-page/featured-post-sidebar-item.d.ts +0 -10
- package/components/shadcn-example/blog-page/featured-post-sidebar-item.js +0 -19
- package/components/shadcn-example/blog-page/index.d.ts +0 -20
- package/components/shadcn-example/blog-page/index.js +0 -36
- package/components/shadcn-example/coming-soon/countdown.d.ts +0 -2
- package/components/shadcn-example/coming-soon/countdown.js +0 -82
- package/components/shadcn-example/coming-soon/index.d.ts +0 -10
- package/components/shadcn-example/coming-soon/index.js +0 -41
- package/components/shadcn-io/color-picker/index.d.ts +0 -43
- package/components/shadcn-io/color-picker/index.js +0 -304
- package/components/shadcn-io/copy-button/index.d.ts +0 -16
- package/components/shadcn-io/copy-button/index.js +0 -121
- package/components/shadcn-io/dropzone/index.d.ts +0 -19
- package/components/shadcn-io/dropzone/index.js +0 -131
- package/components/shadcn-io/gantt/index.d.ts +0 -145
- package/components/shadcn-io/gantt/index.js +0 -766
- package/components/shadcn-io/table/index.d.ts +0 -60
- package/components/shadcn-io/table/index.js +0 -138
- package/templates/appcorp-v1/footer/bottom-bar.d.ts +0 -6
- package/templates/appcorp-v1/footer/bottom-bar.js +0 -25
- package/templates/appcorp-v1/footer/cta.d.ts +0 -6
- package/templates/appcorp-v1/footer/cta.js +0 -21
- package/templates/appcorp-v1/footer/footer-grid.d.ts +0 -14
- package/templates/appcorp-v1/footer/footer-grid.js +0 -52
- package/templates/appcorp-v1/footer/index.d.ts +0 -3
- package/templates/appcorp-v1/footer/index.js +0 -28
- package/templates/appcorp-v1/footer/newsletter.d.ts +0 -11
- package/templates/appcorp-v1/footer/newsletter.js +0 -28
- package/templates/appcorp-v1/footer/stats.d.ts +0 -6
- package/templates/appcorp-v1/footer/stats.js +0 -15
- package/templates/appcorp-v1/header/announcement-bar.d.ts +0 -3
- package/templates/appcorp-v1/header/announcement-bar.js +0 -15
- package/templates/appcorp-v1/header/dropdown-panel.d.ts +0 -8
- package/templates/appcorp-v1/header/dropdown-panel.js +0 -72
- package/templates/appcorp-v1/header/index.d.ts +0 -12
- package/templates/appcorp-v1/header/index.js +0 -16
- package/templates/appcorp-v1/header/mobile-menu.d.ts +0 -14
- package/templates/appcorp-v1/header/mobile-menu.js +0 -101
- package/templates/appcorp-v1/header/navigation-bar.d.ts +0 -3
- package/templates/appcorp-v1/header/navigation-bar.js +0 -117
- package/templates/appcorp-v1/header/social-button.d.ts +0 -7
- package/templates/appcorp-v1/header/social-button.js +0 -48
- package/templates/appcorp-v1/home.d.ts +0 -3
- package/templates/appcorp-v1/home.js +0 -27
- package/templates/data/appcorp-v1.d.ts +0 -93
- package/templates/data/appcorp-v1.js +0 -348
- package/templates/data/edupilotpro-v1.d.ts +0 -33
- package/templates/data/edupilotpro-v1.js +0 -116
- package/templates/data/school-v1.d.ts +0 -328
- package/templates/data/school-v1.js +0 -1563
- package/templates/edupilotpro-v1/feature-card.d.ts +0 -13
- package/templates/edupilotpro-v1/feature-card.js +0 -28
- package/templates/edupilotpro-v1/mockups/dashboard-mockup.d.ts +0 -3
- package/templates/edupilotpro-v1/mockups/dashboard-mockup.js +0 -55
- package/templates/school-v1/footer.d.ts +0 -3
- package/templates/school-v1/footer.js +0 -75
- package/templates/school-v1/index.d.ts +0 -18
- package/templates/school-v1/index.js +0 -60
- package/templates/school-v1/navigation.d.ts +0 -3
- package/templates/school-v1/navigation.js +0 -123
- package/templates/school-v1/sections/about.d.ts +0 -3
- package/templates/school-v1/sections/about.js +0 -49
- package/templates/school-v1/sections/admissions.d.ts +0 -3
- package/templates/school-v1/sections/admissions.js +0 -77
- package/templates/school-v1/sections/contact.d.ts +0 -3
- package/templates/school-v1/sections/contact.js +0 -67
- package/templates/school-v1/sections/faqs.d.ts +0 -3
- package/templates/school-v1/sections/faqs.js +0 -50
- package/templates/school-v1/sections/hero.d.ts +0 -3
- package/templates/school-v1/sections/hero.js +0 -132
- package/templates/school-v1/sections/management.d.ts +0 -3
- package/templates/school-v1/sections/management.js +0 -34
- package/templates/school-v1/sections/policies.d.ts +0 -3
- package/templates/school-v1/sections/policies.js +0 -127
- package/templates/school-v1/sections/programs.d.ts +0 -3
- package/templates/school-v1/sections/programs.js +0 -107
- package/templates/school-v1/sections/testimonials.d.ts +0 -3
- package/templates/school-v1/sections/testimonials.js +0 -46
- package/templates/school-v1/sections/why-choose-us.d.ts +0 -3
- package/templates/school-v1/sections/why-choose-us.js +0 -57
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { useDropzone } from "react-dropzone";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, } from "./ui/carousel";
|
|
6
|
+
import { Button } from "./ui/button";
|
|
7
|
+
import { ImageResizeDialog } from "./image-resize-dialog";
|
|
8
|
+
import { XIcon } from "lucide-react";
|
|
9
|
+
export const EnhancedDropzone = ({ id, label, info, error, accept, maxFiles = 10, maxSize, minSize, disabled, value = [], onChange, onRemoveRemote, resize = false, className, testIdDropzone, testIdInput, testIdPrev, testIdNext, testIdPreviewPrefix, testIdImagePrefix, testIdRemovePrefix, testIdCount, testIdEmptyIcon, testIdEmptyTitle, testIdEmptySubtitle, testIdEmptyNote, testIdMessage, }) => {
|
|
10
|
+
const [localFiles, setLocalFiles] = useState([]);
|
|
11
|
+
const localPreviewsRef = useRef(new Map());
|
|
12
|
+
const [resizeDialogOpen, setResizeDialogOpen] = useState(false);
|
|
13
|
+
const [pendingFile, setPendingFile] = useState(null);
|
|
14
|
+
const [pendingFileUrl, setPendingFileUrl] = useState("");
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const map = localPreviewsRef.current;
|
|
17
|
+
localFiles.forEach((file) => {
|
|
18
|
+
if (!map.has(file)) {
|
|
19
|
+
try {
|
|
20
|
+
const url = URL.createObjectURL(file);
|
|
21
|
+
map.set(file, url);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error("Failed to create object URL:", error);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
const currentFiles = new Set(localFiles);
|
|
29
|
+
Array.from(map.entries()).forEach(([file, url]) => {
|
|
30
|
+
if (!currentFiles.has(file)) {
|
|
31
|
+
URL.revokeObjectURL(url);
|
|
32
|
+
map.delete(file);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}, [localFiles]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const map = localPreviewsRef.current;
|
|
38
|
+
return () => {
|
|
39
|
+
Array.from(map.values()).forEach((url) => {
|
|
40
|
+
URL.revokeObjectURL(url);
|
|
41
|
+
});
|
|
42
|
+
map.clear();
|
|
43
|
+
};
|
|
44
|
+
}, []);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
return () => {
|
|
47
|
+
if (pendingFileUrl) {
|
|
48
|
+
URL.revokeObjectURL(pendingFileUrl);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}, [pendingFileUrl]);
|
|
52
|
+
const isImageOnly = React.useMemo(() => {
|
|
53
|
+
if (!accept)
|
|
54
|
+
return false;
|
|
55
|
+
const acceptTypes = Array.isArray(accept)
|
|
56
|
+
? accept
|
|
57
|
+
: Object.keys(accept || {});
|
|
58
|
+
return acceptTypes.some((type) => typeof type === "string" && type.toLowerCase().includes("image"));
|
|
59
|
+
}, [accept]);
|
|
60
|
+
const normalizedAccept = React.useMemo(() => {
|
|
61
|
+
if (!accept)
|
|
62
|
+
return undefined;
|
|
63
|
+
if (Array.isArray(accept)) {
|
|
64
|
+
return accept.reduce((acc, type) => {
|
|
65
|
+
acc[type] = [];
|
|
66
|
+
return acc;
|
|
67
|
+
}, {});
|
|
68
|
+
}
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
return accept;
|
|
71
|
+
}, [accept]);
|
|
72
|
+
const dropzoneOptions = {
|
|
73
|
+
accept: normalizedAccept,
|
|
74
|
+
maxFiles,
|
|
75
|
+
maxSize,
|
|
76
|
+
minSize,
|
|
77
|
+
disabled,
|
|
78
|
+
onDrop: useCallback((acceptedFiles) => {
|
|
79
|
+
if (resize && acceptedFiles.length > 0) {
|
|
80
|
+
const file = acceptedFiles[0];
|
|
81
|
+
const url = URL.createObjectURL(file);
|
|
82
|
+
setPendingFile(file);
|
|
83
|
+
setPendingFileUrl(url);
|
|
84
|
+
setResizeDialogOpen(true);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
setLocalFiles((prev) => {
|
|
88
|
+
const combined = [...prev, ...acceptedFiles];
|
|
89
|
+
const limited = maxFiles ? combined.slice(0, maxFiles) : combined;
|
|
90
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(limited);
|
|
91
|
+
return limited;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}, [maxFiles, onChange, resize]),
|
|
95
|
+
};
|
|
96
|
+
const { getRootProps, getInputProps, isDragActive } = useDropzone(dropzoneOptions);
|
|
97
|
+
const handleRemoveRemote = useCallback((url) => {
|
|
98
|
+
onRemoveRemote === null || onRemoveRemote === void 0 ? void 0 : onRemoveRemote(url);
|
|
99
|
+
}, [onRemoveRemote]);
|
|
100
|
+
const handleRemoveLocal = useCallback((index) => {
|
|
101
|
+
setLocalFiles((prev) => {
|
|
102
|
+
const updated = prev.filter((_, i) => i !== index);
|
|
103
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(updated);
|
|
104
|
+
return updated;
|
|
105
|
+
});
|
|
106
|
+
}, [onChange]);
|
|
107
|
+
const handleCropComplete = useCallback((croppedImage) => {
|
|
108
|
+
setLocalFiles((prev) => {
|
|
109
|
+
const combined = [...prev, croppedImage];
|
|
110
|
+
const limited = maxFiles ? combined.slice(0, maxFiles) : combined;
|
|
111
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(limited);
|
|
112
|
+
return limited;
|
|
113
|
+
});
|
|
114
|
+
}, [maxFiles, onChange]);
|
|
115
|
+
const handleResizeDialogClose = useCallback(() => {
|
|
116
|
+
setResizeDialogOpen(false);
|
|
117
|
+
setPendingFileUrl("");
|
|
118
|
+
setPendingFile(null);
|
|
119
|
+
}, []);
|
|
120
|
+
const allPreviews = [
|
|
121
|
+
...value.map((url) => ({ type: "remote", url, index: 0 })),
|
|
122
|
+
...localFiles.map((file, index) => ({
|
|
123
|
+
type: "local",
|
|
124
|
+
url: localPreviewsRef.current.get(file) || "",
|
|
125
|
+
index,
|
|
126
|
+
})),
|
|
127
|
+
];
|
|
128
|
+
const formatBytes = (bytes) => {
|
|
129
|
+
if (bytes === 0)
|
|
130
|
+
return "0 B";
|
|
131
|
+
const k = 1024;
|
|
132
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
133
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
134
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
|
135
|
+
};
|
|
136
|
+
const getConstraintText = () => {
|
|
137
|
+
const constraints = [];
|
|
138
|
+
if (maxFiles && maxFiles > 1) {
|
|
139
|
+
constraints.push(`Up to ${maxFiles} files`);
|
|
140
|
+
}
|
|
141
|
+
if (minSize || maxSize) {
|
|
142
|
+
const sizes = [];
|
|
143
|
+
if (minSize)
|
|
144
|
+
sizes.push(`min ${formatBytes(minSize)}`);
|
|
145
|
+
if (maxSize)
|
|
146
|
+
sizes.push(`max ${formatBytes(maxSize)}`);
|
|
147
|
+
constraints.push(`${sizes.join(", ")}`);
|
|
148
|
+
}
|
|
149
|
+
return constraints.join(" • ");
|
|
150
|
+
};
|
|
151
|
+
return (React.createElement("div", { className: cn("w-full", className), "data-slot": "enhanced-dropzone", "data-testid": testIdDropzone },
|
|
152
|
+
label && (React.createElement("label", { className: "mb-2 block text-sm font-medium" }, label)),
|
|
153
|
+
React.createElement("div", Object.assign({}, getRootProps(), { className: cn("relative w-full rounded-md border border-dashed p-6 text-center min-h-70 flex items-center justify-center", isDragActive && "ring-2 ring-ring ring-offset-2", disabled && "opacity-60 pointer-events-none") }),
|
|
154
|
+
React.createElement("input", Object.assign({}, getInputProps(), { id: id, "data-testid": testIdInput })),
|
|
155
|
+
isImageOnly && allPreviews.length > 0 ? (React.createElement("div", { className: "flex flex-col items-center w-full" },
|
|
156
|
+
React.createElement("div", { className: "relative w-full max-w-md" },
|
|
157
|
+
React.createElement(Carousel, { className: "w-full" },
|
|
158
|
+
React.createElement(CarouselPrevious, { type: "button", onClick: (e) => e.stopPropagation(), onPointerDown: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), "data-testid": testIdPrev }),
|
|
159
|
+
React.createElement(CarouselNext, { type: "button", onClick: (e) => e.stopPropagation(), onPointerDown: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), "data-testid": testIdNext }),
|
|
160
|
+
React.createElement(CarouselContent, { className: "ml-0" }, allPreviews.map((preview, idx) => (React.createElement(CarouselItem, { key: `${preview.type}-${preview.url}-${idx}`, className: "pl-4", "data-testid": `${testIdPreviewPrefix !== null && testIdPreviewPrefix !== void 0 ? testIdPreviewPrefix : "dropzone-preview"}-${idx}` },
|
|
161
|
+
React.createElement("div", { className: "relative aspect-square w-full max-w-xs mx-auto" }, preview.url ? (React.createElement(React.Fragment, null,
|
|
162
|
+
React.createElement("img", { src: preview.url, alt: `Preview ${idx + 1}`, className: "h-full w-full rounded-lg object-cover", onClick: (e) => e.stopPropagation(), "data-testid": `${testIdImagePrefix !== null && testIdImagePrefix !== void 0 ? testIdImagePrefix : "dropzone-image"}-${idx}` }),
|
|
163
|
+
React.createElement(Button, { type: "button", size: "icon", variant: "destructive", onClick: (e) => {
|
|
164
|
+
e.stopPropagation();
|
|
165
|
+
if (preview.type === "remote") {
|
|
166
|
+
handleRemoveRemote(preview.url);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
handleRemoveLocal(preview.index);
|
|
170
|
+
}
|
|
171
|
+
}, className: "absolute right-2 top-2 h-8 w-8 rounded-full", "aria-label": "Remove image", "data-testid": `${testIdRemovePrefix !== null && testIdRemovePrefix !== void 0 ? testIdRemovePrefix : "dropzone-remove"}-${idx}` },
|
|
172
|
+
React.createElement(XIcon, { className: "h-4 w-4" })))) : (React.createElement("div", { className: "flex h-full w-full items-center justify-center rounded-lg bg-muted text-muted-foreground" },
|
|
173
|
+
React.createElement("span", { className: "text-sm" }, "Loading...")))))))))),
|
|
174
|
+
React.createElement("p", { className: "mt-4 text-sm font-medium text-muted-foreground", "data-testid": testIdCount },
|
|
175
|
+
allPreviews.length,
|
|
176
|
+
" image",
|
|
177
|
+
allPreviews.length !== 1 ? "s" : "",
|
|
178
|
+
" ",
|
|
179
|
+
"selected"))) : !isImageOnly && allPreviews.length > 0 ? (React.createElement("div", { className: "flex flex-col items-center w-full gap-4" },
|
|
180
|
+
React.createElement("div", { className: "w-full max-w-md" },
|
|
181
|
+
React.createElement("div", { className: "space-y-2" }, allPreviews.map((preview, idx) => {
|
|
182
|
+
var _a;
|
|
183
|
+
return (React.createElement("div", { key: `${preview.type}-${preview.url}-${idx}`, "data-testid": `${testIdPreviewPrefix !== null && testIdPreviewPrefix !== void 0 ? testIdPreviewPrefix : "dropzone-preview"}-${idx}`, className: "flex items-center justify-between gap-3 p-3 rounded-lg border bg-muted/50" },
|
|
184
|
+
React.createElement("span", { className: "text-sm truncate flex-1", "data-testid": `${testIdImagePrefix !== null && testIdImagePrefix !== void 0 ? testIdImagePrefix : "dropzone-image"}-${idx}` }, preview.type === "remote"
|
|
185
|
+
? new URL(preview.url).pathname.split("/").pop() ||
|
|
186
|
+
"File"
|
|
187
|
+
: ((_a = localFiles[preview.index]) === null || _a === void 0 ? void 0 : _a.name) || "File"),
|
|
188
|
+
React.createElement(Button, { type: "button", size: "icon", variant: "ghost", onClick: (e) => {
|
|
189
|
+
e.stopPropagation();
|
|
190
|
+
if (preview.type === "remote") {
|
|
191
|
+
handleRemoveRemote(preview.url);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
handleRemoveLocal(preview.index);
|
|
195
|
+
}
|
|
196
|
+
}, className: "h-8 w-8 shrink-0", "aria-label": "Remove file", "data-testid": `${testIdRemovePrefix !== null && testIdRemovePrefix !== void 0 ? testIdRemovePrefix : "dropzone-remove"}-${idx}` },
|
|
197
|
+
React.createElement(XIcon, { className: "h-4 w-4" }))));
|
|
198
|
+
}))),
|
|
199
|
+
React.createElement("p", { className: "text-sm font-medium text-muted-foreground", "data-testid": testIdCount },
|
|
200
|
+
allPreviews.length,
|
|
201
|
+
" file",
|
|
202
|
+
allPreviews.length !== 1 ? "s" : "",
|
|
203
|
+
" ",
|
|
204
|
+
"selected"))) : (React.createElement("div", { className: "flex flex-col items-center justify-center gap-2" },
|
|
205
|
+
React.createElement("div", { className: "flex h-12 w-12 items-center justify-center rounded-lg bg-muted text-muted-foreground", "data-testid": testIdEmptyIcon },
|
|
206
|
+
React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "lucide lucide-image" },
|
|
207
|
+
React.createElement("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
|
|
208
|
+
React.createElement("circle", { cx: "9", cy: "9", r: "2" }),
|
|
209
|
+
React.createElement("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" }))),
|
|
210
|
+
React.createElement("div", { className: "space-y-1 text-center" },
|
|
211
|
+
React.createElement("p", { className: "font-medium text-sm", "data-testid": testIdEmptyTitle }, maxFiles === 1
|
|
212
|
+
? `Upload ${isImageOnly ? "an image" : "a file"}`
|
|
213
|
+
: `Upload ${isImageOnly ? "images" : "files"}`),
|
|
214
|
+
React.createElement("p", { className: "text-muted-foreground text-xs", "data-testid": testIdEmptySubtitle }, "Drag and drop or click to browse"),
|
|
215
|
+
(maxFiles > 1 || minSize || maxSize) && (React.createElement("p", { className: "text-muted-foreground text-xs", "data-testid": testIdEmptyNote }, getConstraintText())))))),
|
|
216
|
+
(error || info) && (React.createElement("div", { className: "mt-2", "data-testid": testIdMessage }, error ? (React.createElement("p", { className: "text-xs text-destructive" }, error)) : info ? (React.createElement("p", { className: "text-xs text-blue-600 dark:text-blue-400" }, info)) : null)),
|
|
217
|
+
resize && pendingFile && (React.createElement(ImageResizeDialog, { open: resizeDialogOpen, onClose: handleResizeDialogClose, imageUrl: pendingFileUrl, onCropComplete: handleCropComplete, fileName: pendingFile.name }))));
|
|
218
|
+
};
|
|
@@ -1,65 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
* Props for `EnhancedDropzone` component.
|
|
5
|
-
*/
|
|
6
|
-
export type EnhancedDropzoneProps = {
|
|
7
|
-
/** HTML id attribute applied to the hidden file input and used for labeling. */
|
|
8
|
-
id?: string;
|
|
9
|
-
/** Additional className(s) applied to the root container. */
|
|
10
|
-
className?: string;
|
|
11
|
-
/** Optional label text displayed above the dropzone. */
|
|
12
|
-
label?: string;
|
|
13
|
-
/** Informational helper text shown below the component (non-error). */
|
|
14
|
-
info?: string;
|
|
15
|
-
/** Error message text shown below the component; puts component into error state. */
|
|
16
|
-
error?: string;
|
|
17
|
-
/**
|
|
18
|
-
* Accepted file types. Matches `react-dropzone` `accept` option or a list of
|
|
19
|
-
* mime types / extensions (e.g. `['image/*']` or `{ 'image/*': [] }`).
|
|
20
|
-
*/
|
|
21
|
-
accept?: DropzoneOptions["accept"] | string[];
|
|
22
|
-
/** Maximum number of files allowed to be selected. Defaults to `10`. */
|
|
23
|
-
maxFiles?: number;
|
|
24
|
-
/** Maximum file size in bytes. */
|
|
25
|
-
maxSize?: number;
|
|
26
|
-
/** Minimum file size in bytes. */
|
|
27
|
-
minSize?: number;
|
|
28
|
-
/** Whether the dropzone is disabled (prevents interactions). */
|
|
29
|
-
disabled?: boolean;
|
|
30
|
-
/** Remote image URLs to display as previews (read-only remote values). */
|
|
31
|
-
value?: string[];
|
|
32
|
-
/** Called when the selected files change (local additions/removals). */
|
|
33
|
-
onChange?: (files: File[]) => void;
|
|
34
|
-
/** Called when a remote URL preview is removed by the user. */
|
|
35
|
-
onRemoveRemote?: (url: string) => void;
|
|
36
|
-
/** Enable image resize/crop functionality. When true, opens a dialog to crop images to square aspect ratio. */
|
|
37
|
-
resize?: boolean;
|
|
38
|
-
/** `data-testid` for the root dropzone container. */
|
|
39
|
-
testIdDropzone?: string;
|
|
40
|
-
/** `data-testid` for the hidden file input element. */
|
|
41
|
-
testIdInput?: string;
|
|
42
|
-
/** `data-testid` for the carousel previous button. */
|
|
43
|
-
testIdPrev?: string;
|
|
44
|
-
/** `data-testid` for the carousel next button. */
|
|
45
|
-
testIdNext?: string;
|
|
46
|
-
/** Prefix used for preview items: `${prefix}-${index}`. */
|
|
47
|
-
testIdPreviewPrefix?: string;
|
|
48
|
-
/** Prefix for preview image elements: `${prefix}-${index}`. */
|
|
49
|
-
testIdImagePrefix?: string;
|
|
50
|
-
/** Prefix for remove buttons on previews: `${prefix}-${index}`. */
|
|
51
|
-
testIdRemovePrefix?: string;
|
|
52
|
-
/** `data-testid` for the preview count text element. */
|
|
53
|
-
testIdCount?: string;
|
|
54
|
-
/** `data-testid` for the empty-state icon wrapper. */
|
|
55
|
-
testIdEmptyIcon?: string;
|
|
56
|
-
/** `data-testid` for the empty-state title. */
|
|
57
|
-
testIdEmptyTitle?: string;
|
|
58
|
-
/** `data-testid` for the empty-state subtitle. */
|
|
59
|
-
testIdEmptySubtitle?: string;
|
|
60
|
-
/** `data-testid` for the empty-state note (max files info). */
|
|
61
|
-
testIdEmptyNote?: string;
|
|
62
|
-
/** `data-testid` for the error/info message container. */
|
|
63
|
-
testIdMessage?: string;
|
|
64
|
-
};
|
|
2
|
+
import type { EnhancedDropzoneProps } from "./enhanced-dropzone-impl";
|
|
3
|
+
export type { EnhancedDropzoneProps };
|
|
65
4
|
export declare const EnhancedDropzone: React.FC<EnhancedDropzoneProps>;
|
|
@@ -1,296 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
t[p] = s[p];
|
|
9
|
-
}
|
|
10
|
-
return t;
|
|
11
|
-
};
|
|
12
|
-
return __assign.apply(this, arguments);
|
|
2
|
+
import React, { Suspense } from "react";
|
|
3
|
+
const DropzoneImpl = React.lazy(() => import("./enhanced-dropzone-impl").then((m) => ({ default: m.EnhancedDropzone })));
|
|
4
|
+
export const EnhancedDropzone = (props) => {
|
|
5
|
+
return (React.createElement(Suspense, { fallback: React.createElement("div", { className: "w-full min-h-70 rounded-md border border-dashed p-6 text-center flex items-center justify-center" },
|
|
6
|
+
React.createElement("p", { className: "text-sm text-muted-foreground" }, "Loading dropzone...")) },
|
|
7
|
+
React.createElement(DropzoneImpl, Object.assign({}, props))));
|
|
13
8
|
};
|
|
14
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
-
if (k2 === undefined) k2 = k;
|
|
16
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
-
}
|
|
20
|
-
Object.defineProperty(o, k2, desc);
|
|
21
|
-
}) : (function(o, m, k, k2) {
|
|
22
|
-
if (k2 === undefined) k2 = k;
|
|
23
|
-
o[k2] = m[k];
|
|
24
|
-
}));
|
|
25
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
-
}) : function(o, v) {
|
|
28
|
-
o["default"] = v;
|
|
29
|
-
});
|
|
30
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
-
var ownKeys = function(o) {
|
|
32
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
-
var ar = [];
|
|
34
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
-
return ar;
|
|
36
|
-
};
|
|
37
|
-
return ownKeys(o);
|
|
38
|
-
};
|
|
39
|
-
return function (mod) {
|
|
40
|
-
if (mod && mod.__esModule) return mod;
|
|
41
|
-
var result = {};
|
|
42
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
-
__setModuleDefault(result, mod);
|
|
44
|
-
return result;
|
|
45
|
-
};
|
|
46
|
-
})();
|
|
47
|
-
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
48
|
-
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
49
|
-
if (ar || !(i in from)) {
|
|
50
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
51
|
-
ar[i] = from[i];
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
55
|
-
};
|
|
56
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
-
exports.EnhancedDropzone = void 0;
|
|
58
|
-
var react_1 = __importStar(require("react"));
|
|
59
|
-
var react_dropzone_1 = require("react-dropzone");
|
|
60
|
-
var utils_1 = require("../lib/utils");
|
|
61
|
-
var carousel_1 = require("./ui/carousel");
|
|
62
|
-
var button_1 = require("./ui/button");
|
|
63
|
-
var image_resize_dialog_1 = require("./image-resize-dialog");
|
|
64
|
-
var lucide_react_1 = require("lucide-react");
|
|
65
|
-
var EnhancedDropzone = function (_a) {
|
|
66
|
-
var id = _a.id, label = _a.label, info = _a.info, error = _a.error, accept = _a.accept, _b = _a.maxFiles, maxFiles = _b === void 0 ? 10 : _b, maxSize = _a.maxSize, minSize = _a.minSize, disabled = _a.disabled, _c = _a.value, value = _c === void 0 ? [] : _c, onChange = _a.onChange, onRemoveRemote = _a.onRemoveRemote, _d = _a.resize, resize = _d === void 0 ? false : _d, className = _a.className, testIdDropzone = _a.testIdDropzone, testIdInput = _a.testIdInput, testIdPrev = _a.testIdPrev, testIdNext = _a.testIdNext, testIdPreviewPrefix = _a.testIdPreviewPrefix, testIdImagePrefix = _a.testIdImagePrefix, testIdRemovePrefix = _a.testIdRemovePrefix, testIdCount = _a.testIdCount, testIdEmptyIcon = _a.testIdEmptyIcon, testIdEmptyTitle = _a.testIdEmptyTitle, testIdEmptySubtitle = _a.testIdEmptySubtitle, testIdEmptyNote = _a.testIdEmptyNote, testIdMessage = _a.testIdMessage;
|
|
67
|
-
// Local files selected by user
|
|
68
|
-
var _e = (0, react_1.useState)([]), localFiles = _e[0], setLocalFiles = _e[1];
|
|
69
|
-
// Track object URLs created for local files
|
|
70
|
-
var localPreviewsRef = (0, react_1.useRef)(new Map());
|
|
71
|
-
// State for resize dialog
|
|
72
|
-
var _f = (0, react_1.useState)(false), resizeDialogOpen = _f[0], setResizeDialogOpen = _f[1];
|
|
73
|
-
var _g = (0, react_1.useState)(null), pendingFile = _g[0], setPendingFile = _g[1];
|
|
74
|
-
var _h = (0, react_1.useState)(""), pendingFileUrl = _h[0], setPendingFileUrl = _h[1];
|
|
75
|
-
// Create and manage object URLs for local files
|
|
76
|
-
(0, react_1.useEffect)(function () {
|
|
77
|
-
var map = localPreviewsRef.current;
|
|
78
|
-
// Create URLs for new files
|
|
79
|
-
localFiles.forEach(function (file) {
|
|
80
|
-
if (!map.has(file)) {
|
|
81
|
-
try {
|
|
82
|
-
var url = URL.createObjectURL(file);
|
|
83
|
-
map.set(file, url);
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
console.error("Failed to create object URL:", error);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
// Clean up URLs for removed files
|
|
91
|
-
var currentFiles = new Set(localFiles);
|
|
92
|
-
Array.from(map.entries()).forEach(function (_a) {
|
|
93
|
-
var file = _a[0], url = _a[1];
|
|
94
|
-
if (!currentFiles.has(file)) {
|
|
95
|
-
URL.revokeObjectURL(url);
|
|
96
|
-
map.delete(file);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}, [localFiles]);
|
|
100
|
-
// Cleanup on unmount
|
|
101
|
-
(0, react_1.useEffect)(function () {
|
|
102
|
-
var map = localPreviewsRef.current;
|
|
103
|
-
return function () {
|
|
104
|
-
Array.from(map.values()).forEach(function (url) {
|
|
105
|
-
URL.revokeObjectURL(url);
|
|
106
|
-
});
|
|
107
|
-
map.clear();
|
|
108
|
-
};
|
|
109
|
-
}, []);
|
|
110
|
-
// Cleanup pending file URL when dialog closes
|
|
111
|
-
(0, react_1.useEffect)(function () {
|
|
112
|
-
return function () {
|
|
113
|
-
if (pendingFileUrl) {
|
|
114
|
-
URL.revokeObjectURL(pendingFileUrl);
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
}, [pendingFileUrl]);
|
|
118
|
-
// Check if accept includes image types
|
|
119
|
-
var isImageOnly = react_1.default.useMemo(function () {
|
|
120
|
-
if (!accept)
|
|
121
|
-
return false;
|
|
122
|
-
var acceptTypes = Array.isArray(accept)
|
|
123
|
-
? accept
|
|
124
|
-
: Object.keys(accept || {});
|
|
125
|
-
return acceptTypes.some(function (type) { return typeof type === "string" && type.toLowerCase().includes("image"); });
|
|
126
|
-
}, [accept]);
|
|
127
|
-
// Convert accept array to react-dropzone format if needed
|
|
128
|
-
var normalizedAccept = react_1.default.useMemo(function () {
|
|
129
|
-
if (!accept)
|
|
130
|
-
return undefined;
|
|
131
|
-
if (Array.isArray(accept)) {
|
|
132
|
-
// Convert string array like ['image/*'] to object format { 'image/*': [] }
|
|
133
|
-
return accept.reduce(function (acc, type) {
|
|
134
|
-
acc[type] = [];
|
|
135
|
-
return acc;
|
|
136
|
-
}, {});
|
|
137
|
-
}
|
|
138
|
-
// Already in object format
|
|
139
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
|
-
return accept;
|
|
141
|
-
}, [accept]);
|
|
142
|
-
var dropzoneOptions = {
|
|
143
|
-
accept: normalizedAccept,
|
|
144
|
-
maxFiles: maxFiles,
|
|
145
|
-
maxSize: maxSize,
|
|
146
|
-
minSize: minSize,
|
|
147
|
-
disabled: disabled,
|
|
148
|
-
onDrop: (0, react_1.useCallback)(function (acceptedFiles) {
|
|
149
|
-
if (resize && acceptedFiles.length > 0) {
|
|
150
|
-
// If resize is enabled, open the resize dialog with the first file
|
|
151
|
-
var file = acceptedFiles[0];
|
|
152
|
-
var url = URL.createObjectURL(file);
|
|
153
|
-
setPendingFile(file);
|
|
154
|
-
setPendingFileUrl(url);
|
|
155
|
-
setResizeDialogOpen(true);
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
// If resize is disabled, add files directly
|
|
159
|
-
setLocalFiles(function (prev) {
|
|
160
|
-
var combined = __spreadArray(__spreadArray([], prev, true), acceptedFiles, true);
|
|
161
|
-
var limited = maxFiles ? combined.slice(0, maxFiles) : combined;
|
|
162
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(limited);
|
|
163
|
-
return limited;
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}, [maxFiles, onChange, resize]),
|
|
167
|
-
};
|
|
168
|
-
var _j = (0, react_dropzone_1.useDropzone)(dropzoneOptions), getRootProps = _j.getRootProps, getInputProps = _j.getInputProps, isDragActive = _j.isDragActive;
|
|
169
|
-
// Remove remote URL
|
|
170
|
-
var handleRemoveRemote = (0, react_1.useCallback)(function (url) {
|
|
171
|
-
onRemoveRemote === null || onRemoveRemote === void 0 ? void 0 : onRemoveRemote(url);
|
|
172
|
-
}, [onRemoveRemote]);
|
|
173
|
-
// Remove local file
|
|
174
|
-
var handleRemoveLocal = (0, react_1.useCallback)(function (index) {
|
|
175
|
-
setLocalFiles(function (prev) {
|
|
176
|
-
var updated = prev.filter(function (_, i) { return i !== index; });
|
|
177
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(updated);
|
|
178
|
-
return updated;
|
|
179
|
-
});
|
|
180
|
-
}, [onChange]);
|
|
181
|
-
// Handle crop completion from resize dialog
|
|
182
|
-
var handleCropComplete = (0, react_1.useCallback)(function (croppedImage) {
|
|
183
|
-
setLocalFiles(function (prev) {
|
|
184
|
-
var combined = __spreadArray(__spreadArray([], prev, true), [croppedImage], false);
|
|
185
|
-
var limited = maxFiles ? combined.slice(0, maxFiles) : combined;
|
|
186
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(limited);
|
|
187
|
-
return limited;
|
|
188
|
-
});
|
|
189
|
-
}, [maxFiles, onChange]);
|
|
190
|
-
// Handle resize dialog close
|
|
191
|
-
var handleResizeDialogClose = (0, react_1.useCallback)(function () {
|
|
192
|
-
setResizeDialogOpen(false);
|
|
193
|
-
// Cleanup will happen via the pendingFileUrl useEffect
|
|
194
|
-
setPendingFileUrl("");
|
|
195
|
-
setPendingFile(null);
|
|
196
|
-
}, []);
|
|
197
|
-
// Get all preview URLs (remote + local)
|
|
198
|
-
var allPreviews = __spreadArray(__spreadArray([], value.map(function (url) { return ({ type: "remote", url: url, index: 0 }); }), true), localFiles.map(function (file, index) { return ({
|
|
199
|
-
type: "local",
|
|
200
|
-
url: localPreviewsRef.current.get(file) || "",
|
|
201
|
-
index: index,
|
|
202
|
-
}); }), true);
|
|
203
|
-
// Format bytes to human-readable size
|
|
204
|
-
var formatBytes = function (bytes) {
|
|
205
|
-
if (bytes === 0)
|
|
206
|
-
return "0 B";
|
|
207
|
-
var k = 1024;
|
|
208
|
-
var sizes = ["B", "KB", "MB", "GB"];
|
|
209
|
-
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
210
|
-
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
|
211
|
-
};
|
|
212
|
-
// Build constraint text for the note
|
|
213
|
-
var getConstraintText = function () {
|
|
214
|
-
var constraints = [];
|
|
215
|
-
if (maxFiles && maxFiles > 1) {
|
|
216
|
-
constraints.push("Up to ".concat(maxFiles, " files"));
|
|
217
|
-
}
|
|
218
|
-
if (minSize || maxSize) {
|
|
219
|
-
var sizes = [];
|
|
220
|
-
if (minSize)
|
|
221
|
-
sizes.push("min ".concat(formatBytes(minSize)));
|
|
222
|
-
if (maxSize)
|
|
223
|
-
sizes.push("max ".concat(formatBytes(maxSize)));
|
|
224
|
-
constraints.push("".concat(sizes.join(", ")));
|
|
225
|
-
}
|
|
226
|
-
return constraints.join(" • ");
|
|
227
|
-
};
|
|
228
|
-
return (react_1.default.createElement("div", { className: (0, utils_1.cn)("w-full", className), "data-slot": "enhanced-dropzone", "data-testid": testIdDropzone },
|
|
229
|
-
label && (react_1.default.createElement("label", { className: "mb-2 block text-sm font-medium" }, label)),
|
|
230
|
-
react_1.default.createElement("div", __assign({}, getRootProps(), { className: (0, utils_1.cn)("relative w-full rounded-md border border-dashed p-6 text-center min-h-70 flex items-center justify-center", isDragActive && "ring-2 ring-ring ring-offset-2", disabled && "opacity-60 pointer-events-none") }),
|
|
231
|
-
react_1.default.createElement("input", __assign({}, getInputProps(), { id: id, "data-testid": testIdInput })),
|
|
232
|
-
isImageOnly && allPreviews.length > 0 ? (react_1.default.createElement("div", { className: "flex flex-col items-center w-full" },
|
|
233
|
-
react_1.default.createElement("div", { className: "relative w-full max-w-md" },
|
|
234
|
-
react_1.default.createElement(carousel_1.Carousel, { className: "w-full" },
|
|
235
|
-
react_1.default.createElement(carousel_1.CarouselPrevious, { type: "button", onClick: function (e) { return e.stopPropagation(); }, onPointerDown: function (e) { return e.stopPropagation(); }, onMouseDown: function (e) { return e.stopPropagation(); }, "data-testid": testIdPrev }),
|
|
236
|
-
react_1.default.createElement(carousel_1.CarouselNext, { type: "button", onClick: function (e) { return e.stopPropagation(); }, onPointerDown: function (e) { return e.stopPropagation(); }, onMouseDown: function (e) { return e.stopPropagation(); }, "data-testid": testIdNext }),
|
|
237
|
-
react_1.default.createElement(carousel_1.CarouselContent, { className: "ml-0" }, allPreviews.map(function (preview, idx) { return (react_1.default.createElement(carousel_1.CarouselItem, { key: "".concat(preview.type, "-").concat(preview.url, "-").concat(idx), className: "pl-4", "data-testid": "".concat(testIdPreviewPrefix !== null && testIdPreviewPrefix !== void 0 ? testIdPreviewPrefix : "dropzone-preview", "-").concat(idx) },
|
|
238
|
-
react_1.default.createElement("div", { className: "relative aspect-square w-full max-w-xs mx-auto" }, preview.url ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
239
|
-
react_1.default.createElement("img", { src: preview.url, alt: "Preview ".concat(idx + 1), className: "h-full w-full rounded-lg object-cover", onClick: function (e) { return e.stopPropagation(); }, "data-testid": "".concat(testIdImagePrefix !== null && testIdImagePrefix !== void 0 ? testIdImagePrefix : "dropzone-image", "-").concat(idx) }),
|
|
240
|
-
react_1.default.createElement(button_1.Button, { type: "button", size: "icon", variant: "destructive", onClick: function (e) {
|
|
241
|
-
e.stopPropagation();
|
|
242
|
-
if (preview.type === "remote") {
|
|
243
|
-
handleRemoveRemote(preview.url);
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
handleRemoveLocal(preview.index);
|
|
247
|
-
}
|
|
248
|
-
}, className: "absolute right-2 top-2 h-8 w-8 rounded-full", "aria-label": "Remove image", "data-testid": "".concat(testIdRemovePrefix !== null && testIdRemovePrefix !== void 0 ? testIdRemovePrefix : "dropzone-remove", "-").concat(idx) },
|
|
249
|
-
react_1.default.createElement(lucide_react_1.XIcon, { className: "h-4 w-4" })))) : (react_1.default.createElement("div", { className: "flex h-full w-full items-center justify-center rounded-lg bg-muted text-muted-foreground" },
|
|
250
|
-
react_1.default.createElement("span", { className: "text-sm" }, "Loading...")))))); })))),
|
|
251
|
-
react_1.default.createElement("p", { className: "mt-4 text-sm font-medium text-muted-foreground", "data-testid": testIdCount },
|
|
252
|
-
allPreviews.length,
|
|
253
|
-
" image",
|
|
254
|
-
allPreviews.length !== 1 ? "s" : "",
|
|
255
|
-
" ",
|
|
256
|
-
"selected"))) : !isImageOnly && allPreviews.length > 0 ? (react_1.default.createElement("div", { className: "flex flex-col items-center w-full gap-4" },
|
|
257
|
-
react_1.default.createElement("div", { className: "w-full max-w-md" },
|
|
258
|
-
react_1.default.createElement("div", { className: "space-y-2" }, allPreviews.map(function (preview, idx) {
|
|
259
|
-
var _a;
|
|
260
|
-
return (react_1.default.createElement("div", { key: "".concat(preview.type, "-").concat(preview.url, "-").concat(idx), "data-testid": "".concat(testIdPreviewPrefix !== null && testIdPreviewPrefix !== void 0 ? testIdPreviewPrefix : "dropzone-preview", "-").concat(idx), className: "flex items-center justify-between gap-3 p-3 rounded-lg border bg-muted/50" },
|
|
261
|
-
react_1.default.createElement("span", { className: "text-sm truncate flex-1", "data-testid": "".concat(testIdImagePrefix !== null && testIdImagePrefix !== void 0 ? testIdImagePrefix : "dropzone-image", "-").concat(idx) }, preview.type === "remote"
|
|
262
|
-
? new URL(preview.url).pathname.split("/").pop() ||
|
|
263
|
-
"File"
|
|
264
|
-
: ((_a = localFiles[preview.index]) === null || _a === void 0 ? void 0 : _a.name) || "File"),
|
|
265
|
-
react_1.default.createElement(button_1.Button, { type: "button", size: "icon", variant: "ghost", onClick: function (e) {
|
|
266
|
-
e.stopPropagation();
|
|
267
|
-
if (preview.type === "remote") {
|
|
268
|
-
handleRemoveRemote(preview.url);
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
handleRemoveLocal(preview.index);
|
|
272
|
-
}
|
|
273
|
-
}, className: "h-8 w-8 shrink-0", "aria-label": "Remove file", "data-testid": "".concat(testIdRemovePrefix !== null && testIdRemovePrefix !== void 0 ? testIdRemovePrefix : "dropzone-remove", "-").concat(idx) },
|
|
274
|
-
react_1.default.createElement(lucide_react_1.XIcon, { className: "h-4 w-4" }))));
|
|
275
|
-
}))),
|
|
276
|
-
react_1.default.createElement("p", { className: "text-sm font-medium text-muted-foreground", "data-testid": testIdCount },
|
|
277
|
-
allPreviews.length,
|
|
278
|
-
" file",
|
|
279
|
-
allPreviews.length !== 1 ? "s" : "",
|
|
280
|
-
" ",
|
|
281
|
-
"selected"))) : (react_1.default.createElement("div", { className: "flex flex-col items-center justify-center gap-2" },
|
|
282
|
-
react_1.default.createElement("div", { className: "flex h-12 w-12 items-center justify-center rounded-lg bg-muted text-muted-foreground", "data-testid": testIdEmptyIcon },
|
|
283
|
-
react_1.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "lucide lucide-image" },
|
|
284
|
-
react_1.default.createElement("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
|
|
285
|
-
react_1.default.createElement("circle", { cx: "9", cy: "9", r: "2" }),
|
|
286
|
-
react_1.default.createElement("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" }))),
|
|
287
|
-
react_1.default.createElement("div", { className: "space-y-1 text-center" },
|
|
288
|
-
react_1.default.createElement("p", { className: "font-medium text-sm", "data-testid": testIdEmptyTitle }, maxFiles === 1
|
|
289
|
-
? "Upload ".concat(isImageOnly ? "an image" : "a file")
|
|
290
|
-
: "Upload ".concat(isImageOnly ? "images" : "files")),
|
|
291
|
-
react_1.default.createElement("p", { className: "text-muted-foreground text-xs", "data-testid": testIdEmptySubtitle }, "Drag and drop or click to browse"),
|
|
292
|
-
(maxFiles > 1 || minSize || maxSize) && (react_1.default.createElement("p", { className: "text-muted-foreground text-xs", "data-testid": testIdEmptyNote }, getConstraintText())))))),
|
|
293
|
-
(error || info) && (react_1.default.createElement("div", { className: "mt-2", "data-testid": testIdMessage }, error ? (react_1.default.createElement("p", { className: "text-xs text-destructive" }, error)) : info ? (react_1.default.createElement("p", { className: "text-xs text-blue-600 dark:text-blue-400" }, info)) : null)),
|
|
294
|
-
resize && pendingFile && (react_1.default.createElement(image_resize_dialog_1.ImageResizeDialog, { open: resizeDialogOpen, onClose: handleResizeDialogClose, imageUrl: pendingFileUrl, onCropComplete: handleCropComplete, fileName: pendingFile.name }))));
|
|
295
|
-
};
|
|
296
|
-
exports.EnhancedDropzone = EnhancedDropzone;
|