@sonamu-kit/react-components 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/components/index.d.ts +65 -0
- package/dist/components/ui/accordion.d.ts +7 -0
- package/dist/components/ui/alert-dialog.d.ts +20 -0
- package/dist/components/ui/alert.d.ts +8 -0
- package/dist/components/ui/aspect-ratio.d.ts +3 -0
- package/dist/components/ui/async-select.d.ts +36 -0
- package/dist/components/ui/avatar.d.ts +6 -0
- package/dist/components/ui/badge.d.ts +9 -0
- package/dist/components/ui/breadcrumb.d.ts +19 -0
- package/dist/components/ui/button.d.ts +13 -0
- package/dist/components/ui/calendar.d.ts +5 -0
- package/dist/components/ui/card.d.ts +9 -0
- package/dist/components/ui/carousel.d.ts +18 -0
- package/dist/components/ui/checkbox.d.ts +8 -0
- package/dist/components/ui/collapsible.d.ts +5 -0
- package/dist/components/ui/combobox.d.ts +20 -0
- package/dist/components/ui/command.d.ts +80 -0
- package/dist/components/ui/common-modal.d.ts +28 -0
- package/dist/components/ui/context-menu.d.ts +27 -0
- package/dist/components/ui/date-input.d.ts +7 -0
- package/dist/components/ui/date-picker.d.ts +26 -0
- package/dist/components/ui/date-selector-multiple.d.ts +38 -0
- package/dist/components/ui/dialog.d.ts +19 -0
- package/dist/components/ui/drawer.d.ts +22 -0
- package/dist/components/ui/dropdown-menu.d.ts +27 -0
- package/dist/components/ui/form.d.ts +23 -0
- package/dist/components/ui/hover-card.d.ts +6 -0
- package/dist/components/ui/image-uploader.d.ts +14 -0
- package/dist/components/ui/input-otp.d.ts +34 -0
- package/dist/components/ui/input.d.ts +7 -0
- package/dist/components/ui/label.d.ts +5 -0
- package/dist/components/ui/menubar.d.ts +28 -0
- package/dist/components/ui/month-picker-multiple.d.ts +41 -0
- package/dist/components/ui/multi-image-uploader.d.ts +15 -0
- package/dist/components/ui/multi-select.d.ts +229 -0
- package/dist/components/ui/navigation-menu.d.ts +12 -0
- package/dist/components/ui/pagination.d.ts +10 -0
- package/dist/components/ui/popover.d.ts +7 -0
- package/dist/components/ui/progress.d.ts +4 -0
- package/dist/components/ui/radio-group.d.ts +5 -0
- package/dist/components/ui/resizable.d.ts +23 -0
- package/dist/components/ui/scroll-area.d.ts +5 -0
- package/dist/components/ui/select.d.ts +20 -0
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/sheet.d.ts +25 -0
- package/dist/components/ui/sidebar.d.ts +69 -0
- package/dist/components/ui/skeleton.d.ts +2 -0
- package/dist/components/ui/slider.d.ts +8 -0
- package/dist/components/ui/sonner.d.ts +4 -0
- package/dist/components/ui/switch.d.ts +8 -0
- package/dist/components/ui/table.d.ts +24 -0
- package/dist/components/ui/tabs.d.ts +7 -0
- package/dist/components/ui/textarea.d.ts +7 -0
- package/dist/components/ui/toast.d.ts +15 -0
- package/dist/components/ui/toaster.d.ts +1 -0
- package/dist/components/ui/toggle-group.d.ts +12 -0
- package/dist/components/ui/toggle.d.ts +12 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/use-toast.d.ts +44 -0
- package/dist/index.d.ts +3 -0
- package/dist/lib/caster.d.ts +3 -0
- package/dist/lib/helpers.d.ts +72 -0
- package/dist/lib/index.d.ts +6 -0
- package/{src/lib/lazy-upload.ts → dist/lib/lazy-upload.d.ts} +1 -12
- package/dist/lib/use-mobile.d.ts +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/react-components.es.js +28375 -0
- package/package.json +105 -76
- package/COMPONENTS_LIST.md +0 -106
- package/COMPONENTS_STATUS.md +0 -114
- package/HELPERS_GUIDE.md +0 -489
- package/MIGRATION_PLAN.md +0 -404
- package/SETUP_GUIDE.md +0 -125
- package/components.json +0 -21
- package/postcss.config.js +0 -6
- package/src/components/index.ts +0 -315
- package/src/components/ui/accordion.tsx +0 -54
- package/src/components/ui/alert-dialog.tsx +0 -115
- package/src/components/ui/alert.tsx +0 -49
- package/src/components/ui/aspect-ratio.tsx +0 -5
- package/src/components/ui/async-select.tsx +0 -186
- package/src/components/ui/avatar.tsx +0 -45
- package/src/components/ui/badge.tsx +0 -38
- package/src/components/ui/breadcrumb.tsx +0 -102
- package/src/components/ui/button.tsx +0 -54
- package/src/components/ui/calendar.tsx +0 -193
- package/src/components/ui/card.tsx +0 -65
- package/src/components/ui/carousel.tsx +0 -243
- package/src/components/ui/checkbox.tsx +0 -67
- package/src/components/ui/collapsible.tsx +0 -9
- package/src/components/ui/combobox.tsx +0 -135
- package/src/components/ui/command.tsx +0 -143
- package/src/components/ui/common-modal.tsx +0 -95
- package/src/components/ui/context-menu.tsx +0 -189
- package/src/components/ui/date-picker.tsx +0 -112
- package/src/components/ui/date-selector-multiple.tsx +0 -197
- package/src/components/ui/dialog.tsx +0 -104
- package/src/components/ui/drawer.tsx +0 -100
- package/src/components/ui/dropdown-menu.tsx +0 -189
- package/src/components/ui/form.tsx +0 -171
- package/src/components/ui/hover-card.tsx +0 -27
- package/src/components/ui/image-uploader.tsx +0 -251
- package/src/components/ui/input-otp.tsx +0 -69
- package/src/components/ui/input.tsx +0 -38
- package/src/components/ui/label.tsx +0 -19
- package/src/components/ui/menubar.tsx +0 -231
- package/src/components/ui/month-picker-multiple.tsx +0 -351
- package/src/components/ui/multi-image-uploader.tsx +0 -283
- package/src/components/ui/multi-select.tsx +0 -1143
- package/src/components/ui/navigation-menu.tsx +0 -120
- package/src/components/ui/pagination.tsx +0 -72
- package/src/components/ui/popover.tsx +0 -42
- package/src/components/ui/progress.tsx +0 -25
- package/src/components/ui/radio-group.tsx +0 -38
- package/src/components/ui/resizable.tsx +0 -42
- package/src/components/ui/scroll-area.tsx +0 -46
- package/src/components/ui/select.tsx +0 -235
- package/src/components/ui/separator.tsx +0 -24
- package/src/components/ui/sheet.tsx +0 -119
- package/src/components/ui/sidebar.tsx +0 -683
- package/src/components/ui/skeleton.tsx +0 -7
- package/src/components/ui/slider.tsx +0 -57
- package/src/components/ui/sonner.tsx +0 -39
- package/src/components/ui/switch.tsx +0 -63
- package/src/components/ui/table.tsx +0 -94
- package/src/components/ui/tabs.tsx +0 -53
- package/src/components/ui/textarea.tsx +0 -34
- package/src/components/ui/toast.tsx +0 -122
- package/src/components/ui/toaster.tsx +0 -29
- package/src/components/ui/toggle-group.tsx +0 -55
- package/src/components/ui/toggle.tsx +0 -41
- package/src/components/ui/tooltip.tsx +0 -28
- package/src/hooks/index.ts +0 -2
- package/src/hooks/use-toast.ts +0 -189
- package/src/icons.d.ts +0 -1
- package/src/index.ts +0 -4
- package/src/lib/caster.ts +0 -66
- package/src/lib/helpers.ts +0 -394
- package/src/lib/index.ts +0 -31
- package/src/lib/use-mobile.ts +0 -19
- package/src/lib/utils.ts +0 -6
- package/src/styles/globals.css +0 -658
- package/tailwind.config.ts +0 -8
- package/tsconfig.json +0 -31
- package/tsconfig.node.json +0 -11
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import type React from "react";
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
-
import Loader2Icon from "~icons/lucide/loader2";
|
|
4
|
-
import PlusIcon from "~icons/lucide/plus";
|
|
5
|
-
import XIcon from "~icons/lucide/x";
|
|
6
|
-
import { cn } from "../../lib/utils";
|
|
7
|
-
import { Button } from "./button";
|
|
8
|
-
|
|
9
|
-
export type MultiImageUploaderProps = {
|
|
10
|
-
value?: string[];
|
|
11
|
-
onValueChange?: (value: string[]) => void;
|
|
12
|
-
onBlur?: React.FocusEventHandler<HTMLInputElement>;
|
|
13
|
-
uploader?: (file: File) => Promise<string>;
|
|
14
|
-
placeholder?: string;
|
|
15
|
-
accept?: string;
|
|
16
|
-
disabled?: boolean;
|
|
17
|
-
className?: string;
|
|
18
|
-
previewSize?: "sm" | "md" | "lg";
|
|
19
|
-
maxImages?: number;
|
|
20
|
-
mode?: "eager" | "lazy";
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export function MultiImageUploader({
|
|
24
|
-
value = [],
|
|
25
|
-
onValueChange,
|
|
26
|
-
onBlur,
|
|
27
|
-
uploader,
|
|
28
|
-
placeholder = "Click to upload",
|
|
29
|
-
accept = "image/*",
|
|
30
|
-
disabled = false,
|
|
31
|
-
className,
|
|
32
|
-
previewSize = "md",
|
|
33
|
-
maxImages,
|
|
34
|
-
mode = "eager",
|
|
35
|
-
}: MultiImageUploaderProps) {
|
|
36
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
37
|
-
const [isUploading, setIsUploading] = useState(false);
|
|
38
|
-
const [dragOver, setDragOver] = useState(false);
|
|
39
|
-
const [pendingFiles, setPendingFiles] = useState<File[]>([]);
|
|
40
|
-
const [previewUrls, setPreviewUrls] = useState<string[]>([]);
|
|
41
|
-
|
|
42
|
-
const sizeClasses = {
|
|
43
|
-
sm: "h-20 w-20",
|
|
44
|
-
md: "h-32 w-32",
|
|
45
|
-
lg: "h-48 w-48",
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const handleFilesChange = useCallback(
|
|
49
|
-
async (files: FileList | null) => {
|
|
50
|
-
if (!files || files.length === 0 || disabled) return;
|
|
51
|
-
|
|
52
|
-
const fileArray = Array.from(files);
|
|
53
|
-
|
|
54
|
-
if (mode === "lazy") {
|
|
55
|
-
// Lazy mode: store files and create previews
|
|
56
|
-
const newPendingFiles = [...pendingFiles, ...fileArray];
|
|
57
|
-
const limitedFiles = maxImages ? newPendingFiles.slice(0, maxImages) : newPendingFiles;
|
|
58
|
-
|
|
59
|
-
setPendingFiles(limitedFiles);
|
|
60
|
-
|
|
61
|
-
// Create preview URLs
|
|
62
|
-
const newPreviewUrls = fileArray.map((file) => URL.createObjectURL(file));
|
|
63
|
-
const allPreviewUrls = [...previewUrls, ...newPreviewUrls];
|
|
64
|
-
const limitedPreviewUrls = maxImages ? allPreviewUrls.slice(0, maxImages) : allPreviewUrls;
|
|
65
|
-
|
|
66
|
-
setPreviewUrls(limitedPreviewUrls);
|
|
67
|
-
// Don't call onValueChange in lazy mode - we'll update it after upload
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Eager mode: upload immediately
|
|
72
|
-
if (!uploader) {
|
|
73
|
-
console.error("uploader prop is required in eager mode");
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
setIsUploading(true);
|
|
78
|
-
try {
|
|
79
|
-
const uploadPromises = fileArray.map((file) => uploader(file));
|
|
80
|
-
const uploadedUrls = await Promise.all(uploadPromises);
|
|
81
|
-
const newValue = [...(value || []), ...uploadedUrls];
|
|
82
|
-
|
|
83
|
-
// Limit to maxImages if specified
|
|
84
|
-
const limitedValue = maxImages ? newValue.slice(0, maxImages) : newValue;
|
|
85
|
-
onValueChange?.(limitedValue);
|
|
86
|
-
} catch (error) {
|
|
87
|
-
console.error("Upload failed:", error);
|
|
88
|
-
} finally {
|
|
89
|
-
setIsUploading(false);
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
[uploader, onValueChange, disabled, value, maxImages, mode, pendingFiles, previewUrls],
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
96
|
-
handleFilesChange(e.target.files);
|
|
97
|
-
e.target.value = "";
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const handleDrop = useCallback(
|
|
101
|
-
(e: React.DragEvent<HTMLDivElement>) => {
|
|
102
|
-
e.preventDefault();
|
|
103
|
-
e.stopPropagation();
|
|
104
|
-
setDragOver(false);
|
|
105
|
-
handleFilesChange(e.dataTransfer.files);
|
|
106
|
-
},
|
|
107
|
-
[handleFilesChange],
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
|
|
111
|
-
e.preventDefault();
|
|
112
|
-
e.stopPropagation();
|
|
113
|
-
if (!disabled) {
|
|
114
|
-
setDragOver(true);
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
|
|
119
|
-
e.preventDefault();
|
|
120
|
-
e.stopPropagation();
|
|
121
|
-
setDragOver(false);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const handleRemove = (index: number) => {
|
|
125
|
-
if (mode === "lazy") {
|
|
126
|
-
// In lazy mode, all displayed items are from previewUrls
|
|
127
|
-
const newPendingFiles = [...pendingFiles];
|
|
128
|
-
newPendingFiles.splice(index, 1);
|
|
129
|
-
setPendingFiles(newPendingFiles);
|
|
130
|
-
|
|
131
|
-
// Clean up preview URL
|
|
132
|
-
const urlToRevoke = previewUrls[index];
|
|
133
|
-
if (urlToRevoke) {
|
|
134
|
-
URL.revokeObjectURL(urlToRevoke);
|
|
135
|
-
}
|
|
136
|
-
const newPreviewUrls = [...previewUrls];
|
|
137
|
-
newPreviewUrls.splice(index, 1);
|
|
138
|
-
setPreviewUrls(newPreviewUrls);
|
|
139
|
-
} else {
|
|
140
|
-
// Eager mode: remove from uploaded values
|
|
141
|
-
const newValue = [...(value || [])];
|
|
142
|
-
newValue.splice(index, 1);
|
|
143
|
-
onValueChange?.(newValue);
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
// Commit function for lazy mode
|
|
148
|
-
const commitUpload = useCallback(async (): Promise<string[]> => {
|
|
149
|
-
if (mode !== "lazy" || pendingFiles.length === 0 || !uploader) {
|
|
150
|
-
return value || [];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
setIsUploading(true);
|
|
154
|
-
try {
|
|
155
|
-
const uploadPromises = pendingFiles.map((file) => uploader(file));
|
|
156
|
-
const uploadedUrls = await Promise.all(uploadPromises);
|
|
157
|
-
|
|
158
|
-
// Clean up preview URLs
|
|
159
|
-
for (const url of previewUrls) {
|
|
160
|
-
URL.revokeObjectURL(url);
|
|
161
|
-
}
|
|
162
|
-
setPreviewUrls([]);
|
|
163
|
-
setPendingFiles([]);
|
|
164
|
-
|
|
165
|
-
const finalValue = [...(value || []), ...uploadedUrls];
|
|
166
|
-
onValueChange?.(finalValue);
|
|
167
|
-
return finalValue;
|
|
168
|
-
} catch (error) {
|
|
169
|
-
console.error("Upload failed:", error);
|
|
170
|
-
return value || [];
|
|
171
|
-
} finally {
|
|
172
|
-
setIsUploading(false);
|
|
173
|
-
}
|
|
174
|
-
}, [mode, pendingFiles, uploader, value, previewUrls, onValueChange]);
|
|
175
|
-
|
|
176
|
-
const handleClick = () => {
|
|
177
|
-
if (!disabled && !isUploading) {
|
|
178
|
-
inputRef.current?.click();
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
// Listen for commit event in lazy mode
|
|
183
|
-
useEffect(() => {
|
|
184
|
-
if (mode !== "lazy") return;
|
|
185
|
-
|
|
186
|
-
const handleCommit = async (event: Event) => {
|
|
187
|
-
const customEvent = event as CustomEvent<{
|
|
188
|
-
channel: string;
|
|
189
|
-
done: (urls: string[]) => void;
|
|
190
|
-
}>;
|
|
191
|
-
|
|
192
|
-
if (customEvent.detail?.channel !== "image-uploader") return;
|
|
193
|
-
|
|
194
|
-
const result = await commitUpload();
|
|
195
|
-
customEvent.detail.done(result);
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
document.addEventListener("app:image-uploader/commit", handleCommit);
|
|
199
|
-
return () => {
|
|
200
|
-
document.removeEventListener("app:image-uploader/commit", handleCommit);
|
|
201
|
-
};
|
|
202
|
-
}, [mode, commitUpload]);
|
|
203
|
-
|
|
204
|
-
// Clean up preview URLs on unmount
|
|
205
|
-
useEffect(() => {
|
|
206
|
-
return () => {
|
|
207
|
-
for (const url of previewUrls) {
|
|
208
|
-
URL.revokeObjectURL(url);
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
}, [previewUrls]);
|
|
212
|
-
|
|
213
|
-
const totalCount = mode === "lazy" ? pendingFiles.length : value?.length || 0;
|
|
214
|
-
const canAddMore = !maxImages || totalCount < maxImages;
|
|
215
|
-
const displayUrls = mode === "lazy" ? previewUrls : value;
|
|
216
|
-
|
|
217
|
-
return (
|
|
218
|
-
<div className={cn("flex flex-wrap gap-3", className)}>
|
|
219
|
-
<input
|
|
220
|
-
ref={inputRef}
|
|
221
|
-
type="file"
|
|
222
|
-
accept={accept}
|
|
223
|
-
multiple
|
|
224
|
-
onChange={handleInputChange}
|
|
225
|
-
onBlur={onBlur}
|
|
226
|
-
disabled={disabled || isUploading}
|
|
227
|
-
className="sr-only"
|
|
228
|
-
/>
|
|
229
|
-
|
|
230
|
-
{/* Existing and pending images */}
|
|
231
|
-
{displayUrls?.map((url, index) => (
|
|
232
|
-
<div
|
|
233
|
-
key={`${url}-${index}`}
|
|
234
|
-
className={cn("relative rounded-lg border overflow-hidden", sizeClasses[previewSize])}
|
|
235
|
-
>
|
|
236
|
-
<img src={url} alt={`Uploaded ${index + 1}`} className="h-full w-full object-cover" />
|
|
237
|
-
{!disabled && (
|
|
238
|
-
<Button
|
|
239
|
-
type="button"
|
|
240
|
-
variant="destructive"
|
|
241
|
-
size="icon"
|
|
242
|
-
className="absolute top-1 right-1 h-6 w-6 rounded-full"
|
|
243
|
-
onClick={() => handleRemove(index)}
|
|
244
|
-
>
|
|
245
|
-
<XIcon className="h-3 w-3" />
|
|
246
|
-
</Button>
|
|
247
|
-
)}
|
|
248
|
-
</div>
|
|
249
|
-
))}
|
|
250
|
-
|
|
251
|
-
{/* Add button */}
|
|
252
|
-
{canAddMore && (
|
|
253
|
-
<div
|
|
254
|
-
onClick={handleClick}
|
|
255
|
-
onDrop={handleDrop}
|
|
256
|
-
onDragOver={handleDragOver}
|
|
257
|
-
onDragLeave={handleDragLeave}
|
|
258
|
-
className={cn(
|
|
259
|
-
"flex items-center justify-center rounded-lg border-2 border-dashed cursor-pointer transition-all",
|
|
260
|
-
sizeClasses[previewSize],
|
|
261
|
-
dragOver
|
|
262
|
-
? "border-primary bg-primary/5"
|
|
263
|
-
: "border-muted-foreground/25 hover:border-muted-foreground/50",
|
|
264
|
-
disabled && "opacity-50 cursor-not-allowed",
|
|
265
|
-
isUploading && "cursor-wait",
|
|
266
|
-
)}
|
|
267
|
-
>
|
|
268
|
-
{isUploading ? (
|
|
269
|
-
<div className="flex flex-col items-center gap-2 text-muted-foreground">
|
|
270
|
-
<Loader2Icon className="h-6 w-6 animate-spin" />
|
|
271
|
-
<span className="text-xs">Uploading...</span>
|
|
272
|
-
</div>
|
|
273
|
-
) : (
|
|
274
|
-
<div className="flex flex-col items-center gap-2 text-muted-foreground p-2">
|
|
275
|
-
<PlusIcon className="h-6 w-6" />
|
|
276
|
-
<span className="text-xs text-center">{placeholder}</span>
|
|
277
|
-
</div>
|
|
278
|
-
)}
|
|
279
|
-
</div>
|
|
280
|
-
)}
|
|
281
|
-
</div>
|
|
282
|
-
);
|
|
283
|
-
}
|