tp-react-elements 3.0.0 → 3.0.1
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/_virtual/index.esm13.js +2 -2
- package/dist/_virtual/index.esm14.js +2 -2
- package/dist/_virtual/index.esm4.js +2 -2
- package/dist/_virtual/index.esm6.js +2 -2
- package/dist/_virtual/index.esm7.js +2 -2
- package/dist/components/DatePicker/index.d.ts +1 -0
- package/dist/components/DatePicker/index.esm.js +2 -1
- package/dist/components/Form/FormConstants.esm.js +1 -0
- package/dist/components/Form/FormRender.esm.js +6 -2
- package/dist/components/FormComponents/DatePicker/MonthPickerMultiRender.d.ts +6 -0
- package/dist/components/FormComponents/DatePicker/MonthPickerMultiRender.esm.js +117 -0
- package/dist/components/FormComponents/index.esm.js +13 -5
- package/dist/components/PaginatedGrid/DynamicFilterBar.d.ts +12 -4
- package/dist/components/PaginatedGrid/DynamicFilterBar.esm.js +48 -16
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.esm.js +23 -13
- package/dist/node_modules/react-date-range/dist/components/Calendar/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DateInput/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DateRange/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DefinedRange/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/Month/index.esm.js +1 -1
- package/dist/utils/Interface/FormInterface.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DateInput = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateInput as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Month = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { Month as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DateRange = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateRange as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DefinedRange = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DefinedRange as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Calendar = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { Calendar as __exports };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { default as DatepickerWrapperV2 } from '../FormComponents/DatePicker/DatepickerWrapperV2';
|
|
2
2
|
export { default as Monthpickerrender } from '../FormComponents/DatePicker/Monthpickerrender';
|
|
3
|
+
export { default as MonthPickerMultiRender } from '../FormComponents/DatePicker/MonthPickerMultiRender';
|
|
3
4
|
export { default as YearPickerField } from '../FormComponents/YearPickerField/YearPickerField';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import DatepickerWrapperV2 from '../FormComponents/DatePicker/DatepickerWrapperV2.esm.js';
|
|
2
2
|
import Monthpickerrender from '../FormComponents/DatePicker/Monthpickerrender.esm.js';
|
|
3
|
+
import MonthPickerMultiRender from '../FormComponents/DatePicker/MonthPickerMultiRender.esm.js';
|
|
3
4
|
export { default as YearPickerField } from '../FormComponents/YearPickerField/YearPickerField.esm.js';
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
|
|
7
|
-
export { DatepickerWrapperV2, Monthpickerrender };
|
|
8
|
+
export { DatepickerWrapperV2, MonthPickerMultiRender, Monthpickerrender };
|
|
@@ -282,6 +282,7 @@ const useFormValidatingContext = (formArray) => {
|
|
|
282
282
|
renderCustomError(field);
|
|
283
283
|
break;
|
|
284
284
|
case 'monthpicker':
|
|
285
|
+
case 'monthpicker-multi':
|
|
285
286
|
initialValues[field.name] = null;
|
|
286
287
|
if (field.required && field.errorMessage) {
|
|
287
288
|
validationShape[field.name] = (validationShape[field.name] || Yup.string())
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import require$$0__default, { lazy, Suspense } from 'react';
|
|
3
|
+
import FormCheckBoxGroup from '../FormComponents/FormCheckBoxGroup/FormCheckBoxGroup.esm.js';
|
|
3
4
|
import SingleSelect from '../FormComponents/Select/SingleSelect.esm.js';
|
|
4
5
|
import MultiSelectAutocomplete from '../FormComponents/Select/MultiSelectAutocomplete.esm.js';
|
|
5
6
|
import SingleSelectNonAutoComplete from '../FormComponents/Select/SingleSelectNonAutoComplete.esm.js';
|
|
@@ -13,7 +14,6 @@ import DatetimepickerWrapper from '../FormComponents/DatePicker/DatetimepickerWr
|
|
|
13
14
|
import FormTextAreaField from '../FormComponents/FormTextAreaField/FormTextAreaField.esm.js';
|
|
14
15
|
import FormCheckBox from '../FormComponents/FormCheckBox/FormCheckBox.esm.js';
|
|
15
16
|
import FormRadioGroup from '../FormComponents/FormRadioGroup/FormRadioGroup.esm.js';
|
|
16
|
-
import FormCheckBoxGroup from '../FormComponents/FormCheckBoxGroup/FormCheckBoxGroup.esm.js';
|
|
17
17
|
import FormNumberFieldDecimal from '../FormComponents/FormNumberField/FormNumberFieldDecimal.esm.js';
|
|
18
18
|
import FormNumberField from '../FormComponents/FormNumberField/FormNumberField.esm.js';
|
|
19
19
|
import PasswordField from '../FormComponents/PasswordField/PasswordField.esm.js';
|
|
@@ -30,6 +30,7 @@ const FormRenderMultiFileUpload = lazy(() => import('../FileUpload/index.esm.js'
|
|
|
30
30
|
const MultiFileWithPreview = lazy(() => import('../FileUpload/index.esm.js').then((m) => ({ default: m.MultiFileWithPreview })));
|
|
31
31
|
const DatepickerWrapperV2 = lazy(() => import('../DatePicker/index.esm.js').then((m) => ({ default: m.DatepickerWrapperV2 })));
|
|
32
32
|
const Monthpickerrender = lazy(() => import('../DatePicker/index.esm.js').then((m) => ({ default: m.Monthpickerrender })));
|
|
33
|
+
const MonthPickerMultiRender = lazy(() => import('../DatePicker/index.esm.js').then((m) => ({ default: m.MonthPickerMultiRender })));
|
|
33
34
|
const TimePickerFieldWrapper = lazy(() => import('../TimePicker/index.esm.js').then((m) => ({ default: m.TimePicker })));
|
|
34
35
|
// Loading fallback component
|
|
35
36
|
const LoadingFallback = () => jsx("div", { style: { minHeight: '36px' }, children: "Loading..." });
|
|
@@ -119,9 +120,12 @@ const RenderForm = (props) => {
|
|
|
119
120
|
// Time picker
|
|
120
121
|
case 'timepicker':
|
|
121
122
|
return (jsx(Suspense, { fallback: jsx(LoadingFallback, {}), children: jsx(TimePickerFieldWrapper, { props: props, variant: variant }) }));
|
|
122
|
-
// Month picker
|
|
123
|
+
// Month picker (single month)
|
|
123
124
|
case 'monthpicker':
|
|
124
125
|
return (jsx(Suspense, { fallback: jsx(LoadingFallback, {}), children: jsx(Monthpickerrender, { props: props, variant: variant }) }));
|
|
126
|
+
// Month picker (multiple months)
|
|
127
|
+
case 'monthpicker-multi':
|
|
128
|
+
return (jsx(Suspense, { fallback: jsx(LoadingFallback, {}), children: jsx(MonthPickerMultiRender, { props: props, variant: variant }) }));
|
|
125
129
|
// Multi-select dropdown
|
|
126
130
|
case 'multiselect':
|
|
127
131
|
return (jsx(Suspense, { fallback: jsx(LoadingFallback, {}), children: jsx(MultiSelectV1, { props: props, variant: variant }) }));
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useMemo, useCallback } from 'react';
|
|
3
|
+
import { FormControl, Box, TextField, InputAdornment, Popover, IconButton, Typography, Button } from '@mui/material';
|
|
4
|
+
import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
|
|
5
|
+
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
|
6
|
+
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
7
|
+
import { useWatch } from 'react-hook-form';
|
|
8
|
+
import FormBottomField from '../FormBottomField/FormBottomField.esm.js';
|
|
9
|
+
import 'dayjs';
|
|
10
|
+
import { renderLabel } from '../../../utils/Constants/FormConstants.esm.js';
|
|
11
|
+
|
|
12
|
+
const MONTH_LABELS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
13
|
+
const parseMY = (s) => {
|
|
14
|
+
const m = /^(\d{2})\/(\d{4})$/.exec(s.trim());
|
|
15
|
+
return m ? [Number(m[1]), Number(m[2])] : null; // [month, year]
|
|
16
|
+
};
|
|
17
|
+
const toKey = (month, year) => year * 100 + month; // sortable comparable int
|
|
18
|
+
const toMY = (month, year) => `${String(month).padStart(2, '0')}/${year}`;
|
|
19
|
+
const parseValue = (value) => value ? value.split(',').map((s) => s.trim()).filter(Boolean) : [];
|
|
20
|
+
const MonthPickerMultiRender = ({ props, variant, }) => {
|
|
21
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
22
|
+
const [viewYear, setViewYear] = useState(() => new Date().getFullYear());
|
|
23
|
+
const [draft, setDraft] = useState([]);
|
|
24
|
+
const value = useWatch({ control: props.control, name: props.item.name });
|
|
25
|
+
const committed = useMemo(() => parseValue(value), [value]);
|
|
26
|
+
const isError = !!props.errors?.[props.item.name];
|
|
27
|
+
const minKey = useMemo(() => {
|
|
28
|
+
const parsed = props.item.minDate ? parseMY(props.item.minDate) : null;
|
|
29
|
+
return parsed ? toKey(parsed[0], parsed[1]) : null;
|
|
30
|
+
}, [props.item.minDate]);
|
|
31
|
+
const maxKey = useMemo(() => {
|
|
32
|
+
const parsed = props.item.maxDate ? parseMY(props.item.maxDate) : null;
|
|
33
|
+
return parsed ? toKey(parsed[0], parsed[1]) : null;
|
|
34
|
+
}, [props.item.maxDate]);
|
|
35
|
+
const handleOpen = useCallback((e) => {
|
|
36
|
+
if (props.item.disable)
|
|
37
|
+
return;
|
|
38
|
+
const committedNow = parseValue(value);
|
|
39
|
+
setDraft(committedNow);
|
|
40
|
+
if (committedNow.length > 0) {
|
|
41
|
+
const years = committedNow.map((s) => Number(s.split('/')[1]));
|
|
42
|
+
setViewYear(Math.min(...years));
|
|
43
|
+
}
|
|
44
|
+
setAnchorEl(e.currentTarget);
|
|
45
|
+
}, [props.item.disable, value]);
|
|
46
|
+
const handleClose = useCallback(() => setAnchorEl(null), []);
|
|
47
|
+
const toggleMonth = useCallback((my) => {
|
|
48
|
+
setDraft((prev) => (prev.includes(my) ? prev.filter((v) => v !== my) : [...prev, my]));
|
|
49
|
+
}, []);
|
|
50
|
+
const handleApply = useCallback(() => {
|
|
51
|
+
const sorted = [...draft].sort((a, b) => {
|
|
52
|
+
const [am, ay] = a.split('/').map(Number);
|
|
53
|
+
const [bm, by] = b.split('/').map(Number);
|
|
54
|
+
return toKey(am, ay) - toKey(bm, by);
|
|
55
|
+
});
|
|
56
|
+
const next = sorted.join(',');
|
|
57
|
+
props.setValue(props.item.name, next);
|
|
58
|
+
props.item.onChangeFn && props.item.onChangeFn(next);
|
|
59
|
+
props.clearErrors && props.clearErrors(props.item.name);
|
|
60
|
+
setAnchorEl(null);
|
|
61
|
+
}, [draft, props]);
|
|
62
|
+
const displayValue = useMemo(() => {
|
|
63
|
+
if (committed.length === 0)
|
|
64
|
+
return '';
|
|
65
|
+
if (committed.length <= 3) {
|
|
66
|
+
return committed
|
|
67
|
+
.map((my) => {
|
|
68
|
+
const [m, y] = my.split('/').map(Number);
|
|
69
|
+
return `${MONTH_LABELS[m - 1]} ${y}`;
|
|
70
|
+
})
|
|
71
|
+
.join(', ');
|
|
72
|
+
}
|
|
73
|
+
return `${committed.length} months selected`;
|
|
74
|
+
}, [committed]);
|
|
75
|
+
const muiLabel = variant !== 'standard' ? `${props.item.label}${props.item.required ? ' *' : ''}` : '';
|
|
76
|
+
return (jsxs(FormControl, { fullWidth: true, sx: { position: 'relative' }, disabled: props.item.disable, children: [renderLabel(variant, props), jsx(Box, { onClick: handleOpen, sx: { cursor: props.item.disable ? 'default' : 'pointer' }, children: jsx(TextField, { fullWidth: true, value: displayValue, label: muiLabel, placeholder: props.item.placeholder || 'Select month(s)…', error: isError, size: "small", InputProps: {
|
|
77
|
+
readOnly: true,
|
|
78
|
+
endAdornment: (jsx(InputAdornment, { position: "end", children: jsx(CalendarTodayIcon, { sx: { fontSize: 15, color: 'text.disabled' } }) })),
|
|
79
|
+
sx: { cursor: 'inherit' },
|
|
80
|
+
}, inputProps: {
|
|
81
|
+
'aria-labelledby': `${props.item.name}-label`,
|
|
82
|
+
'aria-describedby': `${props.item.name}-helper ${props.item.name}-error`,
|
|
83
|
+
'aria-required': props.item.required ? true : undefined,
|
|
84
|
+
style: { cursor: 'inherit' },
|
|
85
|
+
}, sx: {
|
|
86
|
+
'& .MuiFormLabel-root': { top: -10 },
|
|
87
|
+
'& .MuiInputLabel-shrink': { top: 0 },
|
|
88
|
+
} }) }), jsxs(Popover, { open: Boolean(anchorEl), anchorEl: anchorEl, onClose: handleClose, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.5, borderRadius: 2, boxShadow: 6, width: 280 } }, children: [jsxs(Box, { sx: {
|
|
89
|
+
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
90
|
+
px: 1.5, py: 0.75,
|
|
91
|
+
borderBottom: '1px solid', borderColor: 'divider',
|
|
92
|
+
}, children: [jsx(IconButton, { size: "small", onClick: () => setViewYear((y) => y - 1), children: jsx(ChevronLeftIcon, { sx: { fontSize: 18 } }) }), jsx(Typography, { fontWeight: 600, sx: { fontSize: 14, userSelect: 'none' }, children: viewYear }), jsx(IconButton, { size: "small", onClick: () => setViewYear((y) => y + 1), children: jsx(ChevronRightIcon, { sx: { fontSize: 18 } }) })] }), jsx(Box, { sx: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 0.75, p: 1.5 }, children: MONTH_LABELS.map((mon, idx) => {
|
|
93
|
+
const monthNum = idx + 1;
|
|
94
|
+
const my = toMY(monthNum, viewYear);
|
|
95
|
+
const key = toKey(monthNum, viewYear);
|
|
96
|
+
const outOfRange = (minKey !== null && key < minKey) || (maxKey !== null && key > maxKey);
|
|
97
|
+
const isSelected = draft.includes(my);
|
|
98
|
+
return (jsx(Button, { variant: isSelected ? 'contained' : 'outlined', size: "small", disableElevation: true, disabled: outOfRange, onClick: () => toggleMonth(my), sx: {
|
|
99
|
+
fontSize: 12,
|
|
100
|
+
textTransform: 'none',
|
|
101
|
+
borderRadius: 2,
|
|
102
|
+
py: 0.75,
|
|
103
|
+
borderColor: isSelected ? 'primary.main' : 'divider',
|
|
104
|
+
fontWeight: isSelected ? 700 : 400,
|
|
105
|
+
}, children: mon }, my));
|
|
106
|
+
}) }), draft.length > 0 && (jsxs(Box, { sx: {
|
|
107
|
+
px: 2, py: 0.75,
|
|
108
|
+
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
109
|
+
borderTop: '1px solid', borderColor: 'divider',
|
|
110
|
+
}, children: [jsxs(Typography, { sx: { fontSize: 11, color: 'primary.main', fontWeight: 600 }, children: [draft.length, " month", draft.length > 1 ? 's' : '', " selected"] }), jsx(Button, { size: "small", color: "inherit", sx: { fontSize: 11, py: 0, textTransform: 'none', minWidth: 'auto' }, onClick: () => setDraft([]), children: "Clear" })] })), jsx(Box, { sx: {
|
|
111
|
+
px: 1.5, py: 1,
|
|
112
|
+
display: 'flex', justifyContent: 'flex-end',
|
|
113
|
+
borderTop: '1px solid', borderColor: 'divider',
|
|
114
|
+
}, children: jsx(Button, { size: "small", variant: "contained", disableElevation: true, onClick: handleApply, children: "Apply" }) })] }), jsx(FormBottomField, { ...props })] }, props.item.name));
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export { MonthPickerMultiRender as default };
|
|
@@ -4,11 +4,22 @@ export { default as SingleSelectSearchApi } from './Select/SingleSelectSearchApi
|
|
|
4
4
|
export { default as MultiSelectV1 } from './Select/MultiSelectv1.esm.js';
|
|
5
5
|
export { default as MultiSelectTable } from './Select/MultiSelectTable.esm.js';
|
|
6
6
|
export { default as SearchableSelect } from './Select/SearchableSelect.esm.js';
|
|
7
|
+
export { default as FormCheckBoxGroup } from './FormCheckBoxGroup/FormCheckBoxGroup.esm.js';
|
|
7
8
|
export { default as DatepickerWrapperV2 } from './DatePicker/DatepickerWrapperV2.esm.js';
|
|
9
|
+
export { default as Monthpickerrender } from './DatePicker/Monthpickerrender.esm.js';
|
|
10
|
+
import 'react/jsx-runtime';
|
|
11
|
+
import 'react';
|
|
12
|
+
import '@mui/material';
|
|
13
|
+
import '@mui/icons-material/CalendarToday';
|
|
14
|
+
import '@mui/icons-material/ChevronLeft';
|
|
15
|
+
import '@mui/icons-material/ChevronRight';
|
|
16
|
+
import 'react-hook-form';
|
|
8
17
|
export { default as FormBottomField } from './FormBottomField/FormBottomField.esm.js';
|
|
9
|
-
|
|
10
|
-
export { default as
|
|
18
|
+
import 'dayjs';
|
|
19
|
+
export { default as YearPickerField } from './YearPickerField/YearPickerField.esm.js';
|
|
11
20
|
export { default as FormErrorField } from './FormErrorField/FormErrorField.esm.js';
|
|
21
|
+
export { default as HelperText } from './HelperText/HelperText.esm.js';
|
|
22
|
+
export { default as FormCheckBox } from './FormCheckBox/FormCheckBox.esm.js';
|
|
12
23
|
export { default as FormNumberField } from './FormNumberField/FormNumberField.esm.js';
|
|
13
24
|
export { default as FormNumberFieldDecimal } from './FormNumberField/FormNumberFieldDecimal.esm.js';
|
|
14
25
|
export { default as FormRadioGroup } from './FormRadioGroup/FormRadioGroup.esm.js';
|
|
@@ -16,10 +27,7 @@ export { default as FormRenderFileUpload } from './FileUpload/FormRenderFileUplo
|
|
|
16
27
|
export { default as FormRenderMultiFileUpload } from './FileUpload/FormRenderMultiFileUpload.esm.js';
|
|
17
28
|
export { default as FormTextAreaField } from './FormTextAreaField/FormTextAreaField.esm.js';
|
|
18
29
|
export { default as FormTextField } from './FormTextField/FormTextField.esm.js';
|
|
19
|
-
export { default as HelperText } from './HelperText/HelperText.esm.js';
|
|
20
|
-
export { default as Monthpickerrender } from './DatePicker/Monthpickerrender.esm.js';
|
|
21
30
|
export { default as PasswordField } from './PasswordField/PasswordField.esm.js';
|
|
22
31
|
export { default as RichTextEditor } from './RichTextEditor/RichTextEditor.esm.js';
|
|
23
32
|
export { default as RichTextEditorWrapper } from './RichTextEditor/RichTextEditorWrapper.esm.js';
|
|
24
33
|
export { default as TimePicker } from './TimePicker/TimePicker.esm.js';
|
|
25
|
-
export { default as YearPickerField } from './YearPickerField/YearPickerField.esm.js';
|
|
@@ -98,10 +98,11 @@ export type ServerFilterDef = {
|
|
|
98
98
|
defaultVisible?: boolean;
|
|
99
99
|
} | {
|
|
100
100
|
/**
|
|
101
|
-
* Month-picker chip — lets users select **one or more months**
|
|
102
|
-
* Selected months are stored as a comma-separated string of
|
|
103
|
-
* in `filterValues[filterKey]` (e.g. `"2025-01,2025-03"
|
|
104
|
-
*
|
|
101
|
+
* Month-picker chip — by default lets users select **one or more months**
|
|
102
|
+
* across years. Selected months are stored as a comma-separated string of
|
|
103
|
+
* `"YYYY-MM"` values in `filterValues[filterKey]` (e.g. `"2025-01,2025-03"`,
|
|
104
|
+
* or just `"2025-01"` when `single: true`). Split on `","` in your
|
|
105
|
+
* `fetchPage` handler to build the server query.
|
|
105
106
|
*/
|
|
106
107
|
type: 'monthPicker';
|
|
107
108
|
id: string;
|
|
@@ -109,6 +110,13 @@ export type ServerFilterDef = {
|
|
|
109
110
|
filterKey: string;
|
|
110
111
|
/** Show this chip in the filter bar on first render. Default: `false`. */
|
|
111
112
|
defaultVisible?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Restrict selection to a single month instead of the default
|
|
115
|
+
* multi-select behaviour. Clicking a month immediately applies and
|
|
116
|
+
* closes the popover (no Apply button), matching other single-value
|
|
117
|
+
* chips like the date picker. Default: `false` (multi-select).
|
|
118
|
+
*/
|
|
119
|
+
single?: boolean;
|
|
112
120
|
};
|
|
113
121
|
interface AvailableColumn {
|
|
114
122
|
id: string;
|
|
@@ -253,15 +253,28 @@ const MultiSelectFilterChip = ({ label, selectedValues, options, onApply, onRemo
|
|
|
253
253
|
const [anchor, setAnchor] = useState(null);
|
|
254
254
|
// Draft is only committed on Apply — prevents an API call per checkbox tick.
|
|
255
255
|
const [draft, setDraft] = useState([]);
|
|
256
|
+
const [search, setSearch] = useState('');
|
|
256
257
|
const active = selectedValues.length > 0;
|
|
257
258
|
const openPopover = (e) => {
|
|
258
259
|
setDraft(selectedValues); // always start from the committed state
|
|
260
|
+
setSearch(''); // always start with all options visible
|
|
259
261
|
setAnchor(e.currentTarget);
|
|
260
262
|
};
|
|
261
263
|
const toggleDraft = (value) => setDraft((prev) => prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value]);
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
|
|
264
|
+
// Case-insensitive filter — same pattern as SearchableSelectChip.
|
|
265
|
+
const filteredOptions = search
|
|
266
|
+
? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()))
|
|
267
|
+
: options;
|
|
268
|
+
// "All" reflects/toggles only the currently visible (filtered) options —
|
|
269
|
+
// degrades to the original whole-list behaviour when search is empty.
|
|
270
|
+
const allSelected = filteredOptions.length > 0 && filteredOptions.every((o) => draft.includes(o.value));
|
|
271
|
+
const someSelected = !allSelected && filteredOptions.some((o) => draft.includes(o.value));
|
|
272
|
+
const toggleAll = () => setDraft((prev) => {
|
|
273
|
+
const filteredValues = filteredOptions.map((o) => o.value);
|
|
274
|
+
return allSelected
|
|
275
|
+
? prev.filter((v) => !filteredValues.includes(v))
|
|
276
|
+
: [...new Set([...prev, ...filteredValues])];
|
|
277
|
+
});
|
|
265
278
|
const handleApply = () => {
|
|
266
279
|
onApply(draft);
|
|
267
280
|
setAnchor(null);
|
|
@@ -284,19 +297,22 @@ const MultiSelectFilterChip = ({ label, selectedValues, options, onApply, onRemo
|
|
|
284
297
|
px: 2, py: 0.75,
|
|
285
298
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
286
299
|
borderBottom: '1px solid', borderColor: 'divider',
|
|
287
|
-
}, children: [jsx(Typography, { sx: { fontSize: 11, color: draft.length > 0 ? 'primary.main' : 'text.secondary', fontWeight: draft.length > 0 ? 600 : 400 }, children: draft.length > 0 ? `${draft.length} of ${options.length} selected` : 'Select options' }), draft.length > 0 && (jsx(Button, { size: "small", color: "inherit", sx: { fontSize: 11, py: 0, textTransform: 'none', minWidth: 'auto' }, onClick: () => setDraft([]), children: "Clear all" }))] }),
|
|
300
|
+
}, children: [jsx(Typography, { sx: { fontSize: 11, color: draft.length > 0 ? 'primary.main' : 'text.secondary', fontWeight: draft.length > 0 ? 600 : 400 }, children: draft.length > 0 ? `${draft.length} of ${options.length} selected` : 'Select options' }), draft.length > 0 && (jsx(Button, { size: "small", color: "inherit", sx: { fontSize: 11, py: 0, textTransform: 'none', minWidth: 'auto' }, onClick: () => setDraft([]), children: "Clear all" }))] }), jsx(Box, { sx: { px: 1.5, pt: 1, pb: 0.5 }, children: jsx(TextField, { size: "small", fullWidth: true, autoFocus: true, placeholder: "Search\u2026", value: search, onChange: (e) => setSearch(e.target.value), onKeyDown: (e) => { if (e.key === 'Escape')
|
|
301
|
+
setAnchor(null); }, InputProps: {
|
|
302
|
+
startAdornment: (jsx(InputAdornment, { position: "start", children: jsx(SearchIcon, { sx: { fontSize: 16, color: 'text.disabled' } }) })),
|
|
303
|
+
}, sx: { '& .MuiOutlinedInput-root': { borderRadius: 2, fontSize: 13 } } }) }), jsxs(ListItemButton, { onClick: toggleAll, sx: { px: 1, py: 0.25 }, children: [jsx(Checkbox, { checked: allSelected, indeterminate: someSelected, size: "small", disableRipple: true, sx: { p: 0.5, mr: 0.5, '& .MuiSvgIcon-root': { fontSize: 16 } } }), jsx(ListItemText, { primary: "All", primaryTypographyProps: {
|
|
288
304
|
fontSize: 13,
|
|
289
305
|
fontStyle: 'italic',
|
|
290
306
|
color: allSelected || someSelected ? 'primary.main' : 'text.secondary',
|
|
291
307
|
fontWeight: allSelected ? 600 : 400,
|
|
292
|
-
} })] }), jsx(Divider, {}), jsx(List, { dense: true, disablePadding: true, sx: { maxHeight: 240, overflowY: 'auto', py: 0.5 }, children: options.map((opt) => {
|
|
308
|
+
} })] }), jsx(Divider, {}), jsx(List, { dense: true, disablePadding: true, sx: { maxHeight: 240, overflowY: 'auto', py: 0.5 }, children: filteredOptions.length === 0 ? (jsx(Box, { sx: { px: 2, py: 1.5 }, children: jsxs(Typography, { variant: "caption", color: "text.disabled", children: ["No options match \"", search, "\""] }) })) : (filteredOptions.map((opt) => {
|
|
293
309
|
const checked = draft.includes(opt.value);
|
|
294
310
|
return (jsxs(ListItemButton, { onClick: () => toggleDraft(opt.value), sx: { px: 1, py: 0.25 }, children: [jsx(Checkbox, { checked: checked, size: "small", disableRipple: true, sx: { p: 0.5, mr: 0.5, '& .MuiSvgIcon-root': { fontSize: 16 } } }), jsx(ListItemText, { primary: opt.label, primaryTypographyProps: {
|
|
295
311
|
fontSize: 13,
|
|
296
312
|
color: checked ? 'primary.main' : 'text.primary',
|
|
297
313
|
fontWeight: checked ? 600 : 400,
|
|
298
314
|
} })] }, opt.value));
|
|
299
|
-
}) }), jsx(Box, { sx: {
|
|
315
|
+
})) }), jsx(Box, { sx: {
|
|
300
316
|
px: 1.5, py: 1,
|
|
301
317
|
display: 'flex', justifyContent: 'flex-end',
|
|
302
318
|
borderTop: '1px solid', borderColor: 'divider',
|
|
@@ -304,14 +320,19 @@ const MultiSelectFilterChip = ({ label, selectedValues, options, onApply, onRemo
|
|
|
304
320
|
};
|
|
305
321
|
// ─── MonthPickerChip ─────────────────────────────────────────────────────────
|
|
306
322
|
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
// clicked (single API call per filter action).
|
|
323
|
+
// By default a multi-month selection chip: users navigate by year and tick
|
|
324
|
+
// individual months, draft changes are buffered locally, and `onApply` is
|
|
325
|
+
// called once when Apply is clicked (single API call per filter action).
|
|
310
326
|
//
|
|
311
|
-
//
|
|
327
|
+
// Pass `single` to restrict to one month at a time — clicking a month then
|
|
328
|
+
// immediately applies + closes the popover instead of buffering, matching
|
|
329
|
+
// the other single-value chips (e.g. DatePickerChip).
|
|
330
|
+
//
|
|
331
|
+
// Value format: comma-separated "YYYY-MM" strings, e.g. "2025-01,2025-03"
|
|
332
|
+
// (or just "2025-01" when `single` is set).
|
|
312
333
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
313
334
|
const MONTH_LABELS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
314
|
-
const MonthPickerChip = ({ label, value, onApply, onRemove, chipStyle, baseId }) => {
|
|
335
|
+
const MonthPickerChip = ({ label, value, onApply, onRemove, chipStyle, baseId, single = false }) => {
|
|
315
336
|
const parseYMs = (v) => v ? v.split(',').filter(Boolean) : [];
|
|
316
337
|
const [anchor, setAnchor] = useState(null);
|
|
317
338
|
const [viewYear, setViewYear] = useState(() => new Date().getFullYear());
|
|
@@ -328,7 +349,18 @@ const MonthPickerChip = ({ label, value, onApply, onRemove, chipStyle, baseId })
|
|
|
328
349
|
}
|
|
329
350
|
setAnchor(e.currentTarget);
|
|
330
351
|
};
|
|
331
|
-
const toggleMonth = (ym) =>
|
|
352
|
+
const toggleMonth = (ym) => {
|
|
353
|
+
if (single) {
|
|
354
|
+
// Clicking the already-selected month clears it; otherwise replace
|
|
355
|
+
// the selection and commit immediately (no Apply button in this mode).
|
|
356
|
+
const next = draft.includes(ym) ? [] : [ym];
|
|
357
|
+
setDraft(next);
|
|
358
|
+
onApply(next.join(','));
|
|
359
|
+
setAnchor(null);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
setDraft((prev) => prev.includes(ym) ? prev.filter((v) => v !== ym) : [...prev, ym]);
|
|
363
|
+
};
|
|
332
364
|
const handleApply = () => {
|
|
333
365
|
// Sort selected months chronologically before storing
|
|
334
366
|
onApply([...draft].sort().join(','));
|
|
@@ -365,15 +397,15 @@ const MonthPickerChip = ({ label, value, onApply, onRemove, chipStyle, baseId })
|
|
|
365
397
|
borderColor: isSelected ? 'primary.main' : 'divider',
|
|
366
398
|
fontWeight: isSelected ? 700 : 400,
|
|
367
399
|
}, children: mon }, ym));
|
|
368
|
-
}) }), draft.length > 0 && (jsxs(Box, { sx: {
|
|
400
|
+
}) }), !single && draft.length > 0 && (jsxs(Box, { sx: {
|
|
369
401
|
px: 2, py: 0.75,
|
|
370
402
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
371
403
|
borderTop: '1px solid', borderColor: 'divider',
|
|
372
|
-
}, children: [jsxs(Typography, { sx: { fontSize: 11, color: 'primary.main', fontWeight: 600 }, children: [draft.length, " month", draft.length > 1 ? 's' : '', " selected"] }), jsx(Button, { size: "small", color: "inherit", sx: { fontSize: 11, py: 0, textTransform: 'none', minWidth: 'auto' }, onClick: () => setDraft([]), children: "Clear" })] })), jsx(Box, { sx: {
|
|
404
|
+
}, children: [jsxs(Typography, { sx: { fontSize: 11, color: 'primary.main', fontWeight: 600 }, children: [draft.length, " month", draft.length > 1 ? 's' : '', " selected"] }), jsx(Button, { size: "small", color: "inherit", sx: { fontSize: 11, py: 0, textTransform: 'none', minWidth: 'auto' }, onClick: () => setDraft([]), children: "Clear" })] })), !single && (jsx(Box, { sx: {
|
|
373
405
|
px: 1.5, py: 1,
|
|
374
406
|
display: 'flex', justifyContent: 'flex-end',
|
|
375
407
|
borderTop: '1px solid', borderColor: 'divider',
|
|
376
|
-
}, children: jsx(Button, { size: "small", variant: "contained", disableElevation: true, onClick: handleApply, children: "Apply" }) })] })] }));
|
|
408
|
+
}, children: jsx(Button, { size: "small", variant: "contained", disableElevation: true, onClick: handleApply, children: "Apply" }) }))] })] }));
|
|
377
409
|
};
|
|
378
410
|
const GlobalSearchInput = memo(({ value, onChange, chipStyle, placeholder = 'Search…' }) => {
|
|
379
411
|
const [draft, setDraft] = useState(value);
|
|
@@ -531,7 +563,7 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
|
|
|
531
563
|
}), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
|
|
532
564
|
}
|
|
533
565
|
if (def?.type === 'monthPicker') {
|
|
534
|
-
return (jsx(MonthPickerChip, { label: def.label, value: filterValues[def.filterKey] ?? '', onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
|
|
566
|
+
return (jsx(MonthPickerChip, { label: def.label, value: filterValues[def.filterKey] ?? '', onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined, single: def.single }, id));
|
|
535
567
|
}
|
|
536
568
|
if (def?.type === 'date') {
|
|
537
569
|
return (jsx(DatePickerChip, { label: def.label, value: getColValue(def.columnKey), onApply: (v) => applyColFilter(def.columnKey, v), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
|
|
@@ -2,7 +2,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { useMemo, useState, useRef, useLayoutEffect, useCallback, useEffect } from 'react';
|
|
3
3
|
import { MRT_ToggleGlobalFilterButton, MRT_ShowHideColumnsButton, MRT_ToggleDensePaddingButton, MRT_ToggleFullScreenButton, useMaterialReactTable, MaterialReactTable } from 'material-react-table';
|
|
4
4
|
import { useQuery, keepPreviousData } from '@tanstack/react-query';
|
|
5
|
-
import { Box, Tooltip, IconButton, GlobalStyles, Typography, Select, MenuItem, Skeleton
|
|
5
|
+
import { Box, Tooltip, IconButton, GlobalStyles, Typography, Select, MenuItem, Skeleton } from '@mui/material';
|
|
6
6
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
|
7
7
|
import FileDownloadIcon from '@mui/icons-material/FileDownload';
|
|
8
8
|
import FirstPageIcon from '@mui/icons-material/FirstPage';
|
|
@@ -12,6 +12,11 @@ import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
|
12
12
|
import DynamicFilterBar from './DynamicFilterBar.esm.js';
|
|
13
13
|
import { getXLSX } from '../../utils/Constants/xlsx-js-style.esm.js';
|
|
14
14
|
|
|
15
|
+
// MRT's default overscan (rows rendered outside the visible viewport) is
|
|
16
|
+
// modest; bumping it reduces blank-row flash during fast wheel-scrolling
|
|
17
|
+
// through large virtualized batches. Static (no prop dependency), so it's
|
|
18
|
+
// hoisted for a stable reference across renders.
|
|
19
|
+
const ROW_VIRTUALIZER_OPTIONS = { overscan: 8 };
|
|
15
20
|
const UI_DENSITY_PRESETS = {
|
|
16
21
|
default: {
|
|
17
22
|
footerMinHeight: 48,
|
|
@@ -186,19 +191,19 @@ const buildHeaderCellSx = (align, headerMin, headerMax, headerFont, headerBgColo
|
|
|
186
191
|
},
|
|
187
192
|
};
|
|
188
193
|
};
|
|
189
|
-
// ─── Internal:
|
|
190
|
-
|
|
194
|
+
// ─── Internal: skeleton loader ────────────────────────────────────────────────
|
|
195
|
+
//
|
|
196
|
+
// Used for BOTH loading states:
|
|
197
|
+
// - Initial load (isLoading, no data on screen yet) → opaque background
|
|
198
|
+
// - Refetch with existing data (isFetching, page/filter/sort change)
|
|
199
|
+
// → translucent + blurred so the stale rows are still faintly visible
|
|
200
|
+
// underneath, matching the previous OverlayLoader's backdrop treatment
|
|
201
|
+
// but with skeleton rows instead of a generic spinner.
|
|
202
|
+
const SkeletonOverlay = ({ columnCount, rowCount = 10, translucent = false, }) => (jsx(Box, { sx: {
|
|
191
203
|
position: 'absolute', inset: 0,
|
|
192
204
|
display: 'flex', flexDirection: 'column',
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
zIndex: 10, gap: 1.5,
|
|
196
|
-
}, children: [jsx(CircularProgress, { size: 50, thickness: 4 }), jsx(Typography, { variant: "body2", color: "text.secondary", fontWeight: 500, children: message })] }));
|
|
197
|
-
// ─── Internal: skeleton loader (initial load — no data on screen yet) ────────
|
|
198
|
-
const SkeletonOverlay = ({ columnCount, rowCount = 10, }) => (jsx(Box, { sx: {
|
|
199
|
-
position: 'absolute', inset: 0,
|
|
200
|
-
display: 'flex', flexDirection: 'column',
|
|
201
|
-
bgcolor: 'background.paper',
|
|
205
|
+
bgcolor: translucent ? 'rgba(255,255,255,0.80)' : 'background.paper',
|
|
206
|
+
...(translucent ? { backdropFilter: 'blur(2px)' } : {}),
|
|
202
207
|
zIndex: 10, p: 1.5, gap: 1,
|
|
203
208
|
overflow: 'hidden',
|
|
204
209
|
}, children: Array.from({ length: rowCount }).map((_, rowIdx) => (jsx(Box, { sx: { display: 'flex', gap: 1.5 }, children: Array.from({ length: Math.max(columnCount, 1) }).map((_, colIdx) => (jsx(Skeleton, { variant: "rounded", height: 28, sx: { flex: 1, minWidth: 0 }, animation: "wave" }, colIdx))) }, rowIdx))) }));
|
|
@@ -731,6 +736,11 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
731
736
|
enableHiding: true,
|
|
732
737
|
enableStickyHeader: true,
|
|
733
738
|
enableRowVirtualization: true,
|
|
739
|
+
// A larger overscan renders more off-screen rows up front, reducing the
|
|
740
|
+
// brief blank-row flash that can appear when scrolling quickly through
|
|
741
|
+
// a large in-memory batch (rows are already fetched — virtualization
|
|
742
|
+
// only governs what's mounted to the DOM at any given moment).
|
|
743
|
+
rowVirtualizerOptions: ROW_VIRTUALIZER_OPTIONS,
|
|
734
744
|
initialState: {
|
|
735
745
|
density: densityTokens.mrtDensity,
|
|
736
746
|
showGlobalFilter: showGlobalSearchByDefault,
|
|
@@ -824,7 +834,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
824
834
|
display: 'flex',
|
|
825
835
|
flexDirection: 'column',
|
|
826
836
|
overflow: 'hidden',
|
|
827
|
-
}, children: [jsx(GlobalStyles, { styles: tableStyleOverrides }), isLoading && jsx(SkeletonOverlay, { columnCount: processedColumns.length }), !isLoading && isFetching && jsx(
|
|
837
|
+
}, children: [jsx(GlobalStyles, { styles: tableStyleOverrides }), isLoading && jsx(SkeletonOverlay, { columnCount: processedColumns.length }), !isLoading && isFetching && (jsx(SkeletonOverlay, { columnCount: processedColumns.length, translucent: true })), jsx(Box, { sx: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: jsx(MaterialReactTable, { table: table }) })] }));
|
|
828
838
|
}
|
|
829
839
|
|
|
830
840
|
export { GenericPaginatedGrid as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as Calendar } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as Calendar } from '../../../../../_virtual/index.esm7.js';
|
|
2
2
|
import require$$0__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireDayCell } from '../DayCell/index.esm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as DateInput } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as DateInput } from '../../../../../_virtual/index.esm13.js';
|
|
2
2
|
import require$$0__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireClassnames } from '../../../../classnames/index.esm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as DateRange } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as DateRange } from '../../../../../_virtual/index.esm4.js';
|
|
2
2
|
import require$$0__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireCalendar } from '../Calendar/index.esm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as DefinedRange } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as DefinedRange } from '../../../../../_virtual/index.esm6.js';
|
|
2
2
|
import require$$0__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireStyles } from '../../styles.esm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as Month } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as Month } from '../../../../../_virtual/index.esm14.js';
|
|
2
2
|
import require$$0__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireDayCell } from '../DayCell/index.esm.js';
|
|
@@ -11,7 +11,7 @@ export interface TextFieldInputProps {
|
|
|
11
11
|
export interface FormSectionPropsItem {
|
|
12
12
|
name: string;
|
|
13
13
|
label: string;
|
|
14
|
-
inputType: 'text' | 'password' | 'number' | 'select' | 'autocomplete-select' | 'autocomplete-multi-select' | 'single-select-search' | 'datepicker' | 'datetimepicker' | 'multiselect' | 'select-v2' | 'basic-select' | 'searchable-select' | 'decimal' | 'alpha-numerical' | 'yearpicker' | 'dateRangePicker' | 'monthpicker' | 'multiselect' | 'file' | 'multifile' | 'multifile-with-preview' | 'textarea' | 'phoneNumber' | 'pincode' | 'email' | 'toggleSwitch' | 'rich-text-editor' | 'multiEmail' | 'timepicker' | 'checkbox-group' | 'radio-group' | 'checkbox' | 'custom' | 'input-select' | 'date-range-picker' | 'multiselect-table' | '';
|
|
14
|
+
inputType: 'text' | 'password' | 'number' | 'select' | 'autocomplete-select' | 'autocomplete-multi-select' | 'single-select-search' | 'datepicker' | 'datetimepicker' | 'multiselect' | 'select-v2' | 'basic-select' | 'searchable-select' | 'decimal' | 'alpha-numerical' | 'yearpicker' | 'dateRangePicker' | 'monthpicker' | 'monthpicker-multi' | 'multiselect' | 'file' | 'multifile' | 'multifile-with-preview' | 'textarea' | 'phoneNumber' | 'pincode' | 'email' | 'toggleSwitch' | 'rich-text-editor' | 'multiEmail' | 'timepicker' | 'checkbox-group' | 'radio-group' | 'checkbox' | 'custom' | 'input-select' | 'date-range-picker' | 'multiselect-table' | '';
|
|
15
15
|
options?: OptionsProps[];
|
|
16
16
|
required?: boolean;
|
|
17
17
|
errorMessage?: string;
|