@bsol-oss/react-datatable5 13.0.1-beta.37 → 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
@@ -700,11 +700,6 @@ interface FormRootProps<TData extends FieldValues> {
700
700
  setIdMap: Dispatch<SetStateAction<Record<string, unknown>>>;
701
701
  form: UseFormReturn<TData, any, TData>;
702
702
  children: ReactNode;
703
- displayConfig?: {
704
- showSubmitButton?: boolean;
705
- showResetButton?: boolean;
706
- showTitle?: boolean;
707
- };
708
703
  dateTimePickerLabels?: DateTimePickerLabels;
709
704
  idPickerLabels?: IdPickerLabels;
710
705
  enumPickerLabels?: EnumPickerLabels;
@@ -720,16 +715,13 @@ interface CustomJSONSchema7Definition extends JSONSchema7 {
720
715
  customQueryFn: any;
721
716
  children: ReactNode;
722
717
  }
723
- declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, children, displayConfig, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, formButtonLabels, timePickerLabels, insideDialog, }: FormRootProps<TData>) => react_jsx_runtime.JSX.Element;
718
+ declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, children, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, formButtonLabels, timePickerLabels, insideDialog, }: FormRootProps<TData>) => react_jsx_runtime.JSX.Element;
724
719
 
725
720
  interface DefaultFormProps<TData extends FieldValues> {
726
- formConfig: Omit<FormRootProps<TData>, "children">;
727
- showTitle?: boolean;
721
+ formConfig: Omit<FormRootProps<TData>, 'children'>;
728
722
  }
729
723
  declare const DefaultForm: <TData extends FieldValues>({ formConfig, }: DefaultFormProps<TData>) => react_jsx_runtime.JSX.Element;
730
724
 
731
- declare const FormTitle: () => react_jsx_runtime.JSX.Element;
732
-
733
725
  declare const FormBody: () => react_jsx_runtime.JSX.Element;
734
726
 
