tp-react-elements 1.3.10 → 1.3.12

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
@@ -165,9 +165,51 @@ The library provides a comprehensive set of form components that integrate seaml
165
165
  - **RadioGroup** - Radio button selection groups
166
166
  - **Switch** - Toggle switches for boolean values
167
167
  - **Form** - Container component with built-in validation
168
+ - **GenericPaginatedGrid** - Server-paginated data grid with dynamic filter chips (see Storybook **Docs → Paginated Grid**)
168
169
 
169
170
  Refer to our Storybook documentation for detailed API documentation and examples.
170
171
 
172
+ ### Paginated grid (Storybook)
173
+
174
+ ```bash
175
+ npm run storybook
176
+ ```
177
+
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
+ - **Components → PaginatedGrid → Example Grid** — live invoice API demo
181
+
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”:**
198
+
199
+ ```tsx
200
+ <GenericPaginatedGrid
201
+ headerBgColor="#e8eaf6"
202
+ headerMinHeight="48px"
203
+ headerMaxHeight="96px"
204
+ headerFontSize={12}
205
+ bodyFontSize={12}
206
+ layoutMode="grid"
207
+ {...gridProps}
208
+ />
209
+ ```
210
+
211
+ Full examples: Storybook **Docs → Paginated Grid → Examples**.
212
+
171
213
  ## 🏗️ Best Practices for Code Structure and Organization
172
214
 
173
215
  ### Component Organization
@@ -1,3 +1,3 @@
1
- var DefinedRange = {};
1
+ var DateRange = {};
2
2
 
3
- export { DefinedRange as __exports };
3
+ export { DateRange as __exports };
@@ -1,3 +1,3 @@
1
- var DateRange = {};
1
+ var DefinedRange = {};
2
2
 
3
- export { DateRange as __exports };
3
+ export { DefinedRange as __exports };
@@ -82,6 +82,10 @@ export interface DynamicFilterBarProps {
82
82
  availableColumns: AvailableColumn[];
83
83
  /** Maximum number of chips that may be visible at once. Default: 4. */
84
84
  maxVisibleFilters?: number;
85
+ uiDensity?: 'default' | 'compact';
86
+ filterChipFontSize?: number;
87
+ filterChipMinHeight?: number;
88
+ filterLabelFontSize?: number;
85
89
  }
86
- declare const DynamicFilterBar: ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters, }: DynamicFilterBarProps) => import("react/jsx-runtime").JSX.Element;
90
+ declare const DynamicFilterBar: ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters, uiDensity, filterChipFontSize, filterChipMinHeight, filterLabelFontSize, }: DynamicFilterBarProps) => import("react/jsx-runtime").JSX.Element;
87
91
  export default DynamicFilterBar;
@@ -38,19 +38,17 @@ const COLUMN_TYPE_PILL = {
38
38
  'searchable-select': { label: 'Search', color: '#bf360c', bg: '#fbe9e7' },
39
39
  number: { label: 'Number', color: '#e65100', bg: '#fff3e0' },
40
40
  };
