@bsol-oss/react-datatable5 12.0.0-beta.75 → 12.0.0-beta.77

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
@@ -823,6 +823,17 @@ interface EnumPickerLabels {
823
823
  emptySearchResult?: string;
824
824
  initialResults?: string;
825
825
  }
826
+ interface FilePickerLabels {
827
+ fileDropzone?: string;
828
+ browseLibrary?: string;
829
+ dialogTitle?: string;
830
+ searchPlaceholder?: string;
831
+ loading?: string;
832
+ loadingFailed?: string;
833
+ noFilesFound?: string;
834
+ cancel?: string;
835
+ select?: string;
836
+ }
826
837
  interface CustomJSONSchema7 extends JSONSchema7 {
827
838
  gridColumn?: string;
828
839
  gridRow?: string;
@@ -849,12 +860,26 @@ interface CustomJSONSchema7 extends JSONSchema7 {
849
860
  formatOptions?: Intl.NumberFormatOptions;
850
861
  numberStorageType?: 'string' | 'number';
851
862
  errorMessages?: Partial<Record<ValidationErrorType | string, string>>;
863
+ filePicker?: FilePickerProps;
852
864
  }
853
865
  interface TagPickerProps {
854
866
  column: string;
855
867
  schema: CustomJSONSchema7;
856
868
  prefix: string;
857
869
  }
870
+ interface FilePickerMediaFile {
871
+ id: string;
872
+ name: string;
873
+ url?: string;
874
+ size?: string | number;
875
+ comment?: string;
876
+ type?: string;
877
+ }
878
+ interface FilePickerProps {
879
+ onFetchFiles?: (search: string) => Promise<FilePickerMediaFile[]>;
880
+ enableMediaLibrary?: boolean;
881
+ filterImageOnly?: boolean;
882
+ }
858
883
 
859
884
  interface FormRootProps<TData extends FieldValues> {
860
885
  schema: CustomJSONSchema7;
@@ -883,6 +908,7 @@ interface FormRootProps<TData extends FieldValues> {
883
908
  dateTimePickerLabels?: DateTimePickerLabels;
884
909
  idPickerLabels?: IdPickerLabels;
885
910
  enumPickerLabels?: EnumPickerLabels;
911
+ filePickerLabels?: FilePickerLabels;
886
912
  }
887
913
  interface CustomJSONSchema7Definition extends JSONSchema7 {
888
914
  variant: string;
@@ -899,7 +925,7 @@ declare const idPickerSanityCheck: (column: string, foreign_key?: {
899
925
  column?: string | undefined;
900
926
  display_column?: string | undefined;
901
927
  } | undefined) => void;
902
- declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, }: FormRootProps<TData>) => react_jsx_runtime.JSX.Element;
928
+ declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }: FormRootProps<TData>) => react_jsx_runtime.JSX.Element;
903
929
 
904
930
  interface DefaultFormProps<TData extends FieldValues> {
905
931
  formConfig: Omit<FormRootProps<TData>, "children">;
@@ -1071,4 +1097,4 @@ declare module "@tanstack/react-table" {
1071
1097
  }
1072
1098
  }
1073
1099
 
1074
- export { type CalendarProps, CardHeader, type CardHeaderProps, type CustomJSONSchema7, type CustomJSONSchema7Definition, DataDisplay, type DataDisplayProps, type DataResponse, DataTable, type DataTableDefaultState, type DataTableProps, DataTableServer, type DataTableServerProps, type DatePickerLabels, type DatePickerProps, type DateTimePickerLabels, DefaultCardTitle, DefaultForm, type DefaultFormProps, DefaultTable, type DefaultTableProps, DensityToggleButton, type DensityToggleButtonProps, type EditFilterButtonProps, EditSortingButton, type EditSortingButtonProps, type EditViewButtonProps, EmptyState, type EmptyStateProps, type EnumPickerLabels, ErrorAlert, type ErrorAlertProps, type ErrorMessageConfig, type ErrorMessageResult, type FieldErrorConfig, FilterDialog, FormBody, FormRoot, type FormRootProps, FormTitle, type GetColumnsConfigs, type GetDateColorProps, type GetMultiDatesProps, type GetRangeDatesProps, type GetStyleProps, type GetVariantProps, GlobalFilter, type IdPickerLabels, PageSizeControl, type PageSizeControlProps, Pagination, type QueryParams, type RangeCalendarProps, type RangeDatePickerProps, RecordDisplay, type RecordDisplayProps, ReloadButton, type ReloadButtonProps, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, type Result, RowCountText, Table, TableBody, type TableBodyProps, TableCardContainer, type TableCardContainerProps, TableCards, type TableCardsProps, TableComponent, TableControls, type TableControlsProps, TableDataDisplay, type TableDataDisplayProps, TableFilter, TableFilterTags, TableFooter, type TableFooterProps, TableHeader, type TableHeaderProps, type TableHeaderTexts, TableLoadingComponent, type TableLoadingComponentProps, type TableProps, type TableRendererProps, type TableRowSelectorProps, TableSelector, TableSorter, TableViewer, type TagPickerProps, TextCell, type TextCellProps, type UseDataTableProps, type UseDataTableReturn, type UseDataTableServerProps, type UseDataTableServerReturn, type UseFormProps, type ValidationErrorType, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
1100
+ export { type CalendarProps, CardHeader, type CardHeaderProps, type CustomJSONSchema7, type CustomJSONSchema7Definition, DataDisplay, type DataDisplayProps, type DataResponse, DataTable, type DataTableDefaultState, type DataTableProps, DataTableServer, type DataTableServerProps, type DatePickerLabels, type DatePickerProps, type DateTimePickerLabels, DefaultCardTitle, DefaultForm, type DefaultFormProps, DefaultTable, type DefaultTableProps, DensityToggleButton, type DensityToggleButtonProps, type EditFilterButtonProps, EditSortingButton, type EditSortingButtonProps, type EditViewButtonProps, EmptyState, type EmptyStateProps, type EnumPickerLabels, ErrorAlert, type ErrorAlertProps, type ErrorMessageConfig, type ErrorMessageResult, type FieldErrorConfig, type FilePickerLabels, type FilePickerMediaFile, type FilePickerProps, FilterDialog, FormBody, FormRoot, type FormRootProps, FormTitle, type GetColumnsConfigs, type GetDateColorProps, type GetMultiDatesProps, type GetRangeDatesProps, type GetStyleProps, type GetVariantProps, GlobalFilter, type IdPickerLabels, PageSizeControl, type PageSizeControlProps, Pagination, type QueryParams, type RangeCalendarProps, type RangeDatePickerProps, RecordDisplay, type RecordDisplayProps, ReloadButton, type ReloadButtonProps, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, type Result, RowCountText, Table, TableBody, type TableBodyProps, TableCardContainer, type TableCardContainerProps, TableCards, type TableCardsProps, TableComponent, TableControls, type TableControlsProps, TableDataDisplay, type TableDataDisplayProps, TableFilter, TableFilterTags, TableFooter, type TableFooterProps, TableHeader, type TableHeaderProps, type TableHeaderTexts, TableLoadingComponent, type TableLoadingComponentProps, type TableProps, type TableRendererProps, type TableRowSelectorProps, TableSelector, TableSorter, TableViewer, type TagPickerProps, TextCell, type TextCellProps, type UseDataTableProps, type UseDataTableReturn, type UseDataTableServerProps, type UseDataTableServerReturn, type UseFormProps, type ValidationErrorType, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
package/dist/index.js CHANGED
@@ -3908,7 +3908,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
3908
3908
  showSubmitButton: true,
3909
3909
  showResetButton: true,
3910
3910
  showTitle: true,
3911
- }, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, }) => {
3911
+ }, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }) => {
3912
3912
  const [isSuccess, setIsSuccess] = React.useState(false);
3913
3913
  const [isError, setIsError] = React.useState(false);
3914
3914
  const [isSubmiting, setIsSubmiting] = React.useState(false);
@@ -3997,6 +3997,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
3997
3997
  dateTimePickerLabels,
3998
3998
  idPickerLabels,
3999
3999
  enumPickerLabels,
4000
+ filePickerLabels,
4000
4001
  ajvResolver: ajvResolver(schema),
4001
4002
  }, children: jsxRuntime.jsx(reactHookForm.FormProvider, { ...form, children: children }) }));
