tp-react-elements 1.3.11 → 1.3.13

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/README.md CHANGED
@@ -175,23 +175,41 @@ Refer to our Storybook documentation for detailed API documentation and examples
175
175
  npm run storybook
176
176
  ```
177
177
 
178
- - **Docs → Paginated Grid** — full API: grid height, header wrap (`headerMinHeight` / `headerMaxHeight`), `headerBgColor`, filters, troubleshooting
179
- - **Components → PaginatedGrid → Sample Grids** — mock grids (no API): header wrap test, compact layout, viewport fill
178
+ - **Docs → Paginated Grid** — API reference plus **four copy-paste examples** (embedded panel, wrapped headers, typography, invoice filters)
179
+ - **Components → PaginatedGrid → Sample Grids** — mock grids (no API): header wrap, compact layout, viewport fill
180
180
  - **Components → PaginatedGrid → Example Grid** — live invoice API demo
181
181
 
182
- Example header styling:
182
+ **Example — compact embedded grid with smaller fonts:**
183
+
184
+ ```tsx
185
+ <GenericPaginatedGrid
186
+ tableHeight="calc(100vh - 240px)"
187
+ autoFillViewport={false}
188
+ uiDensity="compact"
189
+ headerBgColor="#f5f5f5"
190
+ headerMaxHeight={64}
191
+ headerFontSize={11}
192
+ bodyFontSize={10}
193
+ {...gridProps}
194
+ />
195
+ ```
196
+
197
+ **Example — wrapped header “Department / Business Unit”:**
183
198
 
184
199
  ```tsx
185
200
  <GenericPaginatedGrid
186
201
  headerBgColor="#e8eaf6"
187
202
  headerMinHeight="48px"
188
203
  headerMaxHeight="96px"
189
- tableHeight="calc(100vh - 200px)"
190
- autoFillViewport={false}
204
+ headerFontSize={12}
205
+ bodyFontSize={12}
206
+ layoutMode="grid"
191
207
  {...gridProps}
192
208
  />