41
- // ─── Chip shared style ────────────────────────────────────────────────────────
42
- const chipSx = (active) => ({
41
+ const chipSx = (active, opts) => ({
43
42
  borderRadius: 5,
44
43
  textTransform: 'none',
45
- fontSize: 12,
44
+ fontSize: opts.fontSize,
46
45
  fontWeight: active ? 600 : 400,
47
46
  borderColor: active ? 'primary.main' : 'divider',
48
47
  color: active ? 'primary.main' : 'text.secondary',
49
48
  bgcolor: active ? 'rgba(25,118,210,0.08)' : 'transparent',
50
49
  whiteSpace: 'nowrap',
51
- px: 1.5,
52
- minHeight: 30,
53
- // Cap chip width so long option labels don't break the filter bar layout
50
+ px: opts.px ?? (opts.fontSize <= 11 ? 1 : 1.5),
51
+ minHeight: opts.minHeight,
54
52
  maxWidth: 260,
55
53
  overflow: 'hidden',
56
54
  '&:hover': { borderColor: 'primary.main', bgcolor: 'rgba(25,118,210,0.08)' },
@@ -78,7 +76,7 @@ const fmtDate = (iso) => {
78
76
  const yearSuffix = y !== thisYear ? ` '${y.slice(2)}` : '';
79
77
  return `${Number(d)} ${months[Number(m) - 1]}${yearSuffix}`;
80
78
  };
81
- const DateRangeChip = ({ label, fromDate, toDate, onApply, onRemove }) => {
79
+ const DateRangeChip = ({ label, fromDate, toDate, onApply, onRemove, chipStyle }) => {
82
80
  // Inject react-date-range CSS via JS (not CSS import) to prevent it from being
83
81
  // bundled into the library's extracted CSS file and leaking into consumer apps.
84
82
  useRdrStyles();
@@ -120,14 +118,14 @@ const DateRangeChip = ({ label, fromDate, toDate, onApply, onRemove }) => {
120
118
  setAnchor(null);
121
119
  }
122
120
  };
123
- return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipLabel : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: openPicker, startIcon: jsx(CalendarTodayIcon, { sx: { fontSize: 13 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active), children: jsx(ChipLabel, { text: chipLabel }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, overflow: 'hidden' } }, children: [jsx(distExports.DateRange, { editableDateInputs: false, onChange: handleRangeChange, moveRangeOnFirstSelection: false, ranges: selRange, months: 2, direction: "horizontal", showDateDisplay: false, dateDisplayFormat: "dd/MM/yyyy", rangeColors: ['#1976d2'] }), active && (jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-end', px: 2, pb: 1.5 }, children: jsx(Button, { size: "small", color: "inherit", onClick: () => { onApply('', ''); setAnchor(null); }, children: "Clear" }) }))] })] }));
121
+ return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipLabel : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: openPicker, startIcon: jsx(CalendarTodayIcon, { sx: { fontSize: 13 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipLabel }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, overflow: 'hidden' } }, children: [jsx(distExports.DateRange, { editableDateInputs: false, onChange: handleRangeChange, moveRangeOnFirstSelection: false, ranges: selRange, months: 2, direction: "horizontal", showDateDisplay: false, dateDisplayFormat: "dd/MM/yyyy", rangeColors: ['#1976d2'] }), active && (jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-end', px: 2, pb: 1.5 }, children: jsx(Button, { size: "small", color: "inherit", onClick: () => { onApply('', ''); setAnchor(null); }, children: "Clear" }) }))] })] }));
124
122
  };
125
- const TextFilterChip = ({ label, value, onApply, onRemove, placeholder }) => {
123
+ const TextFilterChip = ({ label, value, onApply, onRemove, placeholder, chipStyle }) => {
126
124
  const [anchor, setAnchor] = useState(null);
127
125
  const [draft, setDraft] = useState('');
128
126
  const active = Boolean(value);
129
127
  const chipText = active ? `${label}: ${value}` : label;
130
- return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: (e) => { setDraft(value); setAnchor(e.currentTarget); }, endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, minWidth: 240 } }, children: jsxs(Box, { sx: { p: 2 }, children: [jsx(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", display: "block", mb: 1.5, children: label }), jsx(TextField, { value: draft, onChange: (e) => setDraft(e.target.value), placeholder: placeholder, size: "small", fullWidth: true, autoFocus: true, onKeyDown: (e) => {
128
+ return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: (e) => { setDraft(value); setAnchor(e.currentTarget); }, endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, minWidth: 240 } }, children: jsxs(Box, { sx: { p: 2 }, children: [jsx(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", display: "block", mb: 1.5, children: label }), jsx(TextField, { value: draft, onChange: (e) => setDraft(e.target.value), placeholder: placeholder, size: "small", fullWidth: true, autoFocus: true, onKeyDown: (e) => {
131
129
  if (e.key === 'Enter') {
132
130
  onApply(draft);
133
131
  setAnchor(null);
@@ -136,7 +134,7 @@ const TextFilterChip = ({ label, value, onApply, onRemove, placeholder }) => {
136
134
  setAnchor(null);
137
135
  } }), jsxs(Stack, { direction: "row", spacing: 1, justifyContent: "flex-end", mt: 1.5, children: [active && (jsx(Button, { size: "small", color: "inherit", onClick: () => { onApply(''); setAnchor(null); }, children: "Clear" })), jsx(Button, { size: "small", variant: "contained", disableElevation: true, onClick: () => { onApply(draft); setAnchor(null); }, children: "Apply" })] })] }) })] }));
138
136
  };
139
- const DatePickerChip = ({ label, value, onApply, onRemove }) => {
137
+ const DatePickerChip = ({ label, value, onApply, onRemove, chipStyle }) => {
140
138
  const [open, setOpen] = useState(false);
141
139
  const [anchorEl, setAnchorEl] = useState(null);
142
140
  const active = Boolean(value);
@@ -160,7 +158,7 @@ const DatePickerChip = ({ label, value, onApply, onRemove }) => {
160
158
  // LocalizationProvider must wrap DatePicker — scoped here so it only
161
159
  // loads when the chip is actually used (DatePickerChip is tree-shaken
162
160
  // if the 'date' filter type is never included in serverFilterDefs).
163
- jsxs(LocalizationProvider, { dateAdapter: AdapterDayjs, children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: handleChipClick, startIcon: jsx(CalendarTodayIcon, { sx: { fontSize: 13 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(DatePicker, { value: dayjsValue, open: open, onClose: () => setOpen(false), onChange: handleChange, slotProps: {
161
+ jsxs(LocalizationProvider, { dateAdapter: AdapterDayjs, children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: handleChipClick, startIcon: jsx(CalendarTodayIcon, { sx: { fontSize: 13 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(DatePicker, { value: dayjsValue, open: open, onClose: () => setOpen(false), onChange: handleChange, slotProps: {
164
162
  textField: { sx: { display: 'none' }, size: 'small' },
165
163
  popper: {
166
164
  anchorEl,
@@ -168,7 +166,7 @@ const DatePickerChip = ({ label, value, onApply, onRemove }) => {
168
166
  },
169
167
  } })] }));
170
168
  };
171
- const DropdownFilterChip = ({ label, value, options, onApply, onRemove }) => {
169
+ const DropdownFilterChip = ({ label, value, options, onApply, onRemove, chipStyle }) => {
172
170
  const [anchor, setAnchor] = useState(null);
173
171
  const active = Boolean(value);
174
172
  const selectedLabel = options.find((o) => o.value === value)?.label ?? '';
@@ -199,7 +197,7 @@ const DropdownFilterChip = ({ label, value, options, onApply, onRemove }) => {
199
197
  ],
200
198
  };
201
199
  const chipText = active ? `${label}: ${selectedLabel}` : label;
202
- 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(ArrowDropDownIcon, { sx: { fontSize: '15px !important', mr: -0.5 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, p: 1.5, minWidth: 240 } }, children: jsx(SingleSelectNonAutoComplete, { props: {
200
+ 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(ArrowDropDownIcon, { sx: { fontSize: '15px !important', mr: -0.5 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, p: 1.5, minWidth: 240 } }, children: jsx(SingleSelectNonAutoComplete, { props: {
203
201
  item,
204
202
  register,
205
203
  control,
@@ -210,7 +208,7 @@ const DropdownFilterChip = ({ label, value, options, onApply, onRemove }) => {
210
208
  watch,
211
209
  }, variant: "outlined" }) })] }));
212
210
  };
213
- const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeholder = 'Search…', }) => {
211
+ const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeholder = 'Search…', chipStyle, }) => {
214
212
  const [anchor, setAnchor] = useState(null);
215
213
  const [search, setSearch] = useState('');
216
214
  const active = Boolean(value);
@@ -228,7 +226,7 @@ const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeh
228
226
  ? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()))
229
227
  : options;
230
228
  const chipText = active ? `${label}: ${selectedLabel}` : label;
231
- return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: open, startIcon: jsx(SearchIcon, { sx: { fontSize: '14px !important', mr: -0.25 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active), children: jsx(ChipLabel, { text: chipText }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: {
229
+ return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: open, startIcon: jsx(SearchIcon, { 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: {
232
230
  sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, width: 280, display: 'flex', flexDirection: 'column' },
233
231
  }, children: [jsx(Box, { sx: { px: 1.5, pt: 1.5, pb: 0.75 }, children: jsx(TextField, { size: "small", fullWidth: true, autoFocus: true, placeholder: placeholder, value: search, onChange: (e) => setSearch(e.target.value), onKeyDown: (e) => { if (e.key === 'Escape')
234
232
  setAnchor(null); }, InputProps: {
@@ -246,7 +244,7 @@ const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeh
246
244
  } }), isSelected && (jsx(CheckIcon, { sx: { fontSize: 15, color: 'primary.main', flexShrink: 0 } }))] }, opt.value));
247
245
  }))] })] })] }));
248
246
  };
249
- const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, maxVisible = 4, }) => {
247
+ const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, maxVisible = 4, chipStyle, }) => {
250
248
  const [anchor, setAnchor] = useState(null);
251
249
  const atLimit = visibleIds.length >= maxVisible;
252
250
  // Shared pill box renderer
@@ -262,9 +260,9 @@ const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, o
262
260
  lineHeight: 1.6,
263
261
  }, children: label }));
264
262
  return (jsxs(Fragment, { children: [jsx(Tooltip, { title: "Add or remove filter chips", children: jsx(Button, { size: "small", variant: "outlined", startIcon: jsx(TuneIcon, { sx: { fontSize: 14 } }), onClick: (e) => setAnchor(e.currentTarget), sx: {
265
- borderRadius: 5, textTransform: 'none', fontSize: 12,
263
+ borderRadius: 5, textTransform: 'none', fontSize: chipStyle.fontSize,
266
264
  borderColor: 'divider', color: 'text.secondary',
267
- px: 1.5, minHeight: 30,
265
+ px: chipStyle.px ?? 1.5, minHeight: chipStyle.minHeight,
268
266
  '&:hover': { borderColor: 'primary.main', color: 'primary.main' },
269
267
  }, children: "Manage Filters" }) }), 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: 280 } }, children: [atLimit && (jsx(Box, { sx: {
270
268
  px: 2, py: 0.75,
@@ -292,7 +290,12 @@ const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, o
292
290
  } }), selected && jsx(CheckIcon, { sx: { fontSize: 16, color: 'primary.main', flexShrink: 0 } })] }) }) }, col.id));
293
291
  }) })] }))] })] })] }));
294
292
  };