4002
4003
  };
@@ -4370,6 +4371,92 @@ const DatePicker = ({ column, schema, prefix }) => {
4370
4371
  } })] }) })] }) }));
4371
4372
  };
4372
4373
 
4374
+ dayjs.extend(utc);
4375
+ dayjs.extend(timezone);
4376
+ const DateRangePicker = ({ column, schema, prefix, }) => {
4377
+ const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
4378
+ const { timezone, dateTimePickerLabels } = useSchemaContext();
4379
+ const formI18n = useFormI18n(column, prefix);
4380
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
4381
+ const isRequired = required?.some((columnId) => columnId === column);
4382
+ const colLabel = formI18n.colLabel;
4383
+ const [open, setOpen] = React.useState(false);
4384
+ const selectedDateRange = watch(colLabel);
4385
+ // Convert string[] to Date[] for the picker
4386
+ const selectedDates = (selectedDateRange ?? [])
4387
+ .map((dateStr) => {
4388
+ if (!dateStr)
4389
+ return null;
4390
+ const parsed = dayjs(dateStr).tz(timezone);
4391
+ return parsed.isValid() ? parsed.toDate() : null;
4392
+ })
4393
+ .filter((date) => date !== null);
4394
+ // Format display string
4395
+ const getDisplayText = () => {
4396
+ if (!selectedDateRange || selectedDateRange.length === 0) {
4397
+ return '';
4398
+ }
4399
+ if (selectedDateRange.length === 1) {
4400
+ const date = dayjs(selectedDateRange[0]).tz(timezone);
4401
+ return date.isValid() ? date.format(displayDateFormat) : '';
4402
+ }
4403
+ if (selectedDateRange.length === 2) {
4404
+ const startDate = dayjs(selectedDateRange[0]).tz(timezone);
4405
+ const endDate = dayjs(selectedDateRange[1]).tz(timezone);
4406
+ if (startDate.isValid() && endDate.isValid()) {
4407
+ return `${startDate.format(displayDateFormat)} - ${endDate.format(displayDateFormat)}`;
4408
+ }
4409
+ }
4410
+ return '';
4411
+ };
4412
+ React.useEffect(() => {
4413
+ try {
4414
+ if (selectedDateRange && selectedDateRange.length > 0) {
4415
+ // Format dates according to dateFormat from schema
4416
+ const formatted = selectedDateRange
4417
+ .map((dateStr) => {
4418
+ if (!dateStr)
4419
+ return null;
4420
+ const parsed = dayjs(dateStr).tz(timezone);
4421
+ return parsed.isValid() ? parsed.format(dateFormat) : null;
4422
+ })
4423
+ .filter((date) => date !== null);
4424
+ // Update the form value only if different to avoid loops
4425
+ // Compare arrays element by element
4426
+ const needsUpdate = formatted.length !== selectedDateRange.length ||
4427
+ formatted.some((val, idx) => val !== selectedDateRange[idx]);
4428
+ if (needsUpdate && formatted.length > 0) {
4429
+ setValue(colLabel, formatted, {
4430
+ shouldValidate: true,
4431
+ shouldDirty: true,
4432
+ });
4433
+ }
4434
+ }
4435
+ }
4436
+ catch (e) {
4437
+ console.error(e);
4438
+ }
4439
+ }, [selectedDateRange, dateFormat, colLabel, setValue, timezone]);
4440
+ return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4441
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
4442
+ setOpen(true);
4443
+ }, justifyContent: 'start', children: [jsxRuntime.jsx(md.MdDateRange, {}), getDisplayText()] }) }), jsxRuntime.jsx(PopoverContent, { minW: '600px', children: jsxRuntime.jsxs(PopoverBody, { children: [jsxRuntime.jsx(PopoverTitle, {}), jsxRuntime.jsx(RangeDatePicker, { selected: selectedDates, onDateSelected: ({ selected, selectable, date }) => {
4444
+ const newDates = getRangeDates({
4445
+ selectable,
4446
+ date,
4447
+ selectedDates,
4448
+ }) ?? [];
4449
+ // Convert Date[] to string[]
4450
+ const formattedDates = newDates
4451
+ .map((dateObj) => dayjs(dateObj).tz(timezone).format(dateFormat))
4452
+ .filter((dateStr) => dateStr);
4453
+ setValue(colLabel, formattedDates, {
4454
+ shouldValidate: true,
4455
+ shouldDirty: true,
4456
+ });
4457
+ }, monthsToDisplay: 2 })] }) })] }) }));
4458
+ };
4459
+
4373
4460
  function filterArray(array, searchTerm) {
4374
4461
  // Convert the search term to lower case for case-insensitive comparison
4375
4462
  const lowerCaseSearchTerm = searchTerm.toLowerCase();
@@ -4782,7 +4869,7 @@ function getText(_ref2) {
4782
4869
  return source.getStringData(textMediaType);
4783
4870
  }
4784
4871
 
4785
- const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }, placeholder = "Drop files here or click to upload", }) => {
4872
+ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }, placeholder = 'Drop files here or click to upload', }) => {
4786
4873
  const ref = React.useRef(null);
4787
4874
  const [isDraggedOver, setIsDraggedOver] = React.useState(false);
4788
4875
  React.useEffect(() => {
@@ -4796,7 +4883,7 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
4796
4883
  onDrop: ({ source }) => {
4797
4884
  const files = getFiles({ source });
4798
4885
  const text = getText({ source });
4799
- console.log(files, text, "dfposa");
4886
+ console.log(files, text, 'dfposa');
4800
4887
  onDrop({ files, text });
4801
4888
  },
4802
4889
  });
@@ -4805,9 +4892,9 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
4805
4892
  function getColor(isDraggedOver) {
4806
4893
  if (isDraggedOver) {
4807
4894
  return {
4808
- backgroundColor: "blue.400",
4895
+ backgroundColor: 'blue.400',
4809
4896
  _dark: {
4810
- backgroundColor: "blue.400",
4897
+ backgroundColor: 'blue.400',
4811
4898
  },
4812
4899
  };
4813
4900
  }
@@ -4828,27 +4915,211 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
4828
4915
  const filesArray = [...event.target.files];
4829
4916
  onDrop({ files: filesArray });
4830
4917
  };
4831
- return (jsxRuntime.jsxs(react.Grid, { ...getColor(isDraggedOver), ref: ref, cursor: "pointer", onClick: handleClick, borderStyle: "dashed", borderColor: "colorPalette.400", alignContent: "center", justifyContent: "center", borderWidth: 1, borderRadius: 4, ...gridProps, children: [children, !!children === false && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(react.Flex, { children: placeholder }), jsxRuntime.jsx(react.Input, { type: "file", multiple: true, style: { display: "none" }, ref: fileInput, onChange: handleChange })] }))] }));
4918
+ return (jsxRuntime.jsxs(react.Grid, { ...getColor(isDraggedOver), ref: ref, cursor: 'pointer', onClick: handleClick, borderStyle: 'dashed', borderColor: 'colorPalette.400', alignContent: 'center', justifyContent: 'center', borderWidth: 1, borderRadius: 4, minH: "120px", ...gridProps, children: [children, !!children === false && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(react.Flex, { children: placeholder }), jsxRuntime.jsx(react.Input, { type: "file", multiple: true, style: { display: 'none' }, ref: fileInput, onChange: handleChange })] }))] }));
4832
4919
  };
4833
4920
 
