@bsol-oss/react-datatable5 13.0.1-beta.13 → 13.0.1-beta.14
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.js
CHANGED
|
@@ -3203,44 +3203,46 @@ const Checkbox = React__namespace.forwardRef(function Checkbox(props, ref) {
|
|
|
3203
3203
|
return (jsxRuntime.jsxs(react.Checkbox.Root, { ref: rootRef, ...rest, children: [jsxRuntime.jsx(react.Checkbox.HiddenInput, { ref: ref, ...inputProps }), jsxRuntime.jsx(react.Checkbox.Control, { children: icon || jsxRuntime.jsx(react.Checkbox.Indicator, {}) }), children != null && (jsxRuntime.jsx(react.Checkbox.Label, { children: children }))] }));
|
|
3204
3204
|
});
|
|
3205
3205
|
|
|
3206
|
-
|
|
3207
|
-
const getColorForColumn = (id) => {
|
|
3208
|
-
const colors = [
|
|
3209
|
-
'blue',
|
|
3210
|
-
'green',
|
|
3211
|
-
'purple',
|
|
3212
|
-
'orange',
|
|
3213
|
-
'pink',
|
|
3214
|
-
'cyan',
|
|
3215
|
-
'teal',
|
|
3216
|
-
'red',
|
|
3217
|
-
];
|
|
3218
|
-
let hash = 0;
|
|
3219
|
-
for (let i = 0; i < id.length; i++) {
|
|
3220
|
-
hash = id.charCodeAt(i) + ((hash << 5) - hash);
|
|
3221
|
-
}
|
|
3222
|
-
return colors[Math.abs(hash) % colors.length];
|
|
3223
|
-
};
|
|
3224
|
-
const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant, colorPalette, }) => {
|
|
3225
|
-
const { table, tableLabel } = useDataTableContext();
|
|
3206
|
+
const ColumnFilterMenu = ({ displayName, filterOptions, filterVariant, colorPalette, value: controlledValue, onChange, labels, }) => {
|
|
3226
3207
|
const [searchTerm, setSearchTerm] = React.useState('');
|
|
3227
3208
|
const debouncedSearchTerm = usehooks.useDebounce(searchTerm, 300);
|
|
3228
3209
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
3210
|
+
const [internalValue, setInternalValue] = React.useState(undefined);
|
|
3229
3211
|
const [pendingFilterValue, setPendingFilterValue] = React.useState(undefined);
|
|
3230
3212
|
const debouncedFilterValue = usehooks.useDebounce(pendingFilterValue, 300);
|
|
3231
3213
|
const lastAppliedValueRef = React.useRef('__INITIAL__');
|
|
3232
|
-
|
|
3233
|
-
const currentFilterValue =
|
|
3214
|
+
// Use controlled value if provided, otherwise use internal state
|
|
3215
|
+
const currentFilterValue = controlledValue !== undefined ? controlledValue : internalValue;
|
|
3234
3216
|
const isArrayFilter = filterVariant === 'tag';
|
|
3235
|
-
//
|
|
3217
|
+
// Default labels
|
|
3218
|
+
const defaultLabels = {
|
|
3219
|
+
filterByLabel: 'Filter by',
|
|
3220
|
+
filterLabelsPlaceholder: 'Filter labels',
|
|
3221
|
+
noFiltersMatchText: 'No filters match your search',
|
|
3222
|
+
};
|
|
3223
|
+
const finalLabels = { ...defaultLabels, ...labels };
|
|
3224
|
+
// Apply debounced filter value via onChange callback
|
|
3236
3225
|
React.useEffect(() => {
|
|
3237
3226
|
const currentKey = JSON.stringify(debouncedFilterValue);
|
|
3238
3227
|
// Only apply if the value has changed from what we last applied
|
|
3239
3228
|
if (currentKey !== lastAppliedValueRef.current) {
|
|
3240
|
-
|
|
3229
|
+
if (onChange) {
|
|
3230
|
+
onChange(debouncedFilterValue);
|
|
3231
|
+
}
|
|
3232
|
+
else {
|
|
3233
|
+
setInternalValue(debouncedFilterValue);
|
|
3234
|
+
}
|
|
3241
3235
|
lastAppliedValueRef.current = currentKey;
|
|
3242
3236
|
}
|
|
3243
|
-
}, [debouncedFilterValue,
|
|
3237
|
+
}, [debouncedFilterValue, onChange]);
|
|
3238
|
+
// Sync internal value when controlled value changes
|
|
3239
|
+
React.useEffect(() => {
|
|
3240
|
+
if (controlledValue !== undefined) {
|
|
3241
|
+
setInternalValue(controlledValue);
|
|
3242
|
+
setPendingFilterValue(controlledValue);
|
|
3243
|
+
lastAppliedValueRef.current = JSON.stringify(controlledValue);
|
|
3244
|
+
}
|
|
3245
|
+
}, [controlledValue]);
|
|
3244
3246
|
// Debounced filter update function
|
|
3245
3247
|
const debouncedSetFilterValue = (value) => {
|
|
3246
3248
|
setPendingFilterValue(value);
|
|
@@ -3262,9 +3264,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
|
|
|
3262
3264
|
const searchLower = debouncedSearchTerm.toLowerCase();
|
|
3263
3265
|
return filterOptions.filter((option) => option.label.toLowerCase().includes(searchLower));
|
|
3264
3266
|
}, [filterOptions, debouncedSearchTerm]);
|
|
3265
|
-
|
|
3266
|
-
return null;
|
|
3267
|
-
return (jsxRuntime.jsxs(MenuRoot, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [jsxRuntime.jsx(MenuTrigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { variant: "outline", size: "sm", gap: 2, children: [jsxRuntime.jsx(react.Icon, { as: md.MdFilterList }), jsxRuntime.jsxs(react.Text, { children: [displayName, " ", activeCount > 0 && `(${activeCount})`] })] }) }), jsxRuntime.jsx(MenuContent, { maxW: "20rem", minW: "18rem", children: jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 2, p: 2, children: [jsxRuntime.jsxs(react.Heading, { size: "sm", px: 2, children: [tableLabel.filterByLabel, " ", displayName] }), jsxRuntime.jsx(InputGroup, { startElement: jsxRuntime.jsx(react.Icon, { as: md.MdSearch }), children: jsxRuntime.jsx(react.Input, { placeholder: tableLabel.filterLabelsPlaceholder, value: searchTerm, onChange: (e) => setSearchTerm(e.target.value) }) }), jsxRuntime.jsx(react.Box, { maxH: "20rem", overflowY: "auto", css: {
|
|
3267
|
+
return (jsxRuntime.jsxs(MenuRoot, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [jsxRuntime.jsx(MenuTrigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { variant: "outline", size: "sm", gap: 2, children: [jsxRuntime.jsx(react.Icon, { as: md.MdFilterList }), jsxRuntime.jsxs(react.Text, { children: [displayName, " ", activeCount > 0 && `(${activeCount})`] })] }) }), jsxRuntime.jsx(MenuContent, { maxW: "20rem", minW: "18rem", children: jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 2, p: 2, children: [jsxRuntime.jsxs(react.Heading, { size: "sm", px: 2, children: [finalLabels.filterByLabel, " ", displayName] }), jsxRuntime.jsx(InputGroup, { startElement: jsxRuntime.jsx(react.Icon, { as: md.MdSearch }), children: jsxRuntime.jsx(react.Input, { placeholder: finalLabels.filterLabelsPlaceholder, value: searchTerm, onChange: (e) => setSearchTerm(e.target.value) }) }), jsxRuntime.jsx(react.Box, { maxH: "20rem", overflowY: "auto", css: {
|
|
3268
3268
|
'&::-webkit-scrollbar': {
|
|
3269
3269
|
width: '8px',
|
|
3270
3270
|
},
|
|
@@ -3278,7 +3278,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
|
|
|
3278
3278
|
'&::-webkit-scrollbar-thumb:hover': {
|
|
3279
3279
|
background: 'var(--chakra-colors-border-subtle)',
|
|
3280
3280
|
},
|
|
3281
|
-
}, children: jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 1, children: filteredOptions.length === 0 ? (jsxRuntime.jsx(react.Text, { px: 2, py: 4, color: "fg.muted", textAlign: "center", children:
|
|
3281
|
+
}, children: jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 1, children: filteredOptions.length === 0 ? (jsxRuntime.jsx(react.Text, { px: 2, py: 4, color: "fg.muted", textAlign: "center", children: finalLabels.noFiltersMatchText })) : (filteredOptions.map((option) => {
|
|
3282
3282
|
const isActive = isArrayFilter
|
|
3283
3283
|
? currentFilterValue?.includes(option.value) ?? false
|
|
3284
3284
|
: currentFilterValue === option.value;
|
|
@@ -3356,8 +3356,27 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
|
|
|
3356
3356
|
} }) }), jsxRuntime.jsx(react.Box, { flex: 1, minW: 0, children: jsxRuntime.jsxs(react.HStack, { gap: 2, align: "center", children: [jsxRuntime.jsx(react.Box, { w: 3, h: 3, borderRadius: "full", bg: `${colorPalette}.500`, flexShrink: 0 }), jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "medium", truncate: true, children: option.label })] }) })] }) }, option.value));
|
|
3357
3357
|
})) }) })] }) })] }));
|
|
3358
3358
|
};
|
|
3359
|
+
|
|
3360
|
+
// Generate a color based on column id for visual distinction
|
|
3361
|
+
const getColorForColumn = (id) => {
|
|
3362
|
+
const colors = [
|
|
3363
|
+
'blue',
|
|
3364
|
+
'green',
|
|
3365
|
+
'purple',
|
|
3366
|
+
'orange',
|
|
3367
|
+
'pink',
|
|
3368
|
+
'cyan',
|
|
3369
|
+
'teal',
|
|
3370
|
+
'red',
|
|
3371
|
+
];
|
|
3372
|
+
let hash = 0;
|
|
3373
|
+
for (let i = 0; i < id.length; i++) {
|
|
3374
|
+
hash = id.charCodeAt(i) + ((hash << 5) - hash);
|
|
3375
|
+
}
|
|
3376
|
+
return colors[Math.abs(hash) % colors.length];
|
|
3377
|
+
};
|
|
3359
3378
|
const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
|
|
3360
|
-
const { table } = useDataTableContext();
|
|
3379
|
+
const { table, tableLabel } = useDataTableContext();
|
|
3361
3380
|
// Get columns from filterTagsOptions
|
|
3362
3381
|
const columnsWithFilters = React.useMemo(() => {
|
|
3363
3382
|
if (filterTagsOptions.length === 0) {
|
|
@@ -3372,26 +3391,37 @@ const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
|
|
|
3372
3391
|
const meta = column.columnDef.meta;
|
|
3373
3392
|
const displayName = meta?.displayName ?? column.id;
|
|
3374
3393
|
const filterVariant = meta?.filterVariant;
|
|
3394
|
+
if (!column) {
|
|
3395
|
+
return null;
|
|
3396
|
+
}
|
|
3375
3397
|
return {
|
|
3376
3398
|
columnId: option.column,
|
|
3377
3399
|
displayName,
|
|
3378
3400
|
filterOptions: option.options,
|
|
3379
|
-
filterVariant: filterVariant === 'tag' ? 'tag' : 'select',
|
|
3401
|
+
filterVariant: (filterVariant === 'tag' ? 'tag' : 'select'),
|
|
3380
3402
|
colorPalette: getColorForColumn(option.column),
|
|
3403
|
+
column,
|
|
3381
3404
|
};
|
|
3382
3405
|
})
|
|
3383
|
-
.filter((col) => col !== null);
|
|
3406
|
+
.filter((col) => col !== null && col.column !== null && col.column !== undefined);
|
|
3384
3407
|
}, [table, filterTagsOptions]);
|
|
3385
3408
|
if (columnsWithFilters.length === 0) {
|
|
3386
3409
|
return null;
|
|
3387
3410
|
}
|
|
3388
|
-
return (jsxRuntime.jsx(react.Flex, { gap: 2, flexWrap: "wrap", children: columnsWithFilters.map((column) =>
|
|
3411
|
+
return (jsxRuntime.jsx(react.Flex, { gap: 2, flexWrap: "wrap", children: columnsWithFilters.map((column) => {
|
|
3412
|
+
const filterValue = column.column.getFilterValue();
|
|
3413
|
+
return (jsxRuntime.jsx(ColumnFilterMenu, { displayName: column.displayName, filterOptions: column.filterOptions, filterVariant: column.filterVariant, colorPalette: column.colorPalette, value: filterValue, onChange: (value) => column.column.setFilterValue(value), labels: {
|
|
3414
|
+
filterByLabel: tableLabel.filterByLabel,
|
|
3415
|
+
filterLabelsPlaceholder: tableLabel.filterLabelsPlaceholder,
|
|
3416
|
+
noFiltersMatchText: tableLabel.noFiltersMatchText,
|
|
3417
|
+
} }, column.columnId));
|
|
3418
|
+
}) }));
|
|
3389
3419
|
};
|
|
3390
3420
|
|
|
3391
3421
|
const TableControls = ({ fitTableWidth = false, fitTableHeight = false, children = jsxRuntime.jsx(jsxRuntime.Fragment, {}), showGlobalFilter = false, showFilter = false, showFilterName = false, showFilterTags = false, showReload = false, showPagination = true, showPageSizeControl = true, showPageCountText = true, showView = true, filterTagsOptions = [], extraItems = jsxRuntime.jsx(jsxRuntime.Fragment, {}), loading = false, hasError = false, gridProps = {}, }) => {
|
|
3392
3422
|
const { tableLabel, table } = useDataTableContext();
|
|
3393
3423
|
const { hasErrorText } = tableLabel;
|
|
3394
|
-
return (jsxRuntime.jsxs(react.Grid, { templateRows: 'auto 1fr', width: fitTableWidth ? 'fit-content' : '100%', height: fitTableHeight ? 'fit-content' : '100%', gap: '0.5rem', p: 1, ...gridProps, children: [jsxRuntime.jsxs(react.Flex, { flexFlow: 'column', gap: 2, children: [jsxRuntime.jsxs(react.Flex, { justifyContent: 'space-between', children: [jsxRuntime.jsx(react.Box, { children: showView && jsxRuntime.jsx(ViewDialog, { icon: jsxRuntime.jsx(md.MdOutlineViewColumn, {}) }) }), jsxRuntime.jsxs(react.Flex, { gap: '0.5rem', alignItems: 'center', justifySelf: 'end', children: [loading && jsxRuntime.jsx(react.Spinner, { size: 'sm' }), hasError && (jsxRuntime.jsx(Tooltip, { content: hasErrorText, children: jsxRuntime.jsx(react.Icon, { as: bs.BsExclamationCircleFill, color: 'red.400' }) })), showGlobalFilter && jsxRuntime.jsx(GlobalFilter, {}), showFilter && jsxRuntime.jsx(FilterDialog, {}), showReload && jsxRuntime.jsx(ReloadButton, {}), extraItems] })] }), showFilterTags && (jsxRuntime.jsx(
|
|
3424
|
+
return (jsxRuntime.jsxs(react.Grid, { templateRows: 'auto 1fr', width: fitTableWidth ? 'fit-content' : '100%', height: fitTableHeight ? 'fit-content' : '100%', gap: '0.5rem', p: 1, ...gridProps, children: [jsxRuntime.jsxs(react.Flex, { flexFlow: 'column', gap: 2, children: [jsxRuntime.jsxs(react.Flex, { justifyContent: 'space-between', children: [jsxRuntime.jsx(react.Box, { children: showView && jsxRuntime.jsx(ViewDialog, { icon: jsxRuntime.jsx(md.MdOutlineViewColumn, {}) }) }), jsxRuntime.jsxs(react.Flex, { gap: '0.5rem', alignItems: 'center', justifySelf: 'end', children: [loading && jsxRuntime.jsx(react.Spinner, { size: 'sm' }), hasError && (jsxRuntime.jsx(Tooltip, { content: hasErrorText, children: jsxRuntime.jsx(react.Icon, { as: bs.BsExclamationCircleFill, color: 'red.400' }) })), showGlobalFilter && jsxRuntime.jsx(GlobalFilter, {}), showFilter && jsxRuntime.jsx(FilterDialog, {}), showReload && jsxRuntime.jsx(ReloadButton, {}), extraItems] })] }), showFilterTags && (jsxRuntime.jsx(TableFilterTags, { filterTagsOptions: filterTagsOptions }))] }), jsxRuntime.jsx(react.Grid, { children: children }), (showPageSizeControl || showPageCountText || showPagination) && (jsxRuntime.jsxs(react.Flex, { justifyContent: 'space-between', children: [jsxRuntime.jsxs(react.Flex, { gap: '1rem', alignItems: 'center', children: [showPageSizeControl && jsxRuntime.jsx(PageSizeControl, {}), showPageCountText && jsxRuntime.jsx(RowCountText, {})] }), jsxRuntime.jsx(react.Box, { justifySelf: 'end', children: showPagination && jsxRuntime.jsx(Pagination, {}) })] }))] }));
|
|
3395
3425
|
};
|
|
3396
3426
|
|
|
3397
3427
|
const EmptyState$1 = React__namespace.forwardRef(function EmptyState(props, ref) {
|
|
@@ -6152,15 +6182,29 @@ const defaultRenderDisplay = (item) => {
|
|
|
6152
6182
|
for (const field of displayFields) {
|
|
6153
6183
|
if (obj[field] !== undefined && obj[field] !== null) {
|
|
6154
6184
|
const value = obj[field];
|
|
6155
|
-
// Return the value if it's a string or number
|
|
6185
|
+
// Return the value if it's a string or number
|
|
6156
6186
|
if (typeof value === 'string' || typeof value === 'number') {
|
|
6157
6187
|
return String(value);
|
|
6158
6188
|
}
|
|
6189
|
+
// If the value is an object, show warning and recommend custom renderDisplay
|
|
6190
|
+
if (typeof value === 'object' &&
|
|
6191
|
+
!Array.isArray(value) &&
|
|
6192
|
+
!(value instanceof Date)) {
|
|
6193
|
+
console.warn(`[CustomJSONSchema7] Display field "${field}" contains an object value. Consider providing a custom \`renderDisplay\` function in your schema to properly render this item. Field: ${field}, Value: ${JSON.stringify(value).substring(0, 100)}${JSON.stringify(value).length > 100 ? '...' : ''}`);
|
|
6194
|
+
// Still return the stringified value for now
|
|
6195
|
+
return JSON.stringify(value);
|
|
6196
|
+
}
|
|
6159
6197
|
}
|
|
6160
6198
|
}
|
|
6161
6199
|
// If no display field found, fall back to JSON.stringify
|
|
6162
6200
|
return JSON.stringify(item);
|
|
6163
6201
|
}
|
|
6202
|
+
// For strings that look like JSON, show warning and recommend custom renderDisplay
|
|
6203
|
+
if (typeof item === 'string' &&
|
|
6204
|
+
(item.trim().startsWith('{') || item.trim().startsWith('['))) {
|
|
6205
|
+
console.warn(`[CustomJSONSchema7] Item appears to be a JSON string. Consider providing a custom \`renderDisplay\` function in your schema to properly render this item. Current value: ${item.substring(0, 100)}${item.length > 100 ? '...' : ''}`);
|
|
6206
|
+
return item;
|
|
6207
|
+
}
|
|
6164
6208
|
// For non-objects (primitives, arrays, dates), use JSON.stringify
|
|
6165
6209
|
return JSON.stringify(item);
|
|
6166
6210
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -3183,44 +3183,46 @@ const Checkbox = React.forwardRef(function Checkbox(props, ref) {
|
|
|
3183
3183
|
return (jsxs(Checkbox$1.Root, { ref: rootRef, ...rest, children: [jsx(Checkbox$1.HiddenInput, { ref: ref, ...inputProps }), jsx(Checkbox$1.Control, { children: icon || jsx(Checkbox$1.Indicator, {}) }), children != null && (jsx(Checkbox$1.Label, { children: children }))] }));
|
|
3184
3184
|
});
|
|
3185
3185
|
|
|
3186
|
-
|
|
3187
|
-
const getColorForColumn = (id) => {
|
|
3188
|
-
const colors = [
|
|
3189
|
-
'blue',
|
|
3190
|
-
'green',
|
|
3191
|
-
'purple',
|
|
3192
|
-
'orange',
|
|
3193
|
-
'pink',
|
|
3194
|
-
'cyan',
|
|
3195
|
-
'teal',
|
|
3196
|
-
'red',
|
|
3197
|
-
];
|
|
3198
|
-
let hash = 0;
|
|
3199
|
-
for (let i = 0; i < id.length; i++) {
|
|
3200
|
-
hash = id.charCodeAt(i) + ((hash << 5) - hash);
|
|
3201
|
-
}
|
|
3202
|
-
return colors[Math.abs(hash) % colors.length];
|
|
3203
|
-
};
|
|
3204
|
-
const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant, colorPalette, }) => {
|
|
3205
|
-
const { table, tableLabel } = useDataTableContext();
|
|
3186
|
+
const ColumnFilterMenu = ({ displayName, filterOptions, filterVariant, colorPalette, value: controlledValue, onChange, labels, }) => {
|
|
3206
3187
|
const [searchTerm, setSearchTerm] = useState('');
|
|
3207
3188
|
const debouncedSearchTerm = useDebounce(searchTerm, 300);
|
|
3208
3189
|
const [isOpen, setIsOpen] = useState(false);
|
|
3190
|
+
const [internalValue, setInternalValue] = useState(undefined);
|
|
3209
3191
|
const [pendingFilterValue, setPendingFilterValue] = useState(undefined);
|
|
3210
3192
|
const debouncedFilterValue = useDebounce(pendingFilterValue, 300);
|
|
3211
3193
|
const lastAppliedValueRef = useRef('__INITIAL__');
|
|
3212
|
-
|
|
3213
|
-
const currentFilterValue =
|
|
3194
|
+
// Use controlled value if provided, otherwise use internal state
|
|
3195
|
+
const currentFilterValue = controlledValue !== undefined ? controlledValue : internalValue;
|
|
3214
3196
|
const isArrayFilter = filterVariant === 'tag';
|
|
3215
|
-
//
|
|
3197
|
+
// Default labels
|
|
3198
|
+
const defaultLabels = {
|
|
3199
|
+
filterByLabel: 'Filter by',
|
|
3200
|
+
filterLabelsPlaceholder: 'Filter labels',
|
|
3201
|
+
noFiltersMatchText: 'No filters match your search',
|
|
3202
|
+
};
|
|
3203
|
+
const finalLabels = { ...defaultLabels, ...labels };
|
|
3204
|
+
// Apply debounced filter value via onChange callback
|
|
3216
3205
|
useEffect(() => {
|
|
3217
3206
|
const currentKey = JSON.stringify(debouncedFilterValue);
|
|
3218
3207
|
// Only apply if the value has changed from what we last applied
|
|
3219
3208
|
if (currentKey !== lastAppliedValueRef.current) {
|
|
3220
|
-
|
|
3209
|
+
if (onChange) {
|
|
3210
|
+
onChange(debouncedFilterValue);
|
|
3211
|
+
}
|
|
3212
|
+
else {
|
|
3213
|
+
setInternalValue(debouncedFilterValue);
|
|
3214
|
+
}
|
|
3221
3215
|
lastAppliedValueRef.current = currentKey;
|
|
3222
3216
|
}
|
|
3223
|
-
}, [debouncedFilterValue,
|
|
3217
|
+
}, [debouncedFilterValue, onChange]);
|
|
3218
|
+
// Sync internal value when controlled value changes
|
|
3219
|
+
useEffect(() => {
|
|
3220
|
+
if (controlledValue !== undefined) {
|
|
3221
|
+
setInternalValue(controlledValue);
|
|
3222
|
+
setPendingFilterValue(controlledValue);
|
|
3223
|
+
lastAppliedValueRef.current = JSON.stringify(controlledValue);
|
|
3224
|
+
}
|
|
3225
|
+
}, [controlledValue]);
|
|
3224
3226
|
// Debounced filter update function
|
|
3225
3227
|
const debouncedSetFilterValue = (value) => {
|
|
3226
3228
|
setPendingFilterValue(value);
|
|
@@ -3242,9 +3244,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
|
|
|
3242
3244
|
const searchLower = debouncedSearchTerm.toLowerCase();
|
|
3243
3245
|
return filterOptions.filter((option) => option.label.toLowerCase().includes(searchLower));
|
|
3244
3246
|
}, [filterOptions, debouncedSearchTerm]);
|
|
3245
|
-
|
|
3246
|
-
return null;
|
|
3247
|
-
return (jsxs(MenuRoot, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [jsx(MenuTrigger, { asChild: true, children: jsxs(Button$1, { variant: "outline", size: "sm", gap: 2, children: [jsx(Icon, { as: MdFilterList }), jsxs(Text, { children: [displayName, " ", activeCount > 0 && `(${activeCount})`] })] }) }), jsx(MenuContent, { maxW: "20rem", minW: "18rem", children: jsxs(VStack, { align: "stretch", gap: 2, p: 2, children: [jsxs(Heading, { size: "sm", px: 2, children: [tableLabel.filterByLabel, " ", displayName] }), jsx(InputGroup, { startElement: jsx(Icon, { as: MdSearch }), children: jsx(Input, { placeholder: tableLabel.filterLabelsPlaceholder, value: searchTerm, onChange: (e) => setSearchTerm(e.target.value) }) }), jsx(Box, { maxH: "20rem", overflowY: "auto", css: {
|
|
3247
|
+
return (jsxs(MenuRoot, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [jsx(MenuTrigger, { asChild: true, children: jsxs(Button$1, { variant: "outline", size: "sm", gap: 2, children: [jsx(Icon, { as: MdFilterList }), jsxs(Text, { children: [displayName, " ", activeCount > 0 && `(${activeCount})`] })] }) }), jsx(MenuContent, { maxW: "20rem", minW: "18rem", children: jsxs(VStack, { align: "stretch", gap: 2, p: 2, children: [jsxs(Heading, { size: "sm", px: 2, children: [finalLabels.filterByLabel, " ", displayName] }), jsx(InputGroup, { startElement: jsx(Icon, { as: MdSearch }), children: jsx(Input, { placeholder: finalLabels.filterLabelsPlaceholder, value: searchTerm, onChange: (e) => setSearchTerm(e.target.value) }) }), jsx(Box, { maxH: "20rem", overflowY: "auto", css: {
|
|
3248
3248
|
'&::-webkit-scrollbar': {
|
|
3249
3249
|
width: '8px',
|
|
3250
3250
|
},
|
|
@@ -3258,7 +3258,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
|
|
|
3258
3258
|
'&::-webkit-scrollbar-thumb:hover': {
|
|
3259
3259
|
background: 'var(--chakra-colors-border-subtle)',
|
|
3260
3260
|
},
|
|
3261
|
-
}, children: jsx(VStack, { align: "stretch", gap: 1, children: filteredOptions.length === 0 ? (jsx(Text, { px: 2, py: 4, color: "fg.muted", textAlign: "center", children:
|
|
3261
|
+
}, children: jsx(VStack, { align: "stretch", gap: 1, children: filteredOptions.length === 0 ? (jsx(Text, { px: 2, py: 4, color: "fg.muted", textAlign: "center", children: finalLabels.noFiltersMatchText })) : (filteredOptions.map((option) => {
|
|
3262
3262
|
const isActive = isArrayFilter
|
|
3263
3263
|
? currentFilterValue?.includes(option.value) ?? false
|
|
3264
3264
|
: currentFilterValue === option.value;
|
|
@@ -3336,8 +3336,27 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
|
|
|
3336
3336
|
} }) }), jsx(Box, { flex: 1, minW: 0, children: jsxs(HStack, { gap: 2, align: "center", children: [jsx(Box, { w: 3, h: 3, borderRadius: "full", bg: `${colorPalette}.500`, flexShrink: 0 }), jsx(Text, { fontSize: "sm", fontWeight: "medium", truncate: true, children: option.label })] }) })] }) }, option.value));
|
|
3337
3337
|
})) }) })] }) })] }));
|
|
3338
3338
|
};
|
|
3339
|
+
|
|
3340
|
+
// Generate a color based on column id for visual distinction
|
|
3341
|
+
const getColorForColumn = (id) => {
|
|
3342
|
+
const colors = [
|
|
3343
|
+
'blue',
|
|
3344
|
+
'green',
|
|
3345
|
+
'purple',
|
|
3346
|
+
'orange',
|
|
3347
|
+
'pink',
|
|
3348
|
+
'cyan',
|
|
3349
|
+
'teal',
|
|
3350
|
+
'red',
|
|
3351
|
+
];
|
|
3352
|
+
let hash = 0;
|
|
3353
|
+
for (let i = 0; i < id.length; i++) {
|
|
3354
|
+
hash = id.charCodeAt(i) + ((hash << 5) - hash);
|
|
3355
|
+
}
|
|
3356
|
+
return colors[Math.abs(hash) % colors.length];
|
|
3357
|
+
};
|
|
3339
3358
|
const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
|
|
3340
|
-
const { table } = useDataTableContext();
|
|
3359
|
+
const { table, tableLabel } = useDataTableContext();
|
|
3341
3360
|
// Get columns from filterTagsOptions
|
|
3342
3361
|
const columnsWithFilters = useMemo(() => {
|
|
3343
3362
|
if (filterTagsOptions.length === 0) {
|
|
@@ -3352,26 +3371,37 @@ const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
|
|
|
3352
3371
|
const meta = column.columnDef.meta;
|
|
3353
3372
|
const displayName = meta?.displayName ?? column.id;
|
|
3354
3373
|
const filterVariant = meta?.filterVariant;
|
|
3374
|
+
if (!column) {
|
|
3375
|
+
return null;
|
|
3376
|
+
}
|
|
3355
3377
|
return {
|
|
3356
3378
|
columnId: option.column,
|
|
3357
3379
|
displayName,
|
|
3358
3380
|
filterOptions: option.options,
|
|
3359
|
-
filterVariant: filterVariant === 'tag' ? 'tag' : 'select',
|
|
3381
|
+
filterVariant: (filterVariant === 'tag' ? 'tag' : 'select'),
|
|
3360
3382
|
colorPalette: getColorForColumn(option.column),
|
|
3383
|
+
column,
|
|
3361
3384
|
};
|
|
3362
3385
|
})
|
|
3363
|
-
.filter((col) => col !== null);
|
|
3386
|
+
.filter((col) => col !== null && col.column !== null && col.column !== undefined);
|
|
3364
3387
|
}, [table, filterTagsOptions]);
|
|
3365
3388
|
if (columnsWithFilters.length === 0) {
|
|
3366
3389
|
return null;
|
|
3367
3390
|
}
|
|
3368
|
-
return (jsx(Flex, { gap: 2, flexWrap: "wrap", children: columnsWithFilters.map((column) =>
|
|
3391
|
+
return (jsx(Flex, { gap: 2, flexWrap: "wrap", children: columnsWithFilters.map((column) => {
|
|
3392
|
+
const filterValue = column.column.getFilterValue();
|
|
3393
|
+
return (jsx(ColumnFilterMenu, { displayName: column.displayName, filterOptions: column.filterOptions, filterVariant: column.filterVariant, colorPalette: column.colorPalette, value: filterValue, onChange: (value) => column.column.setFilterValue(value), labels: {
|
|
3394
|
+
filterByLabel: tableLabel.filterByLabel,
|
|
3395
|
+
filterLabelsPlaceholder: tableLabel.filterLabelsPlaceholder,
|
|
3396
|
+
noFiltersMatchText: tableLabel.noFiltersMatchText,
|
|
3397
|
+
} }, column.columnId));
|
|
3398
|
+
}) }));
|
|
3369
3399
|
};
|
|
3370
3400
|
|
|
3371
3401
|
const TableControls = ({ fitTableWidth = false, fitTableHeight = false, children = jsx(Fragment, {}), showGlobalFilter = false, showFilter = false, showFilterName = false, showFilterTags = false, showReload = false, showPagination = true, showPageSizeControl = true, showPageCountText = true, showView = true, filterTagsOptions = [], extraItems = jsx(Fragment, {}), loading = false, hasError = false, gridProps = {}, }) => {
|
|
3372
3402
|
const { tableLabel, table } = useDataTableContext();
|
|
3373
3403
|
const { hasErrorText } = tableLabel;
|
|
3374
|
-
return (jsxs(Grid, { templateRows: 'auto 1fr', width: fitTableWidth ? 'fit-content' : '100%', height: fitTableHeight ? 'fit-content' : '100%', gap: '0.5rem', p: 1, ...gridProps, children: [jsxs(Flex, { flexFlow: 'column', gap: 2, children: [jsxs(Flex, { justifyContent: 'space-between', children: [jsx(Box, { children: showView && jsx(ViewDialog, { icon: jsx(MdOutlineViewColumn, {}) }) }), jsxs(Flex, { gap: '0.5rem', alignItems: 'center', justifySelf: 'end', children: [loading && jsx(Spinner, { size: 'sm' }), hasError && (jsx(Tooltip, { content: hasErrorText, children: jsx(Icon, { as: BsExclamationCircleFill, color: 'red.400' }) })), showGlobalFilter && jsx(GlobalFilter, {}), showFilter && jsx(FilterDialog, {}), showReload && jsx(ReloadButton, {}), extraItems] })] }), showFilterTags && (jsx(
|
|
3404
|
+
return (jsxs(Grid, { templateRows: 'auto 1fr', width: fitTableWidth ? 'fit-content' : '100%', height: fitTableHeight ? 'fit-content' : '100%', gap: '0.5rem', p: 1, ...gridProps, children: [jsxs(Flex, { flexFlow: 'column', gap: 2, children: [jsxs(Flex, { justifyContent: 'space-between', children: [jsx(Box, { children: showView && jsx(ViewDialog, { icon: jsx(MdOutlineViewColumn, {}) }) }), jsxs(Flex, { gap: '0.5rem', alignItems: 'center', justifySelf: 'end', children: [loading && jsx(Spinner, { size: 'sm' }), hasError && (jsx(Tooltip, { content: hasErrorText, children: jsx(Icon, { as: BsExclamationCircleFill, color: 'red.400' }) })), showGlobalFilter && jsx(GlobalFilter, {}), showFilter && jsx(FilterDialog, {}), showReload && jsx(ReloadButton, {}), extraItems] })] }), showFilterTags && (jsx(TableFilterTags, { filterTagsOptions: filterTagsOptions }))] }), jsx(Grid, { children: children }), (showPageSizeControl || showPageCountText || showPagination) && (jsxs(Flex, { justifyContent: 'space-between', children: [jsxs(Flex, { gap: '1rem', alignItems: 'center', children: [showPageSizeControl && jsx(PageSizeControl, {}), showPageCountText && jsx(RowCountText, {})] }), jsx(Box, { justifySelf: 'end', children: showPagination && jsx(Pagination, {}) })] }))] }));
|
|
3375
3405
|
};
|
|
3376
3406
|
|
|
3377
3407
|
const EmptyState$1 = React.forwardRef(function EmptyState(props, ref) {
|
|
@@ -6132,15 +6162,29 @@ const defaultRenderDisplay = (item) => {
|
|
|
6132
6162
|
for (const field of displayFields) {
|
|
6133
6163
|
if (obj[field] !== undefined && obj[field] !== null) {
|
|
6134
6164
|
const value = obj[field];
|
|
6135
|
-
// Return the value if it's a string or number
|
|
6165
|
+
// Return the value if it's a string or number
|
|
6136
6166
|
if (typeof value === 'string' || typeof value === 'number') {
|
|
6137
6167
|
return String(value);
|
|
6138
6168
|
}
|
|
6169
|
+
// If the value is an object, show warning and recommend custom renderDisplay
|
|
6170
|
+
if (typeof value === 'object' &&
|
|
6171
|
+
!Array.isArray(value) &&
|
|
6172
|
+
!(value instanceof Date)) {
|
|
6173
|
+
console.warn(`[CustomJSONSchema7] Display field "${field}" contains an object value. Consider providing a custom \`renderDisplay\` function in your schema to properly render this item. Field: ${field}, Value: ${JSON.stringify(value).substring(0, 100)}${JSON.stringify(value).length > 100 ? '...' : ''}`);
|
|
6174
|
+
// Still return the stringified value for now
|
|
6175
|
+
return JSON.stringify(value);
|
|
6176
|
+
}
|
|
6139
6177
|
}
|
|
6140
6178
|
}
|
|
6141
6179
|
// If no display field found, fall back to JSON.stringify
|
|
6142
6180
|
return JSON.stringify(item);
|
|
6143
6181
|
}
|
|
6182
|
+
// For strings that look like JSON, show warning and recommend custom renderDisplay
|
|
6183
|
+
if (typeof item === 'string' &&
|
|
6184
|
+
(item.trim().startsWith('{') || item.trim().startsWith('['))) {
|
|
6185
|
+
console.warn(`[CustomJSONSchema7] Item appears to be a JSON string. Consider providing a custom \`renderDisplay\` function in your schema to properly render this item. Current value: ${item.substring(0, 100)}${item.length > 100 ? '...' : ''}`);
|
|
6186
|
+
return item;
|
|
6187
|
+
}
|
|
6144
6188
|
// For non-objects (primitives, arrays, dates), use JSON.stringify
|
|
6145
6189
|
return JSON.stringify(item);
|
|
6146
6190
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ColumnFilterMenuLabels {
|
|
2
|
+
filterByLabel?: string;
|
|
3
|
+
filterLabelsPlaceholder?: string;
|
|
4
|
+
noFiltersMatchText?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ColumnFilterMenuProps {
|
|
7
|
+
displayName: string;
|
|
8
|
+
filterOptions: {
|
|
9
|
+
label: string;
|
|
10
|
+
value: string;
|
|
11
|
+
}[];
|
|
12
|
+
filterVariant?: 'select' | 'tag';
|
|
13
|
+
colorPalette: string;
|
|
14
|
+
value?: string[] | string | undefined;
|
|
15
|
+
onChange?: (value: string[] | string | undefined) => void;
|
|
16
|
+
labels?: ColumnFilterMenuLabels;
|
|
17
|
+
}
|
|
18
|
+
export declare const ColumnFilterMenu: ({ displayName, filterOptions, filterVariant, colorPalette, value: controlledValue, onChange, labels, }: ColumnFilterMenuProps) => import("react/jsx-runtime").JSX.Element;
|