295
- const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters = 4, }) => {
293
+ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters = 4, uiDensity = 'default', filterChipFontSize, filterChipMinHeight, filterLabelFontSize, }) => {
294
+ const chipStyle = {
295
+ fontSize: filterChipFontSize ?? (uiDensity === 'compact' ? 11 : 12),
296
+ minHeight: filterChipMinHeight ?? (uiDensity === 'compact' ? 26 : 30),
297
+ };
298
+ const labelFontSize = filterLabelFontSize ?? (uiDensity === 'compact' ? 11 : 12);
296
299
  const getColValue = (id) => columnFilters.find((f) => f.id === id)?.value ?? '';
297
300
  const applyColFilter = (id, value) => {
298
301
  const rest = columnFilters.filter((f) => f.id !== id);
@@ -316,36 +319,36 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
316
319
  }
317
320
  onToggleFilter(id);
318
321
  };
319
- return (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 0.75 }, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.4, mr: 0.25 }, children: [jsx(FilterListIcon, { sx: { fontSize: 15, color: 'text.disabled' } }), jsx(Typography, { variant: "body2", fontSize: 12, color: "text.secondary", fontWeight: 500, noWrap: true, children: "Filter by:" })] }), visibleFilterIds.map((id) => {
322
+ return (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 0.75 }, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.4, mr: 0.25 }, children: [jsx(FilterListIcon, { sx: { fontSize: labelFontSize + 3, color: 'text.disabled' } }), jsx(Typography, { sx: { fontSize: labelFontSize }, color: "text.secondary", fontWeight: 500, noWrap: true, children: "Filter by:" })] }), visibleFilterIds.map((id) => {
320
323
  const def = serverFilterDefs.find((d) => d.id === id);
321
324
  if (def?.type === 'dateRange') {
322
- return (jsx(DateRangeChip, { label: def.label, fromDate: filterValues[def.fromKey] ?? '', toDate: filterValues[def.toKey] ?? '', onApply: (from, to) => onFilterValuesChange({ ...filterValues, [def.fromKey]: from, [def.toKey]: to }), onRemove: () => removeFilter(id) }, id));
325
+ return (jsx(DateRangeChip, { label: def.label, fromDate: filterValues[def.fromKey] ?? '', toDate: filterValues[def.toKey] ?? '', onApply: (from, to) => onFilterValuesChange({ ...filterValues, [def.fromKey]: from, [def.toKey]: to }), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
323
326
  }
324
327
  if (def?.type === 'text') {
325
- return (jsx(TextFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), placeholder: def.placeholder }, id));
328
+ return (jsx(TextFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), placeholder: def.placeholder, chipStyle: chipStyle }, id));
326
329
  }
327
330
  if (def?.type === 'dropdown') {
328
- return (jsx(DropdownFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', options: def.options, onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id) }, id));
331
+ return (jsx(DropdownFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', options: def.options, onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
329
332
  }
330
333
  if (def?.type === 'searchableDropdown') {
331
- 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…' }, id));
334
+ 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));
332
335
  }
333
336
  if (def?.type === 'date') {
334
- return (jsx(DatePickerChip, { label: def.label, value: getColValue(def.columnKey), onApply: (v) => applyColFilter(def.columnKey, v), onRemove: () => removeFilter(id) }, id));
337
+ return (jsx(DatePickerChip, { label: def.label, value: getColValue(def.columnKey), onApply: (v) => applyColFilter(def.columnKey, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
335
338
  }
336
339
  // Column filter chip — render the variant that matches the column's filterType
337
340
  const col = availableColumns.find((c) => c.id === id);
338
341
  if (!col)
339
342
  return null;
340
343
  if (col.filterType === 'date') {
341
- return (jsx(DatePickerChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id) }, id));
344
+ return (jsx(DatePickerChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
342
345
  }
343
346
  if (col.filterType === 'select' || col.filterType === 'searchable-select') {
344
- return (jsx(SearchableSelectChip, { label: col.label, value: getColValue(id), options: col.filterOptions ?? [], onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id) }, id));
347
+ return (jsx(SearchableSelectChip, { label: col.label, value: getColValue(id), options: col.filterOptions ?? [], onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
345
348
  }
346
349
  // Default: free-text input
347
- return (jsx(TextFilterChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), placeholder: `Filter by ${col.label}` }, id));
348
- }), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter, maxVisible: maxVisibleFilters })] }));
350
+ 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
+ }), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter, maxVisible: maxVisibleFilters, chipStyle: chipStyle })] }));
349
352
  };
350
353
 
351
354
  export { DynamicFilterBar as default };
@@ -7,6 +7,25 @@ export interface PagedResult<TData> {
7
7
  data: TData[];
8
8
  totalRecords: number;
9
9
  }
10
+ /** Preset for toolbar, filter chips, and pagination footer sizing. */
11
+ export type GridUiDensity = 'default' | 'compact';
12
+ /** Fine-grained UI sizing overrides (merged on top of `uiDensity` preset). */
13
+ export interface GridUiDensityOptions {
14
+ footerMinHeight?: number;
15
+ footerFontSize?: number;
16
+ footerIconSize?: number;
17
+ footerPaddingY?: number;
18
+ filterChipFontSize?: number;
19
+ filterChipMinHeight?: number;
20
+ filterLabelFontSize?: number;
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;
26
+ /** MRT built-in toolbar control density (show/hide columns, density toggle, etc.). */
27
+ mrtDensity?: 'comfortable' | 'compact' | 'spacious';
28
+ }
10
29
  export interface GenericPaginatedGridProps<TData extends object> {
11
30
  /**
12
31
  * Column definitions. Use `TPColumnDef<TData>` (a superset of `MRT_ColumnDef`)
@@ -36,11 +55,35 @@ export interface GenericPaginatedGridProps<TData extends object> {
36
55
  /**
37
56
  * Height of the entire grid component.
38
57
  * When omitted (default) the grid auto-sizes to fill the remaining viewport
39
- * height from its top position — the page never needs to scroll.
40
- * Pass an explicit CSS value only when you need to override
41
- * (e.g. "600px", "calc(100vh - 300px)").
58
+ * height from its top position on mount and window resize only (not on scroll).
59
+ * Pass an explicit CSS value to pin height (e.g. "600px", "calc(100vh - 300px)").
42
60
  */
43
61
  tableHeight?: string;
62
+ /**
63
+ * Minimum height of the grid wrapper (CSS length).
64
+ * Used in auto-height `clamp()` and as `minHeight` when `tableHeight` is set.
65
+ * Default: `300px`.
66
+ */
67
+ tableMinHeight?: string;
68
+ /**
69
+ * Maximum height of the grid wrapper (CSS length).
70
+ * Caps auto-calculated viewport height. Default: none (only viewport clamp).
71
+ */
72
+ tableMaxHeight?: string;
73
+ /**
74
+ * When false, disables viewport auto-height; use `tableHeight` or `tableMinHeight` only.
75
+ * Default: true (unless `tableHeight` is provided).
76
+ */
77
+ autoFillViewport?: boolean;
78
+ /**
79
+ * MRT column layout. Default `grid` — columns grow to fill width (no trailing spacer column).
80
+ * Use `grid-no-grow` only when you need fixed column widths with horizontal scroll.
81
+ */
82
+ layoutMode?: 'grid' | 'semantic' | 'grid-no-grow';
83
+ /** Compact toolbar, filter chips, and footer. Default: `default`. */
84
+ uiDensity?: GridUiDensity;
85
+ /** Override individual sizes from the `uiDensity` preset. */
86
+ uiDensityOptions?: GridUiDensityOptions;
44
87
  /** Choices shown in the "Rows per page" footer dropdown. Default: [1 000, 5 000, 10 000]. */
45
88
  pageSizeOptions?: number[];
46
89
  /**
@@ -48,6 +91,31 @@ export interface GenericPaginatedGridProps<TData extends object> {
48
91
  * Accepts any valid CSS colour string.
49
92
  */
50
93
  headerBgColor?: string;
94
+ /**
95
+ * Minimum height of every header cell (CSS length or px number).
96
+ * Default: `40px` (`34px` when `uiDensity="compact"`).
97
+ */
98
+ headerMinHeight?: string | number;
99
+ /**
100
+ * Maximum height of the header row — **all columns use the same height**.
101
+ * Long titles wrap inside this box. Default: `72px` (`56px` compact).
102
+ */
103
+ headerMaxHeight?: string | number;
104
+ /**
105
+ * Optional max lines for header text (`-webkit-line-clamp`).
106
+ * Omit to allow text to use the full `headerMaxHeight` (recommended for tall headers).
107
+ */
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;
51
119
  /**
52
120
  * Maximum number of filter chips that can be visible at once.
53
121
  * Prevents the filter bar from wrapping to a second row on typical screens.
@@ -55,5 +123,5 @@ export interface GenericPaginatedGridProps<TData extends object> {
55
123
  */
56
124
  maxVisibleFilters?: number;
57
125
  }