4921
+ /**
4922
+ * Format bytes to human-readable string
4923
+ * @param bytes - The number of bytes to format
4924
+ * @returns Formatted string (e.g., "1.5 KB", "2.3 MB")
4925
+ */
4926
+ function formatBytes(bytes) {
4927
+ if (bytes === 0)
4928
+ return '0 Bytes';
4929
+ const k = 1024;
4930
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
4931
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
4932
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
4933
+ }
4934
+
4935
+ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, labels, translate, colLabel, }) {
4936
+ const [searchTerm, setSearchTerm] = React.useState('');
4937
+ const [selectedFileId, setSelectedFileId] = React.useState('');
4938
+ const [failedImageIds, setFailedImageIds] = React.useState(new Set());
4939
+ const { data: filesData, isLoading, isError, } = reactQuery.useQuery({
4940
+ queryKey: ['file-picker-library', searchTerm],
4941
+ queryFn: async () => {
4942
+ if (!onFetchFiles)
4943
+ return { data: [] };
4944
+ const files = await onFetchFiles(searchTerm.trim() || '');
4945
+ return { data: files };
4946
+ },
4947
+ enabled: open && !!onFetchFiles,
4948
+ });
4949
+ const files = (filesData?.data || []);
4950
+ const filteredFiles = filterImageOnly
4951
+ ? files.filter((file) => /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name))
4952
+ : files;
4953
+ const handleSelect = () => {
4954
+ if (selectedFileId) {
4955
+ onSelect(selectedFileId);
4956
+ onClose();
4957
+ setSelectedFileId('');
4958
+ setSearchTerm('');
4959
+ }
4960
+ };
4961
+ const handleClose = () => {
4962
+ onClose();
4963
+ setSelectedFileId('');
4964
+ setSearchTerm('');
4965
+ setFailedImageIds(new Set());
4966
+ };
4967
+ const handleImageError = (fileId) => {
4968
+ setFailedImageIds((prev) => new Set(prev).add(fileId));
4969
+ };
4970
+ if (!onFetchFiles)
4971
+ return null;
4972
+ return (jsxRuntime.jsx(DialogRoot, { open: open, onOpenChange: (e) => !e.open && handleClose(), children: jsxRuntime.jsxs(DialogContent, { maxWidth: "800px", maxHeight: "90vh", children: [jsxRuntime.jsxs(DialogHeader, { children: [jsxRuntime.jsx(DialogTitle, { fontSize: "lg", fontWeight: "bold", children: title }), jsxRuntime.jsx(DialogCloseTrigger, {})] }), jsxRuntime.jsx(DialogBody, { children: jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 4, children: [jsxRuntime.jsxs(react.Box, { position: "relative", children: [jsxRuntime.jsx(react.Input, { placeholder: labels?.searchPlaceholder ??
4973
+ translate(removeIndex(`${colLabel}.search_placeholder`)) ??
4974
+ 'Search files...', value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), bg: "bg.panel", border: "1px solid", borderColor: "border.default", colorPalette: "blue", _focus: {
4975
+ borderColor: 'colorPalette.500',
4976
+ _dark: {
4977
+ borderColor: 'colorPalette.400',
4978
+ },
4979
+ boxShadow: {
4980
+ base: '0 0 0 1px var(--chakra-colors-blue-500)',
4981
+ _dark: '0 0 0 1px var(--chakra-colors-blue-400)',
4982
+ },
4983
+ }, 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 ??
4984
+ translate(removeIndex(`${colLabel}.loading`)) ??
4985
+ 'Loading files...' })] })), isError && (jsxRuntime.jsx(react.Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
4986
+ base: 'colorPalette.200',
4987
+ _dark: 'colorPalette.800',
4988
+ }, colorPalette: "red", borderRadius: "md", p: 4, children: jsxRuntime.jsx(react.Text, { color: {
4989
+ base: 'colorPalette.600',
4990
+ _dark: 'colorPalette.300',
4991
+ }, children: labels?.loadingFailed ??
4992
+ translate(removeIndex(`${colLabel}.error.loading_failed`)) ??
4993
+ '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 ??
4994
+ translate(removeIndex(`${colLabel}.no_files_found`)) ??
4995
+ 'No files found' }) })) : (jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
4996
+ const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
4997
+ const isSelected = selectedFileId === file.id;
4998
+ const imageFailed = failedImageIds.has(file.id);
4999
+ return (jsxRuntime.jsx(react.Box, { p: 3, border: "2px solid", borderColor: isSelected
5000
+ ? {
5001
+ base: 'colorPalette.500',
5002
+ _dark: 'colorPalette.400',
5003
+ }
5004
+ : 'border.default', borderRadius: "md", bg: isSelected
5005
+ ? {
5006
+ base: 'colorPalette.50',
5007
+ _dark: 'colorPalette.900/20',
5008
+ }
5009
+ : 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => setSelectedFileId(file.id), _hover: {
5010
+ borderColor: isSelected
5011
+ ? {
5012
+ base: 'colorPalette.600',
5013
+ _dark: 'colorPalette.400',
5014
+ }
5015
+ : {
5016
+ base: 'colorPalette.300',
5017
+ _dark: 'colorPalette.400',
5018
+ },
5019
+ bg: isSelected
5020
+ ? {
5021
+ base: 'colorPalette.100',
5022
+ _dark: 'colorPalette.800/30',
5023
+ }
5024
+ : 'bg.muted',
5025
+ }, 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'
5026
+ ? formatBytes(file.size)
5027
+ : 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: {
5028
+ base: 'colorPalette.500',
5029
+ _dark: 'colorPalette.400',
5030
+ }, 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));
5031
+ }) })) }))] }) }), jsxRuntime.jsx(DialogFooter, { children: jsxRuntime.jsxs(react.HStack, { gap: 3, justify: "end", children: [jsxRuntime.jsx(react.Button, { variant: "outline", onClick: handleClose, borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: labels?.cancel ??
5032
+ translate(removeIndex(`${colLabel}.cancel`)) ??
5033
+ 'Cancel' }), jsxRuntime.jsx(react.Button, { colorPalette: "blue", onClick: handleSelect, disabled: !selectedFileId, children: labels?.select ??
5034
+ translate(removeIndex(`${colLabel}.select`)) ??
5035
+ 'Select' })] }) })] }) }));
5036
+ }
4834
5037
  const FilePicker = ({ column, schema, prefix }) => {
4835
5038
  const { setValue, formState: { errors }, watch, } = reactHookForm.useFormContext();
4836
- const { translate } = useSchemaContext();
4837
- const { required, gridColumn = 'span 12', gridRow = 'span 1', } = schema;
5039
+ const { filePickerLabels } = useSchemaContext();
5040
+ const formI18n = useFormI18n(column, prefix);
5041
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, } = schema;
4838
5042
  const isRequired = required?.some((columnId) => columnId === column);