735
727
  type MediaLibraryBrowserPropsBase = {
@@ -751,7 +743,7 @@ type MediaLibraryBrowserPropsMultiple = MediaLibraryBrowserPropsBase & {
751
743
  onSelectedFileChange?: (files: FilePickerMediaFile[]) => void;
752
744
  };
753
745
  type MediaLibraryBrowserProps = MediaLibraryBrowserPropsSingle | MediaLibraryBrowserPropsMultiple;
754
- declare const MediaLibraryBrowser: ({ onFetchFiles, filterImageOnly, labels, enabled, multiple, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }: MediaLibraryBrowserProps) => react_jsx_runtime.JSX.Element | null;
746
+ declare const MediaLibraryBrowser: ({ onFetchFiles, filterImageOnly, labels, enabled, multiple, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }: MediaLibraryBrowserProps) => react_jsx_runtime.JSX.Element;
755
747
 
756
748
  interface UseFormProps<T> {
757
749
  preLoadedValues?: T | undefined;
@@ -1477,4 +1469,4 @@ declare module '@tanstack/react-table' {
1477
1469
  }
1478
1470
  }
1479
1471
 
1480
- export { CalendarDisplay, type CalendarDisplayProps, type CalendarEvent, type CalendarProps, CardHeader, type CardHeaderProps, type CustomJSONSchema7, type CustomJSONSchema7Definition, type CustomQueryFn, type CustomQueryFnParams, type CustomQueryFnResponse, DataDisplay, type DataDisplayProps, type DataResponse, DataTable, type DataTableDefaultState, type DataTableProps, DataTableServer, type DataTableServerProps, DatePickerContext, DatePickerInput, type DatePickerInputProps, type DatePickerLabels, type DatePickerProps, type DateTimePickerLabels, DefaultCardTitle, DefaultForm, type DefaultFormProps, DefaultTable, type DefaultTableProps, DefaultTableServer, type DefaultTableServerProps, DensityToggleButton, type DensityToggleButtonProps, type EditFilterButtonProps, EditSortingButton, type EditSortingButtonProps, type EditViewButtonProps, EmptyState, type EmptyStateProps, type EnumPickerLabels, ErrorAlert, type ErrorAlertProps, type FilePickerLabels, type FilePickerMediaFile, type FilePickerProps, FilterDialog, FormBody, type FormButtonLabels, FormRoot, type FormRootProps, FormTitle, type GetDateColorProps, type GetMultiDatesProps, type GetRangeDatesProps, type GetStyleProps, type GetVariantProps, GlobalFilter, type IdPickerLabels, type LoadInitialValuesParams, type LoadInitialValuesResult, MediaLibraryBrowser, type MediaLibraryBrowserProps, PageSizeControl, type PageSizeControlProps, Pagination, type QueryParams, type RangeCalendarProps, type RangeDatePickerLabels, type RangeDatePickerProps, RecordDisplay, type RecordDisplayProps, ReloadButton, type ReloadButtonProps, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, type Result, RowCountText, SelectAllRowsToggle, type SelectAllRowsToggleProps, Table, TableBody, type TableBodyProps, TableCardContainer, type TableCardContainerProps, TableCards, type TableCardsProps, TableComponent, TableControls, type TableControlsProps, TableDataDisplay, type TableDataDisplayProps, TableFilter, TableFilterTags, type TableFilterTagsProps, TableFooter, type TableFooterProps, TableHeader, type TableHeaderProps, type TableHeaderTexts, TableLoadingComponent, type TableLoadingComponentProps, type TableProps, type TableRendererProps, type TableRowSelectorProps, TableSelector, TableSorter, TableViewer, TextCell, type TextCellProps, type TimePickerLabels, TimeRangeZoom, type TimeRangeZoomLabels, type TimeRangeZoomProps, TimeViewportBlock, type TimeViewportBlockItem, type TimeViewportBlockProps, type TimeViewportBlockRenderArgs, TimeViewportBlocks, type TimeViewportBlocksProps, TimeViewportGrid, type TimeViewportGridProps, TimeViewportHeader, type TimeViewportHeaderProps, type TimeViewportHeaderTick, TimeViewportMarkerLine, type TimeViewportMarkerLineProps, TimeViewportRoot, type TimeViewportRootProps, type TimeViewportTrackRenderArgs, type UseDataTableProps, type UseDataTableReturn, type UseDataTableServerProps, type UseDataTableServerReturn, type UseFormProps, type UseTimeRangeZoomResult, type UseTimeViewportBlockGeometryResult, type UseTimeViewportDerivedResult, type UseTimeViewportTicksResult, type ValidationErrorType, ViewDialog, type ViewableTimeRange, defaultRenderDisplay, getMultiDates, getRangeDates, useDataTable, useDataTableContext, useDataTableServer, useForm, useTimeRangeZoom, useTimeViewport, useTimeViewportBlockGeometry, useTimeViewportHeader, useTimeViewportTicks };
1472
+ export { CalendarDisplay, type CalendarDisplayProps, type CalendarEvent, type CalendarProps, CardHeader, type CardHeaderProps, type CustomJSONSchema7, type CustomJSONSchema7Definition, type CustomQueryFn, type CustomQueryFnParams, type CustomQueryFnResponse, DataDisplay, type DataDisplayProps, type DataResponse, DataTable, type DataTableDefaultState, type DataTableProps, DataTableServer, type DataTableServerProps, DatePickerContext, DatePickerInput, type DatePickerInputProps, type DatePickerLabels, type DatePickerProps, type DateTimePickerLabels, DefaultCardTitle, DefaultForm, type DefaultFormProps, DefaultTable, type DefaultTableProps, DefaultTableServer, type DefaultTableServerProps, DensityToggleButton, type DensityToggleButtonProps, type EditFilterButtonProps, EditSortingButton, type EditSortingButtonProps, type EditViewButtonProps, EmptyState, type EmptyStateProps, type EnumPickerLabels, ErrorAlert, type ErrorAlertProps, type FilePickerLabels, type FilePickerMediaFile, type FilePickerProps, FilterDialog, FormBody, type FormButtonLabels, FormRoot, type FormRootProps, type GetDateColorProps, type GetMultiDatesProps, type GetRangeDatesProps, type GetStyleProps, type GetVariantProps, GlobalFilter, type IdPickerLabels, type LoadInitialValuesParams, type LoadInitialValuesResult, MediaLibraryBrowser, type MediaLibraryBrowserProps, PageSizeControl, type PageSizeControlProps, Pagination, type QueryParams, type RangeCalendarProps, type RangeDatePickerLabels, type RangeDatePickerProps, RecordDisplay, type RecordDisplayProps, ReloadButton, type ReloadButtonProps, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, type Result, RowCountText, SelectAllRowsToggle, type SelectAllRowsToggleProps, Table, TableBody, type TableBodyProps, TableCardContainer, type TableCardContainerProps, TableCards, type TableCardsProps, TableComponent, TableControls, type TableControlsProps, TableDataDisplay, type TableDataDisplayProps, TableFilter, TableFilterTags, type TableFilterTagsProps, TableFooter, type TableFooterProps, TableHeader, type TableHeaderProps, type TableHeaderTexts, TableLoadingComponent, type TableLoadingComponentProps, type TableProps, type TableRendererProps, type TableRowSelectorProps, TableSelector, TableSorter, TableViewer, TextCell, type TextCellProps, type TimePickerLabels, TimeRangeZoom, type TimeRangeZoomLabels, type TimeRangeZoomProps, TimeViewportBlock, type TimeViewportBlockItem, type TimeViewportBlockProps, type TimeViewportBlockRenderArgs, TimeViewportBlocks, type TimeViewportBlocksProps, TimeViewportGrid, type TimeViewportGridProps, TimeViewportHeader, type TimeViewportHeaderProps, type TimeViewportHeaderTick, TimeViewportMarkerLine, type TimeViewportMarkerLineProps, TimeViewportRoot, type TimeViewportRootProps, type TimeViewportTrackRenderArgs, type UseDataTableProps, type UseDataTableReturn, type UseDataTableServerProps, type UseDataTableServerReturn, type UseFormProps, type UseTimeRangeZoomResult, type UseTimeViewportBlockGeometryResult, type UseTimeViewportDerivedResult, type UseTimeViewportTicksResult, type ValidationErrorType, ViewDialog, type ViewableTimeRange, defaultRenderDisplay, getMultiDates, getRangeDates, useDataTable, useDataTableContext, useDataTableServer, useForm, useTimeRangeZoom, useTimeViewport, useTimeViewportBlockGeometry, useTimeViewportHeader, useTimeViewportTicks };
package/dist/index.js CHANGED
@@ -4190,11 +4190,6 @@ const SchemaFormContext = React.createContext({
4190
4190
  schema: {},
4191
4191
  onSubmit: async () => { },
4192
4192
  timezone: 'Asia/Hong_Kong',
4193
- displayConfig: {
4194
- showSubmitButton: false,
4195
- showResetButton: false,
4196
- showTitle: true,
4197
- },
4198
4193
  });
4199
4194
 
4200
4195
  const useSchemaContext = () => {
@@ -5386,108 +5381,81 @@ function formatBytes(bytes) {
5386
5381
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
5387
5382
  }
5388
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
+ }
5389
5388
  const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, enabled = true, multiple = false, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }) => {
5390
- const [searchTerm, setSearchTerm] = React.useState('');
5391
- const [internalSelectedFile, setInternalSelectedFile] = React.useState(multiple ? [] : undefined);
5392
- const [failedImageIds, setFailedImageIds] = React.useState(new Set());
5393
- // Use controlled or internal state for selectedFile
5394
- const selectedFile = controlledSelectedFile ?? internalSelectedFile;
5395
- const setSelectedFile = onSelectedFileChange ?? setInternalSelectedFile;
5396
- const { data: filesData, isLoading, isError, } = reactQuery.useQuery({
5397
- queryKey: ['file-picker-library', searchTerm],
5389
+ const [search, setSearch] = React.useState('');
5390
+ const query = reactQuery.useQuery({
5391
+ queryKey: ['media-library', search, filterImageOnly],
5398
5392
  queryFn: async () => {
5399
5393
  if (!onFetchFiles)
5400
- return { data: [] };
5401
- const files = await onFetchFiles(searchTerm.trim() || '');
5402
- return { data: files };
5394
+ return [];
5395
+ const list = await onFetchFiles(search);
5396
+ return filterImageOnly ? filterImageFiles(list) : list;
5403
5397
  },
5404
5398
  enabled: enabled && !!onFetchFiles,
5405
5399
  });
5406
- const files = (filesData?.data || []);
5407
- const filteredFiles = filterImageOnly
5408
- ? files.filter((file) => /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name))
5409
- : files;
5410
- const handleFileClick = (file) => {
5411
- if (multiple) {
5412
- const currentSelection = Array.isArray(selectedFile) ? selectedFile : [];
5413
- const isAlreadySelected = currentSelection.some((f) => f.id === file.id);
5414
- const newSelection = isAlreadySelected
5415
- ? currentSelection.filter((f) => f.id !== file.id)
5416
- : [...currentSelection, file];
5417
- setSelectedFile(newSelection);
5418
- if (onFileSelect) {
5419
- onFileSelect(newSelection);
5420
- }
5421
- }
5422
- else {
5423
- const newFile = selectedFile === file ? undefined : file;
5424
- setSelectedFile(newFile);
5425
- if (onFileSelect && newFile) {
5426
- onFileSelect(newFile);
5427
- }
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);
5428
5416
  }
5429
5417
  };
5430
- const handleImageError = (fileId) => {
5431
- setFailedImageIds((prev) => new Set(prev).add(fileId));
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);
5432
5427
  };