193
209
  ```
194
210
 
211
+ Full examples: Storybook **Docs → Paginated Grid → Examples**.
212
+
195
213
  ## 🏗️ Best Practices for Code Structure and Organization
196
214
 
197
215
  ### Component Organization
@@ -1,3 +1,3 @@
1
- var DateInput = {};
1
+ var InputRangeField = {};
2
2
 
3
- export { DateInput as __exports };
3
+ export { InputRangeField as __exports };
@@ -1,3 +1,3 @@
1
- var InputRangeField = {};
1
+ var DateInput = {};
2
2
 
3
- export { InputRangeField as __exports };
3
+ export { DateInput as __exports };
@@ -1,3 +1,3 @@
1
- var Calendar = {};
1
+ var DateRangePicker = {};
2
2
 
3
- export { Calendar as __exports };
3
+ export { DateRangePicker as __exports };
@@ -1,3 +1,3 @@
1
- var DateRangePicker = {};
1
+ var Calendar = {};
2
2
 
3
- export { DateRangePicker as __exports };
3
+ export { Calendar as __exports };
@@ -1,3 +1,3 @@
1
- var DateRange = {};
1
+ var DefinedRange = {};
2
2
 
3
- export { DateRange as __exports };
3
+ export { DefinedRange as __exports };
@@ -1,3 +1,3 @@
1
- var DefinedRange = {};
1
+ var DateRange = {};
2
2
 
3
- export { DefinedRange as __exports };
3
+ export { DateRange as __exports };
@@ -10,6 +10,11 @@ import type { MRT_ColumnFiltersState } from 'material-react-table';
10
10
  * quickly filter long option lists. Same `filterKey` / `options`
11
11
  * shape as `dropdown`.
12
12
  * - `date` — single date-picker chip. Applied client-side via MRT `columnFilters`.
13
+ * - `multiSelect` — checkbox list that allows picking multiple values at once.
14
+ * Selected values are joined as a comma-separated string in
15
+ * `filterValues[filterKey]` (e.g. `"Pending,Shipped"`).
16
+ * Your `fetchPage` function should split on `","` to build the
17
+ * correct server query.
13
18
  */
14
19
  export type ServerFilterDef = {
15
20
  type: 'dateRange';
@@ -55,6 +60,21 @@ export type ServerFilterDef = {
55
60
  id: string;
56
61
  label: string;
57
62
  columnKey: string;
63
+ } | {
64
+ /**
65
+ * Multi-select checkbox chip — users can pick **multiple** values at once.
66
+ * Selected values are stored as a comma-separated string in
67
+ * `filterValues[filterKey]` (e.g. `"Pending,Shipped,Delivered"`).
68
+ * Split on `","` in your `fetchPage` handler to build the server query.
69
+ */
70
+ type: 'multiSelect';
71
+ id: string;
72
+ label: string;
73
+ filterKey: string;
74
+ options: {
75
+ label: string;
76
+ value: string;
77
+ }[];
58
78
  };
59
79
  interface AvailableColumn {
60
80
  id: string;
@@ -64,7 +84,7 @@ interface AvailableColumn {
64
84
  * Sourced from the `filterType` prop on the column's `TPColumnDef`.
65
85
  * Defaults to `'text'` when omitted.
66
86
  */
67
- filterType?: 'text' | 'date' | 'select' | 'searchable-select' | 'number';
87
+ filterType?: 'text' | 'date' | 'select' | 'searchable-select' | 'number' | 'multi-select';
68
88
  /** Options for `'select'` / `'searchable-select'` chip variants. */
69
89
  filterOptions?: {
70
90
  label: string;
@@ -9,7 +9,7 @@ import { DatePicker } from '@mui/x-date-pickers/DatePicker';
9
9
  import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
10
10
  import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
11
11
  import SingleSelectNonAutoComplete from '../FormComponents/Select/SingleSelectNonAutoComplete.esm.js';
12
- import { Box, Typography, Tooltip, Button, Popover, TextField, Stack, InputAdornment, List, ListItemButton, ListItemText, Divider } from '@mui/material';
12
+ import { Box, Typography, Tooltip, Button, Popover, TextField, Stack, InputAdornment, List, ListItemButton, ListItemText, Checkbox, Divider } from '@mui/material';
13
13
  import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
14
14
  import TuneIcon from '@mui/icons-material/Tune';
15
15
  import CloseIcon from '@mui/icons-material/Close';
@@ -29,6 +29,7 @@ const TYPE_PILL = {
29
29
  text: { label: 'Text', color: '#2e7d32', bg: '#e8f5e9' },
30
30
  dropdown: { label: 'Select', color: '#6a1b9a', bg: '#f3e5f5' },
31
31
  searchableDropdown: { label: 'Search', color: '#bf360c', bg: '#fbe9e7' },
32
+ multiSelect: { label: 'Multi', color: '#0277bd', bg: '#e1f5fe' },
32
33
  };
33
34
  // Column filter type pills (used in COLUMN FILTERS section of Manage Filters)
34
35
  const COLUMN_TYPE_PILL = {
@@ -37,6 +38,7 @@ const COLUMN_TYPE_PILL = {
37
38
  select: { label: 'Select', color: '#bf360c', bg: '#fbe9e7' },
38
39
  'searchable-select': { label: 'Search', color: '#bf360c', bg: '#fbe9e7' },
39
40
  number: { label: 'Number', color: '#e65100', bg: '#fff3e0' },
41
+ 'multi-select': { label: 'Multi', color: '#0277bd', bg: '#e1f5fe' },
40
42
  };
41
43
  const chipSx = (active, opts) => ({
42
44
  borderRadius: 5,
@@ -244,6 +246,41 @@ const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeh
244
246
  } }), isSelected && (jsx(CheckIcon, { sx: { fontSize: 15, color: 'primary.main', flexShrink: 0 } }))] }, opt.value));
245
247
  }))] })] })] }));
246
248
  };
249
+ const MultiSelectFilterChip = ({ label, selectedValues, options, onApply, onRemove, chipStyle, }) => {
250
+ const [anchor, setAnchor] = useState(null);
251
+ const active = selectedValues.length > 0;
252
+ const toggle = (value) => {
253
+ const next = selectedValues.includes(value)
254
+ ? selectedValues.filter((v) => v !== value)
255
+ : [...selectedValues, value];
256
+ onApply(next);
257
+ };
258
+ const chipText = (() => {
259
+ if (!active)
260
+ return label;
261
+ if (selectedValues.length === 1) {
262
+ const found = options.find((o) => o.value === selectedValues[0]);
263
+ return `${label}: ${found?.label ?? selectedValues[0]}`;
264
+ }
265
+ if (selectedValues.length === 2) {
266
+ const names = selectedValues.map((v) => options.find((o) => o.value === v)?.label ?? v);
267
+ return `${label}: ${names.join(', ')}`;
268
+ }
269
+ return `${label}: ${selectedValues.length} selected`;
270
+ })();
271
+ return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: (e) => setAnchor(e.currentTarget), startIcon: jsx(FilterListIcon, { sx: { fontSize: '14px !important', mr: -0.25 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, width: 260 } }, children: [active && (jsxs(Box, { sx: {
272
+ px: 2, py: 0.75,
273
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
274
+ borderBottom: '1px solid', borderColor: 'divider',
275
+ }, children: [jsxs(Typography, { sx: { fontSize: 11, color: 'primary.main', fontWeight: 600 }, children: [selectedValues.length, " selected"] }), jsx(Button, { size: "small", color: "inherit", sx: { fontSize: 11, py: 0, textTransform: 'none', minWidth: 'auto' }, onClick: () => onApply([]), children: "Clear all" })] })), jsx(List, { dense: true, disablePadding: true, sx: { maxHeight: 300, overflowY: 'auto', py: 0.5 }, children: options.map((opt) => {
276
+ const checked = selectedValues.includes(opt.value);
277
+ return (jsxs(ListItemButton, { onClick: () => toggle(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: {
278
+ fontSize: 13,
279
+ color: checked ? 'primary.main' : 'text.primary',
280
+ fontWeight: checked ? 600 : 400,
281
+ } })] }, opt.value));
282
+ }) })] })] }));
283
+ };
247
284
  const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, maxVisible = 4, chipStyle, }) => {
248
285
  const [anchor, setAnchor] = useState(null);
249
286
  const atLimit = visibleIds.length >= maxVisible;
@@ -296,11 +333,21 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
296
333
  minHeight: filterChipMinHeight ?? (uiDensity === 'compact' ? 26 : 30),
297
334
  };
298
335
  const labelFontSize = filterLabelFontSize ?? (uiDensity === 'compact' ? 11 : 12);
336
+ // ── Single-value column filter helpers ──────────────────────────────────────
299
337
  const getColValue = (id) => columnFilters.find((f) => f.id === id)?.value ?? '';
300
338
  const applyColFilter = (id, value) => {
301
339
  const rest = columnFilters.filter((f) => f.id !== id);
302
340
  onColumnFilterChange(value ? [...rest, { id, value }] : rest);
303
341
  };
342
+ // ── Multi-value column filter helpers ────────────────────────────────────────
343
+ const getColValues = (id) => {
344
+ const val = columnFilters.find((f) => f.id === id)?.value;
345
+ return Array.isArray(val) ? val : [];
346
+ };
347
+ const applyColMultiFilter = (id, values) => {
348
+ const rest = columnFilters.filter((f) => f.id !== id);
349
+ onColumnFilterChange(values.length > 0 ? [...rest, { id, value: values }] : rest);
350
+ };
304
351
  const removeFilter = (id) => {
305
352
  const def = serverFilterDefs.find((d) => d.id === id);
306
353
  if (def?.type === 'dateRange') {
@@ -308,14 +355,22 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
308
355
  }
309
356
  else if (def?.type === 'text' ||
310
357
  def?.type === 'dropdown' ||
311
- def?.type === 'searchableDropdown') {
358
+ def?.type === 'searchableDropdown' ||
359
+ def?.type === 'multiSelect') {
312
360
  onFilterValuesChange({ ...filterValues, [def.filterKey]: '' });
313
361
  }
314
362
  else if (def?.type === 'date') {
315
363
  applyColFilter(def.columnKey, '');
316
364
  }
317
365
  else {
318
- applyColFilter(id, '');
366
+ // Column filter — multi-select stores string[], others store string
367
+ const colDef = availableColumns.find((c) => c.id === id);
368
+ if (colDef?.filterType === 'multi-select') {
369
+ applyColMultiFilter(id, []);
370
+ }
371
+ else {
372
+ applyColFilter(id, '');
373
+ }
319
374
  }
320
375
  onToggleFilter(id);
321
376
  };
@@ -333,6 +388,16 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
333
388
  if (def?.type === 'searchableDropdown') {
334
389
  return (jsx(SearchableSelectChip, { label: def.label, value: filterValues[def.filterKey] ?? '', options: def.options, onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), placeholder: def.placeholder ?? 'Search…', chipStyle: chipStyle }, id));
335
390
  }
391
+ if (def?.type === 'multiSelect') {
392
+ // filterValues[key] stores comma-separated values: "Pending,Shipped"
393
+ const selected = (filterValues[def.filterKey] ?? '')
394
+ .split(',')
395
+ .filter(Boolean);
396
+ return (jsx(MultiSelectFilterChip, { label: def.label, selectedValues: selected, options: def.options, onApply: (vals) => onFilterValuesChange({
397
+ ...filterValues,
398
+ [def.filterKey]: vals.join(','),
399
+ }), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
400
+ }
336
401
  if (def?.type === 'date') {
337
402
  return (jsx(DatePickerChip, { label: def.label, value: getColValue(def.columnKey), onApply: (v) => applyColFilter(def.columnKey, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
338
403
  }
@@ -346,6 +411,9 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
346
411
  if (col.filterType === 'select' || col.filterType === 'searchable-select') {
347
412
  return (jsx(SearchableSelectChip, { label: col.label, value: getColValue(id), options: col.filterOptions ?? [], onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
348
413
  }
414
+ if (col.filterType === 'multi-select') {
415
+ return (jsx(MultiSelectFilterChip, { label: col.label, selectedValues: getColValues(id), options: col.filterOptions ?? [], onApply: (vals) => applyColMultiFilter(id, vals), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
416
+ }
349
417
  // Default: free-text input
350
418
  return (jsx(TextFilterChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), placeholder: `Filter by ${col.label}`, chipStyle: chipStyle }, id));
351
419
  }), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter, maxVisible: maxVisibleFilters, chipStyle: chipStyle })] }));
@@ -19,6 +19,10 @@ export interface GridUiDensityOptions {
19
19
  filterChipMinHeight?: number;
20
20
  filterLabelFontSize?: number;
21
21
  toolbarIconFontSize?: number | string;
22
+ /** Header column title font size (px number). */
23
+ headerFontSize?: number;
24
+ /** Body cell data font size (px number). */
25
+ bodyFontSize?: number;
22
26
  /** MRT built-in toolbar control density (show/hide columns, density toggle, etc.). */
23
27
  mrtDensity?: 'comfortable' | 'compact' | 'spacious';
24
28
  }
@@ -102,6 +106,16 @@ export interface GenericPaginatedGridProps<TData extends object> {
102
106
  * Omit to allow text to use the full `headerMaxHeight` (recommended for tall headers).
103
107
  */
104
108
  headerMaxLines?: number;
109
+ /**
110
+ * Font size for header column titles (CSS length or px number).
111
+ * Default: `12px` (`11px` when `uiDensity="compact"`). Overridden by `uiDensityOptions.headerFontSize`.
112
+ */
113
+ headerFontSize?: string | number;
114
+ /**
115
+ * Font size for body/data cells (CSS length or px number).
116
+ * Default: `12px` (`11px` compact). Overridden by `uiDensityOptions.bodyFontSize`.
117
+ */
118
+ bodyFontSize?: string | number;
105
119
  /**
106
120
  * Maximum number of filter chips that can be visible at once.
107
121
  * Prevents the filter bar from wrapping to a second row on typical screens.
@@ -109,5 +123,5 @@ export interface GenericPaginatedGridProps<TData extends object> {
109
123
  */
110
124
  maxVisibleFilters?: number;
111
125
  }
112
- declare function GenericPaginatedGrid<TData extends object>({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize, exportFilename, tableHeight, tableMinHeight, tableMaxHeight, autoFillViewport, layoutMode, uiDensity, uiDensityOptions, pageSizeOptions, headerBgColor, headerMinHeight, headerMaxHeight, headerMaxLines, maxVisibleFilters, }: GenericPaginatedGridProps<TData>): import("react/jsx-runtime").JSX.Element;
126
+ declare function GenericPaginatedGrid<TData extends object>({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize, exportFilename, tableHeight, tableMinHeight, tableMaxHeight, autoFillViewport, layoutMode, uiDensity, uiDensityOptions, pageSizeOptions, headerBgColor, headerMinHeight, headerMaxHeight, headerMaxLines, headerFontSize, bodyFontSize, maxVisibleFilters, }: GenericPaginatedGridProps<TData>): import("react/jsx-runtime").JSX.Element;
113
127
  export default GenericPaginatedGrid;
@@ -21,6 +21,8 @@ const UI_DENSITY_PRESETS = {
21
21
  filterChipMinHeight: 30,
22
22
  filterLabelFontSize: 12,
23
23
  toolbarIconFontSize: 'small',
24
+ headerFontSize: 12,
25
+ bodyFontSize: 12,
24
26
  mrtDensity: 'comfortable',
25
27
  },
26
28
  compact: {
@@ -32,6 +34,8 @@ const UI_DENSITY_PRESETS = {
32
34
  filterChipMinHeight: 26,
33
35
  filterLabelFontSize: 11,
34
36
  toolbarIconFontSize: 'inherit',
37
+ headerFontSize: 11,
38
+ bodyFontSize: 11,
35
39
  mrtDensity: 'compact',
36
40
  },
37
41
  };
@@ -44,7 +48,13 @@ const toCssSize = (value, fallback) => {
44
48
  * Header overrides applied via GlobalStyles (literal prop values).
45
49
  * MRT sets `backgroundColor: 'inherit'` and fixed density padding — must use !important.
46
50
  */
47
- const buildHeaderGlobalStyles = (headerMin, headerMax, headerBgColor) => {
51
+ const buildBodyGlobalStyles = (bodyFont) => ({
52
+ '.tp-gpg-root .MuiTableBody-root .MuiTableCell-root': {
53
+ fontSize: `${bodyFont} !important`,
54
+ lineHeight: 1.35,
55
+ },
56
+ });
57
+ const buildHeaderGlobalStyles = (headerMin, headerMax, headerFont, headerBgColor) => {
48
58
  const headCell = {
49
59
  height: `${headerMax} !important`,
50
60
  minHeight: `${headerMin} !important`,
@@ -69,7 +79,10 @@ const buildHeaderGlobalStyles = (headerMin, headerMax, headerBgColor) => {
69
79
  alignItems: 'stretch !important',
70
80
  ...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
71
81
  },
72
- '.tp-gpg-root .MuiTableHead-root .MuiTableCell-head': headCell,
82
+ '.tp-gpg-root .MuiTableHead-root .MuiTableCell-head': {
83
+ ...headCell,
84
+ fontSize: `${headerFont} !important`,
85
+ },
73
86
  '.tp-gpg-root .MuiTableHead-root .Mui-TableHeadCell-Content': {
74
87
  height: '100% !important',
75
88
  width: '100% !important',
@@ -92,6 +105,7 @@ const buildHeaderGlobalStyles = (headerMin, headerMax, headerBgColor) => {
92
105
  overflowWrap: 'anywhere !important',
93
106
  width: '100% !important',
94
107
  maxWidth: '100% !important',
108
+ fontSize: `${headerFont} !important`,
95
109
  display: 'block !important',
96
110
  WebkitLineClamp: 'unset !important',
97
111
  WebkitBoxOrient: 'unset !important',
@@ -101,11 +115,12 @@ const buildHeaderGlobalStyles = (headerMin, headerMax, headerBgColor) => {
101
115
  wordBreak: 'break-word !important',
102
116
  overflowWrap: 'anywhere !important',
103
117
  width: '100% !important',
118
+ fontSize: `${headerFont} !important`,
104
119
  },
105
120
  };
106
121
  };
107
122
  /** Per-cell sx merged into MRT (via muiTableHeadCellProps). */
108
- const buildHeaderCellSx = (align, headerMin, headerMax, headerBgColor, headerMaxLines) => {
123
+ const buildHeaderCellSx = (align, headerMin, headerMax, headerFont, headerBgColor, headerMaxLines) => {
109
124
  const textAlign = align;
110
125
  const flexJustify = align === 'right' ? 'flex-end' : align === 'center' ? 'center' : 'flex-start';
111
126
  const wrapperClamp = headerMaxLines != null && headerMaxLines > 0
@@ -127,6 +142,7 @@ const buildHeaderCellSx = (align, headerMin, headerMax, headerBgColor, headerMax
127
142
  height: `${headerMax} !important`,
128
143
  minHeight: `${headerMin} !important`,
129
144
  maxHeight: `${headerMax} !important`,
145
+ fontSize: headerFont,
130
146
  boxSizing: 'border-box',
131
147
  borderRight: '1px solid rgba(224,224,224,1)',
132
148
  borderBottom: '2px solid rgba(0,0,0,0.15)',
@@ -153,6 +169,7 @@ const buildHeaderCellSx = (align, headerMin, headerMax, headerBgColor, headerMax
153
169
  wordBreak: 'break-word',
154
170
  overflowWrap: 'anywhere',
155
171
  lineHeight: 1.25,
172
+ fontSize: headerFont,
156
173
  width: '100%',
157
174
  textAlign,
158
175
  ...wrapperClamp,
@@ -160,6 +177,7 @@ const buildHeaderCellSx = (align, headerMin, headerMax, headerBgColor, headerMax
160
177
  '& .MuiTableSortLabel-root': {
161
178
  whiteSpace: 'normal',
162
179
  wordBreak: 'break-word',
180
+ fontSize: headerFont,
163
181
  width: '100%',
164
182
  margin: 0,
165
183
  },
@@ -203,11 +221,22 @@ const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoa
203
221
  }, children: pageIndex + 1 }), jsx(Tooltip, { title: "Next page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onNext, disabled: !canNext, sx: { p: 0.5 }, children: jsx(ChevronRightIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Tooltip, { title: "Last page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onLast, disabled: !canNext, sx: { p: 0.5 }, children: jsx(LastPageIcon, { sx: { fontSize: density.footerIconSize } }) }) }) })] })] }));
204
222
  };
205
223
  // ─── GenericPaginatedGrid ─────────────────────────────────────────────────────
206
- function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize = 10000, exportFilename = 'export', tableHeight, tableMinHeight = '300px', tableMaxHeight, autoFillViewport = true, layoutMode = 'grid', uiDensity = 'default', uiDensityOptions, pageSizeOptions = [1000, 5000, 10000], headerBgColor, headerMinHeight, headerMaxHeight, headerMaxLines, maxVisibleFilters = 4, }) {
224
+ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize = 10000, exportFilename = 'export', tableHeight, tableMinHeight = '300px', tableMaxHeight, autoFillViewport = true, layoutMode = 'grid', uiDensity = 'default', uiDensityOptions, pageSizeOptions = [1000, 5000, 10000], headerBgColor, headerMinHeight, headerMaxHeight, headerMaxLines, headerFontSize, bodyFontSize, maxVisibleFilters = 4, }) {
207
225
  const densityTokens = useMemo(() => ({ ...UI_DENSITY_PRESETS[uiDensity], ...uiDensityOptions }), [uiDensity, uiDensityOptions]);
208
226
  const resolvedHeaderMinHeight = toCssSize(headerMinHeight, uiDensity === 'compact' ? '34px' : '40px');
209
227
  const resolvedHeaderMaxHeight = toCssSize(headerMaxHeight, uiDensity === 'compact' ? '56px' : '72px');
210
- const headerGlobalStyleOverrides = useMemo(() => buildHeaderGlobalStyles(resolvedHeaderMinHeight, resolvedHeaderMaxHeight, headerBgColor), [resolvedHeaderMinHeight, resolvedHeaderMaxHeight, headerBgColor]);
228
+ const resolvedHeaderFontSize = toCssSize(headerFontSize ?? densityTokens.headerFontSize, uiDensity === 'compact' ? '11px' : '12px');
229
+ const resolvedBodyFontSize = toCssSize(bodyFontSize ?? densityTokens.bodyFontSize, uiDensity === 'compact' ? '11px' : '12px');
230
+ const tableStyleOverrides = useMemo(() => ({
231
+ ...buildHeaderGlobalStyles(resolvedHeaderMinHeight, resolvedHeaderMaxHeight, resolvedHeaderFontSize, headerBgColor),
232
+ ...buildBodyGlobalStyles(resolvedBodyFontSize),
233
+ }), [
234
+ resolvedHeaderMinHeight,
235
+ resolvedHeaderMaxHeight,
236
+ resolvedHeaderFontSize,
237
+ resolvedBodyFontSize,
238
+ headerBgColor,
239
+ ]);
211
240
  const defaultVisible = defaultVisibleFilterIds
212
241
  ?? (serverFilterDefs.length > 0 ? [serverFilterDefs[0].id] : []);
213
242
  // ── State ──────────────────────────────────────────────────────────────────
@@ -286,6 +315,13 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
286
315
  ? row.getValue(columnId) === filterValue
287
316
  : true;
288
317
  }
318
+ else if (filterType === 'multi-select') {
319
+ // Inclusion check: pass when the cell value is one of the selected values.
320
+ // filterValue is string[] (stored directly in MRT columnFilters).
321
+ mrtCol.filterFn = (row, columnId, filterValue) => !filterValue || filterValue.length === 0
322
+ ? true
323
+ : filterValue.includes(row.getValue(columnId) ?? '');
324
+ }
289
325
  // 'text' and 'number' use MRT's built-in client-side filtering
290
326
  }
291
327
  return mrtCol;
@@ -474,16 +510,32 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
474
510
  return {
475
511
  align,
476
512
  sx: [
477
- buildHeaderCellSx(align, resolvedHeaderMinHeight, resolvedHeaderMaxHeight, headerBgColor, headerMaxLines),
513
+ buildHeaderCellSx(align, resolvedHeaderMinHeight, resolvedHeaderMaxHeight, resolvedHeaderFontSize, headerBgColor, headerMaxLines),
478
514
  ...(colSx ? [colSx] : []),
479
515
  ],
480
516
  };
481
517
  },
482
- muiTableBodyCellProps: {
483
- sx: {
484
- whiteSpace: 'nowrap',
485
- borderRight: '1px solid rgba(224,224,224,1)',
486
- },
518
+ muiTableBodyCellProps: ({ column }) => {
519
+ const colProps = column.columnDef.muiTableBodyCellProps;
520
+ let colSx;
521
+ if (typeof colProps === 'function') {
522
+ const resolved = colProps({ column });
523
+ colSx = resolved?.sx;
524
+ }
525
+ else if (colProps) {
526
+ colSx = colProps.sx;
527
+ }
528
+ return {
529
+ sx: [
530
+ {
531
+ whiteSpace: 'nowrap',
532
+ fontSize: resolvedBodyFontSize,
533
+ lineHeight: 1.35,
534
+ borderRight: '1px solid rgba(224,224,224,1)',
535
+ },
536
+ ...(colSx ? [colSx] : []),
537
+ ],
538
+ };
487
539
  },
488
540
  muiToolbarAlertBannerProps: isError
489
541
  ? { color: 'error', children: 'Failed to load data. Please try again.' }
@@ -505,7 +557,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
505
557
  '.tp-gpg-root button[aria-label="Move"]': { display: 'none !important' },
506
558
  '.tp-gpg-root [data-column-id="mrt-row-spacer"]': { display: 'none !important' },
507
559
  '.tp-gpg-root .Mui-TableHeadCell-ColumnSpacer': { display: 'none !important' },
508
- ...headerGlobalStyleOverrides,
560
+ ...tableStyleOverrides,
509
561
  } }), isLoading && jsx(OverlayLoader, { message: "Loading data\u2026" }), !isLoading && isFetching && jsx(OverlayLoader, { message: "Updating data\u2026" }), jsx(Box, { sx: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: jsx(MaterialReactTable, { table: table }) })] }));
510
562
  }
511
563
 
@@ -31,3 +31,24 @@ export declare const productColumns: TPColumnDef<ProductRow>[];
31
31
  export declare const productFilterDefs: ServerFilterDef[];
32
32
  export declare const productDefaultFilters: Record<string, string>;
33
33
  export declare function fetchProductPage(filters: Record<string, string>, pageIndex: number, pageSize?: number): Promise<PagedResult<ProductRow>>;
34
+ export interface OrderRow {
35
+ orderId: string;
36
+ customer: string;
37
+ category: string;
38
+ status: string;
39
+ priority: string;
40
+ amount: number;
41
+ qty: number;
42
+ orderDate: string;
43
+ }
44
+ export declare const orderColumns: TPColumnDef<OrderRow>[];
45
+ /**
46
+ * Server filter definitions for the Orders demo grid.
47
+ *
48
+ * `multiSelect` chips for Status and Category send comma-separated values
49
+ * in `filterValues` (e.g. `status: "Pending,Shipped"`).
50
+ * `fetchOrderPage` splits on `","` to apply the server-side filter.
51
+ */
52
+ export declare const orderFilterDefs: ServerFilterDef[];
53
+ export declare const orderDefaultFilters: Record<string, string>;
54
+ export declare function fetchOrderPage(filters: Record<string, string>, pageIndex: number, pageSize?: number): Promise<PagedResult<OrderRow>>;
@@ -2,18 +2,19 @@ import type { MRT_ColumnDef } from 'material-react-table';
2
2
  /**
3
3
  * Filter chip variant shown in Manage Filters → Column Filters.
4
4
  *
5
- * | Value | Chip rendered |
6
- * |---------------------|---------------------------|
7
- * | `'text'` | Free-text input (default) |
8
- * | `'date'` | Calendar date picker |
9
- * | `'select'` | Searchable option list |
10
- * | `'searchable-select'` | Searchable option list |
11
- * | `'number'` | Numeric input |
5
+ * | Value | Chip rendered |
6
+ * |---------------------|------------------------------------------------------|
7
+ * | `'text'` | Free-text input (default) |
8
+ * | `'date'` | Calendar date picker |
9
+ * | `'select'` | Searchable single-select option list |
10
+ * | `'searchable-select'` | Searchable single-select option list |
11
+ * | `'number'` | Numeric input |
12
+ * | `'multi-select'` | Checkbox list — pick many values, filters client-side|
12
13
  *
13
14
  * Set `enableColumnFilter: false` to hide the column from Manage Filters
14
15
  * entirely (e.g. for computed or numeric columns that don't need filtering).
15
16
  */
16
- export type ColumnFilterType = 'text' | 'date' | 'select' | 'searchable-select' | 'number';
17
+ export type ColumnFilterType = 'text' | 'date' | 'select' | 'searchable-select' | 'number' | 'multi-select';
17
18
  /**
18
19
  * Drop-in replacement for `MRT_ColumnDef` that adds top-level
19
20
  * `filterType` and `options` so you never have to write `meta` blocks
@@ -27,7 +28,7 @@ export type ColumnFilterType = 'text' | 'date' | 'select' | 'searchable-select'
27
28
  * // Date picker chip
28
29
  * { accessorKey: 'invoiceDate', header: 'Invoice Date', filterType: 'date' },
29
30
  *
30
- * // Searchable option list chip
31
+ * // Searchable single-select chip
31
32
  * {
32
33
  * accessorKey: 'status',
33
34
  * header: 'Status',
@@ -38,6 +39,18 @@ export type ColumnFilterType = 'text' | 'date' | 'select' | 'searchable-select'
38
39
  * ],
39
40
  * },
40
41
  *
42
+ * // Multi-select checkbox chip — pick many values, client-side
43
+ * {
44
+ * accessorKey: 'priority',
45
+ * header: 'Priority',
46
+ * filterType: 'multi-select',
47
+ * options: [
48
+ * { label: 'Low', value: 'Low' },
49
+ * { label: 'Medium', value: 'Medium' },
50
+ * { label: 'High', value: 'High' },
51
+ * ],
52
+ * },
53
+ *
41
54
  * // Hidden from Manage Filters (numeric column)
42
55
  * { accessorKey: 'qty', header: 'Qty', enableColumnFilter: false },
43
56
  * ];
@@ -1,4 +1,4 @@
1
- import { __exports as Calendar } from '../../../../../_virtual/index.esm4.js';
1
+ import { __exports as Calendar } from '../../../../../_virtual/index.esm5.js';
2
2
  import React__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.esm13.js';
1
+ import { __exports as DateInput } from '../../../../../_virtual/index.esm14.js';
2
2
  import React__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.esm6.js';
1
+ import { __exports as DateRange } from '../../../../../_virtual/index.esm7.js';
2
2
  import React__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 DateRangePicker } from '../../../../../_virtual/index.esm5.js';
1
+ import { __exports as DateRangePicker } from '../../../../../_virtual/index.esm4.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
  import { __require as requireDateRange } from '../DateRange/index.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as DefinedRange } from '../../../../../_virtual/index.esm7.js';
1
+ import { __exports as DefinedRange } from '../../../../../_virtual/index.esm6.js';
2
2
  import React__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 InputRangeField } from '../../../../../_virtual/index.esm14.js';
1
+ import { __exports as InputRangeField } from '../../../../../_virtual/index.esm13.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tp-react-elements",
3
- "version": "1.3.11",
3
+ "version": "1.3.13",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "React form components library built with React Hook Form and Yup",