4839
- const currentFiles = (watch(column) ?? []);
4840
- const colLabel = `${prefix}${column}`;
4841
- return (jsxRuntime.jsxs(Field, { label: `${translate.t(`${colLabel}.field_label`)}`, required: isRequired, gridColumn: gridColumn ?? 'span 4', gridRow: gridRow ?? 'span 1', display: 'grid', gridTemplateRows: 'auto 1fr auto', alignItems: 'stretch', errorText: errors[`${colLabel}`]
4842
- ? translate.t(removeIndex(`${colLabel}.field_required`))
4843
- : undefined, invalid: !!errors[colLabel], children: [jsxRuntime.jsx(FileDropzone, { onDrop: ({ files }) => {
4844
- const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => cur.name === name));
4845
- setValue(colLabel, [...currentFiles, ...newFiles]);
4846
- }, placeholder: translate.t(removeIndex(`${colLabel}.fileDropzone`)) }), jsxRuntime.jsx(react.Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file) => {
4847
- return (jsxRuntime.jsx(react.Card.Root, { variant: 'subtle', children: jsxRuntime.jsxs(react.Card.Body, { gap: "2", cursor: 'pointer', onClick: () => {
4848
- setValue(column, currentFiles.filter(({ name }) => {
4849
- return name !== file.name;
4850
- }));
4851
- }, display: 'flex', flexFlow: 'row', alignItems: 'center', padding: '2', children: [file.type.startsWith('image/') && (jsxRuntime.jsx(react.Image, { src: URL.createObjectURL(file), alt: file.name, boxSize: "50px", objectFit: "cover", borderRadius: "md", marginRight: "2" })), jsxRuntime.jsx(react.Box, { children: file.name }), jsxRuntime.jsx(ti.TiDeleteOutline, {})] }) }, file.name));
5043
+ const currentValue = watch(column) ?? [];
5044
+ const currentFiles = Array.isArray(currentValue)
5045
+ ? currentValue
5046
+ : [];
5047
+ const colLabel = formI18n.colLabel;
5048
+ const [dialogOpen, setDialogOpen] = React.useState(false);
5049
+ const [failedImageIds, setFailedImageIds] = React.useState(new Set());
5050
+ const { onFetchFiles, enableMediaLibrary = false, filterImageOnly = false, } = filePicker || {};
5051
+ const showMediaLibrary = enableMediaLibrary && !!onFetchFiles;
5052
+ const handleImageError = (fileIdentifier) => {
5053
+ setFailedImageIds((prev) => new Set(prev).add(fileIdentifier));
5054
+ };
5055
+ const handleMediaLibrarySelect = (fileId) => {
5056
+ const newFiles = [...currentFiles, fileId];
5057
+ setValue(colLabel, newFiles);
5058
+ };
5059
+ const handleRemove = (index) => {
5060
+ const newFiles = currentFiles.filter((_, i) => i !== index);
5061
+ setValue(colLabel, newFiles);
5062
+ };
5063
+ const isFileObject = (value) => {
5064
+ return value instanceof File;
5065
+ };
5066
+ const getFileIdentifier = (file, index) => {
5067
+ if (isFileObject(file)) {
5068
+ return `${file.name}-${file.size}-${index}`;
5069
+ }
5070
+ return file;
5071
+ };
5072
+ const getFileName = (file) => {
5073
+ if (isFileObject(file)) {
5074
+ return file.name;
5075
+ }
5076
+ return typeof file === 'string' ? file : 'Unknown file';
5077
+ };
5078
+ const getFileSize = (file) => {
5079
+ if (isFileObject(file)) {
5080
+ return file.size;
5081
+ }
5082
+ return undefined;
5083
+ };
5084
+ const isImageFile = (file) => {
5085
+ if (isFileObject(file)) {
5086
+ return file.type.startsWith('image/');
5087
+ }
5088
+ if (typeof file === 'string') {
5089
+ return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file);
5090
+ }
5091
+ return false;
5092
+ };
5093
+ const getImageUrl = (file) => {
5094
+ if (isFileObject(file)) {
5095
+ return URL.createObjectURL(file);
5096
+ }
5097
+ return undefined;
5098
+ };
5099
+ return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5100
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 2, children: [jsxRuntime.jsx(FileDropzone, { onDrop: ({ files }) => {
5101
+ const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => {
5102
+ if (isFileObject(cur)) {
5103
+ return cur.name === name;
5104
+ }
5105
+ return false;
5106
+ }));
5107
+ setValue(colLabel, [...currentFiles, ...newFiles]);
5108
+ }, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }), showMediaLibrary && (jsxRuntime.jsx(react.Button, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
5109
+ formI18n.t('browse_library') ??
5110
+ 'Browse from Library' }))] }), showMediaLibrary && (jsxRuntime.jsx(FilePickerDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
5111
+ formI18n.t('dialog_title') ??
5112
+ 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel })), jsxRuntime.jsx(react.Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
5113
+ const fileIdentifier = getFileIdentifier(file, index);
5114
+ const fileName = getFileName(file);
5115
+ const fileSize = getFileSize(file);
5116
+ const isImage = isImageFile(file);
5117
+ const imageUrl = getImageUrl(file);
5118
+ const imageFailed = failedImageIds.has(fileIdentifier);
5119
+ 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: {
5120
+ borderColor: 'colorPalette.300',
5121
+ bg: 'bg.muted',
5122
+ }, 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", children: isImage && imageUrl && !imageFailed ? (jsxRuntime.jsx(react.Image, { src: imageUrl, alt: fileName, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(fileIdentifier) })) : isImage && (imageFailed || !imageUrl) ? (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: fileName }), fileSize !== undefined && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: formatBytes(fileSize) }))] }), jsxRuntime.jsx(react.Icon, { as: ti.TiDeleteOutline, boxSize: 5, color: "fg.muted" })] }) }, fileIdentifier));
4852
5123
  }) })] }));
4853
5124
  };
4854
5125
 
@@ -5972,59 +6243,62 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
5972
6243
  const SchemaRenderer = ({ schema, prefix, column, }) => {
5973
6244
  const colSchema = schema;
5974
6245
  const { type, variant, properties: innerProperties, foreign_key, format, items, } = schema;
5975
- if (variant === "custom-input") {
6246
+ if (variant === 'custom-input') {
5976
6247
  return jsxRuntime.jsx(CustomInput, { schema: colSchema, prefix, column });
5977
6248
  }
5978
- if (type === "string") {
6249
+ if (type === 'string') {
5979
6250
  if ((schema.enum ?? []).length > 0) {
5980
6251
  return jsxRuntime.jsx(EnumPicker, { schema: colSchema, prefix, column });
5981
6252
  }
5982
- if (variant === "id-picker") {
6253
+ if (variant === 'id-picker') {
5983
6254
  idPickerSanityCheck(column, foreign_key);
5984
6255
  return jsxRuntime.jsx(IdPicker, { schema: colSchema, prefix, column });
5985
6256
  }
5986
- if (format === "date") {
6257
+ if (format === 'date') {
5987
6258
  return jsxRuntime.jsx(DatePicker, { schema: colSchema, prefix, column });
5988
6259
  }
5989
- if (format === "time") {
6260
+ if (format === 'time') {
5990
6261
  return jsxRuntime.jsx(TimePicker, { schema: colSchema, prefix, column });
5991
6262
  }
5992
- if (format === "date-time") {
6263
+ if (format === 'date-time') {
5993
6264
  return jsxRuntime.jsx(DateTimePicker, { schema: colSchema, prefix, column });
5994
6265
  }
5995
- if (variant === "text-area") {
6266
+ if (variant === 'text-area') {
5996
6267
  return jsxRuntime.jsx(TextAreaInput, { schema: colSchema, prefix, column });
5997
6268
  }
5998
6269
  return jsxRuntime.jsx(StringInputField, { schema: colSchema, prefix, column });
5999
6270
  }
6000
- if (type === "number" || type === "integer") {
6271
+ if (type === 'number' || type === 'integer') {
6001
6272
  return jsxRuntime.jsx(NumberInputField, { schema: colSchema, prefix, column });
6002
6273
  }
6003
- if (type === "boolean") {
6274
+ if (type === 'boolean') {
6004
6275
  return jsxRuntime.jsx(BooleanPicker, { schema: colSchema, prefix, column });
6005
6276
  }
6006
- if (type === "object") {
6277
+ if (type === 'object') {
6007
6278
  if (innerProperties) {
6008
6279
  return jsxRuntime.jsx(ObjectInput, { schema: colSchema, prefix, column });
6009
6280
  }
6010
6281
  return jsxRuntime.jsx(RecordInput$1, { schema: colSchema, prefix, column });
6011
6282
  }
6012
- if (type === "array") {
6013
- if (variant === "id-picker") {
6283
+ if (type === 'array') {
6284
+ if (variant === 'id-picker') {
6014
6285
  idPickerSanityCheck(column, foreign_key);
6015
6286
  return (jsxRuntime.jsx(IdPicker, { schema: colSchema, prefix, column, isMultiple: true }));
6016
6287
  }
6017
- if (variant === "tag-picker") {
6288
+ if (variant === 'tag-picker') {
6018
6289
  return jsxRuntime.jsx(TagPicker, { schema: colSchema, prefix, column });
6019
6290
  }
6020
- if (variant === "file-picker") {
6291
+ if (variant === 'file-picker') {
6021
6292
  return jsxRuntime.jsx(FilePicker, { schema: colSchema, prefix, column });
6022
6293
  }
6023
- if (variant === "enum-picker") {
6294
+ if (variant === 'date-range') {
6295
+ return jsxRuntime.jsx(DateRangePicker, { schema: colSchema, prefix, column });
6296
+ }
6297
+ if (variant === 'enum-picker') {
6024
6298
  const { items } = colSchema;
6025
6299
  const { enum: enumItems } = items;
6026
6300
  const enumSchema = {
6027
- type: "string",
6301
+ type: 'string',
6028
6302
  enum: enumItems,
6029
6303
  };
6030
6304
  return (jsxRuntime.jsx(EnumPicker, { isMultiple: true, schema: enumSchema, prefix, column }));
@@ -6034,7 +6308,7 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
6034
6308
  }
6035
6309
  return jsxRuntime.jsx(react.Text, { children: `array ${column}` });
6036
6310
  }
6037
- if (type === "null") {
6311
+ if (type === 'null') {
6038
6312
  return jsxRuntime.jsx(react.Text, { children: `null ${column}` });
6039
6313
  }
6040
6314
  return jsxRuntime.jsx(react.Text, { children: "missing type" });
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal,
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 } from 'react-icons/lu';
6
+ import { LuX, LuCheck, LuChevronRight, LuImage, LuFile, LuSearch } from 'react-icons/lu';
7
7
  import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdClear, MdOutlineChecklist, 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';
@@ -3888,7 +3888,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
3888
3888
  showSubmitButton: true,
3889
3889
  showResetButton: true,
3890
3890
  showTitle: true,
3891
- }, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, }) => {
3891
+ }, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }) => {
3892
3892
  const [isSuccess, setIsSuccess] = useState(false);
3893
3893
  const [isError, setIsError] = useState(false);
3894
3894
  const [isSubmiting, setIsSubmiting] = useState(false);
@@ -3977,6 +3977,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
3977
3977
  dateTimePickerLabels,
3978
3978
  idPickerLabels,
3979
3979
  enumPickerLabels,
3980
+ filePickerLabels,
3980
3981
  ajvResolver: ajvResolver(schema),
3981
3982
  }, children: jsx(FormProvider, { ...form, children: children }) }));
3982
3983
  };
@@ -4350,6 +4351,92 @@ const DatePicker = ({ column, schema, prefix }) => {
4350
4351
  } })] }) })] }) }));