58
- declare function GenericPaginatedGrid<TData extends object>({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize, exportFilename, tableHeight, pageSizeOptions, headerBgColor, 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;
59
127
  export default GenericPaginatedGrid;
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { useState, useRef, useLayoutEffect, useMemo } from 'react';
2
+ import { useMemo, useState, useRef, useLayoutEffect } from 'react';
3
3
  import { useMaterialReactTable, MaterialReactTable } from 'material-react-table';
4
4
  import { useQuery, keepPreviousData } from '@tanstack/react-query';
5
5
  import { Box, Tooltip, IconButton, GlobalStyles, Typography, Select, MenuItem, CircularProgress } from '@mui/material';
@@ -11,6 +11,178 @@ import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
11
11
  import ChevronRightIcon from '@mui/icons-material/ChevronRight';
12
12
  import DynamicFilterBar from './DynamicFilterBar.esm.js';
13
13
 
14
+ const UI_DENSITY_PRESETS = {
15
+ default: {
16
+ footerMinHeight: 48,
17
+ footerFontSize: 14,
18
+ footerIconSize: 20,
19
+ footerPaddingY: 0.75,
20
+ filterChipFontSize: 12,
21
+ filterChipMinHeight: 30,
22
+ filterLabelFontSize: 12,
23
+ toolbarIconFontSize: 'small',
24
+ headerFontSize: 12,
25
+ bodyFontSize: 12,
26
+ mrtDensity: 'comfortable',
27
+ },
28
+ compact: {
29
+ footerMinHeight: 32,
30
+ footerFontSize: 12,
31
+ footerIconSize: 18,
32
+ footerPaddingY: 0.25,
33
+ filterChipFontSize: 11,
34
+ filterChipMinHeight: 26,
35
+ filterLabelFontSize: 11,
36
+ toolbarIconFontSize: 'inherit',
37
+ headerFontSize: 11,
38
+ bodyFontSize: 11,
39
+ mrtDensity: 'compact',
40
+ },
41
+ };
42
+ const toCssSize = (value, fallback) => {
43
+ if (value === undefined)
44
+ return fallback;
45
+ return typeof value === 'number' ? `${value}px` : value;
46
+ };
47
+ /**
48
+ * Header overrides applied via GlobalStyles (literal prop values).
49
+ * MRT sets `backgroundColor: 'inherit'` and fixed density padding — must use !important.
50
+ */
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) => {
58
+ const headCell = {
59
+ height: `${headerMax} !important`,
60
+ minHeight: `${headerMin} !important`,
61
+ maxHeight: `${headerMax} !important`,
62
+ boxSizing: 'border-box !important',
63
+ alignSelf: 'stretch !important',
64
+ whiteSpace: 'normal !important',
65
+ verticalAlign: 'middle !important',
66
+ pt: '8px !important',
67
+ pb: '8px !important',
68
+ px: '14px !important',
69
+ };
70
+ if (headerBgColor) {
71
+ headCell.backgroundColor = `${headerBgColor} !important`;
72
+ headCell.backgroundImage = 'none !important';
73
+ }
74
+ return {
75
+ '.tp-gpg-root .MuiTableHead-root .MuiTableRow-root': {
76
+ height: `${headerMax} !important`,
77
+ minHeight: `${headerMin} !important`,
78
+ maxHeight: `${headerMax} !important`,
79
+ alignItems: 'stretch !important',
80
+ ...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
81
+ },
82
+ '.tp-gpg-root .MuiTableHead-root .MuiTableCell-head': {
83
+ ...headCell,
84
+ fontSize: `${headerFont} !important`,
85
+ },
86
+ '.tp-gpg-root .MuiTableHead-root .Mui-TableHeadCell-Content': {
87
+ height: '100% !important',
88
+ width: '100% !important',
89
+ minHeight: '0 !important',
90
+ alignItems: 'center !important',
91
+ },
92
+ '.tp-gpg-root .MuiTableHead-root .Mui-TableHeadCell-Content-Labels': {
93
+ overflow: 'visible !important',
94
+ flexWrap: 'wrap !important',
95
+ whiteSpace: 'normal !important',
96
+ textOverflow: 'clip !important',
97
+ width: '100% !important',
98
+ maxWidth: '100% !important',
99
+ },
100
+ '.tp-gpg-root .MuiTableHead-root .Mui-TableHeadCell-Content-Wrapper': {
101
+ overflow: 'visible !important',
102
+ whiteSpace: 'normal !important',
103
+ textOverflow: 'clip !important',
104
+ wordBreak: 'break-word !important',
105
+ overflowWrap: 'anywhere !important',
106
+ width: '100% !important',
107
+ maxWidth: '100% !important',
108
+ fontSize: `${headerFont} !important`,
109
+ display: 'block !important',
110
+ WebkitLineClamp: 'unset !important',
111
+ WebkitBoxOrient: 'unset !important',
112
+ },
113
+ '.tp-gpg-root .MuiTableHead-root .MuiTableSortLabel-root': {
114
+ whiteSpace: 'normal !important',
115
+ wordBreak: 'break-word !important',
116
+ overflowWrap: 'anywhere !important',
117
+ width: '100% !important',
118
+ fontSize: `${headerFont} !important`,
119
+ },
120
+ };
121
+ };
122
+ /** Per-cell sx merged into MRT (via muiTableHeadCellProps). */
123
+ const buildHeaderCellSx = (align, headerMin, headerMax, headerFont, headerBgColor, headerMaxLines) => {
124
+ const textAlign = align;
125
+ const flexJustify = align === 'right' ? 'flex-end' : align === 'center' ? 'center' : 'flex-start';
126
+ const wrapperClamp = headerMaxLines != null && headerMaxLines > 0
127
+ ? {
128
+ display: '-webkit-box',
129
+ WebkitLineClamp: headerMaxLines,
130
+ WebkitBoxOrient: 'vertical',
131
+ overflow: 'hidden',
132
+ }
133
+ : {
134
+ display: 'block',
135
+ WebkitLineClamp: 'unset',
136
+ overflow: 'visible',
137
+ };
138
+ return {
139
+ ...(headerBgColor
140
+ ? { backgroundColor: `${headerBgColor} !important`, backgroundImage: 'none' }
141
+ : {}),
142
+ height: `${headerMax} !important`,
143
+ minHeight: `${headerMin} !important`,
144
+ maxHeight: `${headerMax} !important`,
145
+ fontSize: headerFont,
146
+ boxSizing: 'border-box',
147
+ borderRight: '1px solid rgba(224,224,224,1)',
148
+ borderBottom: '2px solid rgba(0,0,0,0.15)',
149
+ '& button[aria-label="Move"]': { display: 'none !important' },
150
+ '& .MuiTableSortLabel-icon': { display: 'none !important' },
151
+ '& .Mui-TableHeadCell-ResizeHandle-Wrapper': { height: '100%', top: 0 },
152
+ '& .Mui-TableHeadCell-ResizeHandle-Divider': { borderRight: 'none' },
153
+ '& .Mui-TableHeadCell-Content': {
154
+ alignItems: 'center',
155
+ justifyContent: flexJustify,
156
+ height: '100%',
157
+ width: '100%',
158
+ },
159
+ '& .Mui-TableHeadCell-Content-Labels': {
160
+ flexWrap: 'wrap',
161
+ whiteSpace: 'normal',
162
+ textAlign,
163
+ width: '100%',
164
+ justifyContent: flexJustify,
165
+ overflow: 'visible',
166
+ },
167
+ '& .Mui-TableHeadCell-Content-Wrapper': {
168
+ whiteSpace: 'normal',
169
+ wordBreak: 'break-word',
170
+ overflowWrap: 'anywhere',
171
+ lineHeight: 1.25,
172
+ fontSize: headerFont,
173
+ width: '100%',
174
+ textAlign,
175
+ ...wrapperClamp,
176
+ },
177
+ '& .MuiTableSortLabel-root': {
178
+ whiteSpace: 'normal',
179
+ wordBreak: 'break-word',
180
+ fontSize: headerFont,
181
+ width: '100%',
182
+ margin: 0,
183
+ },
184
+ };
185
+ };
14
186
  // ─── Internal: overlay loader ─────────────────────────────────────────────────
15
187
  const OverlayLoader = ({ message }) => (jsxs(Box, { sx: {
16
188
  position: 'absolute', inset: 0,
@@ -19,31 +191,52 @@ const OverlayLoader = ({ message }) => (jsxs(Box, { sx: {
19
191
  bgcolor: 'rgba(255,255,255,0.80)', backdropFilter: 'blur(2px)',
20
192
  zIndex: 10, gap: 1.5,
21
193
  }, children: [jsx(CircularProgress, { size: 50, thickness: 4 }), jsx(Typography, { variant: "body2", color: "text.secondary", fontWeight: 500, children: message })] }));
22
- const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoading, canPrev, canNext, onFirst, onPrev, onNext, onLast, onPageSizeChange, pageSizeOptions, }) => {
194
+ const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoading, canPrev, canNext, onFirst, onPrev, onNext, onLast, onPageSizeChange, pageSizeOptions, density, }) => {
195
+ const fs = density.footerFontSize;
196
+ const selectH = density.footerMinHeight - 8;
23
197
  const startRow = totalRecords === 0 ? 0 : pageIndex * pageSize + 1;
24
198
  const endRow = Math.min((pageIndex + 1) * pageSize, totalRecords);
25
199
  return (jsxs(Box, { sx: {
26
200
  display: 'flex', alignItems: 'center', justifyContent: 'flex-end',
27
- gap: { xs: 1.5, md: 3 }, px: 2, py: 0.75,
201
+ gap: { xs: 1, md: 2 }, px: 1.5, py: density.footerPaddingY,
28
202
  borderTop: '1px solid', borderColor: 'divider',
29
- bgcolor: 'background.paper', flexWrap: 'wrap', minHeight: 48,
30
- }, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [jsx(Typography, { variant: "body2", color: "text.secondary", noWrap: true, children: "Rows per page:" }), jsx(Select, { value: pageSize, onChange: (e) => onPageSizeChange(Number(e.target.value)), size: "small", variant: "outlined", sx: {
31
- height: 28, fontSize: 13, minWidth: 80,
32
- '.MuiSelect-select': { py: '3px !important' },
203
+ bgcolor: 'background.paper', flexWrap: 'wrap',
204
+ minHeight: density.footerMinHeight,
205
+ flexShrink: 0,
206
+ }, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.75 }, children: [jsx(Typography, { sx: { fontSize: fs }, color: "text.secondary", noWrap: true, children: "Rows per page:" }), jsx(Select, { value: pageSize, onChange: (e) => onPageSizeChange(Number(e.target.value)), size: "small", variant: "outlined", sx: {
207
+ height: selectH, fontSize: fs - 1, minWidth: 72,
208
+ '.MuiSelect-select': { py: '2px !important' },
33
209
  '.MuiOutlinedInput-notchedOutline': { borderColor: 'divider' },
34
- }, children: pageSizeOptions.map((opt) => (jsx(MenuItem, { value: opt, sx: { fontSize: 13 }, children: opt.toLocaleString() }, opt))) })] }), jsx(Typography, { variant: "body2", color: "text.secondary", noWrap: true, children: isLoading || (isFetching && totalRecords === 0)
210
+ }, children: pageSizeOptions.map((opt) => (jsx(MenuItem, { value: opt, sx: { fontSize: fs - 1 }, children: opt.toLocaleString() }, opt))) })] }), jsx(Typography, { sx: { fontSize: fs }, color: "text.secondary", noWrap: true, children: isLoading || (isFetching && totalRecords === 0)
35
211
  ? 'Loading…'
36
212
  : totalRecords === 0
37
213
  ? 'No records'
38
- : `${startRow.toLocaleString()}–${endRow.toLocaleString()} of ${totalRecords.toLocaleString()}` }), jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.25 }, children: [jsx(Tooltip, { title: "First page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onFirst, disabled: !canPrev, children: jsx(FirstPageIcon, { sx: { fontSize: 20 } }) }) }) }), jsx(Tooltip, { title: "Previous page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onPrev, disabled: !canPrev, children: jsx(ChevronLeftIcon, { sx: { fontSize: 20 } }) }) }) }), jsx(Typography, { variant: "body2", fontWeight: 600, sx: {
39
- mx: 0.5, minWidth: 28, height: 28,
214
+ : `${startRow.toLocaleString()}–${endRow.toLocaleString()} of ${totalRecords.toLocaleString()}` }), jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.25 }, children: [jsx(Tooltip, { title: "First page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onFirst, disabled: !canPrev, sx: { p: 0.5 }, children: jsx(FirstPageIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Tooltip, { title: "Previous page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onPrev, disabled: !canPrev, sx: { p: 0.5 }, children: jsx(ChevronLeftIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Typography, { fontWeight: 600, sx: {
215
+ mx: 0.5,
216
+ minWidth: density.footerMinHeight - 12,
217
+ height: density.footerMinHeight - 12,
40
218
  display: 'flex', alignItems: 'center', justifyContent: 'center',
41
219
  borderRadius: 1, border: '1px solid', borderColor: 'primary.main',
42
- color: 'primary.main', bgcolor: 'rgba(25,118,210,0.07)', fontSize: 13,
43
- }, children: pageIndex + 1 }), jsx(Tooltip, { title: "Next page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onNext, disabled: !canNext, children: jsx(ChevronRightIcon, { sx: { fontSize: 20 } }) }) }) }), jsx(Tooltip, { title: "Last page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onLast, disabled: !canNext, children: jsx(LastPageIcon, { sx: { fontSize: 20 } }) }) }) })] })] }));
220
+ color: 'primary.main', bgcolor: 'rgba(25,118,210,0.07)', fontSize: fs - 1,
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 } }) }) }) })] })] }));
44
222
  };
