@bsol-oss/react-datatable5 13.0.1-beta.38 → 13.0.1-beta.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts
CHANGED
|
@@ -743,7 +743,7 @@ type MediaLibraryBrowserPropsMultiple = MediaLibraryBrowserPropsBase & {
|
|
|
743
743
|
onSelectedFileChange?: (files: FilePickerMediaFile[]) => void;
|
|
744
744
|
};
|
|
745
745
|
type MediaLibraryBrowserProps = MediaLibraryBrowserPropsSingle | MediaLibraryBrowserPropsMultiple;
|
|
746
|
-
declare const MediaLibraryBrowser: ({ onFetchFiles, filterImageOnly, labels, enabled, multiple, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }: MediaLibraryBrowserProps) => react_jsx_runtime.JSX.Element
|
|
746
|
+
declare const MediaLibraryBrowser: ({ onFetchFiles, filterImageOnly, labels, enabled, multiple, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }: MediaLibraryBrowserProps) => react_jsx_runtime.JSX.Element;
|
|
747
747
|
|
|
748
748
|
interface UseFormProps<T> {
|
|
749
749
|
preLoadedValues?: T | undefined;
|
package/dist/index.js
CHANGED
|
@@ -5381,108 +5381,81 @@ function formatBytes(bytes) {
|
|
|
5381
5381
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
5382
5382
|
}
|
|
5383
5383
|
|
|
5384
|
+
const IMAGE_EXT = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i;
|
|
5385
|
+
function filterImageFiles(files) {
|
|
5386
|
+
return files.filter((f) => IMAGE_EXT.test(f.name));
|
|
5387
|
+
}
|
|
5384
5388
|
const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, enabled = true, multiple = false, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }) => {
|
|
5385
|
-
const [
|
|
5386
|
-
const
|
|
5387
|
-
|
|
5388
|
-
// Use controlled or internal state for selectedFile
|
|
5389
|
-
const selectedFile = controlledSelectedFile ?? internalSelectedFile;
|
|
5390
|
-
const setSelectedFile = onSelectedFileChange ?? setInternalSelectedFile;
|
|
5391
|
-
const { data: filesData, isLoading, isError, } = reactQuery.useQuery({
|
|
5392
|
-
queryKey: ['file-picker-library', searchTerm],
|
|
5389
|
+
const [search, setSearch] = React.useState('');
|
|
5390
|
+
const query = reactQuery.useQuery({
|
|
5391
|
+
queryKey: ['media-library', search, filterImageOnly],
|
|
5393
5392
|
queryFn: async () => {
|
|
5394
5393
|
if (!onFetchFiles)
|
|
5395
|
-
return
|
|
5396
|
-
const
|
|
5397
|
-
return
|
|
5394
|
+
return [];
|
|
5395
|
+
const list = await onFetchFiles(search);
|
|
5396
|
+
return filterImageOnly ? filterImageFiles(list) : list;
|
|
5398
5397
|
},
|
|
5399
5398
|
enabled: enabled && !!onFetchFiles,
|
|
5400
5399
|
});
|
|
5401
|
-
const files = (
|
|
5402
|
-
const
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
if (multiple
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
else {
|
|
5418
|
-
const newFile = selectedFile === file ? undefined : file;
|
|
5419
|
-
setSelectedFile(newFile);
|
|
5420
|
-
if (onFileSelect && newFile) {
|
|
5421
|
-
onFileSelect(newFile);
|
|
5422
|
-
}
|
|
5400
|
+
const files = React.useMemo(() => (query.data ?? []), [query.data]);
|
|
5401
|
+
const selectedIds = React.useMemo(() => {
|
|
5402
|
+
if (multiple && Array.isArray(controlledSelectedFile)) {
|
|
5403
|
+
return new Set(controlledSelectedFile.map((f) => f.id));
|
|
5404
|
+
}
|
|
5405
|
+
if (!multiple &&
|
|
5406
|
+
controlledSelectedFile &&
|
|
5407
|
+
!Array.isArray(controlledSelectedFile)) {
|
|
5408
|
+
return new Set([controlledSelectedFile.id]);
|
|
5409
|
+
}
|
|
5410
|
+
return new Set();
|
|
5411
|
+
}, [multiple, controlledSelectedFile]);
|
|
5412
|
+
const handleSingleSelect = (file) => {
|
|
5413
|
+
if (!multiple) {
|
|
5414
|
+
onSelectedFileChange?.(file);
|
|
5415
|
+
onFileSelect?.(file);
|
|
5423
5416
|
}
|
|
5424
5417
|
};
|
|
5425
|
-
const
|
|
5426
|
-
|
|
5418
|
+
const handleMultipleToggle = (file, checked) => {
|
|
5419
|
+
const current = multiple && Array.isArray(controlledSelectedFile)
|
|
5420
|
+
? [...controlledSelectedFile]
|
|
5421
|
+
: [];
|
|
5422
|
+
const next = checked
|
|
5423
|
+
? [...current, file]
|
|
5424
|
+
: current.filter((f) => f.id !== file.id);
|
|
5425
|
+
onSelectedFileChange?.(next);
|
|
5426
|
+
onFileSelect?.(next);
|
|
5427
5427
|
};
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
:
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
? {
|
|
5460
|
-
base: 'colorPalette.50',
|
|
5461
|
-
_dark: 'colorPalette.900/20',
|
|
5462
|
-
}
|
|
5463
|
-
: 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => handleFileClick(file), _hover: {
|
|
5464
|
-
borderColor: isSelected
|
|
5465
|
-
? {
|
|
5466
|
-
base: 'colorPalette.600',
|
|
5467
|
-
_dark: 'colorPalette.400',
|
|
5468
|
-
}
|
|
5469
|
-
: {
|
|
5470
|
-
base: 'colorPalette.300',
|
|
5471
|
-
_dark: 'colorPalette.400',
|
|
5472
|
-
},
|
|
5473
|
-
bg: isSelected
|
|
5474
|
-
? {
|
|
5475
|
-
base: 'colorPalette.100',
|
|
5476
|
-
_dark: 'colorPalette.800/30',
|
|
5477
|
-
}
|
|
5478
|
-
: 'bg.muted',
|
|
5479
|
-
}, transition: "all 0.2s", children: jsxRuntime.jsxs(react.HStack, { gap: 3, children: [jsxRuntime.jsx(react.Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, children: isImage && file.url && !imageFailed ? (jsxRuntime.jsx(react.Image, { src: file.url, alt: file.name, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(file.id) })) : isImage && (imageFailed || !file.url) ? (jsxRuntime.jsx(react.Icon, { as: lu.LuImage, boxSize: 6, color: "fg.muted" })) : (jsxRuntime.jsx(react.Icon, { as: lu.LuFile, boxSize: 6, color: "fg.muted" })) }), jsxRuntime.jsxs(react.VStack, { align: "start", flex: 1, gap: 1, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.name }), jsxRuntime.jsxs(react.HStack, { gap: 2, children: [file.size && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
|
|
5480
|
-
? formatBytes(file.size)
|
|
5481
|
-
: file.size }) })), file.comment && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [file.size && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: "\u2022" })), jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.comment })] }))] })] }), isSelected && (jsxRuntime.jsx(react.Box, { width: "24px", height: "24px", borderRadius: "full", bg: {
|
|
5482
|
-
base: 'colorPalette.500',
|
|
5483
|
-
_dark: 'colorPalette.400',
|
|
5484
|
-
}, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsxRuntime.jsx(react.Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
|
|
5485
|
-
}) })) }))] }));
|
|
5428
|
+
const isLoading = query.isPending;
|
|
5429
|
+
const isError = query.isError;
|
|
5430
|
+
const searchPlaceholder = labels?.searchPlaceholder ?? 'Search files...';
|
|
5431
|
+
const loadingText = labels?.loading ?? 'Loading...';
|
|
5432
|
+
const errorText = labels?.loadingFailed ?? 'Failed to load files';
|
|
5433
|
+
const emptyText = labels?.noFilesFound ?? 'No files found';
|
|
5434
|
+
return (jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 4, children: [jsxRuntime.jsx(InputGroup, { startElement: jsxRuntime.jsx(react.Icon, { as: lu.LuSearch, color: "fg.muted" }), children: jsxRuntime.jsx(react.Input, { placeholder: searchPlaceholder, value: search, onChange: (e) => setSearch(e.target.value), bg: "bg.panel", borderColor: "border.default" }) }), isLoading && (jsxRuntime.jsxs(react.HStack, { gap: 2, py: 6, justify: "center", children: [jsxRuntime.jsx(react.Spinner, { size: "sm", colorPalette: "blue" }), jsxRuntime.jsx(react.Text, { fontSize: "sm", color: "fg.muted", children: loadingText })] })), isError && (jsxRuntime.jsx(react.Box, { py: 4, px: 3, borderRadius: "md", bg: { base: 'red.50', _dark: 'red.900/20' }, borderWidth: "1px", borderColor: { base: 'red.200', _dark: 'red.800' }, children: jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'red.600', _dark: 'red.300' }, children: errorText }) })), !isLoading && !isError && files.length === 0 && (jsxRuntime.jsx(react.Box, { py: 6, textAlign: "center", children: jsxRuntime.jsx(react.Text, { fontSize: "sm", color: "fg.muted", children: emptyText }) })), !isLoading && !isError && files.length > 0 && (jsxRuntime.jsx(react.SimpleGrid, { columns: { base: 2, sm: 3, md: 4 }, gap: 3, children: files.map((file) => {
|
|
5435
|
+
const isImage = IMAGE_EXT.test(file.name);
|
|
5436
|
+
const isSelected = selectedIds.has(file.id);
|
|
5437
|
+
const fileSize = typeof file.size === 'number'
|
|
5438
|
+
? formatBytes(file.size)
|
|
5439
|
+
: file.size ?? null;
|
|
5440
|
+
if (multiple) {
|
|
5441
|
+
return (jsxRuntime.jsxs(CheckboxCard, { checked: isSelected, onCheckedChange: (e) => handleMultipleToggle(file, e.checked === true), variant: "outline", borderColor: "border.default", _hover: { borderColor: 'border.emphasized', bg: 'bg.muted' }, cursor: "pointer", children: [jsxRuntime.jsx(react.Box, { width: "100%", aspectRatio: 1, bg: "bg.muted", borderRadius: "md", overflow: "hidden", mb: 2, display: "flex", alignItems: "center", justifyContent: "center", children: isImage && file.url ? (jsxRuntime.jsx(react.Image, { src: file.url, alt: file.name, width: "100%", height: "100%", objectFit: "cover" })) : isImage ? (jsxRuntime.jsx(react.Icon, { as: lu.LuImage, boxSize: 8, color: "fg.muted" })) : (jsxRuntime.jsx(react.Icon, { as: lu.LuFile, boxSize: 8, color: "fg.muted" })) }), jsxRuntime.jsx(react.Text, { fontSize: "xs", fontWeight: "medium", color: "fg.default", lineClamp: 2, children: file.name }), fileSize && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: fileSize }))] }, file.id));
|
|
5442
|
+
}
|
|
5443
|
+
return (jsxRuntime.jsxs(react.Box, { role: "button", tabIndex: 0, onClick: () => handleSingleSelect(file), onKeyDown: (e) => {
|
|
5444
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
5445
|
+
e.preventDefault();
|
|
5446
|
+
handleSingleSelect(file);
|
|
5447
|
+
}
|
|
5448
|
+
}, padding: 3, borderRadius: "md", borderWidth: "2px", borderColor: isSelected ? 'colorPalette.500' : 'border.default', bg: isSelected
|
|
5449
|
+
? { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }
|
|
5450
|
+
: 'bg.panel', _hover: {
|
|
5451
|
+
borderColor: isSelected
|
|
5452
|
+
? 'colorPalette.500'
|
|
5453
|
+
: 'border.emphasized',
|
|
5454
|
+
bg: isSelected
|
|
5455
|
+
? { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }
|
|
5456
|
+
: 'bg.muted',
|
|
5457
|
+
}, cursor: "pointer", transition: "all 0.2s", children: [jsxRuntime.jsx(react.Box, { width: "100%", aspectRatio: 1, bg: "bg.muted", borderRadius: "md", overflow: "hidden", mb: 2, display: "flex", alignItems: "center", justifyContent: "center", children: isImage && file.url ? (jsxRuntime.jsx(react.Image, { src: file.url, alt: file.name, width: "100%", height: "100%", objectFit: "cover" })) : isImage ? (jsxRuntime.jsx(react.Icon, { as: lu.LuImage, boxSize: 8, color: "fg.muted" })) : (jsxRuntime.jsx(react.Icon, { as: lu.LuFile, boxSize: 8, color: "fg.muted" })) }), jsxRuntime.jsx(react.Text, { fontSize: "xs", fontWeight: "medium", color: "fg.default", lineClamp: 2, children: file.name }), fileSize && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: fileSize }))] }, file.id));
|
|
5458
|
+
}) }))] }));
|
|
5486
5459
|
};
|
|
5487
5460
|
|
|
5488
5461
|
function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, }) {
|
|
@@ -5650,7 +5623,8 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
|
5650
5623
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
|
|
5651
5624
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
5652
5625
|
const isSingleSelect = type === 'string';
|
|
5653
|
-
const
|
|
5626
|
+
const colLabel = formI18n.colLabel;
|
|
5627
|
+
const currentValue = watch(colLabel) ?? (isSingleSelect ? '' : []);
|
|
5654
5628
|
// Handle string IDs only
|
|
5655
5629
|
const currentFileIds = isSingleSelect
|
|
5656
5630
|
? currentValue
|
|
@@ -5659,7 +5633,6 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
|
5659
5633
|
: Array.isArray(currentValue)
|
|
5660
5634
|
? currentValue
|
|
5661
5635
|
: [];
|
|
5662
|
-
const colLabel = formI18n.colLabel;
|
|
5663
5636
|
const fieldError = getNestedError(errors, colLabel);
|
|
5664
5637
|
const [dialogOpen, setDialogOpen] = React.useState(false);
|
|
5665
5638
|
const [failedImageIds, setFailedImageIds] = React.useState(new Set());
|
|
@@ -5747,7 +5720,7 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
|
5747
5720
|
: /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(fileId);
|
|
5748
5721
|
const imageFailed = failedImageIds.has(fileId);
|
|
5749
5722
|
const displayName = file?.name ?? fileId;
|
|
5750
|
-
return (jsxRuntime.jsx(react.Card.Root, { variant: 'subtle',
|
|
5723
|
+
return (jsxRuntime.jsx(react.Card.Root, { variant: 'subtle', children: jsxRuntime.jsxs(react.Card.Body, { gap: "2", cursor: 'pointer', onClick: () => handleRemove(index), display: 'flex', flexFlow: 'row', alignItems: 'center', padding: '2', border: "2px solid", borderColor: "border.default", borderRadius: "md", _hover: {
|
|
5751
5724
|
borderColor: 'colorPalette.300',
|
|
5752
5725
|
bg: 'bg.muted',
|
|
5753
5726
|
}, transition: "all 0.2s", children: [jsxRuntime.jsx(react.Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, marginRight: "2", overflow: "hidden", children: isImage && file?.url && !imageFailed ? (jsxRuntime.jsx(react.Image, { src: file.url, alt: displayName, boxSize: "60px", objectFit: "cover", onError: () => handleImageError(fileId) })) : isImage && !imageFailed ? (jsxRuntime.jsx(react.Icon, { as: lu.LuImage, boxSize: 6, color: "fg.muted" })) : (jsxRuntime.jsx(react.Icon, { as: lu.LuFile, boxSize: 6, color: "fg.muted" })) }), jsxRuntime.jsxs(react.VStack, { align: "start", flex: 1, gap: 1, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: displayName }), file?.size && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, CheckboxCard as CheckboxCard$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, Tooltip as Tooltip$1, Group, InputElement, Tag as Tag$1, Checkbox as Checkbox$1, Icon, VStack, Heading, EmptyState as EmptyState$2, List, Table as Table$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Clipboard, Badge, Link, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, useCombobox, Show, Skeleton, NumberInput, Textarea as Textarea$1, InputGroup as InputGroup$1, Select, Stack } from '@chakra-ui/react';
|
|
2
|
+
import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, CheckboxCard as CheckboxCard$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, Tooltip as Tooltip$1, Group, InputElement, Tag as Tag$1, Checkbox as Checkbox$1, Icon, VStack, Heading, EmptyState as EmptyState$2, List, Table as Table$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Clipboard, Badge, Link, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, SimpleGrid, Tabs, useCombobox, Show, Skeleton, NumberInput, Textarea as Textarea$1, InputGroup as InputGroup$1, Select, Stack } from '@chakra-ui/react';
|
|
3
3
|
import { AiOutlineColumnWidth } from 'react-icons/ai';
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import { createContext, useContext, useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
|
@@ -5361,108 +5361,81 @@ function formatBytes(bytes) {
|
|
|
5361
5361
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
5362
5362
|
}
|
|
5363
5363
|
|
|
5364
|
+
const IMAGE_EXT = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i;
|
|
5365
|
+
function filterImageFiles(files) {
|
|
5366
|
+
return files.filter((f) => IMAGE_EXT.test(f.name));
|
|
5367
|
+
}
|
|
5364
5368
|
const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, enabled = true, multiple = false, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }) => {
|
|
5365
|
-
const [
|
|
5366
|
-
const
|
|
5367
|
-
|
|
5368
|
-
// Use controlled or internal state for selectedFile
|
|
5369
|
-
const selectedFile = controlledSelectedFile ?? internalSelectedFile;
|
|
5370
|
-
const setSelectedFile = onSelectedFileChange ?? setInternalSelectedFile;
|
|
5371
|
-
const { data: filesData, isLoading, isError, } = useQuery({
|
|
5372
|
-
queryKey: ['file-picker-library', searchTerm],
|
|
5369
|
+
const [search, setSearch] = useState('');
|
|
5370
|
+
const query = useQuery({
|
|
5371
|
+
queryKey: ['media-library', search, filterImageOnly],
|
|
5373
5372
|
queryFn: async () => {
|
|
5374
5373
|
if (!onFetchFiles)
|
|
5375
|
-
return
|
|
5376
|
-
const
|
|
5377
|
-
return
|
|
5374
|
+
return [];
|
|
5375
|
+
const list = await onFetchFiles(search);
|
|
5376
|
+
return filterImageOnly ? filterImageFiles(list) : list;
|
|
5378
5377
|
},
|
|
5379
5378
|
enabled: enabled && !!onFetchFiles,
|
|
5380
5379
|
});
|
|
5381
|
-
const files = (
|
|
5382
|
-
const
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
if (multiple
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
else {
|
|
5398
|
-
const newFile = selectedFile === file ? undefined : file;
|
|
5399
|
-
setSelectedFile(newFile);
|
|
5400
|
-
if (onFileSelect && newFile) {
|
|
5401
|
-
onFileSelect(newFile);
|
|
5402
|
-
}
|
|
5380
|
+
const files = useMemo(() => (query.data ?? []), [query.data]);
|
|
5381
|
+
const selectedIds = useMemo(() => {
|
|
5382
|
+
if (multiple && Array.isArray(controlledSelectedFile)) {
|
|
5383
|
+
return new Set(controlledSelectedFile.map((f) => f.id));
|
|
5384
|
+
}
|
|
5385
|
+
if (!multiple &&
|
|
5386
|
+
controlledSelectedFile &&
|
|
5387
|
+
!Array.isArray(controlledSelectedFile)) {
|
|
5388
|
+
return new Set([controlledSelectedFile.id]);
|
|
5389
|
+
}
|
|
5390
|
+
return new Set();
|
|
5391
|
+
}, [multiple, controlledSelectedFile]);
|
|
5392
|
+
const handleSingleSelect = (file) => {
|
|
5393
|
+
if (!multiple) {
|
|
5394
|
+
onSelectedFileChange?.(file);
|
|
5395
|
+
onFileSelect?.(file);
|
|
5403
5396
|
}
|
|
5404
5397
|
};
|
|
5405
|
-
const
|
|
5406
|
-
|
|
5398
|
+
const handleMultipleToggle = (file, checked) => {
|
|
5399
|
+
const current = multiple && Array.isArray(controlledSelectedFile)
|
|
5400
|
+
? [...controlledSelectedFile]
|
|
5401
|
+
: [];
|
|
5402
|
+
const next = checked
|
|
5403
|
+
? [...current, file]
|
|
5404
|
+
: current.filter((f) => f.id !== file.id);
|
|
5405
|
+
onSelectedFileChange?.(next);
|
|
5406
|
+
onFileSelect?.(next);
|
|
5407
5407
|
};
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
:
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
? {
|
|
5440
|
-
base: 'colorPalette.50',
|
|
5441
|
-
_dark: 'colorPalette.900/20',
|
|
5442
|
-
}
|
|
5443
|
-
: 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => handleFileClick(file), _hover: {
|
|
5444
|
-
borderColor: isSelected
|
|
5445
|
-
? {
|
|
5446
|
-
base: 'colorPalette.600',
|
|
5447
|
-
_dark: 'colorPalette.400',
|
|
5448
|
-
}
|
|
5449
|
-
: {
|
|
5450
|
-
base: 'colorPalette.300',
|
|
5451
|
-
_dark: 'colorPalette.400',
|
|
5452
|
-
},
|
|
5453
|
-
bg: isSelected
|
|
5454
|
-
? {
|
|
5455
|
-
base: 'colorPalette.100',
|
|
5456
|
-
_dark: 'colorPalette.800/30',
|
|
5457
|
-
}
|
|
5458
|
-
: 'bg.muted',
|
|
5459
|
-
}, transition: "all 0.2s", children: jsxs(HStack, { gap: 3, children: [jsx(Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, children: isImage && file.url && !imageFailed ? (jsx(Image, { src: file.url, alt: file.name, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(file.id) })) : isImage && (imageFailed || !file.url) ? (jsx(Icon, { as: LuImage, boxSize: 6, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 6, color: "fg.muted" })) }), jsxs(VStack, { align: "start", flex: 1, gap: 1, children: [jsx(Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.name }), jsxs(HStack, { gap: 2, children: [file.size && (jsx(Fragment, { children: jsx(Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
|
|
5460
|
-
? formatBytes(file.size)
|
|
5461
|
-
: file.size }) })), file.comment && (jsxs(Fragment, { children: [file.size && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: "\u2022" })), jsx(Text, { fontSize: "xs", color: "fg.muted", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.comment })] }))] })] }), isSelected && (jsx(Box, { width: "24px", height: "24px", borderRadius: "full", bg: {
|
|
5462
|
-
base: 'colorPalette.500',
|
|
5463
|
-
_dark: 'colorPalette.400',
|
|
5464
|
-
}, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsx(Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
|
|
5465
|
-
}) })) }))] }));
|
|
5408
|
+
const isLoading = query.isPending;
|
|
5409
|
+
const isError = query.isError;
|
|
5410
|
+
const searchPlaceholder = labels?.searchPlaceholder ?? 'Search files...';
|
|
5411
|
+
const loadingText = labels?.loading ?? 'Loading...';
|
|
5412
|
+
const errorText = labels?.loadingFailed ?? 'Failed to load files';
|
|
5413
|
+
const emptyText = labels?.noFilesFound ?? 'No files found';
|
|
5414
|
+
return (jsxs(VStack, { align: "stretch", gap: 4, children: [jsx(InputGroup, { startElement: jsx(Icon, { as: LuSearch, color: "fg.muted" }), children: jsx(Input, { placeholder: searchPlaceholder, value: search, onChange: (e) => setSearch(e.target.value), bg: "bg.panel", borderColor: "border.default" }) }), isLoading && (jsxs(HStack, { gap: 2, py: 6, justify: "center", children: [jsx(Spinner, { size: "sm", colorPalette: "blue" }), jsx(Text, { fontSize: "sm", color: "fg.muted", children: loadingText })] })), isError && (jsx(Box, { py: 4, px: 3, borderRadius: "md", bg: { base: 'red.50', _dark: 'red.900/20' }, borderWidth: "1px", borderColor: { base: 'red.200', _dark: 'red.800' }, children: jsx(Text, { fontSize: "sm", color: { base: 'red.600', _dark: 'red.300' }, children: errorText }) })), !isLoading && !isError && files.length === 0 && (jsx(Box, { py: 6, textAlign: "center", children: jsx(Text, { fontSize: "sm", color: "fg.muted", children: emptyText }) })), !isLoading && !isError && files.length > 0 && (jsx(SimpleGrid, { columns: { base: 2, sm: 3, md: 4 }, gap: 3, children: files.map((file) => {
|
|
5415
|
+
const isImage = IMAGE_EXT.test(file.name);
|
|
5416
|
+
const isSelected = selectedIds.has(file.id);
|
|
5417
|
+
const fileSize = typeof file.size === 'number'
|
|
5418
|
+
? formatBytes(file.size)
|
|
5419
|
+
: file.size ?? null;
|
|
5420
|
+
if (multiple) {
|
|
5421
|
+
return (jsxs(CheckboxCard, { checked: isSelected, onCheckedChange: (e) => handleMultipleToggle(file, e.checked === true), variant: "outline", borderColor: "border.default", _hover: { borderColor: 'border.emphasized', bg: 'bg.muted' }, cursor: "pointer", children: [jsx(Box, { width: "100%", aspectRatio: 1, bg: "bg.muted", borderRadius: "md", overflow: "hidden", mb: 2, display: "flex", alignItems: "center", justifyContent: "center", children: isImage && file.url ? (jsx(Image, { src: file.url, alt: file.name, width: "100%", height: "100%", objectFit: "cover" })) : isImage ? (jsx(Icon, { as: LuImage, boxSize: 8, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 8, color: "fg.muted" })) }), jsx(Text, { fontSize: "xs", fontWeight: "medium", color: "fg.default", lineClamp: 2, children: file.name }), fileSize && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: fileSize }))] }, file.id));
|
|
5422
|
+
}
|
|
5423
|
+
return (jsxs(Box, { role: "button", tabIndex: 0, onClick: () => handleSingleSelect(file), onKeyDown: (e) => {
|
|
5424
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
5425
|
+
e.preventDefault();
|
|
5426
|
+
handleSingleSelect(file);
|
|
5427
|
+
}
|
|
5428
|
+
}, padding: 3, borderRadius: "md", borderWidth: "2px", borderColor: isSelected ? 'colorPalette.500' : 'border.default', bg: isSelected
|
|
5429
|
+
? { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }
|
|
5430
|
+
: 'bg.panel', _hover: {
|
|
5431
|
+
borderColor: isSelected
|
|
5432
|
+
? 'colorPalette.500'
|
|
5433
|
+
: 'border.emphasized',
|
|
5434
|
+
bg: isSelected
|
|
5435
|
+
? { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }
|
|
5436
|
+
: 'bg.muted',
|
|
5437
|
+
}, cursor: "pointer", transition: "all 0.2s", children: [jsx(Box, { width: "100%", aspectRatio: 1, bg: "bg.muted", borderRadius: "md", overflow: "hidden", mb: 2, display: "flex", alignItems: "center", justifyContent: "center", children: isImage && file.url ? (jsx(Image, { src: file.url, alt: file.name, width: "100%", height: "100%", objectFit: "cover" })) : isImage ? (jsx(Icon, { as: LuImage, boxSize: 8, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 8, color: "fg.muted" })) }), jsx(Text, { fontSize: "xs", fontWeight: "medium", color: "fg.default", lineClamp: 2, children: file.name }), fileSize && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: fileSize }))] }, file.id));
|
|
5438
|
+
}) }))] }));
|
|
5466
5439
|
};
|
|
5467
5440
|
|
|
5468
5441
|
function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, }) {
|
|
@@ -5630,7 +5603,8 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
|
5630
5603
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
|
|
5631
5604
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
5632
5605
|
const isSingleSelect = type === 'string';
|
|
5633
|
-
const
|
|
5606
|
+
const colLabel = formI18n.colLabel;
|
|
5607
|
+
const currentValue = watch(colLabel) ?? (isSingleSelect ? '' : []);
|
|
5634
5608
|
// Handle string IDs only
|
|
5635
5609
|
const currentFileIds = isSingleSelect
|
|
5636
5610
|
? currentValue
|
|
@@ -5639,7 +5613,6 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
|
5639
5613
|
: Array.isArray(currentValue)
|
|
5640
5614
|
? currentValue
|
|
5641
5615
|
: [];
|
|
5642
|
-
const colLabel = formI18n.colLabel;
|
|
5643
5616
|
const fieldError = getNestedError(errors, colLabel);
|
|
5644
5617
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
5645
5618
|
const [failedImageIds, setFailedImageIds] = useState(new Set());
|
|
@@ -5727,7 +5700,7 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
|
5727
5700
|
: /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(fileId);
|
|
5728
5701
|
const imageFailed = failedImageIds.has(fileId);
|
|
5729
5702
|
const displayName = file?.name ?? fileId;
|
|
5730
|
-
return (jsx(Card.Root, { variant: 'subtle',
|
|
5703
|
+
return (jsx(Card.Root, { variant: 'subtle', children: jsxs(Card.Body, { gap: "2", cursor: 'pointer', onClick: () => handleRemove(index), display: 'flex', flexFlow: 'row', alignItems: 'center', padding: '2', border: "2px solid", borderColor: "border.default", borderRadius: "md", _hover: {
|
|
5731
5704
|
borderColor: 'colorPalette.300',
|
|
5732
5705
|
bg: 'bg.muted',
|
|
5733
5706
|
}, transition: "all 0.2s", children: [jsx(Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, marginRight: "2", overflow: "hidden", children: isImage && file?.url && !imageFailed ? (jsx(Image, { src: file.url, alt: displayName, boxSize: "60px", objectFit: "cover", onError: () => handleImageError(fileId) })) : isImage && !imageFailed ? (jsx(Icon, { as: LuImage, boxSize: 6, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 6, color: "fg.muted" })) }), jsxs(VStack, { align: "start", flex: 1, gap: 1, children: [jsx(Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: displayName }), file?.size && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
|
|
@@ -18,5 +18,5 @@ type MediaLibraryBrowserPropsMultiple = MediaLibraryBrowserPropsBase & {
|
|
|
18
18
|
onSelectedFileChange?: (files: FilePickerMediaFile[]) => void;
|
|
19
19
|
};
|
|
20
20
|
export type MediaLibraryBrowserProps = MediaLibraryBrowserPropsSingle | MediaLibraryBrowserPropsMultiple;
|
|
21
|
-
export declare const MediaLibraryBrowser: ({ onFetchFiles, filterImageOnly, labels, enabled, multiple, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }: MediaLibraryBrowserProps) => import("react/jsx-runtime").JSX.Element
|
|
21
|
+
export declare const MediaLibraryBrowser: ({ onFetchFiles, filterImageOnly, labels, enabled, multiple, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }: MediaLibraryBrowserProps) => import("react/jsx-runtime").JSX.Element;
|
|
22
22
|
export {};
|