4351
4352
  };
4352
4353
 
4354
+ dayjs.extend(utc);
4355
+ dayjs.extend(timezone);
4356
+ const DateRangePicker = ({ column, schema, prefix, }) => {
4357
+ const { watch, formState: { errors }, setValue, } = useFormContext();
4358
+ const { timezone, dateTimePickerLabels } = useSchemaContext();
4359
+ const formI18n = useFormI18n(column, prefix);
4360
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
4361
+ const isRequired = required?.some((columnId) => columnId === column);
4362
+ const colLabel = formI18n.colLabel;
4363
+ const [open, setOpen] = useState(false);
4364
+ const selectedDateRange = watch(colLabel);
4365
+ // Convert string[] to Date[] for the picker
4366
+ const selectedDates = (selectedDateRange ?? [])
4367
+ .map((dateStr) => {
4368
+ if (!dateStr)
4369
+ return null;
4370
+ const parsed = dayjs(dateStr).tz(timezone);
4371
+ return parsed.isValid() ? parsed.toDate() : null;
4372
+ })
4373
+ .filter((date) => date !== null);
4374
+ // Format display string
4375
+ const getDisplayText = () => {
4376
+ if (!selectedDateRange || selectedDateRange.length === 0) {
4377
+ return '';
4378
+ }
4379
+ if (selectedDateRange.length === 1) {
4380
+ const date = dayjs(selectedDateRange[0]).tz(timezone);
4381
+ return date.isValid() ? date.format(displayDateFormat) : '';
4382
+ }
4383
+ if (selectedDateRange.length === 2) {
4384
+ const startDate = dayjs(selectedDateRange[0]).tz(timezone);
4385
+ const endDate = dayjs(selectedDateRange[1]).tz(timezone);
4386
+ if (startDate.isValid() && endDate.isValid()) {
4387
+ return `${startDate.format(displayDateFormat)} - ${endDate.format(displayDateFormat)}`;
4388
+ }
4389
+ }
4390
+ return '';
4391
+ };
4392
+ useEffect(() => {
4393
+ try {
4394
+ if (selectedDateRange && selectedDateRange.length > 0) {
4395
+ // Format dates according to dateFormat from schema
4396
+ const formatted = selectedDateRange
4397
+ .map((dateStr) => {
4398
+ if (!dateStr)
4399
+ return null;
4400
+ const parsed = dayjs(dateStr).tz(timezone);
4401
+ return parsed.isValid() ? parsed.format(dateFormat) : null;
4402
+ })
4403
+ .filter((date) => date !== null);
4404
+ // Update the form value only if different to avoid loops
4405
+ // Compare arrays element by element
4406
+ const needsUpdate = formatted.length !== selectedDateRange.length ||
4407
+ formatted.some((val, idx) => val !== selectedDateRange[idx]);
4408
+ if (needsUpdate && formatted.length > 0) {
4409
+ setValue(colLabel, formatted, {
4410
+ shouldValidate: true,
4411
+ shouldDirty: true,
4412
+ });
4413
+ }
4414
+ }
4415
+ }
4416
+ catch (e) {
4417
+ console.error(e);
4418
+ }
4419
+ }, [selectedDateRange, dateFormat, colLabel, setValue, timezone]);
4420
+ return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4421
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(PopoverTrigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
4422
+ setOpen(true);
4423
+ }, justifyContent: 'start', children: [jsx(MdDateRange, {}), getDisplayText()] }) }), jsx(PopoverContent, { minW: '600px', children: jsxs(PopoverBody, { children: [jsx(PopoverTitle, {}), jsx(RangeDatePicker, { selected: selectedDates, onDateSelected: ({ selected, selectable, date }) => {
4424
+ const newDates = getRangeDates({
4425
+ selectable,
4426
+ date,
4427
+ selectedDates,
4428
+ }) ?? [];
4429
+ // Convert Date[] to string[]
4430
+ const formattedDates = newDates
4431
+ .map((dateObj) => dayjs(dateObj).tz(timezone).format(dateFormat))
4432
+ .filter((dateStr) => dateStr);
4433
+ setValue(colLabel, formattedDates, {
4434
+ shouldValidate: true,
4435
+ shouldDirty: true,
4436
+ });
4437
+ }, monthsToDisplay: 2 })] }) })] }) }));
4438
+ };
4439
+
4353
4440
  function filterArray(array, searchTerm) {
4354
4441
  // Convert the search term to lower case for case-insensitive comparison
4355
4442
  const lowerCaseSearchTerm = searchTerm.toLowerCase();
@@ -4762,7 +4849,7 @@ function getText(_ref2) {
4762
4849
  return source.getStringData(textMediaType);
4763
4850
  }
4764
4851
 
4765
- const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }, placeholder = "Drop files here or click to upload", }) => {
4852
+ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }, placeholder = 'Drop files here or click to upload', }) => {
4766
4853
  const ref = useRef(null);
4767
4854
  const [isDraggedOver, setIsDraggedOver] = useState(false);
4768
4855
  useEffect(() => {
@@ -4776,7 +4863,7 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
4776
4863
  onDrop: ({ source }) => {
4777
4864
  const files = getFiles({ source });
4778
4865
  const text = getText({ source });
4779
- console.log(files, text, "dfposa");
4866
+ console.log(files, text, 'dfposa');
4780
4867
  onDrop({ files, text });
4781
4868
  },
4782
4869
  });
@@ -4785,9 +4872,9 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
4785
4872
  function getColor(isDraggedOver) {
4786
4873
  if (isDraggedOver) {
4787
4874
  return {
4788
- backgroundColor: "blue.400",
4875
+ backgroundColor: 'blue.400',
4789
4876
  _dark: {
4790
- backgroundColor: "blue.400",
4877
+ backgroundColor: 'blue.400',
4791
4878
  },
4792
4879
  };
4793
4880
  }
@@ -4808,27 +4895,211 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
4808
4895
  const filesArray = [...event.target.files];
4809
4896
  onDrop({ files: filesArray });
4810
4897
  };
4811
- return (jsxs(Grid, { ...getColor(isDraggedOver), ref: ref, cursor: "pointer", onClick: handleClick, borderStyle: "dashed", borderColor: "colorPalette.400", alignContent: "center", justifyContent: "center", borderWidth: 1, borderRadius: 4, ...gridProps, children: [children, !!children === false && (jsxs(Fragment, { children: [jsx(Flex, { children: placeholder }), jsx(Input, { type: "file", multiple: true, style: { display: "none" }, ref: fileInput, onChange: handleChange })] }))] }));
4898
+ return (jsxs(Grid, { ...getColor(isDraggedOver), ref: ref, cursor: 'pointer', onClick: handleClick, borderStyle: 'dashed', borderColor: 'colorPalette.400', alignContent: 'center', justifyContent: 'center', borderWidth: 1, borderRadius: 4, minH: "120px", ...gridProps, children: [children, !!children === false && (jsxs(Fragment, { children: [jsx(Flex, { children: placeholder }), jsx(Input, { type: "file", multiple: true, style: { display: 'none' }, ref: fileInput, onChange: handleChange })] }))] }));
4812
4899
  };