5433
- if (!onFetchFiles)
5434
- return null;
5435
- return (jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 4, children: [jsxRuntime.jsxs(react.Box, { position: "relative", children: [jsxRuntime.jsx(react.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: {
5436
- borderColor: 'colorPalette.500',
5437
- _dark: {
5438
- borderColor: 'colorPalette.400',
5439
- },
5440
- boxShadow: {
5441
- base: '0 0 0 1px var(--chakra-colors-blue-500)',
5442
- _dark: '0 0 0 1px var(--chakra-colors-blue-400)',
5443
- },
5444
- }, pl: 10 }), jsxRuntime.jsx(react.Icon, { as: lu.LuSearch, position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", color: "fg.muted", boxSize: 4 })] }), isLoading && (jsxRuntime.jsxs(react.Box, { textAlign: "center", py: 8, children: [jsxRuntime.jsx(react.Spinner, { size: "lg", colorPalette: "blue" }), jsxRuntime.jsx(react.Text, { mt: 4, color: "fg.muted", children: labels?.loading ?? 'Loading files...' })] })), isError && (jsxRuntime.jsx(react.Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
5445
- base: 'colorPalette.200',
5446
- _dark: 'colorPalette.800',
5447
- }, colorPalette: "red", borderRadius: "md", p: 4, children: jsxRuntime.jsx(react.Text, { color: {
5448
- base: 'colorPalette.600',
5449
- _dark: 'colorPalette.300',
5450
- }, children: labels?.loadingFailed ?? 'Failed to load files' }) })), !isLoading && !isError && (jsxRuntime.jsx(react.Box, { maxHeight: "400px", overflowY: "auto", children: filteredFiles.length === 0 ? (jsxRuntime.jsx(react.Box, { textAlign: "center", py: 8, children: jsxRuntime.jsx(react.Text, { color: "fg.muted", children: labels?.noFilesFound ?? 'No files found' }) })) : (jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
5451
- const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
5452
- const isSelected = multiple
5453
- ? Array.isArray(selectedFile) &&
5454
- selectedFile.some((f) => f.id === file.id)
5455
- : selectedFile?.id ===
5456
- file.id;
5457
- const imageFailed = failedImageIds.has(file.id);
5458
- return (jsxRuntime.jsx(react.Box, { p: 3, border: "2px solid", borderColor: isSelected
5459
- ? {
5460
- base: 'colorPalette.500',
5461
- _dark: 'colorPalette.400',
5462
- }
5463
- : 'border.default', borderRadius: "md", bg: isSelected
5464
- ? {
5465
- base: 'colorPalette.50',
5466
- _dark: 'colorPalette.900/20',
5467
- }
5468
- : 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => handleFileClick(file), _hover: {
5469
- borderColor: isSelected
5470
- ? {
5471
- base: 'colorPalette.600',
5472
- _dark: 'colorPalette.400',
5473
- }
5474
- : {
5475
- base: 'colorPalette.300',
5476
- _dark: 'colorPalette.400',
5477
- },
5478
- bg: isSelected
5479
- ? {
5480
- base: 'colorPalette.100',
5481
- _dark: 'colorPalette.800/30',
5482
- }
5483
- : 'bg.muted',
5484
- }, 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'
5485
- ? formatBytes(file.size)
5486
- : 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: {
5487
- base: 'colorPalette.500',
5488
- _dark: 'colorPalette.400',
5489
- }, 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));
5490
- }) })) }))] }));
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
+ }) }))] }));
5491
5459
  };
