@bsol-oss/react-datatable5 12.0.0-beta.81 → 12.0.0-beta.82

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.mjs CHANGED
@@ -1,9 +1,9 @@
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, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Image, Alert, Field as Field$1, Popover, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading, Skeleton } 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, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Image, Alert, Field as Field$1, Popover, Tabs, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading, Skeleton } from '@chakra-ui/react';
3
3
  import { AiOutlineColumnWidth } from 'react-icons/ai';
4
4
  import * as React from 'react';
5
5
  import React__default, { createContext, useContext, useState, useEffect, useRef, forwardRef } from 'react';
6
- import { LuX, LuCheck, LuChevronRight, LuImage, LuFile, LuSearch } from 'react-icons/lu';
6
+ import { LuX, LuCheck, LuChevronRight, LuSearch, LuImage, LuFile } from 'react-icons/lu';
7
7
  import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineChecklist, MdClear, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdDateRange } from 'react-icons/md';
8
8
  import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
9
9
  import { BiDownArrow, BiUpArrow, BiError } from 'react-icons/bi';
@@ -4624,10 +4624,13 @@ function formatBytes(bytes) {
4624
4624
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
4625
4625
  }
4626
4626
 
4627
- function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, labels, translate, colLabel, }) {
4627
+ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, enabled = true, multiple = false, onFileSelect, selectedFileId: controlledSelectedFileId, onSelectedFileIdChange, }) => {
4628
4628
  const [searchTerm, setSearchTerm] = useState('');
4629
- const [selectedFileId, setSelectedFileId] = useState('');
4629
+ const [internalSelectedFileId, setInternalSelectedFileId] = useState(multiple ? [] : '');
4630
4630
  const [failedImageIds, setFailedImageIds] = useState(new Set());
4631
+ // Use controlled or internal state for selectedFileId
4632
+ const selectedFileId = controlledSelectedFileId ?? internalSelectedFileId;
4633
+ const setSelectedFileId = onSelectedFileIdChange ?? setInternalSelectedFileId;
4631
4634
  const { data: filesData, isLoading, isError, } = useQuery({
4632
4635
  queryKey: ['file-picker-library', searchTerm],
4633
4636
  queryFn: async () => {
@@ -4636,91 +4639,176 @@ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = fa
4636
4639
  const files = await onFetchFiles(searchTerm.trim() || '');
4637
4640
  return { data: files };
4638
4641
  },
4639
- enabled: open && !!onFetchFiles,
4642
+ enabled: enabled && !!onFetchFiles,
4640
4643
  });
4641
4644
  const files = (filesData?.data || []);
4642
4645
  const filteredFiles = filterImageOnly
4643
4646
  ? files.filter((file) => /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name))
4644
4647
  : files;
