@vinaup/media-ui 1.0.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.

Potentially problematic release.


This version of @vinaup/media-ui might be problematic. Click here for more details.

package/dist/index.mjs ADDED
@@ -0,0 +1,873 @@
1
+ // src/components/MediaUpload.tsx
2
+ import "@mantine/dropzone/styles.css";
3
+ import { Group, Text, Loader, Stack, Paper, Image, Grid, GridCol, UnstyledButton } from "@mantine/core";
4
+ import { Dropzone, DropzoneAccept, DropzoneIdle, DropzoneReject } from "@mantine/dropzone";
5
+ import { TbUpload, TbPhoto } from "react-icons/tb";
6
+ import { HiOutlineX } from "react-icons/hi";
7
+ import { useState, useEffect } from "react";
8
+ import { notifications } from "@mantine/notifications";
9
+
10
+ // src/utils/helpers.ts
11
+ var validateImageFile = (file) => {
12
+ const validTypes = ["image/png", "image/jpeg", "image/jpg"];
13
+ return validTypes.includes(file.type);
14
+ };
15
+ var formatFileSize = (bytes) => {
16
+ if (bytes === 0) return "0 Bytes";
17
+ const k = 1024;
18
+ const sizes = ["Bytes", "KB", "MB"];
19
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
20
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
21
+ };
22
+ var cx = (...classNames) => {
23
+ const validClassNames = classNames.filter(Boolean);
24
+ const result = validClassNames.join(" ");
25
+ return result;
26
+ };
27
+
28
+ // src/components/styles/media-upload.module.scss
29
+ var media_upload_module_default = {};
30
+
31
+ // src/components/MediaUpload.tsx
32
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
33
+ function MediaUpload({
34
+ onUpload,
35
+ onSave,
36
+ onUploadSuccess,
37
+ onUploadError,
38
+ maxSize = 2 * 1024 ** 2,
39
+ acceptedTypes = ["image/png", "image/jpeg", "image/jpg"],
40
+ multiple = true,
41
+ folder = "media",
42
+ classNames
43
+ }) {
44
+ const [isUploading, setIsUploading] = useState(false);
45
+ const [uploadFiles, setUploadFiles] = useState([]);
46
+ const handleDrop = async (files) => {
47
+ if (files.length === 0) return;
48
+ const invalidFiles = files.filter((file) => !validateImageFile(file));
49
+ if (invalidFiles.length > 0) {
50
+ notifications.show({
51
+ title: "Invalid file type",
52
+ message: `${invalidFiles.length} file(s) have invalid type.`,
53
+ color: "red"
54
+ });
55
+ return;
56
+ }
57
+ setIsUploading(true);
58
+ const newUploadFiles = files.map((file, index) => ({
59
+ id: `${Date.now()}-${index}`,
60
+ file,
61
+ preview: URL.createObjectURL(file),
62
+ status: "uploading"
63
+ }));
64
+ setUploadFiles((prev) => [...newUploadFiles, ...prev]);
65
+ try {
66
+ const uploadResults = await onUpload(files);
67
+ setUploadFiles(
68
+ (prev) => prev.map((item) => {
69
+ const idx = newUploadFiles.findIndex((f) => f.id === item.id);
70
+ if (idx !== -1 && uploadResults[idx]) {
71
+ return { ...item, status: "success", url: uploadResults[idx].url };
72
+ }
73
+ return item;
74
+ })
75
+ );
76
+ if (onSave) {
77
+ const mediaData = uploadResults.map((result) => ({
78
+ name: result.name,
79
+ title: null,
80
+ description: null,
81
+ url: result.url,
82
+ type: "image",
83
+ folder
84
+ }));
85
+ const savedMedia = await onSave(mediaData);
86
+ onUploadSuccess?.(savedMedia);
87
+ }
88
+ notifications.show({
89
+ title: "Upload successful",
90
+ message: `Successfully uploaded ${uploadResults.length} image(s)`,
91
+ color: "green"
92
+ });
93
+ } catch (error) {
94
+ setUploadFiles(
95
+ (prev) => prev.map(
96
+ (item) => newUploadFiles.some((f) => f.id === item.id) ? { ...item, status: "error", error: error.message } : item
97
+ )
98
+ );
99
+ onUploadError?.(error);
100
+ notifications.show({
101
+ title: "Upload failed",
102
+ message: error.message,
103
+ color: "red"
104
+ });
105
+ } finally {
106
+ setIsUploading(false);
107
+ }
108
+ };
109
+ const handleReject = (files) => {
110
+ notifications.show({
111
+ title: "Files rejected",
112
+ message: `${files.length} file(s) were rejected. Please check file type and size (\u2264 2MB).`,
113
+ color: "red"
114
+ });
115
+ };
116
+ useEffect(() => {
117
+ return () => {
118
+ uploadFiles.forEach((item) => {
119
+ if (item.preview.startsWith("blob:")) {
120
+ URL.revokeObjectURL(item.preview);
121
+ }
122
+ });
123
+ };
124
+ }, [uploadFiles]);
125
+ const handleCopyLink = async (url) => {
126
+ try {
127
+ await navigator.clipboard.writeText(url);
128
+ notifications.show({
129
+ title: "Link copied",
130
+ message: "Image URL has been copied to clipboard",
131
+ color: "green"
132
+ });
133
+ } catch (error) {
134
+ notifications.show({
135
+ title: "Copy failed",
136
+ message: error instanceof Error ? error.message : "Failed to copy link to clipboard",
137
+ color: "red"
138
+ });
139
+ }
140
+ };
141
+ return /* @__PURE__ */ jsxs(
142
+ Stack,
143
+ {
144
+ gap: "lg",
145
+ classNames: {
146
+ root: classNames?.rootStack?.root
147
+ },
148
+ children: [
149
+ /* @__PURE__ */ jsx(
150
+ Dropzone,
151
+ {
152
+ onDrop: handleDrop,
153
+ onReject: handleReject,
154
+ maxSize,
155
+ accept: acceptedTypes,
156
+ disabled: isUploading,
157
+ multiple,
158
+ classNames: classNames?.dropzone,
159
+ children: /* @__PURE__ */ jsxs(
160
+ Group,
161
+ {
162
+ justify: "center",
163
+ gap: "xl",
164
+ mih: 220,
165
+ classNames: {
166
+ root: classNames?.dropzoneGroup?.root
167
+ },
168
+ children: [
169
+ isUploading ? /* @__PURE__ */ jsx(Loader, { size: 52 }) : /* @__PURE__ */ jsxs(Fragment, { children: [
170
+ /* @__PURE__ */ jsx(DropzoneAccept, { children: /* @__PURE__ */ jsx(TbUpload, { size: 52, color: "blue" }) }),
171
+ /* @__PURE__ */ jsx(DropzoneReject, { children: /* @__PURE__ */ jsx(HiOutlineX, { size: 52, color: "red" }) }),
172
+ /* @__PURE__ */ jsx(DropzoneIdle, { children: /* @__PURE__ */ jsx(TbPhoto, { size: 52 }) })
173
+ ] }),
174
+ /* @__PURE__ */ jsxs("div", { children: [
175
+ /* @__PURE__ */ jsx(
176
+ Text,
177
+ {
178
+ size: "xl",
179
+ inline: true,
180
+ classNames: {
181
+ root: classNames?.dropzoneText?.root
182
+ },
183
+ children: "Drag images here or click to select files"
184
+ }
185
+ ),
186
+ /* @__PURE__ */ jsx(
187
+ Text,
188
+ {
189
+ size: "sm",
190
+ c: "dimmed",
191
+ inline: true,
192
+ mt: 7,
193
+ classNames: {
194
+ root: classNames?.dropzoneSubtext?.root
195
+ },
196
+ children: "(png, jpg, jpeg; Size \u2264 2M)"
197
+ }
198
+ )
199
+ ] })
200
+ ]
201
+ }
202
+ )
203
+ }
204
+ ),
205
+ uploadFiles.length > 0 && /* @__PURE__ */ jsxs(
206
+ Stack,
207
+ {
208
+ gap: "xs",
209
+ classNames: {
210
+ root: classNames?.recentStack?.root
211
+ },
212
+ children: [
213
+ /* @__PURE__ */ jsx(
214
+ Text,
215
+ {
216
+ fw: 500,
217
+ classNames: {
218
+ root: cx(media_upload_module_default.sectionTitleRoot, classNames?.sectionTitle?.root)
219
+ },
220
+ children: "Recently Uploaded Images"
221
+ }
222
+ ),
223
+ /* @__PURE__ */ jsx(Grid, { classNames: classNames?.grid, children: uploadFiles.map((item) => /* @__PURE__ */ jsx(GridCol, { span: { base: 6, sm: 6, md: 3, lg: 2 }, children: /* @__PURE__ */ jsx(
224
+ Paper,
225
+ {
226
+ p: "sm",
227
+ withBorder: true,
228
+ radius: "md",
229
+ classNames: {
230
+ root: classNames?.itemPaper?.root
231
+ },
232
+ children: /* @__PURE__ */ jsxs(
233
+ Stack,
234
+ {
235
+ gap: 6,
236
+ classNames: {
237
+ root: classNames?.itemStack?.root
238
+ },
239
+ children: [
240
+ /* @__PURE__ */ jsxs("div", { className: cx(media_upload_module_default.imageContainer, classNames?.imageContainer), children: [
241
+ /* @__PURE__ */ jsx(
242
+ Image,
243
+ {
244
+ src: item.preview,
245
+ alt: item.file.name,
246
+ fit: "cover",
247
+ classNames: {
248
+ root: cx(media_upload_module_default.itemImageRoot, classNames?.itemImage?.root)
249
+ }
250
+ }
251
+ ),
252
+ item.status === "uploading" && /* @__PURE__ */ jsx("div", { className: cx(media_upload_module_default.uploadingOverlay, classNames?.uploadingOverlay), children: /* @__PURE__ */ jsx(Loader, { size: "sm", color: "white" }) }),
253
+ item.status === "success" && /* @__PURE__ */ jsx("div", { className: cx(
254
+ media_upload_module_default.statusBadge,
255
+ media_upload_module_default.statusBadgeSuccess,
256
+ classNames?.statusBadge,
257
+ classNames?.statusBadgeSuccess
258
+ ), children: /* @__PURE__ */ jsx(Text, { size: "xs", c: "white", fw: 700, children: "\u2713" }) }),
259
+ item.status === "error" && /* @__PURE__ */ jsx("div", { className: cx(
260
+ media_upload_module_default.statusBadge,
261
+ media_upload_module_default.statusBadgeError,
262
+ classNames?.statusBadge,
263
+ classNames?.statusBadgeError
264
+ ), children: /* @__PURE__ */ jsx(HiOutlineX, { size: 16, color: "white" }) })
265
+ ] }),
266
+ /* @__PURE__ */ jsx(
267
+ Text,
268
+ {
269
+ size: "xs",
270
+ c: "dimmed",
271
+ lineClamp: 1,
272
+ title: item.file.name,
273
+ classNames: {
274
+ root: classNames?.itemFilename?.root
275
+ },
276
+ children: item.file.name
277
+ }
278
+ ),
279
+ /* @__PURE__ */ jsxs(
280
+ Group,
281
+ {
282
+ justify: "space-between",
283
+ align: "end",
284
+ gap: "xs",
285
+ classNames: {
286
+ root: classNames?.itemGroup?.root
287
+ },
288
+ children: [
289
+ /* @__PURE__ */ jsx(
290
+ Text,
291
+ {
292
+ size: "xs",
293
+ c: "dimmed",
294
+ classNames: {
295
+ root: classNames?.itemFilesize?.root
296
+ },
297
+ children: formatFileSize(item.file.size)
298
+ }
299
+ ),
300
+ item.status === "success" && item.url && /* @__PURE__ */ jsx(
301
+ UnstyledButton,
302
+ {
303
+ onClick: () => handleCopyLink(item.url),
304
+ classNames: {
305
+ root: cx(media_upload_module_default.copyButtonRoot, classNames?.copyButton?.root)
306
+ },
307
+ children: /* @__PURE__ */ jsx(
308
+ Text,
309
+ {
310
+ size: "sm",
311
+ c: "blue",
312
+ classNames: {
313
+ root: classNames?.copyButtonText?.root
314
+ },
315
+ children: "Copy link"
316
+ }
317
+ )
318
+ }
319
+ )
320
+ ]
321
+ }
322
+ ),
323
+ item.status === "error" && /* @__PURE__ */ jsx(
324
+ Text,
325
+ {
326
+ size: "xs",
327
+ c: "red",
328
+ classNames: {
329
+ root: classNames?.itemError?.root
330
+ },
331
+ children: item.error || "Upload failed"
332
+ }
333
+ )
334
+ ]
335
+ }
336
+ )
337
+ }
338
+ ) }, item.id)) })
339
+ ]
340
+ }
341
+ )
342
+ ]
343
+ }
344
+ );
345
+ }
346
+
347
+ // src/components/MediaGrid.tsx
348
+ import { Grid as Grid2, GridCol as GridCol2, Group as Group2, Image as Image2, Paper as Paper2, Stack as Stack2, Text as Text2, TextInput, Select } from "@mantine/core";
349
+ import { useState as useState2 } from "react";
350
+ import { IoSearch } from "react-icons/io5";
351
+
352
+ // src/components/styles/media-grid.module.scss
353
+ var media_grid_module_default = {};
354
+
355
+ // src/components/MediaGrid.tsx
356
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
357
+ function MediaGrid({
358
+ images,
359
+ selectedImageId = null,
360
+ onImageClick,
361
+ sortOptions = [
362
+ { value: "createdAt", label: "By created date" },
363
+ { value: "updatedAt", label: "By updated date" },
364
+ { value: "title", label: "By title" }
365
+ ],
366
+ classNames
367
+ }) {
368
+ const [searchQuery, setSearchQuery] = useState2("");
369
+ const [sortBy, setSortBy] = useState2("createdAt");
370
+ const filteredAndSortedImages = () => {
371
+ let result = [...images];
372
+ if (searchQuery.trim()) {
373
+ const query = searchQuery.toLowerCase();
374
+ result = result.filter(
375
+ (image) => image.title?.toLowerCase().includes(query)
376
+ );
377
+ }
378
+ if (sortBy) {
379
+ result.sort((a, b) => {
380
+ switch (sortBy) {
381
+ case "createdAt":
382
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
383
+ case "updatedAt":
384
+ return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
385
+ case "title":
386
+ return (a.title ?? "").localeCompare(b.title ?? "");
387
+ default:
388
+ return 0;
389
+ }
390
+ });
391
+ }
392
+ return result;
393
+ };
394
+ return /* @__PURE__ */ jsxs2(
395
+ Stack2,
396
+ {
397
+ gap: "md",
398
+ classNames: {
399
+ root: classNames?.rootStack?.root
400
+ },
401
+ children: [
402
+ /* @__PURE__ */ jsxs2(
403
+ Group2,
404
+ {
405
+ justify: "space-between",
406
+ align: "center",
407
+ classNames: {
408
+ root: classNames?.filterGroup?.root
409
+ },
410
+ children: [
411
+ /* @__PURE__ */ jsx2(
412
+ Select,
413
+ {
414
+ placeholder: "Sort by",
415
+ value: sortBy,
416
+ onChange: setSortBy,
417
+ data: sortOptions,
418
+ w: 200,
419
+ clearable: false,
420
+ classNames: classNames?.sortSelect
421
+ }
422
+ ),
423
+ /* @__PURE__ */ jsx2(
424
+ TextInput,
425
+ {
426
+ placeholder: "Search by title...",
427
+ value: searchQuery,
428
+ onChange: (e) => setSearchQuery(e.currentTarget.value),
429
+ leftSection: /* @__PURE__ */ jsx2(IoSearch, { size: 16 }),
430
+ w: 250,
431
+ classNames: classNames?.searchInput
432
+ }
433
+ )
434
+ ]
435
+ }
436
+ ),
437
+ /* @__PURE__ */ jsx2(
438
+ Grid2,
439
+ {
440
+ gutter: "md",
441
+ classNames: classNames?.grid,
442
+ children: filteredAndSortedImages().map((image) => {
443
+ const isSelected = selectedImageId === image.id;
444
+ return /* @__PURE__ */ jsx2(GridCol2, { span: { base: 6, sm: 4, md: 3, lg: 2 }, children: /* @__PURE__ */ jsx2(
445
+ Paper2,
446
+ {
447
+ p: "xs",
448
+ withBorder: true,
449
+ radius: "md",
450
+ onClick: () => onImageClick(image.id),
451
+ classNames: {
452
+ root: cx(
453
+ media_grid_module_default.itemPaperRoot,
454
+ classNames?.itemPaper?.root,
455
+ isSelected ? media_grid_module_default.selectedPaper : void 0,
456
+ isSelected ? classNames?.selectedPaper : void 0
457
+ )
458
+ },
459
+ children: /* @__PURE__ */ jsxs2(
460
+ Stack2,
461
+ {
462
+ gap: 6,
463
+ classNames: {
464
+ root: classNames?.itemStack?.root
465
+ },
466
+ children: [
467
+ /* @__PURE__ */ jsx2("div", { className: cx(media_grid_module_default.imageContainer, classNames?.imageContainer), children: /* @__PURE__ */ jsx2(
468
+ Image2,
469
+ {
470
+ src: image.url,
471
+ alt: image.title || image.name,
472
+ fit: "cover",
473
+ classNames: {
474
+ root: cx(media_grid_module_default.itemImageRoot, classNames?.itemImage?.root)
475
+ }
476
+ }
477
+ ) }),
478
+ /* @__PURE__ */ jsx2(
479
+ Text2,
480
+ {
481
+ size: "xs",
482
+ c: "dimmed",
483
+ lineClamp: 1,
484
+ title: image.title ?? image.name,
485
+ classNames: {
486
+ root: cx(media_grid_module_default.itemTextRoot, classNames?.itemText?.root)
487
+ },
488
+ children: image.title ?? image.name
489
+ }
490
+ )
491
+ ]
492
+ }
493
+ )
494
+ }
495
+ ) }, image.id);
496
+ })
497
+ }
498
+ )
499
+ ]
500
+ }
501
+ );
502
+ }
503
+
504
+ // src/components/MediaDetail.tsx
505
+ import {
506
+ ActionIcon,
507
+ Group as Group3,
508
+ Image as Image3,
509
+ Paper as Paper3,
510
+ Stack as Stack3,
511
+ Text as Text3,
512
+ Textarea,
513
+ Title
514
+ } from "@mantine/core";
515
+ import { useState as useState3 } from "react";
516
+ import { MdOutlineFileCopy } from "react-icons/md";
517
+ import { GrTrash } from "react-icons/gr";
518
+ import { IoCheckmark } from "react-icons/io5";
519
+
520
+ // src/components/styles/media-detail.module.scss
521
+ var media_detail_module_default = {};
522
+
523
+ // src/components/MediaDetail.tsx
524
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
525
+ function MediaDetail({
526
+ image,
527
+ onUpdate,
528
+ onDelete,
529
+ onCopyLink,
530
+ onNotify,
531
+ classNames
532
+ }) {
533
+ const [title, setTitle] = useState3(image.title || "");
534
+ const [description, setDescription] = useState3(image.description || "");
535
+ const [originalTitle, setOriginalTitle] = useState3(image.title || "");
536
+ const [originalDescription, setOriginalDescription] = useState3(image.description || "");
537
+ const isTitleDirty = title !== originalTitle;
538
+ const isDescriptionDirty = description !== originalDescription;
539
+ const [isSavingTitle, setIsSavingTitle] = useState3(false);
540
+ const [isSavingDescription, setIsSavingDescription] = useState3(false);
541
+ const handleSaveTitle = async () => {
542
+ setIsSavingTitle(true);
543
+ try {
544
+ await onUpdate(image.id, { title: title || null });
545
+ setOriginalTitle(title);
546
+ onNotify?.("success", "Title saved");
547
+ } catch (error) {
548
+ onNotify?.("error", error.message);
549
+ } finally {
550
+ setIsSavingTitle(false);
551
+ }
552
+ };
553
+ const handleSaveDescription = async () => {
554
+ setIsSavingDescription(true);
555
+ try {
556
+ await onUpdate(image.id, { description: description || null });
557
+ setOriginalDescription(description);
558
+ onNotify?.("success", "Description saved");
559
+ } catch (error) {
560
+ onNotify?.("error", error.message);
561
+ } finally {
562
+ setIsSavingDescription(false);
563
+ }
564
+ };
565
+ const handleCopyLink = async () => {
566
+ if (onCopyLink) {
567
+ onCopyLink(image.url);
568
+ } else {
569
+ try {
570
+ await navigator.clipboard.writeText(image.url);
571
+ onNotify?.("success", "Link copied");
572
+ } catch {
573
+ onNotify?.("error", "Failed to copy link");
574
+ }
575
+ }
576
+ };
577
+ return /* @__PURE__ */ jsx3(Fragment2, { children: /* @__PURE__ */ jsxs3(
578
+ Paper3,
579
+ {
580
+ p: "md",
581
+ radius: "md",
582
+ withBorder: true,
583
+ classNames: {
584
+ root: cx(media_detail_module_default.paperRoot, classNames?.paper?.root)
585
+ },
586
+ children: [
587
+ /* @__PURE__ */ jsxs3(Stack3, { gap: "md", children: [
588
+ /* @__PURE__ */ jsx3(
589
+ Title,
590
+ {
591
+ order: 4,
592
+ classNames: {
593
+ root: cx(media_detail_module_default.titleRoot, classNames?.title?.root)
594
+ },
595
+ children: "Image Details"
596
+ }
597
+ ),
598
+ /* @__PURE__ */ jsx3("div", { className: cx(media_detail_module_default.imageWrapper, classNames?.imageWrapper), children: /* @__PURE__ */ jsx3(
599
+ Image3,
600
+ {
601
+ src: image.url,
602
+ alt: image.title || image.name,
603
+ fit: "contain",
604
+ radius: "md",
605
+ classNames: {
606
+ root: cx(media_detail_module_default.imageRoot, classNames?.image?.root)
607
+ }
608
+ }
609
+ ) }),
610
+ /* @__PURE__ */ jsxs3(
611
+ Stack3,
612
+ {
613
+ gap: "xs",
614
+ classNames: {
615
+ root: cx(media_detail_module_default.fieldsStackRoot, classNames?.fieldsStack?.root)
616
+ },
617
+ children: [
618
+ /* @__PURE__ */ jsxs3("div", { className: cx(media_detail_module_default.fieldGroup, classNames?.fieldGroup), children: [
619
+ /* @__PURE__ */ jsx3(
620
+ Text3,
621
+ {
622
+ size: "sm",
623
+ fw: 500,
624
+ c: "dimmed",
625
+ classNames: {
626
+ root: cx(media_detail_module_default.fieldLabelRoot, classNames?.fieldLabel?.root)
627
+ },
628
+ children: "Name"
629
+ }
630
+ ),
631
+ /* @__PURE__ */ jsx3(
632
+ Text3,
633
+ {
634
+ classNames: {
635
+ root: cx(media_detail_module_default.fieldValueRoot, classNames?.fieldValue?.root)
636
+ },
637
+ children: image.name
638
+ }
639
+ )
640
+ ] }),
641
+ /* @__PURE__ */ jsxs3("div", { className: cx(media_detail_module_default.fieldGroup, classNames?.fieldGroup), children: [
642
+ /* @__PURE__ */ jsxs3(Group3, { gap: 4, align: "center", children: [
643
+ /* @__PURE__ */ jsx3(
644
+ Text3,
645
+ {
646
+ size: "sm",
647
+ fw: 500,
648
+ c: "dimmed",
649
+ classNames: {
650
+ root: cx(media_detail_module_default.fieldLabelRoot, classNames?.fieldLabel?.root)
651
+ },
652
+ children: "Title"
653
+ }
654
+ ),
655
+ isTitleDirty && /* @__PURE__ */ jsx3(
656
+ ActionIcon,
657
+ {
658
+ size: "xs",
659
+ variant: "filled",
660
+ color: "green",
661
+ onClick: handleSaveTitle,
662
+ loading: isSavingTitle,
663
+ title: "Save title",
664
+ classNames: {
665
+ root: cx(media_detail_module_default.saveButtonRoot, classNames?.saveButton?.root)
666
+ },
667
+ children: /* @__PURE__ */ jsx3(IoCheckmark, { size: 12 })
668
+ }
669
+ )
670
+ ] }),
671
+ /* @__PURE__ */ jsx3(
672
+ Textarea,
673
+ {
674
+ value: title,
675
+ placeholder: "Enter title",
676
+ onChange: (e) => setTitle(e.target.value),
677
+ minRows: 2,
678
+ autosize: true,
679
+ classNames: {
680
+ root: cx(media_detail_module_default.textareaRoot, classNames?.textarea?.root),
681
+ input: cx(media_detail_module_default.textareaInput, classNames?.textarea?.input),
682
+ wrapper: cx(media_detail_module_default.textareaWrapper, classNames?.textarea?.wrapper)
683
+ }
684
+ }
685
+ )
686
+ ] }),
687
+ /* @__PURE__ */ jsxs3("div", { className: cx(media_detail_module_default.fieldGroup, classNames?.fieldGroup), children: [
688
+ /* @__PURE__ */ jsxs3(Group3, { gap: 4, align: "center", children: [
689
+ /* @__PURE__ */ jsx3(
690
+ Text3,
691
+ {
692
+ size: "sm",
693
+ fw: 500,
694
+ c: "dimmed",
695
+ classNames: {
696
+ root: cx(media_detail_module_default.fieldLabelRoot, classNames?.fieldLabel?.root)
697
+ },
698
+ children: "Description"
699
+ }
700
+ ),
701
+ isDescriptionDirty && /* @__PURE__ */ jsx3(
702
+ ActionIcon,
703
+ {
704
+ size: "xs",
705
+ variant: "filled",
706
+ color: "green",
707
+ onClick: handleSaveDescription,
708
+ loading: isSavingDescription,
709
+ title: "Save description",
710
+ classNames: {
711
+ root: cx(media_detail_module_default.saveButtonRoot, classNames?.saveButton?.root)
712
+ },
713
+ children: /* @__PURE__ */ jsx3(IoCheckmark, { size: 12 })
714
+ }
715
+ )
716
+ ] }),
717
+ /* @__PURE__ */ jsx3(
718
+ Textarea,
719
+ {
720
+ value: description,
721
+ placeholder: "Enter description",
722
+ onChange: (e) => setDescription(e.target.value),
723
+ minRows: 3,
724
+ autosize: true,
725
+ classNames: {
726
+ root: cx(media_detail_module_default.textareaRoot, classNames?.textarea?.root),
727
+ input: cx(media_detail_module_default.textareaInput, classNames?.textarea?.input),
728
+ wrapper: cx(media_detail_module_default.textareaWrapper, classNames?.textarea?.wrapper)
729
+ }
730
+ }
731
+ )
732
+ ] }),
733
+ /* @__PURE__ */ jsxs3("div", { className: cx(media_detail_module_default.fieldGroup, classNames?.fieldGroup), children: [
734
+ /* @__PURE__ */ jsx3(
735
+ Text3,
736
+ {
737
+ size: "sm",
738
+ fw: 500,
739
+ c: "dimmed",
740
+ classNames: {
741
+ root: cx(media_detail_module_default.fieldLabelRoot, classNames?.fieldLabel?.root)
742
+ },
743
+ children: "Type"
744
+ }
745
+ ),
746
+ /* @__PURE__ */ jsx3(
747
+ Text3,
748
+ {
749
+ classNames: {
750
+ root: cx(media_detail_module_default.fieldValueRoot, classNames?.fieldValue?.root)
751
+ },
752
+ children: image.type
753
+ }
754
+ )
755
+ ] }),
756
+ /* @__PURE__ */ jsxs3("div", { className: cx(media_detail_module_default.fieldGroup, classNames?.fieldGroup), children: [
757
+ /* @__PURE__ */ jsx3(
758
+ Text3,
759
+ {
760
+ size: "sm",
761
+ fw: 500,
762
+ c: "dimmed",
763
+ classNames: {
764
+ root: cx(media_detail_module_default.fieldLabelRoot, classNames?.fieldLabel?.root)
765
+ },
766
+ children: "URL"
767
+ }
768
+ ),
769
+ /* @__PURE__ */ jsxs3(Group3, { gap: "xs", align: "center", wrap: "nowrap", children: [
770
+ /* @__PURE__ */ jsx3(
771
+ Text3,
772
+ {
773
+ size: "xs",
774
+ c: "dimmed",
775
+ classNames: {
776
+ root: cx(media_detail_module_default.urlText, classNames?.urlText)
777
+ },
778
+ children: image.url
779
+ }
780
+ ),
781
+ /* @__PURE__ */ jsx3(
782
+ ActionIcon,
783
+ {
784
+ variant: "subtle",
785
+ color: "gray",
786
+ onClick: handleCopyLink,
787
+ size: "sm",
788
+ classNames: {
789
+ root: cx(media_detail_module_default.copyButtonRoot, classNames?.copyButton?.root)
790
+ },
791
+ children: /* @__PURE__ */ jsx3(MdOutlineFileCopy, { size: 18 })
792
+ }
793
+ )
794
+ ] })
795
+ ] }),
796
+ /* @__PURE__ */ jsxs3("div", { className: cx(media_detail_module_default.fieldGroup, classNames?.fieldGroup), children: [
797
+ /* @__PURE__ */ jsx3(
798
+ Text3,
799
+ {
800
+ size: "sm",
801
+ fw: 500,
802
+ c: "dimmed",
803
+ classNames: {
804
+ root: cx(media_detail_module_default.fieldLabelRoot, classNames?.fieldLabel?.root)
805
+ },
806
+ children: "Created At"
807
+ }
808
+ ),
809
+ /* @__PURE__ */ jsx3(
810
+ Text3,
811
+ {
812
+ size: "sm",
813
+ classNames: {
814
+ root: cx(media_detail_module_default.fieldValueRoot, classNames?.fieldValue?.root)
815
+ },
816
+ children: new Date(image.createdAt).toLocaleString()
817
+ }
818
+ )
819
+ ] }),
820
+ /* @__PURE__ */ jsxs3("div", { className: cx(media_detail_module_default.fieldGroup, classNames?.fieldGroup), children: [
821
+ /* @__PURE__ */ jsx3(
822
+ Text3,
823
+ {
824
+ size: "sm",
825
+ fw: 500,
826
+ c: "dimmed",
827
+ classNames: {
828
+ root: cx(media_detail_module_default.fieldLabelRoot, classNames?.fieldLabel?.root)
829
+ },
830
+ children: "Updated At"
831
+ }
832
+ ),
833
+ /* @__PURE__ */ jsx3(
834
+ Text3,
835
+ {
836
+ size: "sm",
837
+ classNames: {
838
+ root: cx(media_detail_module_default.fieldValueRoot, classNames?.fieldValue?.root)
839
+ },
840
+ children: new Date(image.updatedAt).toLocaleString()
841
+ }
842
+ )
843
+ ] })
844
+ ]
845
+ }
846
+ )
847
+ ] }),
848
+ onDelete && /* @__PURE__ */ jsx3(
849
+ ActionIcon,
850
+ {
851
+ variant: "filled",
852
+ color: "red",
853
+ size: "lg",
854
+ onClick: () => onDelete(image.id),
855
+ classNames: {
856
+ root: cx(media_detail_module_default.deleteButtonRoot, classNames?.deleteButton?.root)
857
+ },
858
+ children: /* @__PURE__ */ jsx3(GrTrash, { size: 20 })
859
+ }
860
+ )
861
+ ]
862
+ }
863
+ ) });
864
+ }
865
+ export {
866
+ MediaDetail,
867
+ MediaGrid,
868
+ MediaUpload,
869
+ cx,
870
+ formatFileSize,
871
+ validateImageFile
872
+ };
873
+ //# sourceMappingURL=index.mjs.map