5492
5460
 
5493
5461
  function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, }) {
@@ -5655,7 +5623,8 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5655
5623
  const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
5656
5624
  const isRequired = required?.some((columnId) => columnId === column);
5657
5625
  const isSingleSelect = type === 'string';
5658
- const currentValue = watch(column) ?? (isSingleSelect ? '' : []);
5626
+ const colLabel = formI18n.colLabel;
5627
+ const currentValue = watch(colLabel) ?? (isSingleSelect ? '' : []);
5659
5628
  // Handle string IDs only
5660
5629
  const currentFileIds = isSingleSelect
5661
5630
  ? currentValue
@@ -5664,7 +5633,6 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5664
5633
  : Array.isArray(currentValue)
5665
5634
  ? currentValue
5666
5635
  : [];
5667
- const colLabel = formI18n.colLabel;
5668
5636
  const fieldError = getNestedError(errors, colLabel);
5669
5637
  const [dialogOpen, setDialogOpen] = React.useState(false);
5670
5638
  const [failedImageIds, setFailedImageIds] = React.useState(new Set());
@@ -5752,7 +5720,7 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5752
5720
  : /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(fileId);
5753
5721
  const imageFailed = failedImageIds.has(fileId);
5754
5722
  const displayName = file?.name ?? fileId;
5755
- return (jsxRuntime.jsx(react.Card.Root, { variant: 'subtle', colorPalette: "blue", 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: {
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: {
5756
5724
  borderColor: 'colorPalette.300',
5757
5725
  bg: 'bg.muted',
5758
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'
@@ -7848,66 +7816,24 @@ const ColumnRenderer = ({ column, properties, prefix, parentRequired, }) => {
7848
7816
  return jsxRuntime.jsx(SchemaRenderer, { schema: schemaWithRequired, prefix, column });
7849
7817
  };
7850
7818
 
7851
- const SubmitButton = () => {
7852
- const { onSubmit, formButtonLabels } = useSchemaContext();
7853
- const methods = reactHookForm.useFormContext();
7854
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7855
- const onValid = async (data) => {
7856
- // Validation is handled by react-hook-form
7857
- // This function will only be called if validation passes
7858
- if (onSubmit) {
7859
- await onSubmit(data);
7860
- }
7861
- };
7862
- return (jsxRuntime.jsx(react.Button, { onClick: () => {
7863
- methods.handleSubmit(onValid)();
7864
- }, formNoValidate: true, children: formButtonLabels?.submit ?? 'Submit' }));
7865
- };
7866
-
7867
7819
  const FormBody = () => {
7868
- const { schema, displayConfig, formButtonLabels } = useSchemaContext();
7869
- const { showSubmitButton, showResetButton } = displayConfig;
7870
- const methods = reactHookForm.useFormContext();
7871
- console.log('errors', methods.formState.errors);
7820
+ const { schema } = useSchemaContext();
7872
7821
  const { properties } = schema;
7873
7822
  const ordered = Object.keys(properties);
7874
- return (jsxRuntime.jsxs(react.Flex, { flexFlow: 'column', gap: "2", children: [jsxRuntime.jsx(react.Grid, { gap: "4", gridTemplateColumns: 'repeat(12, 1fr)', autoFlow: 'row', children: ordered.map((column) => {
7875
- if (!properties) {
7876
- console.error('properties is undefined when using FormBody', schema);
7877
- return null;
7878
- }
7879
- return (jsxRuntime.jsx(ColumnRenderer, { properties: properties, prefix: ``, parentRequired: schema.required, column }, `form-input-${column}`));
7880
- }) }), (showResetButton || showSubmitButton) && (jsxRuntime.jsxs(react.Flex, { justifyContent: 'end', gap: "2", children: [showResetButton && (jsxRuntime.jsx(react.Button, { onClick: () => {
7881
- methods.reset();
7882
- }, variant: 'subtle', children: formButtonLabels?.reset ?? 'Reset' })), showSubmitButton && jsxRuntime.jsx(SubmitButton, {})] }))] }));
7883
- };
7884
-
7885
- const FormTitle = () => {
7886
- const { schema } = useSchemaContext();
7887
- // Debug log when form title is missing
7888
- if (!schema.title) {
7889
- console.debug('[Form Title] Missing title in root schema. Add title property to schema.', {
7890
- schema: {
7891
- type: schema.type,
7892
- properties: schema.properties
7893
- ? Object.keys(schema.properties)
7894
- : undefined,
7895
- },
7896
- });
7897
- }
7898
- return jsxRuntime.jsx(react.Heading, { children: schema.title ?? 'Form' });
7823
+ return (jsxRuntime.jsx(react.Flex, { flexFlow: 'column', gap: "2", children: jsxRuntime.jsx(react.Grid, { gap: "4", gridTemplateColumns: 'repeat(12, 1fr)', autoFlow: 'row', children: ordered.map((column) => {
7824
+ if (!properties) {
7825
+ console.error('properties is undefined when using FormBody', schema);
7826
+ return null;
7827
+ }
7828
+ return (jsxRuntime.jsx(ColumnRenderer, { properties: properties, prefix: ``, parentRequired: schema.required, column }, `form-input-${column}`));
7829
+ }) }) }));
7899
7830
  };
7900
7831
 
7901
- const FormRoot = ({ schema, idMap, setIdMap, form, children, displayConfig = {
7902
- showSubmitButton: false,
7903
- showResetButton: false,
7904
- showTitle: true,
7905
- }, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, formButtonLabels, timePickerLabels, insideDialog = false, }) => {
7832
+ const FormRoot = ({ schema, idMap, setIdMap, form, children, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, formButtonLabels, timePickerLabels, insideDialog = false, }) => {
7906
7833
  return (jsxRuntime.jsx(SchemaFormContext.Provider, { value: {
7907
7834
  schema,
7908
7835
  idMap,
7909
7836
  setIdMap,
7910
- displayConfig,
7911
7837
  dateTimePickerLabels,
7912
7838
  idPickerLabels,
7913
7839
  enumPickerLabels,
@@ -7919,8 +7845,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, children, displayConfig = {
7919
7845
  };
7920
7846
 
7921
7847
  const DefaultForm = ({ formConfig, }) => {
7922
- const { showTitle } = formConfig.displayConfig ?? {};
7923
- return (jsxRuntime.jsx(FormRoot, { ...formConfig, children: jsxRuntime.jsxs(react.Grid, { gap: "2", children: [showTitle && jsxRuntime.jsx(FormTitle, {}), jsxRuntime.jsx(FormBody, {})] }) }));
7848
+ return (jsxRuntime.jsx(FormRoot, { ...formConfig, children: jsxRuntime.jsx(react.Grid, { gap: "2", children: jsxRuntime.jsx(FormBody, {}) }) }));
7924
7849
  };
7925
7850
 
7926
7851
  function useForm({ preLoadedValues, schema, }) {
@@ -9500,7 +9425,6 @@ exports.ErrorAlert = ErrorAlert;
9500
9425
  exports.FilterDialog = FilterDialog;
9501
9426
  exports.FormBody = FormBody;
9502
9427
  exports.FormRoot = FormRoot;
9503
- exports.FormTitle = FormTitle;
9504
9428
  exports.GlobalFilter = GlobalFilter;
9505
9429
  exports.MediaLibraryBrowser = MediaLibraryBrowser;
9506
9430
  exports.PageSizeControl = PageSizeControl;
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';
@@ -4170,11 +4170,6 @@ const SchemaFormContext = createContext({
4170
4170
  schema: {},
4171
4171
  onSubmit: async () => { },
4172
4172
  timezone: 'Asia/Hong_Kong',
4173
- displayConfig: {
4174
- showSubmitButton: false,
4175
- showResetButton: false,
4176
- showTitle: true,
4177
- },
4178
4173
  });
4179
4174
 
4180
4175
  const useSchemaContext = () => {
@@ -5366,108 +5361,81 @@ function formatBytes(bytes) {
5366
5361
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
5367
5362
  }
5368
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
+ }
5369
5368
  const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, enabled = true, multiple = false, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }) => {
5370
- const [searchTerm, setSearchTerm] = useState('');
5371
- const [internalSelectedFile, setInternalSelectedFile] = useState(multiple ? [] : undefined);
5372
- const [failedImageIds, setFailedImageIds] = useState(new Set());
5373
- // Use controlled or internal state for selectedFile
5374
- const selectedFile = controlledSelectedFile ?? internalSelectedFile;
5375
- const setSelectedFile = onSelectedFileChange ?? setInternalSelectedFile;
5376
- const { data: filesData, isLoading, isError, } = useQuery({
5377
- queryKey: ['file-picker-library', searchTerm],
5369
+ const [search, setSearch] = useState('');
5370
+ const query = useQuery({
5371
+ queryKey: ['media-library', search, filterImageOnly],
5378
5372
  queryFn: async () => {
5379
5373
  if (!onFetchFiles)
5380
- return { data: [] };
5381
- const files = await onFetchFiles(searchTerm.trim() || '');
5382
- return { data: files };
5374
+ return [];
5375
+ const list = await onFetchFiles(search);
5376
+ return filterImageOnly ? filterImageFiles(list) : list;
5383
5377
  },
5384
5378
  enabled: enabled && !!onFetchFiles,
5385
5379
  });
5386
- const files = (filesData?.data || []);
5387
- const filteredFiles = filterImageOnly
5388
- ? files.filter((file) => /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name))
5389
- : files;
5390
- const handleFileClick = (file) => {
5391
- if (multiple) {
5392
- const currentSelection = Array.isArray(selectedFile) ? selectedFile : [];
5393
- const isAlreadySelected = currentSelection.some((f) => f.id === file.id);
5394
- const newSelection = isAlreadySelected
5395
- ? currentSelection.filter((f) => f.id !== file.id)
5396
- : [...currentSelection, file];
5397
- setSelectedFile(newSelection);
5398
- if (onFileSelect) {
5399
- onFileSelect(newSelection);
5400
- }
5401
- }
5402
- else {
5403
- const newFile = selectedFile === file ? undefined : file;
5404
- setSelectedFile(newFile);
5405
- if (onFileSelect && newFile) {
5406
- onFileSelect(newFile);
5407
- }
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);
5408
5396
  }
5409
5397
  };
5410
- const handleImageError = (fileId) => {
5411
- setFailedImageIds((prev) => new Set(prev).add(fileId));
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);
5412
5407
  };
5413
- if (!onFetchFiles)
5414
- return null;
5415
- 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: {
5416
- borderColor: 'colorPalette.500',
5417
- _dark: {
5418
- borderColor: 'colorPalette.400',
5419
- },
5420
- boxShadow: {
5421
- base: '0 0 0 1px var(--chakra-colors-blue-500)',
5422
- _dark: '0 0 0 1px var(--chakra-colors-blue-400)',
5423
- },
5424
- }, 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: {
5425
- base: 'colorPalette.200',
5426
- _dark: 'colorPalette.800',
5427
- }, colorPalette: "red", borderRadius: "md", p: 4, children: jsx(Text, { color: {
5428
- base: 'colorPalette.600',
5429
- _dark: 'colorPalette.300',
5430
- }, 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) => {
5431
- const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
5432
- const isSelected = multiple
5433
- ? Array.isArray(selectedFile) &&
5434
- selectedFile.some((f) => f.id === file.id)
5435
- : selectedFile?.id ===
5436
- file.id;
5437
- const imageFailed = failedImageIds.has(file.id);
5438
- return (jsx(Box, { p: 3, border: "2px solid", borderColor: isSelected
5439
- ? {
5440
- base: 'colorPalette.500',
5441
- _dark: 'colorPalette.400',
5442
- }
5443
- : 'border.default', borderRadius: "md", bg: isSelected
5444
- ? {
5445
- base: 'colorPalette.50',
5446
- _dark: 'colorPalette.900/20',
5447
- }
5448
- : 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => handleFileClick(file), _hover: {
5449
- borderColor: isSelected
5450
- ? {
5451
- base: 'colorPalette.600',
5452
- _dark: 'colorPalette.400',
5453
- }
5454
- : {
5455
- base: 'colorPalette.300',
5456
- _dark: 'colorPalette.400',
5457
- },
5458
- bg: isSelected
5459
- ? {
5460
- base: 'colorPalette.100',
5461
- _dark: 'colorPalette.800/30',
5462
- }
5463
- : 'bg.muted',
5464
- }, 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'
5465
- ? formatBytes(file.size)
5466
- : 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: {
5467
- base: 'colorPalette.500',
5468
- _dark: 'colorPalette.400',
5469
- }, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsx(Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
5470
- }) })) }))] }));
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
+ }) }))] }));
5471
5439
  };