45
223
  // ─── GenericPaginatedGrid ─────────────────────────────────────────────────────
46
- function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize = 10000, exportFilename = 'export', tableHeight, pageSizeOptions = [1000, 5000, 10000], headerBgColor, 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, }) {
225
+ const densityTokens = useMemo(() => ({ ...UI_DENSITY_PRESETS[uiDensity], ...uiDensityOptions }), [uiDensity, uiDensityOptions]);
226
+ const resolvedHeaderMinHeight = toCssSize(headerMinHeight, uiDensity === 'compact' ? '34px' : '40px');
227
+ const resolvedHeaderMaxHeight = toCssSize(headerMaxHeight, uiDensity === 'compact' ? '56px' : '72px');
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
+ ]);
47
240
  const defaultVisible = defaultVisibleFilterIds
48
241
  ?? (serverFilterDefs.length > 0 ? [serverFilterDefs[0].id] : []);
49
242
  // ── State ──────────────────────────────────────────────────────────────────
@@ -57,58 +250,35 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
57
250
  const [columnFilters, setColumnFilters] = useState([]);
58
251
  const [globalFilter, setGlobalFilter] = useState('');
59
252
  const [columnPinning, setColumnPinning] = useState({});
60
- // ── Viewport-fill: measure this component's top offset and derive height ──────
61
- //
62
- // When tableHeight is not provided, we measure how far the wrapper sits from
63
- // the top of the viewport and set height = 100vh − topOffset − 8px (tiny
64
- // bottom margin). A ResizeObserver on <html> keeps the value in sync if the
65
- // layout shifts (sidebar collapses, font loads, etc.).
253
+ // ── Viewport-fill height (mount + resize only — never on scroll) ─────────────
66
254
  //