4813
4900
 
4901
+ /**
4902
+ * Format bytes to human-readable string
4903
+ * @param bytes - The number of bytes to format
4904
+ * @returns Formatted string (e.g., "1.5 KB", "2.3 MB")
4905
+ */
4906
+ function formatBytes(bytes) {
4907
+ if (bytes === 0)
4908
+ return '0 Bytes';
4909
+ const k = 1024;
4910
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
4911
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
4912
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
4913
+ }
4914
+
4915
+ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, labels, translate, colLabel, }) {
4916
+ const [searchTerm, setSearchTerm] = useState('');
4917
+ const [selectedFileId, setSelectedFileId] = useState('');
4918
+ const [failedImageIds, setFailedImageIds] = useState(new Set());
4919
+ const { data: filesData, isLoading, isError, } = useQuery({
4920
+ queryKey: ['file-picker-library', searchTerm],
4921
+ queryFn: async () => {
4922
+ if (!onFetchFiles)
4923
+ return { data: [] };
4924
+ const files = await onFetchFiles(searchTerm.trim() || '');
4925
+ return { data: files };
4926
+ },
4927
+ enabled: open && !!onFetchFiles,
4928
+ });
4929
+ const files = (filesData?.data || []);
4930
+ const filteredFiles = filterImageOnly
4931
+ ? files.filter((file) => /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name))
4932
+ : files;
4933
+ const handleSelect = () => {
4934
+ if (selectedFileId) {
4935
+ onSelect(selectedFileId);
4936
+ onClose();
4937
+ setSelectedFileId('');
4938
+ setSearchTerm('');
4939
+ }
4940
+ };
4941
+ const handleClose = () => {
4942
+ onClose();
4943
+ setSelectedFileId('');
4944
+ setSearchTerm('');
4945
+ setFailedImageIds(new Set());
4946
+ };
4947
+ const handleImageError = (fileId) => {
4948
+ setFailedImageIds((prev) => new Set(prev).add(fileId));
4949
+ };
4950
+ if (!onFetchFiles)
4951
+ return null;
4952
+ 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 ??
4953
+ translate(removeIndex(`${colLabel}.search_placeholder`)) ??
4954
+ 'Search files...', value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), bg: "bg.panel", border: "1px solid", borderColor: "border.default", colorPalette: "blue", _focus: {
4955
+ borderColor: 'colorPalette.500',
4956
+ _dark: {
4957
+ borderColor: 'colorPalette.400',
4958
+ },
4959
+ boxShadow: {
4960
+ base: '0 0 0 1px var(--chakra-colors-blue-500)',
4961
+ _dark: '0 0 0 1px var(--chakra-colors-blue-400)',
4962
+ },
4963
+ }, 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 ??
4964
+ translate(removeIndex(`${colLabel}.loading`)) ??
4965
+ 'Loading files...' })] })), isError && (jsx(Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
4966
+ base: 'colorPalette.200',
4967
+ _dark: 'colorPalette.800',
4968
+ }, colorPalette: "red", borderRadius: "md", p: 4, children: jsx(Text, { color: {
4969
+ base: 'colorPalette.600',
4970
+ _dark: 'colorPalette.300',
4971
+ }, children: labels?.loadingFailed ??
4972
+ translate(removeIndex(`${colLabel}.error.loading_failed`)) ??
4973
+ '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 ??
4974
+ translate(removeIndex(`${colLabel}.no_files_found`)) ??
4975
+ 'No files found' }) })) : (jsx(VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
4976
+ const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
4977
+ const isSelected = selectedFileId === file.id;
4978
+ const imageFailed = failedImageIds.has(file.id);
4979
+ return (jsx(Box, { p: 3, border: "2px solid", borderColor: isSelected
4980
+ ? {
4981
+ base: 'colorPalette.500',
4982
+ _dark: 'colorPalette.400',
4983
+ }
4984
+ : 'border.default', borderRadius: "md", bg: isSelected
4985
+ ? {
4986
+ base: 'colorPalette.50',
4987
+ _dark: 'colorPalette.900/20',
4988
+ }
4989
+ : 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => setSelectedFileId(file.id), _hover: {
4990
+ borderColor: isSelected
4991
+ ? {
4992
+ base: 'colorPalette.600',
4993
+ _dark: 'colorPalette.400',
4994
+ }
4995
+ : {
4996
+ base: 'colorPalette.300',
4997
+ _dark: 'colorPalette.400',
4998
+ },
4999
+ bg: isSelected
5000
+ ? {
5001
+ base: 'colorPalette.100',
5002
+ _dark: 'colorPalette.800/30',
5003
+ }
5004
+ : 'bg.muted',
5005
+ }, 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'
5006
+ ? formatBytes(file.size)
5007
+ : 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: {
5008
+ base: 'colorPalette.500',
5009
+ _dark: 'colorPalette.400',
5010
+ }, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsx(Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
5011
+ }) })) }))] }) }), 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 ??
5012
+ translate(removeIndex(`${colLabel}.cancel`)) ??
5013
+ 'Cancel' }), jsx(Button$1, { colorPalette: "blue", onClick: handleSelect, disabled: !selectedFileId, children: labels?.select ??
5014
+ translate(removeIndex(`${colLabel}.select`)) ??
5015
+ 'Select' })] }) })] }) }));
5016
+ }
4814
5017
  const FilePicker = ({ column, schema, prefix }) => {
4815
5018
  const { setValue, formState: { errors }, watch, } = useFormContext();
4816
- const { translate } = useSchemaContext();
4817
- const { required, gridColumn = 'span 12', gridRow = 'span 1', } = schema;
5019
+ const { filePickerLabels } = useSchemaContext();
5020
+ const formI18n = useFormI18n(column, prefix);
5021
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, } = schema;
4818
5022
  const isRequired = required?.some((columnId) => columnId === column);