5472
5440
 
5473
5441
  function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, }) {
@@ -5635,7 +5603,8 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5635
5603
  const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
5636
5604
  const isRequired = required?.some((columnId) => columnId === column);
5637
5605
  const isSingleSelect = type === 'string';
5638
- const currentValue = watch(column) ?? (isSingleSelect ? '' : []);
5606
+ const colLabel = formI18n.colLabel;
5607
+ const currentValue = watch(colLabel) ?? (isSingleSelect ? '' : []);
5639
5608
  // Handle string IDs only
5640
5609
  const currentFileIds = isSingleSelect
5641
5610
  ? currentValue
@@ -5644,7 +5613,6 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5644
5613
  : Array.isArray(currentValue)
5645
5614
  ? currentValue
5646
5615
  : [];
5647
- const colLabel = formI18n.colLabel;
5648
5616
  const fieldError = getNestedError(errors, colLabel);
5649
5617
  const [dialogOpen, setDialogOpen] = useState(false);
5650
5618
  const [failedImageIds, setFailedImageIds] = useState(new Set());
@@ -5732,7 +5700,7 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5732
5700
  : /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(fileId);
5733
5701
  const imageFailed = failedImageIds.has(fileId);
5734
5702
  const displayName = file?.name ?? fileId;
5735
- 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: {
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: {
5736
5704
  borderColor: 'colorPalette.300',
5737
5705
  bg: 'bg.muted',
5738
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'
@@ -7828,66 +7796,24 @@ const ColumnRenderer = ({ column, properties, prefix, parentRequired, }) => {
7828
7796
  return jsx(SchemaRenderer, { schema: schemaWithRequired, prefix, column });
7829
7797
  };
7830
7798
 
7831
- const SubmitButton = () => {
7832
- const { onSubmit, formButtonLabels } = useSchemaContext();
7833
- const methods = useFormContext();
7834
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7835
- const onValid = async (data) => {
7836
- // Validation is handled by react-hook-form
7837
- // This function will only be called if validation passes
7838
- if (onSubmit) {
7839
- await onSubmit(data);
7840
- }
7841
- };
7842
- return (jsx(Button$1, { onClick: () => {
7843
- methods.handleSubmit(onValid)();
7844
- }, formNoValidate: true, children: formButtonLabels?.submit ?? 'Submit' }));
7845
- };
7846
-
7847
7799
  const FormBody = () => {
7848
- const { schema, displayConfig, formButtonLabels } = useSchemaContext();
7849
- const { showSubmitButton, showResetButton } = displayConfig;
7850
- const methods = useFormContext();
7851
- console.log('errors', methods.formState.errors);
7800
+ const { schema } = useSchemaContext();
7852
7801
  const { properties } = schema;
7853
7802
  const ordered = Object.keys(properties);
7854
- return (jsxs(Flex, { flexFlow: 'column', gap: "2", children: [jsx(Grid, { gap: "4", gridTemplateColumns: 'repeat(12, 1fr)', autoFlow: 'row', children: ordered.map((column) => {
7855
- if (!properties) {
7856
- console.error('properties is undefined when using FormBody', schema);
7857
- return null;
7858
- }
7859
- return (jsx(ColumnRenderer, { properties: properties, prefix: ``, parentRequired: schema.required, column }, `form-input-${column}`));
7860
- }) }), (showResetButton || showSubmitButton) && (jsxs(Flex, { justifyContent: 'end', gap: "2", children: [showResetButton && (jsx(Button$1, { onClick: () => {
7861
- methods.reset();
7862
- }, variant: 'subtle', children: formButtonLabels?.reset ?? 'Reset' })), showSubmitButton && jsx(SubmitButton, {})] }))] }));
7863
- };
7864
-
7865
- const FormTitle = () => {
7866
- const { schema } = useSchemaContext();
7867
- // Debug log when form title is missing
7868
- if (!schema.title) {
7869
- console.debug('[Form Title] Missing title in root schema. Add title property to schema.', {
7870
- schema: {
7871
- type: schema.type,
7872
- properties: schema.properties
7873
- ? Object.keys(schema.properties)
7874
- : undefined,
7875
- },
7876
- });
7877
- }
7878
- return jsx(Heading, { children: schema.title ?? 'Form' });
7803
+ return (jsx(Flex, { flexFlow: 'column', gap: "2", children: jsx(Grid, { gap: "4", gridTemplateColumns: 'repeat(12, 1fr)', autoFlow: 'row', children: ordered.map((column) => {
7804
+ if (!properties) {
7805
+ console.error('properties is undefined when using FormBody', schema);
7806
+ return null;
7807
+ }
7808
+ return (jsx(ColumnRenderer, { properties: properties, prefix: ``, parentRequired: schema.required, column }, `form-input-${column}`));
7809
+ }) }) }));
7879
7810
  };
7880
7811
 
7881
- const FormRoot = ({ schema, idMap, setIdMap, form, children, displayConfig = {
7882
- showSubmitButton: false,
7883
- showResetButton: false,
7884
- showTitle: true,
7885
- }, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, formButtonLabels, timePickerLabels, insideDialog = false, }) => {
7812
+ const FormRoot = ({ schema, idMap, setIdMap, form, children, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, formButtonLabels, timePickerLabels, insideDialog = false, }) => {
7886
7813
  return (jsx(SchemaFormContext.Provider, { value: {
7887
7814
  schema,
7888
7815
  idMap,
7889
7816
  setIdMap,
7890
- displayConfig,
7891
7817
  dateTimePickerLabels,
7892
7818
  idPickerLabels,
7893
7819
  enumPickerLabels,
@@ -7899,8 +7825,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, children, displayConfig = {
7899
7825
  };
7900
7826
 
7901
7827
  const DefaultForm = ({ formConfig, }) => {
7902
- const { showTitle } = formConfig.displayConfig ?? {};
7903
- return (jsx(FormRoot, { ...formConfig, children: jsxs(Grid, { gap: "2", children: [showTitle && jsx(FormTitle, {}), jsx(FormBody, {})] }) }));
7828
+ return (jsx(FormRoot, { ...formConfig, children: jsx(Grid, { gap: "2", children: jsx(FormBody, {}) }) }));
7904
7829
  };
7905
7830
 
7906
7831
  function useForm({ preLoadedValues, schema, }) {
@@ -9462,4 +9387,4 @@ function DataTableServer({ columns, enableRowSelection = true, enableMultiRowSel
9462
9387
  }, children: jsx(DataTableServerContext.Provider, { value: { url: url ?? '', query }, children: children }) }));
9463
9388
  }
9464
9389
 
9465
- export { CalendarDisplay, CardHeader, DataDisplay, DataTable, DataTableServer, DatePickerContext, DatePickerInput, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, MediaLibraryBrowser, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, SelectAllRowsToggle, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, TimeRangeZoom, TimeViewportBlock, TimeViewportBlocks, TimeViewportGrid, TimeViewportHeader, TimeViewportMarkerLine, TimeViewportRoot, ViewDialog, defaultRenderDisplay, getMultiDates, getRangeDates, useDataTable, useDataTableContext, useDataTableServer, useForm, useTimeRangeZoom, useTimeViewport, useTimeViewportBlockGeometry, useTimeViewportHeader, useTimeViewportTicks };
9390
+ export { CalendarDisplay, CardHeader, DataDisplay, DataTable, DataTableServer, DatePickerContext, DatePickerInput, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, GlobalFilter, MediaLibraryBrowser, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, SelectAllRowsToggle, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, TimeRangeZoom, TimeViewportBlock, TimeViewportBlocks, TimeViewportGrid, TimeViewportHeader, TimeViewportMarkerLine, TimeViewportRoot, ViewDialog, defaultRenderDisplay, getMultiDates, getRangeDates, useDataTable, useDataTableContext, useDataTableServer, useForm, useTimeRangeZoom, useTimeViewport, useTimeViewportBlockGeometry, useTimeViewportHeader, useTimeViewportTicks };
@@ -7,11 +7,6 @@ export interface SchemaFormContext<TData extends FieldValues> {
7
7
  idMap: Record<string, unknown>;
8
8
  setIdMap: Dispatch<SetStateAction<Record<string, unknown>>>;
9
9
  timezone?: string;
10
- displayConfig: {
11
- showSubmitButton?: boolean;
12
- showResetButton?: boolean;
13
- showTitle?: boolean;
14
- };
15
10
  dateTimePickerLabels?: DateTimePickerLabels;
16
11
  idPickerLabels?: IdPickerLabels;
17
12
  enumPickerLabels?: EnumPickerLabels;
@@ -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 | null;
21
+ export declare const MediaLibraryBrowser: ({ onFetchFiles, filterImageOnly, labels, enabled, multiple, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }: MediaLibraryBrowserProps) => import("react/jsx-runtime").JSX.Element;
22
22
  export {};
@@ -1,7 +1,6 @@
1
- import { FormRootProps } from "./FormRoot";
2
- import { FieldValues } from "react-hook-form";
1
+ import { FormRootProps } from './FormRoot';
2
+ import { FieldValues } from 'react-hook-form';
3
3
  export interface DefaultFormProps<TData extends FieldValues> {
4
- formConfig: Omit<FormRootProps<TData>, "children">;
5
- showTitle?: boolean;
4
+ formConfig: Omit<FormRootProps<TData>, 'children'>;
6
5
  }
7
6
  export declare const DefaultForm: <TData extends FieldValues>({ formConfig, }: DefaultFormProps<TData>) => import("react/jsx-runtime").JSX.Element;
@@ -33,11 +33,6 @@ export interface FormRootProps<TData extends FieldValues> {
33
33
  setIdMap: Dispatch<SetStateAction<Record<string, unknown>>>;
34
34
  form: UseFormReturn<TData, any, TData>;
35
35
  children: ReactNode;
36
- displayConfig?: {
37
- showSubmitButton?: boolean;
38
- showResetButton?: boolean;
39
- showTitle?: boolean;
40
- };
41
36
  dateTimePickerLabels?: DateTimePickerLabels;
42
37
  idPickerLabels?: IdPickerLabels;
43
38
  enumPickerLabels?: EnumPickerLabels;
@@ -53,4 +48,4 @@ export interface CustomJSONSchema7Definition extends JSONSchema7 {
53
48
  customQueryFn: any;
54
49
  children: ReactNode;
55
50
  }
56
- export declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, children, displayConfig, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, formButtonLabels, timePickerLabels, insideDialog, }: FormRootProps<TData>) => import("react/jsx-runtime").JSX.Element;
51
+ export declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, children, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, formButtonLabels, timePickerLabels, insideDialog, }: FormRootProps<TData>) => import("react/jsx-runtime").JSX.Element;
@@ -125,7 +125,6 @@ export * from './components/DataTable/useDataTableServer';
125
125
  export * from './components/DataTable/context/useDataTableContext';
126
126
  export * from './components/Form/components/core/DefaultForm';
127
127
  export * from './components/Form/components/core/FormRoot';
128
- export * from './components/Form/components/core/FormTitle';
129
128
  export * from './components/Form/components/core/FormBody';
130
129
  export * from './components/Form/components/types/CustomJSONSchema7';
131
130
  export * from './components/Form/components/MediaLibraryBrowser';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsol-oss/react-datatable5",
3
- "version": "13.0.1-beta.37",
3
+ "version": "13.0.1-beta.39",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -1 +0,0 @@
1
- export declare const FormTitle: () => import("react/jsx-runtime").JSX.Element;
@@ -1 +0,0 @@
1
- export declare const SubmitButton: () => import("react/jsx-runtime").JSX.Element;