67
- // The MRT Paper + TableContainer flex chain distributes that height between
68
- // the toolbar, sticky column headers, scrollable rows, and pagination footer —
69
- // so the grid fills the window exactly and the page never needs to scroll.
255
+ // Previously a scroll listener recalculated height as the grid moved within the
256
+ // viewport, which grew the grid while scrolling and caused double scrollbars.
70
257
  const containerRef = useRef(null);
71
258
  const [autoHeight, setAutoHeight] = useState('');
259
+ const shouldAutoFill = autoFillViewport && !tableHeight;
72
260
  useLayoutEffect(() => {
73
- if (tableHeight)
74
- return; // explicit prop — skip auto calculation
261
+ if (!shouldAutoFill)
262
+ return;
75
263
  const el = containerRef.current;
76
264
  if (!el)
77
265
  return;
78
266
  const update = () => {
79
267
  const top = el.getBoundingClientRect().top;
80
- // CSS max() guarantees a minimum visible height (300px) when the grid
81
- // starts below the fold or when little viewport space remains — both
82
- // cases produce a negative/tiny calc() that would otherwise collapse
83
- // the grid to zero height.
84
- setAutoHeight(`max(300px, calc(100vh - ${Math.ceil(top) + 8}px))`);
268
+ const preferred = `calc(100vh - ${Math.ceil(top) + 8}px)`;
269
+ const maxPart = tableMaxHeight ?? preferred;
270
+ setAutoHeight(`clamp(${tableMinHeight}, ${preferred}, ${maxPart})`);
85
271
  };
86
272
  update();
87
- // Re-measure on any document resize (window resize, sidebar toggle, etc.)
88
273
  const ro = new ResizeObserver(update);
89
- ro.observe(document.documentElement);
90
- // Re-measure when the scroll container scrolls — the grid's
91
- // viewport-relative top offset shifts with scroll position, so the
92
- // height must be recalculated to fill the remaining visible space.
93
- const findScrollParent = (node) => {
94
- let p = node.parentElement;
95
- while (p && p !== document.documentElement) {
96
- const { overflow, overflowY } = window.getComputedStyle(p);
97
- if (/auto|scroll/.test(overflow) || /auto|scroll/.test(overflowY))
98
- return p;
99
- p = p.parentElement;
100
- }
101
- return window;
102
- };
103
- const scrollParent = findScrollParent(el);
104
- scrollParent.addEventListener('scroll', update, { passive: true });
274
+ ro.observe(el);
275
+ window.addEventListener('resize', update, { passive: true });
105
276
  return () => {
106
277
  ro.disconnect();
107
- scrollParent.removeEventListener('scroll', update);
278
+ window.removeEventListener('resize', update);
108
279
  };
109
- }, [tableHeight]);
110
- // Resolved height: explicit prop › auto-calculated › safe fallback
111
- const resolvedHeight = tableHeight || autoHeight || '60vh';
280
+ }, [shouldAutoFill, tableMinHeight, tableMaxHeight]);
281
+ const resolvedHeight = tableHeight || (shouldAutoFill ? autoHeight : undefined) || tableMinHeight;
112
282
  // ── Data fetching ──────────────────────────────────────────────────────────