4648
+ const handleFileClick = (fileId) => {
4649
+ if (multiple) {
4650
+ const currentSelection = Array.isArray(selectedFileId)
4651
+ ? selectedFileId
4652
+ : [];
4653
+ const newSelection = currentSelection.includes(fileId)
4654
+ ? currentSelection.filter((id) => id !== fileId)
4655
+ : [...currentSelection, fileId];
4656
+ setSelectedFileId(newSelection);
4657
+ if (onFileSelect) {
4658
+ onFileSelect(newSelection);
4659
+ }
4660
+ }
4661
+ else {
4662
+ setSelectedFileId(fileId);
4663
+ if (onFileSelect) {
4664
+ onFileSelect(fileId);
4665
+ }
4666
+ }
4667
+ };
4668
+ const handleImageError = (fileId) => {
4669
+ setFailedImageIds((prev) => new Set(prev).add(fileId));
4670
+ };
4671
+ if (!onFetchFiles)
4672
+ return null;
4673
+ return (jsxs(VStack, { align: "stretch", gap: 4, children: [jsxs(Box, { position: "relative", children: [jsx(Input, { placeholder: labels?.searchPlaceholder ?? 'Search files...', value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), bg: "bg.panel", border: "1px solid", borderColor: "border.default", colorPalette: "blue", _focus: {
4674
+ borderColor: 'colorPalette.500',
4675
+ _dark: {
4676
+ borderColor: 'colorPalette.400',
4677
+ },
4678
+ boxShadow: {
4679
+ base: '0 0 0 1px var(--chakra-colors-blue-500)',
4680
+ _dark: '0 0 0 1px var(--chakra-colors-blue-400)',
4681
+ },
4682
+ }, pl: 10 }), jsx(Icon, { as: LuSearch, position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", color: "fg.muted", boxSize: 4 })] }), isLoading && (jsxs(Box, { textAlign: "center", py: 8, children: [jsx(Spinner, { size: "lg", colorPalette: "blue" }), jsx(Text, { mt: 4, color: "fg.muted", children: labels?.loading ?? 'Loading files...' })] })), isError && (jsx(Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
4683
+ base: 'colorPalette.200',
4684
+ _dark: 'colorPalette.800',
4685
+ }, colorPalette: "red", borderRadius: "md", p: 4, children: jsx(Text, { color: {
4686
+ base: 'colorPalette.600',
4687
+ _dark: 'colorPalette.300',
4688
+ }, children: labels?.loadingFailed ?? 'Failed to load files' }) })), !isLoading && !isError && (jsx(Box, { maxHeight: "400px", overflowY: "auto", children: filteredFiles.length === 0 ? (jsx(Box, { textAlign: "center", py: 8, children: jsx(Text, { color: "fg.muted", children: labels?.noFilesFound ?? 'No files found' }) })) : (jsx(VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
4689
+ const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
4690
+ const isSelected = multiple
4691
+ ? Array.isArray(selectedFileId) &&
4692
+ selectedFileId.includes(file.id)
4693
+ : selectedFileId === file.id;
4694
+ const imageFailed = failedImageIds.has(file.id);
4695
+ return (jsx(Box, { p: 3, border: "2px solid", borderColor: isSelected
4696
+ ? {
4697
+ base: 'colorPalette.500',
4698
+ _dark: 'colorPalette.400',
4699
+ }
4700
+ : 'border.default', borderRadius: "md", bg: isSelected
4701
+ ? {
4702
+ base: 'colorPalette.50',
4703
+ _dark: 'colorPalette.900/20',
4704
+ }
4705
+ : 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => handleFileClick(file.id), _hover: {
4706
+ borderColor: isSelected
4707
+ ? {
4708
+ base: 'colorPalette.600',
4709
+ _dark: 'colorPalette.400',
4710
+ }
4711
+ : {
4712
+ base: 'colorPalette.300',
4713
+ _dark: 'colorPalette.400',
4714
+ },
4715
+ bg: isSelected
4716
+ ? {
4717
+ base: 'colorPalette.100',
4718
+ _dark: 'colorPalette.800/30',
4719
+ }
4720
+ : 'bg.muted',
4721
+ }, 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'
4722
+ ? formatBytes(file.size)
4723
+ : 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: {
4724
+ base: 'colorPalette.500',
4725
+ _dark: 'colorPalette.400',
4726
+ }, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsx(Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
4727
+ }) })) }))] }));
4728
+ };
4729
+
4730
+ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, translate, colLabel, }) {
4731
+ const [selectedFileId, setSelectedFileId] = useState('');
4732
+ const [activeTab, setActiveTab] = useState('browse');
4733
+ const [uploadingFiles, setUploadingFiles] = useState(new Set());
4734
+ const [uploadErrors, setUploadErrors] = useState(new Map());
4645
4735
  const handleSelect = () => {
4646
4736
  if (selectedFileId) {
4647
4737
  onSelect(selectedFileId);
4648
4738
  onClose();
4649
4739
  setSelectedFileId('');
4650
- setSearchTerm('');
4740
+ setActiveTab('browse');
4651
4741
  }
4652
4742
  };
4653
4743
  const handleClose = () => {
4654
4744
  onClose();
4655
4745
  setSelectedFileId('');
4656
- setSearchTerm('');
4657
- setFailedImageIds(new Set());
4746
+ setActiveTab('browse');
4747
+ setUploadingFiles(new Set());
4748
+ setUploadErrors(new Map());
4658
4749
  };
4659
- const handleImageError = (fileId) => {
4660
- setFailedImageIds((prev) => new Set(prev).add(fileId));
4750
+ const handleFileUpload = async (files) => {
4751
+ if (!onUploadFile)
4752
+ return;
4753
+ for (const file of files) {
4754
+ const fileKey = `${file.name}-${file.size}`;
4755
+ setUploadingFiles((prev) => new Set(prev).add(fileKey));
4756
+ setUploadErrors((prev) => {
4757
+ const newMap = new Map(prev);
4758
+ newMap.delete(fileKey);
4759
+ return newMap;
4760
+ });
4761
+ try {
4762
+ const fileId = await onUploadFile(file);
4763
+ setSelectedFileId(fileId);
4764
+ setUploadingFiles((prev) => {
4765
+ const newSet = new Set(prev);
4766
+ newSet.delete(fileKey);
4767
+ return newSet;
4768
+ });
4769
+ // Auto-select and close in single-select mode
4770
+ onSelect(fileId);
4771
+ onClose();
4772
+ setSelectedFileId('');
4773
+ setActiveTab('browse');
4774
+ }
4775
+ catch (error) {
4776
+ setUploadingFiles((prev) => {
4777
+ const newSet = new Set(prev);
4778
+ newSet.delete(fileKey);
4779
+ return newSet;
4780
+ });
4781
+ setUploadErrors((prev) => {
4782
+ const newMap = new Map(prev);
4783
+ newMap.set(fileKey, error instanceof Error ? error.message : 'Upload failed');
4784
+ return newMap;
4785
+ });
4786
+ }
4787
+ }
4661
4788
  };
4662
- if (!onFetchFiles)
4789
+ const showTabs = enableUpload && !!onUploadFile && !!onFetchFiles;
4790
+ if (!onFetchFiles && !onUploadFile)
4663
4791
  return null;
4664
- return (jsx(DialogRoot, { open: open, onOpenChange: (e) => !e.open && handleClose(), children: jsxs(DialogContent, { maxWidth: "800px", maxHeight: "90vh", children: [jsxs(DialogHeader, { children: [jsx(DialogTitle, { fontSize: "lg", fontWeight: "bold", children: title }), jsx(DialogCloseTrigger, {})] }), jsx(DialogBody, { children: jsxs(VStack, { align: "stretch", gap: 4, children: [jsxs(Box, { position: "relative", children: [jsx(Input, { placeholder: labels?.searchPlaceholder ??
4665
- translate(removeIndex(`${colLabel}.search_placeholder`)) ??
4666
- 'Search files...', value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), bg: "bg.panel", border: "1px solid", borderColor: "border.default", colorPalette: "blue", _focus: {
4667
- borderColor: 'colorPalette.500',
4668
- _dark: {
4669
- borderColor: 'colorPalette.400',
4670
- },
4671
- boxShadow: {
4672
- base: '0 0 0 1px var(--chakra-colors-blue-500)',
4673
- _dark: '0 0 0 1px var(--chakra-colors-blue-400)',
4674
- },
4675
- }, pl: 10 }), jsx(Icon, { as: LuSearch, position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", color: "fg.muted", boxSize: 4 })] }), isLoading && (jsxs(Box, { textAlign: "center", py: 8, children: [jsx(Spinner, { size: "lg", colorPalette: "blue" }), jsx(Text, { mt: 4, color: "fg.muted", children: labels?.loading ??
4676
- translate(removeIndex(`${colLabel}.loading`)) ??
4677
- 'Loading files...' })] })), isError && (jsx(Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
4678
- base: 'colorPalette.200',
4679
- _dark: 'colorPalette.800',
4680
- }, colorPalette: "red", borderRadius: "md", p: 4, children: jsx(Text, { color: {
4681
- base: 'colorPalette.600',
4682
- _dark: 'colorPalette.300',
4683
- }, children: labels?.loadingFailed ??
4684
- translate(removeIndex(`${colLabel}.error.loading_failed`)) ??
4685
- 'Failed to load files' }) })), !isLoading && !isError && (jsx(Box, { maxHeight: "400px", overflowY: "auto", children: filteredFiles.length === 0 ? (jsx(Box, { textAlign: "center", py: 8, children: jsx(Text, { color: "fg.muted", children: labels?.noFilesFound ??
4686
- translate(removeIndex(`${colLabel}.no_files_found`)) ??
4687
- 'No files found' }) })) : (jsx(VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
4688
- const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
4689
- const isSelected = selectedFileId === file.id;
4690
- const imageFailed = failedImageIds.has(file.id);
4691
- return (jsx(Box, { p: 3, border: "2px solid", borderColor: isSelected
4692
- ? {
4693
- base: 'colorPalette.500',
4694
- _dark: 'colorPalette.400',
4695
- }
4696
- : 'border.default', borderRadius: "md", bg: isSelected
4697
- ? {
4792
+ return (jsx(DialogRoot, { open: open, onOpenChange: (e) => !e.open && handleClose(), children: jsxs(DialogContent, { maxWidth: "800px", maxHeight: "90vh", children: [jsxs(DialogHeader, { children: [jsx(DialogTitle, { fontSize: "lg", fontWeight: "bold", children: title }), jsx(DialogCloseTrigger, {})] }), jsx(DialogBody, { children: showTabs ? (jsxs(Tabs.Root, { value: activeTab, onValueChange: (e) => setActiveTab(e.value ?? 'browse'), children: [jsxs(Tabs.List, { children: [jsx(Tabs.Trigger, { value: "browse", children: labels?.browseTab ??
4793
+ translate(removeIndex(`${colLabel}.browse_tab`)) ??
4794
+ 'Browse Library' }), jsx(Tabs.Trigger, { value: "upload", children: labels?.uploadTab ??
4795
+ translate(removeIndex(`${colLabel}.upload_tab`)) ??
4796
+ 'Upload Files' })] }), jsx(Tabs.Content, { value: "browse", children: onFetchFiles && (jsx(MediaLibraryBrowser, { onFetchFiles: onFetchFiles, filterImageOnly: filterImageOnly, labels: labels, enabled: open && activeTab === 'browse', selectedFileId: selectedFileId, onSelectedFileIdChange: setSelectedFileId })) }), jsx(Tabs.Content, { value: "upload", children: jsxs(VStack, { align: "stretch", gap: 4, children: [jsx(FileDropzone, { onDrop: ({ files }) => handleFileUpload(files), placeholder: labels?.fileDropzone ??
4797
+ translate(removeIndex(`${colLabel}.fileDropzone`)) ??
4798
+ 'Drop files here or click to upload' }), uploadingFiles.size > 0 && (jsx(Box, { children: Array.from(uploadingFiles).map((fileKey) => (jsx(Box, { py: 2, children: jsxs(HStack, { gap: 2, children: [jsx(Spinner, { size: "sm", colorPalette: "blue" }), jsxs(Text, { fontSize: "sm", color: "fg.muted", children: [labels?.uploading ??
4799
+ translate(removeIndex(`${colLabel}.uploading`)) ??
4800
+ 'Uploading...', ' ', fileKey.split('-')[0]] })] }) }, fileKey))) })), uploadErrors.size > 0 && (jsx(VStack, { align: "stretch", gap: 2, children: Array.from(uploadErrors.entries()).map(([fileKey, error]) => (jsx(Box, { bg: {
4698
4801
  base: 'colorPalette.50',
4699
4802
  _dark: 'colorPalette.900/20',
4700
- }
4701
- : 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => setSelectedFileId(file.id), _hover: {
4702
- borderColor: isSelected
4703
- ? {
4803
+ }, border: "1px solid", borderColor: {
4804
+ base: 'colorPalette.200',
4805
+ _dark: 'colorPalette.800',
4806
+ }, colorPalette: "red", borderRadius: "md", p: 3, children: jsxs(Text, { fontSize: "sm", color: {
4704
4807
  base: 'colorPalette.600',
4705
- _dark: 'colorPalette.400',
4706
- }
4707
- : {
4708
- base: 'colorPalette.300',
4709
- _dark: 'colorPalette.400',
4710
- },
4711
- bg: isSelected
4712
- ? {
4713
- base: 'colorPalette.100',
4714
- _dark: 'colorPalette.800/30',
4715
- }
4716
- : 'bg.muted',
4717
- }, 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'
4718
- ? formatBytes(file.size)
4719
- : 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: {
4720
- base: 'colorPalette.500',
4721
- _dark: 'colorPalette.400',
4722
- }, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsx(Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
4723
- }) })) }))] }) }), jsx(DialogFooter, { children: jsxs(HStack, { gap: 3, justify: "end", children: [jsx(Button$1, { variant: "outline", onClick: handleClose, borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: labels?.cancel ??
4808
+ _dark: 'colorPalette.300',
4809
+ }, children: [fileKey.split('-')[0], ":", ' ', labels?.uploadFailed ??
4810
+ translate(removeIndex(`${colLabel}.upload_failed`)) ??
4811
+ 'Upload failed', error && ` - ${error}`] }) }, fileKey))) }))] }) })] })) : onFetchFiles ? (jsx(MediaLibraryBrowser, { onFetchFiles: onFetchFiles, filterImageOnly: filterImageOnly, labels: labels, enabled: open, selectedFileId: selectedFileId, onSelectedFileIdChange: setSelectedFileId })) : null }), jsx(DialogFooter, { children: jsxs(HStack, { gap: 3, justify: "end", children: [jsx(Button$1, { variant: "outline", onClick: handleClose, borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: labels?.cancel ??
4724
4812
  translate(removeIndex(`${colLabel}.cancel`)) ??
4725
4813
  'Cancel' }), jsx(Button$1, { colorPalette: "blue", onClick: handleSelect, disabled: !selectedFileId, children: labels?.select ??
4726
4814
  translate(removeIndex(`${colLabel}.select`)) ??
@@ -4730,84 +4818,71 @@ const FilePicker = ({ column, schema, prefix }) => {
4730
4818
  const { setValue, formState: { errors }, watch, } = useFormContext();
4731
4819
  const { filePickerLabels } = useSchemaContext();
4732
4820
  const formI18n = useFormI18n(column, prefix);
4733
- const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, } = schema;
4821
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', type, } = schema;
4734
4822
  const isRequired = required?.some((columnId) => columnId === column);
4735
- const currentValue = watch(column) ?? [];
4736
- const currentFiles = Array.isArray(currentValue)
4737
- ? currentValue
4738
- : [];
4823
+ const isSingleSelect = type === 'string';
4824
+ const currentValue = watch(column) ?? (isSingleSelect ? '' : []);
4825
+ // Handle File objects only
4826
+ const currentFiles = isSingleSelect
4827
+ ? currentValue && currentValue instanceof File
4828
+ ? [currentValue]
4829
+ : []
4830
+ : Array.isArray(currentValue)
4831
+ ? currentValue.filter((f) => f instanceof File)
4832
+ : [];
4739
4833
  const colLabel = formI18n.colLabel;
4740
- const [dialogOpen, setDialogOpen] = useState(false);
4741
4834
  const [failedImageIds, setFailedImageIds] = useState(new Set());
4742
- const { onFetchFiles, enableMediaLibrary = false, filterImageOnly = false, } = filePicker || {};
4743
- const showMediaLibrary = enableMediaLibrary && !!onFetchFiles;
4835
+ // FilePicker variant: Only handle File objects, no media library browser
4744
4836
  const handleImageError = (fileIdentifier) => {
4745
4837
  setFailedImageIds((prev) => new Set(prev).add(fileIdentifier));
4746
4838
  };
4747
- const handleMediaLibrarySelect = (fileId) => {
4748
- const newFiles = [...currentFiles, fileId];
4749
- setValue(colLabel, newFiles);
4750
- };
4751
4839
  const handleRemove = (index) => {
4752
- const newFiles = currentFiles.filter((_, i) => i !== index);
4753
- setValue(colLabel, newFiles);
4754
- };
4755
- const isFileObject = (value) => {
4756
- return value instanceof File;
4840
+ if (isSingleSelect) {
4841
+ setValue(colLabel, '');
4842
+ }
4843
+ else {
4844
+ const newFiles = currentFiles.filter((_, i) => i !== index);
4845
+ setValue(colLabel, newFiles);
4846
+ }
4757
4847
  };
4758
4848
  const getFileIdentifier = (file, index) => {
4759
- if (isFileObject(file)) {
4760
- return `${file.name}-${file.size}-${index}`;
4761
- }
4762
- return file;
4849
+ // file-picker: file is a File object, create identifier from name and size
4850
+ return `${file.name}-${file.size}-${index}`;
4763
4851
  };
4764
4852
  const getFileName = (file) => {
4765
- if (isFileObject(file)) {
4766
- return file.name;
4767
- }
4768
- return typeof file === 'string' ? file : 'Unknown file';
4853
+ return file.name;
4769
4854
  };
4770
4855
  const getFileSize = (file) => {
4771
- if (isFileObject(file)) {
4772
- return file.size;
4773
- }
4774
- return undefined;
4856
+ return file.size;
4775
4857
  };
4776
4858
  const isImageFile = (file) => {
4777
- if (isFileObject(file)) {
4778
- return file.type.startsWith('image/');
4779
- }
4780
- if (typeof file === 'string') {
4781
- return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file);
4782
- }
4783
- return false;
4859
+ return file.type.startsWith('image/');
4784
4860
  };
4785
4861
  const getImageUrl = (file) => {
4786
- if (isFileObject(file)) {
4787
- return URL.createObjectURL(file);
4788
- }
4789
- return undefined;
4862
+ return URL.createObjectURL(file);
4790
4863
  };
4791
4864
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4792
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxs(VStack, { align: "stretch", gap: 2, children: [jsx(FileDropzone, { onDrop: ({ files }) => {
4793
- const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => {
4794
- if (isFileObject(cur)) {
4795
- return cur.name === name;
4796
- }
4797
- return false;
4798
- }));
4865
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsx(VStack, { align: "stretch", gap: 2, children: jsx(FileDropzone, { onDrop: ({ files }) => {
4866
+ // file-picker variant: Store File objects directly (no ID conversion)
4867
+ if (isSingleSelect) {
4868
+ // In single-select mode, use the first file and replace any existing file
4869
+ if (files.length > 0) {
4870
+ setValue(colLabel, files[0]);
4871
+ }
4872
+ }
4873
+ else {
4874
+ // In multi-select mode, filter duplicates and append
4875
+ const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => cur.name === name));
4799
4876
  setValue(colLabel, [...currentFiles, ...newFiles]);
4800
- }, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }), showMediaLibrary && (jsx(Button$1, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
4801
- formI18n.t('browse_library') ??
4802
- 'Browse from Library' }))] }), showMediaLibrary && (jsx(FilePickerDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
4803
- formI18n.t('dialog_title') ??
4804
- 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel })), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
4877
+ }
4878
+ }, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }) }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
4805
4879
  const fileIdentifier = getFileIdentifier(file, index);
4806
4880
  const fileName = getFileName(file);
4807
4881
  const fileSize = getFileSize(file);
4808
4882
  const isImage = isImageFile(file);
4809
4883
  const imageUrl = getImageUrl(file);
4810
4884
  const imageFailed = failedImageIds.has(fileIdentifier);
4885
+ // File Viewer
4811
4886
  return (jsx(Card.Root, { variant: 'subtle', colorPalette: "blue", 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: {
4812
4887
  borderColor: 'colorPalette.300',
4813
4888
  bg: 'bg.muted',
@@ -4815,6 +4890,66 @@ const FilePicker = ({ column, schema, prefix }) => {
4815
4890
  }) })] }));
4816
4891
  };
4817
4892
 
4893
+ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
4894
+ const { setValue, formState: { errors }, watch, } = useFormContext();
4895
+ const { filePickerLabels } = useSchemaContext();
4896
+ const formI18n = useFormI18n(column, prefix);
4897
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
4898
+ const isRequired = required?.some((columnId) => columnId === column);
4899
+ const isSingleSelect = type === 'string';
4900
+ const currentValue = watch(column) ?? (isSingleSelect ? '' : []);
4901
+ // Handle string IDs only
4902
+ const currentFileIds = isSingleSelect
4903
+ ? currentValue
4904
+ ? [currentValue]
4905
+ : []
4906
+ : Array.isArray(currentValue)
4907
+ ? currentValue
4908
+ : [];
4909
+ const colLabel = formI18n.colLabel;
4910
+ const [dialogOpen, setDialogOpen] = useState(false);
4911
+ const [failedImageIds, setFailedImageIds] = useState(new Set());
4912
+ const { onFetchFiles, filterImageOnly = false, enableUpload = false, onUploadFile, } = filePicker || {};
4913
+ if (!onFetchFiles) {
4914
+ return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4915
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsx(Text, { color: "fg.muted", children: "Media library browser requires onFetchFiles" }) }));
4916
+ }
4917
+ const handleMediaLibrarySelect = (fileId) => {
4918
+ if (isSingleSelect) {
4919
+ setValue(colLabel, fileId);
4920
+ }
4921
+ else {
4922
+ const newFileIds = [...currentFileIds, fileId];
4923
+ setValue(colLabel, newFileIds);
4924
+ }
4925
+ };
4926
+ const handleRemove = (index) => {
4927
+ if (isSingleSelect) {
4928
+ setValue(colLabel, '');
4929
+ }
4930
+ else {
4931
+ const newFileIds = currentFileIds.filter((_, i) => i !== index);
4932
+ setValue(colLabel, newFileIds);
4933
+ }
4934
+ };
4935
+ const isImageId = (fileId) => {
4936
+ return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(fileId);
4937
+ };
4938
+ return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4939
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsx(VStack, { align: "stretch", gap: 2, children: jsx(Button$1, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
4940
+ formI18n.t('browse_library') ??
4941
+ 'Browse from Library' }) }), jsx(FilePickerDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
4942
+ formI18n.t('dialog_title') ??
4943
+ 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, onUploadFile: onUploadFile, enableUpload: enableUpload, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
4944
+ const isImage = isImageId(fileId);
4945
+ const imageFailed = failedImageIds.has(fileId);
4946
+ return (jsx(Card.Root, { variant: 'subtle', colorPalette: "blue", 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: {
4947
+ borderColor: 'colorPalette.300',
4948
+ bg: 'bg.muted',
4949
+ }, 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", children: isImage && !imageFailed ? (jsx(Icon, { as: LuImage, boxSize: 6, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 6, color: "fg.muted" })) }), jsx(VStack, { align: "start", flex: 1, gap: 1, children: jsx(Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: fileId }) }), jsx(Icon, { as: TiDeleteOutline, boxSize: 5, color: "fg.muted" })] }) }, `${fileId}-${index}`));
4950
+ }) })] }));
4951
+ };
4952
+
4818
4953
  const ToggleTip = React.forwardRef(function ToggleTip(props, ref) {
4819
4954
  const { showArrow, children, portalled = true, content, portalRef, ...rest } = props;
4820
4955
  return (jsxs(Popover.Root, { ...rest, positioning: { ...rest.positioning, gutter: 4 }, children: [jsx(Popover.Trigger, { asChild: true, children: children }), jsx(Portal, { disabled: !portalled, container: portalRef, children: jsx(Popover.Positioner, { children: jsxs(Popover.Content, { width: "auto", px: "2", py: "1", textStyle: "xs", rounded: "sm", ref: ref, children: [showArrow && (jsx(Popover.Arrow, { children: jsx(Popover.ArrowTip, {}) })), content] }) }) })] }));
@@ -6005,6 +6140,9 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
6005
6140
  if (variant === 'file-picker') {
6006
6141
  return jsx(FilePicker, { schema: colSchema, prefix, column });
6007
6142
  }
6143
+ if (variant === 'media-library-browser') {
6144
+ return (jsx(FormMediaLibraryBrowser, { schema: colSchema, prefix, column }));
6145
+ }
6008
6146
  if (variant === 'date-range') {
6009
6147
  return jsx(DateRangePicker, { schema: colSchema, prefix, column });
6010
6148
  }
@@ -6396,59 +6534,62 @@ const DateTimeViewer = ({ column, schema, prefix }) => {
6396
6534
  const SchemaViewer = ({ schema, prefix, column, }) => {
6397
6535
  const colSchema = schema;
6398
6536
  const { type, variant, properties: innerProperties, foreign_key, items, format, } = schema;
6399
- if (variant === "custom-input") {
6537
+ if (variant === 'custom-input') {
6400
6538
  return jsx(CustomViewer, { schema: colSchema, prefix, column });
6401
6539
  }
6402
- if (type === "string") {
6540
+ if (type === 'string') {
6403
6541
  if ((schema.enum ?? []).length > 0) {
6404
6542
  return jsx(EnumViewer, { schema: colSchema, prefix, column });
6405
6543
  }
6406
- if (variant === "id-picker") {
6544
+ if (variant === 'id-picker') {
6407
6545
  idPickerSanityCheck(column, foreign_key);
6408
6546
  return jsx(IdViewer, { schema: colSchema, prefix, column });
6409
6547
  }
6410
- if (format === "time") {
6548
+ if (format === 'time') {
6411
6549
  return jsx(TimeViewer, { schema: colSchema, prefix, column });
6412
6550
  }
6413
- if (format === "date") {
6551
+ if (format === 'date') {
6414
6552
  return jsx(DateViewer, { schema: colSchema, prefix, column });
6415
6553
  }
6416
- if (format === "date-time") {
6554
+ if (format === 'date-time') {
6417
6555
  return jsx(DateTimeViewer, { schema: colSchema, prefix, column });
6418
6556
  }
6419
- if (variant === "text-area") {
6557
+ if (variant === 'text-area') {
6420
6558
  return jsx(TextAreaViewer, { schema: colSchema, prefix, column });
6421
6559
  }
6422
6560
  return jsx(StringViewer, { schema: colSchema, prefix, column });
6423
6561
  }
6424
- if (type === "number" || type === "integer") {
6562
+ if (type === 'number' || type === 'integer') {
6425
6563
  return jsx(NumberViewer, { schema: colSchema, prefix, column });
6426
6564
  }
6427
- if (type === "boolean") {
6565
+ if (type === 'boolean') {
6428
6566
  return jsx(BooleanViewer, { schema: colSchema, prefix, column });
6429
6567
  }
6430
- if (type === "object") {
6568
+ if (type === 'object') {
6431
6569
  if (innerProperties) {
6432
6570
  return jsx(ObjectViewer, { schema: colSchema, prefix, column });
6433
6571
  }
6434
6572
  return jsx(RecordInput, { schema: colSchema, prefix, column });
6435
6573
  }
6436
- if (type === "array") {
6437
- if (variant === "id-picker") {
6574
+ if (type === 'array') {
6575
+ if (variant === 'id-picker') {
6438
6576
  idPickerSanityCheck(column, foreign_key);
6439
6577
  return (jsx(IdViewer, { schema: colSchema, prefix, column, isMultiple: true }));
6440
6578
  }
6441
- if (variant === "tag-picker") {
6579
+ if (variant === 'tag-picker') {
6442
6580
  return jsx(TagViewer, { schema: colSchema, prefix, column });
6443
6581
  }
6444
- if (variant === "file-picker") {
6582
+ if (variant === 'file-picker') {
6583
+ return jsx(FileViewer, { schema: colSchema, prefix, column });
6584
+ }
6585
+ if (variant === 'media-library-browser') {
6445
6586
  return jsx(FileViewer, { schema: colSchema, prefix, column });
6446
6587
  }
6447
- if (variant === "enum-picker") {
6588
+ if (variant === 'enum-picker') {
6448
6589
  const { items } = schema;
6449
6590
  const { enum: enumItems } = items;
6450
6591
  const enumSchema = {
6451
- type: "string",
6592
+ type: 'string',
6452
6593
  enum: enumItems,
6453
6594
  };
6454
6595
  return (jsx(EnumViewer, { isMultiple: true, schema: enumSchema, prefix, column }));
@@ -6458,7 +6599,7 @@ const SchemaViewer = ({ schema, prefix, column, }) => {
6458
6599
  }
6459
6600
  return jsx(Text, { children: `array ${column}` });
6460
6601
  }
6461
- if (type === "null") {
6602
+ if (type === 'null') {
6462
6603
  return jsx(Text, { children: `null ${column}` });
6463
6604
  }
6464
6605
  return jsx(Text, { children: "missing type" });
@@ -7372,4 +7513,4 @@ function DataTableServer({ columns, enableRowSelection = true, enableMultiRowSel
7372
7513
  }, children: jsx(DataTableServerContext.Provider, { value: { url, query }, children: children }) }));
7373
7514
  }
7374
7515
 
7375
- export { CardHeader, DataDisplay, DataTable, DataTableServer, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
7516
+ export { CardHeader, DataDisplay, DataTable, DataTableServer, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, MediaLibraryBrowser, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
@@ -0,0 +1,22 @@
1
+ import { FilePickerMediaFile, FilePickerLabels } from './types/CustomJSONSchema7';
2
+ type MediaLibraryBrowserPropsBase = {
3
+ onFetchFiles?: (search: string) => Promise<FilePickerMediaFile[]>;
4
+ filterImageOnly?: boolean;
5
+ labels?: FilePickerLabels;
6
+ enabled?: boolean;
7
+ };
8
+ type MediaLibraryBrowserPropsSingle = MediaLibraryBrowserPropsBase & {
9
+ multiple?: false;
10
+ onFileSelect?: (fileId: string) => void;
11
+ selectedFileId?: string;
12
+ onSelectedFileIdChange?: (fileId: string) => void;
13
+ };
14
+ type MediaLibraryBrowserPropsMultiple = MediaLibraryBrowserPropsBase & {
15
+ multiple: true;
16
+ onFileSelect?: (fileId: string[]) => void;
17
+ selectedFileId?: string[];
18
+ onSelectedFileIdChange?: (fileId: string[]) => void;
19
+ };
20
+ export type MediaLibraryBrowserProps = MediaLibraryBrowserPropsSingle | MediaLibraryBrowserPropsMultiple;
21
+ export declare const MediaLibraryBrowser: ({ onFetchFiles, filterImageOnly, labels, enabled, multiple, onFileSelect, selectedFileId: controlledSelectedFileId, onSelectedFileIdChange, }: MediaLibraryBrowserProps) => import("react/jsx-runtime").JSX.Element | null;
22
+ export {};
@@ -1,2 +1,18 @@
1
+ import { FilePickerMediaFile, FilePickerLabels } from '../types/CustomJSONSchema7';
1
2
  import { InputDefaultProps } from './types';
3
+ interface FilePickerDialogProps {
4
+ open: boolean;
5
+ onClose: () => void;
6
+ onSelect: (fileId: string) => void;
7
+ title: string;
8
+ filterImageOnly?: boolean;
9
+ onFetchFiles?: (search: string) => Promise<FilePickerMediaFile[]>;
10
+ onUploadFile?: (file: File) => Promise<string>;
11
+ enableUpload?: boolean;
12
+ labels?: FilePickerLabels;
13
+ translate: (key: string) => string;
14
+ colLabel: string;
15
+ }
16
+ export declare function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly, onFetchFiles, onUploadFile, enableUpload, labels, translate, colLabel, }: FilePickerDialogProps): import("react/jsx-runtime").JSX.Element | null;
2
17
  export declare const FilePicker: ({ column, schema, prefix }: InputDefaultProps) => import("react/jsx-runtime").JSX.Element;
18
+ export {};
@@ -0,0 +1,2 @@
1
+ import { InputDefaultProps } from './types';
2
+ export declare const FormMediaLibraryBrowser: ({ column, schema, prefix, }: InputDefaultProps) => import("react/jsx-runtime").JSX.Element;