4819
- const currentFiles = (watch(column) ?? []);
4820
- const colLabel = `${prefix}${column}`;
4821
- return (jsxs(Field, { label: `${translate.t(`${colLabel}.field_label`)}`, required: isRequired, gridColumn: gridColumn ?? 'span 4', gridRow: gridRow ?? 'span 1', display: 'grid', gridTemplateRows: 'auto 1fr auto', alignItems: 'stretch', errorText: errors[`${colLabel}`]
4822
- ? translate.t(removeIndex(`${colLabel}.field_required`))
4823
- : undefined, invalid: !!errors[colLabel], children: [jsx(FileDropzone, { onDrop: ({ files }) => {
4824
- const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => cur.name === name));
4825
- setValue(colLabel, [...currentFiles, ...newFiles]);
4826
- }, placeholder: translate.t(removeIndex(`${colLabel}.fileDropzone`)) }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file) => {
4827
- return (jsx(Card.Root, { variant: 'subtle', children: jsxs(Card.Body, { gap: "2", cursor: 'pointer', onClick: () => {
4828
- setValue(column, currentFiles.filter(({ name }) => {
4829
- return name !== file.name;
4830
- }));
4831
- }, display: 'flex', flexFlow: 'row', alignItems: 'center', padding: '2', children: [file.type.startsWith('image/') && (jsx(Image, { src: URL.createObjectURL(file), alt: file.name, boxSize: "50px", objectFit: "cover", borderRadius: "md", marginRight: "2" })), jsx(Box, { children: file.name }), jsx(TiDeleteOutline, {})] }) }, file.name));
5023
+ const currentValue = watch(column) ?? [];
5024
+ const currentFiles = Array.isArray(currentValue)
5025
+ ? currentValue
5026
+ : [];
5027
+ const colLabel = formI18n.colLabel;
5028
+ const [dialogOpen, setDialogOpen] = useState(false);
5029
+ const [failedImageIds, setFailedImageIds] = useState(new Set());
5030
+ const { onFetchFiles, enableMediaLibrary = false, filterImageOnly = false, } = filePicker || {};
5031
+ const showMediaLibrary = enableMediaLibrary && !!onFetchFiles;
5032
+ const handleImageError = (fileIdentifier) => {
5033
+ setFailedImageIds((prev) => new Set(prev).add(fileIdentifier));
5034
+ };
5035
+ const handleMediaLibrarySelect = (fileId) => {
5036
+ const newFiles = [...currentFiles, fileId];
5037
+ setValue(colLabel, newFiles);
5038
+ };
5039
+ const handleRemove = (index) => {
5040
+ const newFiles = currentFiles.filter((_, i) => i !== index);
5041
+ setValue(colLabel, newFiles);
5042
+ };
5043
+ const isFileObject = (value) => {
5044
+ return value instanceof File;
5045
+ };
5046
+ const getFileIdentifier = (file, index) => {
5047
+ if (isFileObject(file)) {
5048
+ return `${file.name}-${file.size}-${index}`;
5049
+ }
5050
+ return file;
5051
+ };
5052
+ const getFileName = (file) => {
5053
+ if (isFileObject(file)) {
5054
+ return file.name;
5055
+ }
5056
+ return typeof file === 'string' ? file : 'Unknown file';
5057
+ };
5058
+ const getFileSize = (file) => {
5059
+ if (isFileObject(file)) {
5060
+ return file.size;
5061
+ }
5062
+ return undefined;
5063
+ };
5064
+ const isImageFile = (file) => {
5065
+ if (isFileObject(file)) {
5066
+ return file.type.startsWith('image/');
5067
+ }
5068
+ if (typeof file === 'string') {
5069
+ return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file);
5070
+ }
5071
+ return false;
5072
+ };
5073
+ const getImageUrl = (file) => {
5074
+ if (isFileObject(file)) {
5075
+ return URL.createObjectURL(file);
5076
+ }
5077
+ return undefined;
5078
+ };
5079
+ return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5080
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxs(VStack, { align: "stretch", gap: 2, children: [jsx(FileDropzone, { onDrop: ({ files }) => {
5081
+ const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => {
5082
+ if (isFileObject(cur)) {
5083
+ return cur.name === name;
5084
+ }
5085
+ return false;
5086
+ }));
5087
+ setValue(colLabel, [...currentFiles, ...newFiles]);
5088
+ }, 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 ??
5089
+ formI18n.t('browse_library') ??
5090
+ 'Browse from Library' }))] }), showMediaLibrary && (jsx(FilePickerDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
5091
+ formI18n.t('dialog_title') ??
5092
+ 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel })), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
5093
+ const fileIdentifier = getFileIdentifier(file, index);
5094
+ const fileName = getFileName(file);
5095
+ const fileSize = getFileSize(file);
5096
+ const isImage = isImageFile(file);
5097
+ const imageUrl = getImageUrl(file);
5098
+ const imageFailed = failedImageIds.has(fileIdentifier);
5099
+ 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: {
5100
+ borderColor: 'colorPalette.300',
5101
+ bg: 'bg.muted',
5102
+ }, 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 && imageUrl && !imageFailed ? (jsx(Image, { src: imageUrl, alt: fileName, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(fileIdentifier) })) : isImage && (imageFailed || !imageUrl) ? (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: fileName }), fileSize !== undefined && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: formatBytes(fileSize) }))] }), jsx(Icon, { as: TiDeleteOutline, boxSize: 5, color: "fg.muted" })] }) }, fileIdentifier));
4832
5103
  }) })] }));
4833
5104
  };
4834
5105
 