113
283
  const { data, isLoading, isError, isFetching, refetch } = useQuery({
114
284
  queryKey: [...queryKey, filterValues, pagination.pageIndex],
@@ -124,9 +294,13 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
124
294
  // MRT doesn't know these props, so we strip them here. We also inject the
125
295
  // appropriate `filterFn` automatically so callers never have to write one.
126
296
  const processedColumns = useMemo(() => {
297
+ const allowGrow = layoutMode === 'grid' || layoutMode === 'semantic';
127
298
  return columns.map((col) => {
128
299
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
129
300
  const { filterType, options, ...mrtCol } = col;
301
+ if (allowGrow && mrtCol.grow === undefined) {
302
+ mrtCol.grow = true;
303
+ }
130
304
  // Auto-inject filterFn only when the user hasn't already supplied one.
131
305
  if (!mrtCol.filterFn && filterType) {
132
306
  if (filterType === 'date') {
@@ -145,7 +319,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
145
319
  }
146
320
  return mrtCol;
147
321
  });
148
- }, [columns]);
322
+ }, [columns, layoutMode]);
149
323
  // ── Derive column list for filter chip picker ──────────────────────────────
150
324
  //
151
325
  // A column is excluded from the "Column Filters" section of Manage Filters
@@ -211,6 +385,22 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
211
385
  const table = useMaterialReactTable({
212
386
  columns: processedColumns,
213
387
  data: rows,
388
+ layoutMode,
389
+ displayColumnDefOptions: {
390
+ 'mrt-row-spacer': {
391
+ size: 0,
392
+ minSize: 0,
393
+ maxSize: 0,
394
+ grow: false,
395
+ enableResizing: false,
396
+ muiTableHeadCellProps: {
397
+ sx: { display: 'none !important', width: 0, maxWidth: 0, p: 0, border: 'none' },
398
+ },
399
+ muiTableBodyCellProps: {
400
+ sx: { display: 'none !important', width: 0, maxWidth: 0, p: 0, border: 'none' },
401
+ },
402
+ },
403
+ },
214
404
  manualPagination: true,
215
405
  rowCount: totalRecords,
216
406
  manualSorting: false,
@@ -246,95 +436,122 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
246
436
  enableHiding: true,
247
437
  enableStickyHeader: true,
248
438
  enableRowVirtualization: true,
249
- // The container fills whatever height is left after the top toolbar and
250
- // pagination footer. minHeight ensures a visible body even when there are
251
- // no records; flex: '1 1 0' lets it grow to fill the remaining space.
439
+ initialState: {
440
+ density: densityTokens.mrtDensity,
441
+ },
442
+ muiTableProps: {
443
+ sx: { width: '100%' },
444
+ },
252
445
  muiTableContainerProps: {
253
446
  sx: {
254
447
  flex: '1 1 0',
255
- minHeight: '150px',
448
+ minHeight: 0,
449
+ maxHeight: '100%',
450
+ overflow: 'auto',
256
451
  },
257
452
  },
258
- // Paper fills the wrapper Box height and acts as the flex column host for
259
- // toolbar → table-container → pagination.
260
453
  muiTablePaperProps: {
261
454
  sx: {
262
455
  height: '100%',
456
+ minHeight: 0,
263
457
  display: 'flex',
264
458
  flexDirection: 'column',
459
+ overflow: 'hidden',
460
+ boxShadow: 'none',
265
461
  },
266
462
  },
267
463
  muiTopToolbarProps: {
268
464
  sx: {
269
465
  alignItems: 'center',
270
466
  flexWrap: 'nowrap',
467
+ minHeight: densityTokens.filterChipMinHeight + 12,
468
+ py: uiDensity === 'compact' ? 0.25 : 0.5,
469
+ '& .MuiIconButton-root': { p: uiDensity === 'compact' ? 0.35 : 0.5 },
470
+ '& .MuiIconButton-root svg': { fontSize: densityTokens.toolbarIconFontSize === 'small' ? 18 : 20 },
471
+ },
472
+ },
473
+ muiTableHeadProps: {
474
+ sx: {
475
+ position: 'sticky',
476
+ top: 0,
477
+ zIndex: 2,
478
+ ...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
479
+ },
480
+ },
481
+ muiTableHeadRowProps: {
482
+ sx: {
483
+ height: resolvedHeaderMaxHeight,
484
+ minHeight: resolvedHeaderMinHeight,
485
+ maxHeight: resolvedHeaderMaxHeight,
486
+ alignItems: 'stretch',
487
+ ...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
271
488
  },
272
489
  },
273
490
  muiTableHeadCellProps: ({ column }) => {
274
491
  const colProps = column.columnDef.muiTableHeadCellProps;
275
- const align = colProps && typeof colProps !== 'function'
276
- ? colProps.align
277
- : undefined;
278
- const justifyContent = align === 'right' ? 'flex-start'
279
- : align === 'center' ? 'center'
280
- : 'flex-end';
492
+ let align = 'left';
493
+ let colSx;
494
+ if (typeof colProps === 'function') {
495
+ const resolved = colProps({ column });
496
+ align = resolved?.align ?? 'left';
497
+ colSx = resolved?.sx;
498
+ }
499
+ else if (colProps) {
500
+ align = colProps.align ?? 'left';
501
+ colSx = colProps.sx;
502
+ }
281
503
  return {
282
- align: (align ?? 'left'),
283
- sx: {
284
- ...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
285
- verticalAlign: 'bottom',
286
- whiteSpace: 'normal',
287
- borderRight: '1px solid rgba(224,224,224,1)',
288
- borderBottom: '2px solid rgba(0,0,0,0.15)',
289
- '& button[aria-label="Move"]': { display: 'none !important' },
290
- '& .MuiTableSortLabel-icon': { display: 'none !important' },
291
- '& .Mui-TableHeadCell-ResizeHandle-Wrapper': {
292
- height: '100%',
293
- top: 0,
294
- },
295
- '& .Mui-TableHeadCell-ResizeHandle-Divider': {
296
- borderRight: 'none',
297
- },
298
- '& .Mui-TableHeadCell-Content': {
299
- alignItems: 'flex-end',
300
- justifyContent,
301
- },
302
- '& .Mui-TableHeadCell-Content-Labels': {
303
- flexWrap: 'wrap',
304
- whiteSpace: 'normal',
305
- overflowWrap: 'normal',
306
- lineHeight: 1.3,
307
- justifyContent,
308
- },
309
- '& .Mui-TableHeadCell-Content-Wrapper': {
310
- whiteSpace: 'normal',
311
- overflow: 'visible',
312
- },
313
- },
504
+ align,
505
+ sx: [
506
+ buildHeaderCellSx(align, resolvedHeaderMinHeight, resolvedHeaderMaxHeight, resolvedHeaderFontSize, headerBgColor, headerMaxLines),
507
+ ...(colSx ? [colSx] : []),
508
+ ],
314
509
  };
315
510
  },
316
- muiTableBodyCellProps: {
317
- sx: {
318
- whiteSpace: 'nowrap',
319
- borderRight: '1px solid rgba(224,224,224,1)',
320
- },
511
+ muiTableBodyCellProps: ({ column }) => {
512
+ const colProps = column.columnDef.muiTableBodyCellProps;
513
+ let colSx;
514
+ if (typeof colProps === 'function') {
515
+ const resolved = colProps({ column });
516
+ colSx = resolved?.sx;
517
+ }
518
+ else if (colProps) {
519
+ colSx = colProps.sx;
520
+ }
521
+ return {
522
+ sx: [
523
+ {
524
+ whiteSpace: 'nowrap',
525
+ fontSize: resolvedBodyFontSize,
526
+ lineHeight: 1.35,
527
+ borderRight: '1px solid rgba(224,224,224,1)',
528
+ },
529
+ ...(colSx ? [colSx] : []),
530
+ ],
531
+ };
321
532
  },
322
533
  muiToolbarAlertBannerProps: isError
323
534
  ? { color: 'error', children: 'Failed to load data. Please try again.' }
324
535
  : undefined,
325
- renderTopToolbarCustomActions: () => (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0, pr: 1, gap: 0.5 }, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flexShrink: 0, gap: 0.25 }, children: [jsx(Tooltip, { title: "Clear filters & refresh", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: () => { handleClearAll(); refetch(); }, disabled: isFetching, children: jsx(RefreshIcon, { fontSize: "small" }) }) }) }), jsx(Tooltip, { title: "Export batch as CSV", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: handleExport, disabled: rows.length === 0 || isFetching, children: jsx(FileDownloadIcon, { fontSize: "small" }) }) }) })] }), jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: jsx(DynamicFilterBar, { serverFilterDefs: serverFilterDefs, visibleFilterIds: visibleFilterIds, onToggleFilter: handleToggleFilter, filterValues: filterValues, onFilterValuesChange: handleFilterValuesChange, columnFilters: columnFilters, onColumnFilterChange: setColumnFilters, availableColumns: availableColumns, maxVisibleFilters: maxVisibleFilters }) })] })),
326
- renderBottomToolbar: ({ table: t }) => (jsx(PaginationFooter, { pageIndex: t.getState().pagination.pageIndex, pageSize: t.getState().pagination.pageSize, totalRecords: totalRecords, isFetching: isFetching, isLoading: isLoading, canPrev: t.getCanPreviousPage() && !isFetching, canNext: t.getCanNextPage() && !isFetching, onFirst: () => t.firstPage(), onPrev: () => t.previousPage(), onNext: () => t.nextPage(), onLast: () => t.lastPage(), onPageSizeChange: (s) => t.setPageSize(s), pageSizeOptions: pageSizeOptions })),
536
+ renderTopToolbarCustomActions: () => (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0, pr: 1, gap: 0.5 }, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flexShrink: 0, gap: 0.25 }, children: [jsx(Tooltip, { title: "Clear filters & refresh", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: () => { handleClearAll(); refetch(); }, disabled: isFetching, sx: { p: uiDensity === 'compact' ? 0.35 : 0.5 }, children: jsx(RefreshIcon, { sx: { fontSize: uiDensity === 'compact' ? 18 : 20 } }) }) }) }), jsx(Tooltip, { title: "Export batch as CSV", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: handleExport, disabled: rows.length === 0 || isFetching, sx: { p: uiDensity === 'compact' ? 0.35 : 0.5 }, children: jsx(FileDownloadIcon, { sx: { fontSize: uiDensity === 'compact' ? 18 : 20 } }) }) }) })] }), jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: jsx(DynamicFilterBar, { serverFilterDefs: serverFilterDefs, visibleFilterIds: visibleFilterIds, onToggleFilter: handleToggleFilter, filterValues: filterValues, onFilterValuesChange: handleFilterValuesChange, columnFilters: columnFilters, onColumnFilterChange: setColumnFilters, availableColumns: availableColumns, maxVisibleFilters: maxVisibleFilters, uiDensity: uiDensity, filterChipFontSize: densityTokens.filterChipFontSize, filterChipMinHeight: densityTokens.filterChipMinHeight, filterLabelFontSize: densityTokens.filterLabelFontSize }) })] })),
537
+ renderBottomToolbar: ({ table: t }) => (jsx(PaginationFooter, { pageIndex: t.getState().pagination.pageIndex, pageSize: t.getState().pagination.pageSize, totalRecords: totalRecords, isFetching: isFetching, isLoading: isLoading, canPrev: t.getCanPreviousPage() && !isFetching, canNext: t.getCanNextPage() && !isFetching, onFirst: () => t.firstPage(), onPrev: () => t.previousPage(), onNext: () => t.nextPage(), onLast: () => t.lastPage(), onPageSizeChange: (s) => t.setPageSize(s), pageSizeOptions: pageSizeOptions, density: densityTokens })),
327
538
  });
328
539
  return (jsxs(Box, { ref: containerRef, className: "tp-gpg-root", sx: {
329
540
  position: 'relative',
330
541
  height: resolvedHeight,
542
+ minHeight: tableMinHeight,
543
+ maxHeight: tableMaxHeight,
331
544
  display: 'flex',
332
545
  flexDirection: 'column',
546
+ overflow: 'hidden',
333
547
  }, children: [jsx(GlobalStyles, { styles: {
334
548
  'button[aria-label="Pin to right"]': { display: 'none' },
335
549
  '.tp-gpg-root .MuiTableSortLabel-icon': { display: 'none !important' },
336
550
  '.tp-gpg-root button[aria-label="Move"]': { display: 'none !important' },
337
- } }), isLoading && jsx(OverlayLoader, { message: "Loading data\u2026" }), !isLoading && isFetching && jsx(OverlayLoader, { message: "Updating data\u2026" }), jsx(MaterialReactTable, { table: table })] }));
551
+ '.tp-gpg-root [data-column-id="mrt-row-spacer"]': { display: 'none !important' },
552
+ '.tp-gpg-root .Mui-TableHeadCell-ColumnSpacer': { display: 'none !important' },
553
+ ...tableStyleOverrides,
554
+ } }), 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 }) })] }));
338
555
  }
339
556
 
340
557
  export { GenericPaginatedGrid as default };
@@ -1,4 +1,5 @@
1
1
  export { default as GenericPaginatedGrid } from './GenericPaginatedGrid';
2
- export type { GenericPaginatedGridProps, PagedResult } from './GenericPaginatedGrid';
2
+ export type { GenericPaginatedGridProps, PagedResult, GridUiDensity, GridUiDensityOptions, } from './GenericPaginatedGrid';
3
3
  export { default as DynamicFilterBar } from './DynamicFilterBar';
4
4
  export type { ServerFilterDef } from './DynamicFilterBar';
5
+ export type { TPColumnDef, ColumnFilterType } from './types';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Mock data + fetch adapters for Storybook sample grids (no API required).
3
+ */
4
+ import type { TPColumnDef } from './types';
5
+ import type { PagedResult } from './GenericPaginatedGrid';
6
+ import type { ServerFilterDef } from './DynamicFilterBar';
7
+ export interface EmployeeRow {
8
+ id: number;
9
+ name: string;
10
+ department: string;
11
+ costCenter: string;
12
+ workLocation: string;
13
+ reportingManager: string;
14
+ employmentType: string;
15
+ status: string;
16
+ joinedOn: string;
17
+ }
18
+ /** Columns with long headers + narrow widths to exercise header wrapping in Storybook. */
19
+ export declare const employeeColumns: TPColumnDef<EmployeeRow>[];
20
+ export declare const employeeFilterDefs: ServerFilterDef[];
21
+ export declare const employeeDefaultFilters: Record<string, string>;
22
+ export declare function fetchEmployeePage(filters: Record<string, string>, pageIndex: number, pageSize?: number): Promise<PagedResult<EmployeeRow>>;
23
+ export interface ProductRow {
24
+ sku: string;
25
+ title: string;
26
+ category: string;
27
+ price: number;
28
+ inStock: boolean;
29
+ }
30
+ export declare const productColumns: TPColumnDef<ProductRow>[];
31
+ export declare const productFilterDefs: ServerFilterDef[];
32
+ export declare const productDefaultFilters: Record<string, string>;
33
+ export declare function fetchProductPage(filters: Record<string, string>, pageIndex: number, pageSize?: number): Promise<PagedResult<ProductRow>>;
package/dist/index.d.ts CHANGED
@@ -15,3 +15,4 @@ export * as Theme from './theme';
15
15
  export type { FormRenderProps } from './utils';
16
16
  export type { FormRenderWrapperProps } from './components/Form/FormRenderWrapper';
17
17
  export type { FormSectionPropsItem, VariantProps } from './utils';
18
+ export type { GenericPaginatedGridProps, GridUiDensity, GridUiDensityOptions, PagedResult, ServerFilterDef, TPColumnDef, } from './components/PaginatedGrid';
@@ -1,4 +1,4 @@
1
- import { __exports as DateRange } from '../../../../../_virtual/index.esm5.js';
1
+ import { __exports as DateRange } 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 requireCalendar } from '../Calendar/index.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as DefinedRange } from '../../../../../_virtual/index.esm4.js';
1
+ import { __exports as DefinedRange } 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 requireStyles } from '../../styles.esm.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tp-react-elements",
3
- "version": "1.3.10",
3
+ "version": "1.3.12",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "React form components library built with React Hook Form and Yup",