@@ -5952,59 +6223,62 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
5952
6223
  const SchemaRenderer = ({ schema, prefix, column, }) => {
5953
6224
  const colSchema = schema;
5954
6225
  const { type, variant, properties: innerProperties, foreign_key, format, items, } = schema;
5955
- if (variant === "custom-input") {
6226
+ if (variant === 'custom-input') {
5956
6227
  return jsx(CustomInput, { schema: colSchema, prefix, column });
5957
6228
  }
5958
- if (type === "string") {
6229
+ if (type === 'string') {
5959
6230
  if ((schema.enum ?? []).length > 0) {
5960
6231
  return jsx(EnumPicker, { schema: colSchema, prefix, column });
5961
6232
  }
5962
- if (variant === "id-picker") {
6233
+ if (variant === 'id-picker') {
5963
6234
  idPickerSanityCheck(column, foreign_key);
5964
6235
  return jsx(IdPicker, { schema: colSchema, prefix, column });
5965
6236
  }
5966
- if (format === "date") {
6237
+ if (format === 'date') {
5967
6238
  return jsx(DatePicker, { schema: colSchema, prefix, column });
5968
6239
  }
5969
- if (format === "time") {
6240
+ if (format === 'time') {
5970
6241
  return jsx(TimePicker, { schema: colSchema, prefix, column });
5971
6242
  }
5972
- if (format === "date-time") {
6243
+ if (format === 'date-time') {
5973
6244
  return jsx(DateTimePicker, { schema: colSchema, prefix, column });
5974
6245
  }
5975
- if (variant === "text-area") {
6246
+ if (variant === 'text-area') {
5976
6247
  return jsx(TextAreaInput, { schema: colSchema, prefix, column });
5977
6248
  }
5978
6249
  return jsx(StringInputField, { schema: colSchema, prefix, column });
5979
6250
  }
5980
- if (type === "number" || type === "integer") {
6251
+ if (type === 'number' || type === 'integer') {
5981
6252
  return jsx(NumberInputField, { schema: colSchema, prefix, column });
5982
6253
  }
5983
- if (type === "boolean") {
6254
+ if (type === 'boolean') {
5984
6255
  return jsx(BooleanPicker, { schema: colSchema, prefix, column });
5985
6256
  }
5986
- if (type === "object") {
6257
+ if (type === 'object') {
5987
6258
  if (innerProperties) {
5988
6259
  return jsx(ObjectInput, { schema: colSchema, prefix, column });
5989
6260
  }
5990
6261
  return jsx(RecordInput$1, { schema: colSchema, prefix, column });
5991
6262
  }
5992
- if (type === "array") {
5993
- if (variant === "id-picker") {
6263
+ if (type === 'array') {
6264
+ if (variant === 'id-picker') {
5994
6265
  idPickerSanityCheck(column, foreign_key);
5995
6266
  return (jsx(IdPicker, { schema: colSchema, prefix, column, isMultiple: true }));
5996
6267
  }
5997
- if (variant === "tag-picker") {
6268
+ if (variant === 'tag-picker') {
5998
6269
  return jsx(TagPicker, { schema: colSchema, prefix, column });
5999
6270
  }
6000
- if (variant === "file-picker") {
6271
+ if (variant === 'file-picker') {
6001
6272
  return jsx(FilePicker, { schema: colSchema, prefix, column });
6002
6273
  }
6003
- if (variant === "enum-picker") {
6274
+ if (variant === 'date-range') {
6275
+ return jsx(DateRangePicker, { schema: colSchema, prefix, column });
6276
+ }
6277
+ if (variant === 'enum-picker') {
6004
6278
  const { items } = colSchema;
6005
6279
  const { enum: enumItems } = items;
6006
6280
  const enumSchema = {
6007
- type: "string",
6281
+ type: 'string',
6008
6282
  enum: enumItems,
6009
6283
  };
6010
6284
  return (jsx(EnumPicker, { isMultiple: true, schema: enumSchema, prefix, column }));
@@ -6014,7 +6288,7 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
6014
6288
  }
6015
6289
  return jsx(Text, { children: `array ${column}` });
6016
6290
  }
6017
- if (type === "null") {
6291
+ if (type === 'null') {
6018
6292
  return jsx(Text, { children: `null ${column}` });
6019
6293
  }
6020
6294
  return jsx(Text, { children: "missing type" });
@@ -3,7 +3,7 @@ import { JSONSchema7 } from 'json-schema';
3
3
  import { Dispatch, ReactNode, SetStateAction } from 'react';
4
4
  import { FieldValues, Resolver } from 'react-hook-form';
5
5
  import { UseTranslationResponse } from 'react-i18next';
6
- import { DateTimePickerLabels, IdPickerLabels, EnumPickerLabels } from './components/types/CustomJSONSchema7';
6
+ import { DateTimePickerLabels, IdPickerLabels, EnumPickerLabels, FilePickerLabels } from './components/types/CustomJSONSchema7';
7
7
  export interface SchemaFormContext<TData extends FieldValues> {
8
8
  schema: JSONSchema7;
9
9
  serverUrl: string;
@@ -43,6 +43,7 @@ export interface SchemaFormContext<TData extends FieldValues> {
43
43
  dateTimePickerLabels?: DateTimePickerLabels;
44
44
  idPickerLabels?: IdPickerLabels;
45
45
  enumPickerLabels?: EnumPickerLabels;
46
+ filePickerLabels?: FilePickerLabels;
46
47
  ajvResolver: Resolver<FieldValues>;
47
48
  }
48
49
  export declare const SchemaFormContext: import("react").Context<SchemaFormContext<unknown>>;
@@ -1,5 +1,5 @@
1
- import { GridProps } from "@chakra-ui/react";
2
- import { ReactNode } from "react";
1
+ import { GridProps } from '@chakra-ui/react';
2
+ import { ReactNode } from 'react';
3
3
  export interface FileDropzoneProps {
4
4
  children?: ReactNode;
5
5
  onDrop?: ({ files, text }: {
@@ -4,7 +4,7 @@ import { JSONSchema7 } from 'json-schema';
4
4
  import { Dispatch, ReactNode, SetStateAction } from 'react';
5
5
  import { FieldValues, SubmitHandler, UseFormReturn } from 'react-hook-form';
6
6
  import { UseTranslationResponse } from 'react-i18next';
7
- import { CustomJSONSchema7, DateTimePickerLabels, IdPickerLabels, EnumPickerLabels } from '../types/CustomJSONSchema7';
7
+ import { CustomJSONSchema7, DateTimePickerLabels, IdPickerLabels, EnumPickerLabels, FilePickerLabels } from '../types/CustomJSONSchema7';
8
8
  export interface FormRootProps<TData extends FieldValues> {
9
9
  schema: CustomJSONSchema7;
10
10
  serverUrl: string;
@@ -32,6 +32,7 @@ export interface FormRootProps<TData extends FieldValues> {
32
32
  dateTimePickerLabels?: DateTimePickerLabels;
33
33
  idPickerLabels?: IdPickerLabels;
34
34
  enumPickerLabels?: EnumPickerLabels;
35
+ filePickerLabels?: FilePickerLabels;
35
36
  }
36
37
  export interface CustomJSONSchema7Definition extends JSONSchema7 {
37
38
  variant: string;
@@ -48,4 +49,4 @@ export declare const idPickerSanityCheck: (column: string, foreign_key?: {
48
49
  column?: string | undefined;
49
50
  display_column?: string | undefined;
50
51
  } | undefined) => void;
51
- export declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, }: FormRootProps<TData>) => import("react/jsx-runtime").JSX.Element;
52
+ export declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }: FormRootProps<TData>) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ import { InputDefaultProps } from './types';
2
+ export declare const DateRangePicker: ({ column, schema, prefix, }: InputDefaultProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { CustomJSONSchema7 } from "../types/CustomJSONSchema7";
1
+ import { CustomJSONSchema7 } from '../types/CustomJSONSchema7';
2
2
  export interface SchemaRendererProps {
3
3
  column: string;
4
4
  schema: CustomJSONSchema7;
@@ -29,6 +29,17 @@ export interface EnumPickerLabels {
29
29
  emptySearchResult?: string;
30
30
  initialResults?: string;
31
31
  }
32
+ export interface FilePickerLabels {
33
+ fileDropzone?: string;
34
+ browseLibrary?: string;
35
+ dialogTitle?: string;
36
+ searchPlaceholder?: string;
37
+ loading?: string;
38
+ loadingFailed?: string;
39
+ noFilesFound?: string;
40
+ cancel?: string;
41
+ select?: string;
42
+ }
32
43
  export interface CustomJSONSchema7 extends JSONSchema7 {
33
44
  gridColumn?: string;
34
45
  gridRow?: string;
@@ -55,9 +66,23 @@ export interface CustomJSONSchema7 extends JSONSchema7 {
55
66
  formatOptions?: Intl.NumberFormatOptions;
56
67
  numberStorageType?: 'string' | 'number';
57
68
  errorMessages?: Partial<Record<ValidationErrorType | string, string>>;
69
+ filePicker?: FilePickerProps;
58
70
  }
59
71
  export interface TagPickerProps {
60
72
  column: string;
61
73
  schema: CustomJSONSchema7;
62
74
  prefix: string;
63
75
  }
76
+ export interface FilePickerMediaFile {
77
+ id: string;
78
+ name: string;
79
+ url?: string;
80
+ size?: string | number;
81
+ comment?: string;
82
+ type?: string;
83
+ }
84
+ export interface FilePickerProps {
85
+ onFetchFiles?: (search: string) => Promise<FilePickerMediaFile[]>;
86
+ enableMediaLibrary?: boolean;
87
+ filterImageOnly?: boolean;
88
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Format bytes to human-readable string
3
+ * @param bytes - The number of bytes to format
4
+ * @returns Formatted string (e.g., "1.5 KB", "2.3 MB")
5
+ */
6
+ export declare function formatBytes(bytes: number): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsol-oss/react-datatable5",
3
- "version": "12.0.0-beta.75",
3
+ "version": "12.0.0-beta.77",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",