tp-react-elements 2.0.1 → 2.0.3

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
@@ -9,18 +9,24 @@ A comprehensive React form library built with Material-UI and React Hook Form, d
9
9
  > Storybook docs (`*.stories.tsx` → `meta.parameters.docs.description`)
10
10
  > so consumers see the same explanation in both places.
11
11
 
12
- - **`SingleSelectSearchApi`** — Fixed a `Tooltip`/Popper mis-anchoring bug where
13
- the selected-value tooltip rendered pinned to the viewport's top-left corner
14
- (0,0) instead of near the input when the field sat inside an
15
- `overflow: hidden` ancestor (e.g. a table cell in `material-react-table` /
16
- `GenericPaginatedGrid`). The `Tooltip` is **retained** (not removed) and now
17
- uses `placement="top-start"`, `arrow`, `disableInteractive`, and a
18
- `slotProps.popper` block with Popper.js modifiers (`offset`,
19
- `preventOverflow` with `boundary: 'clippingParents'`, `flip`) so it
20
- portal-renders to `document.body` and stays correctly anchored. See the
12
+ - **`SingleSelectSearchApi`** — The selected-value `Tooltip` is now suppressed
13
+ via `open={false}` (controlled-closed) instead of relying on the Popper
14
+ modifier fix. Background: the tooltip previously rendered pinned to the
15
+ viewport's top-left corner (0,0) instead of near the input when the field
16
+ sat inside an `overflow: hidden` ancestor (e.g. a table cell in
17
+ `material-react-table` / `GenericPaginatedGrid`). Disabling Popper's `hide`
18
+ modifier and using `rootBoundary: 'viewport'` **verifiably fixed this in
19
+ Storybook (MUI v7)**, but the same build still mis-anchored when consumed
20
+ live in apps pinned to MUI v6 (below our `@mui/material >=7.3.1` peer
21
+ requirement) — MUI v6's Popper/Tooltip runtime ignores the modifier
22
+ configuration. Rather than ship a tooltip that only behaves correctly on
23
+ some MUI majors, we now suppress it at the prop level: `open={false}` forces
24
+ MUI's `Tooltip` into fully-controlled mode so it never opens. The `Tooltip`
25
+ element (and its modifier config) is **retained** (not removed) — flipping
26
+ `open` back to the default is a one-line change once the anchoring fix is
27
+ confirmed across all supported MUI majors. See the
21
28
  **"Tooltip inside a clipped container"** story in
22
- `SingleSelectSearchApi.stories.tsx` for a live repro, and the component's
23
- JSDoc for implementation notes.
29
+ `SingleSelectSearchApi.stories.tsx` and the component's JSDoc for details.
24
30
 
25
31
  ## 🚀 Features
26
32
 
@@ -197,7 +203,9 @@ npm run storybook
197
203
 
198
204
  - **Docs → Paginated Grid** — API reference plus **four copy-paste examples** (embedded panel, wrapped headers, typography, invoice filters)
199
205
  - **Components → PaginatedGrid → Sample Grids** — mock grids (no API): header wrap, compact layout, viewport fill
200
- - **Components → PaginatedGrid → Example Grid** — live invoice API demo
206
+ - **Components → PaginatedGrid → Example Grid** — invoice grid demo using mock data (no API)
207
+
208
+ **Global Search:** every `GenericPaginatedGrid` includes a built-in search icon in the top-right toolbar (next to Show/Hide Columns, Density, and Fullscreen) — no extra props needed. Click it to expand a free-text search that filters across all visible columns of the current batch. See **Docs → Paginated Grid → Global search**.
201
209
 
202
210
  **Example — compact embedded grid with smaller fonts:**
203
211
 
@@ -1,3 +1,3 @@
1
- var DateRangePicker = {};
1
+ var DateRange = {};
2
2
 
3
- export { DateRangePicker as __exports };
3
+ export { DateRange as __exports };
@@ -1,3 +1,3 @@
1
- var DefinedRange = {};
1
+ var DateRangePicker = {};
2
2
 
3
- export { DefinedRange as __exports };
3
+ export { DateRangePicker 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,2 +1,12 @@
1
+ /**
2
+ * `MultiSelect` is currently a stub placeholder pending activation.
3
+ *
4
+ * Use one of these fully-implemented alternatives instead:
5
+ * - `MultiSelectAutocomplete` (react-hook-form aware) — Docs → Multi Select Autocomplete
6
+ * - `MultiSelectField` (standalone) — Docs → Multi Select Field
7
+ * - `MultiSelectV1` (react-hook-form aware, used via FormRender `inputType: 'multiselect'`)
8
+ *
9
+ * See Docs → Multi Select (docs/multi-select.mdx) for status details.
10
+ */
1
11
  declare const MultiSelect: () => import("react/jsx-runtime").JSX.Element;
2
12
  export default MultiSelect;
@@ -22,22 +22,27 @@ export interface SearchOption {
22
22
  * Searches fire only after `minSearchLength` chars AND after the `debounceMs`
23
23
  * debounce delay — preventing a request per keystroke.
24
24
  *
25
- * ### Selected-value tooltip & anchoring inside clipped containers
26
- * The input is wrapped in a MUI `Tooltip` showing `resolvedValue.label` on
27
- * hover (useful when a narrow column truncates the label). The `Tooltip` is
28
- * configured with `placement="top-start"`, `arrow`, `disableInteractive`, and
29
- * a `slotProps.popper` block carrying Popper.js modifiers — `offset`,
30
- * `preventOverflow` (`boundary: 'clippingParents'`), and `flip`.
25
+ * ### Selected-value tooltip — currently suppressed via `open={false}`
26
+ * The input is wrapped in a MUI `Tooltip` that would show `resolvedValue.label`
27
+ * on hover (useful when a narrow column truncates the label). It carries
28
+ * `placement="top-start"`, `arrow`, `disableInteractive`, and a
29
+ * `slotProps.popper` block with Popper.js modifiers (`hide` disabled, `offset`,
30
+ * `preventOverflow`, `flip`) that correctly anchor it to the input even inside
31
+ * an `overflow: hidden` ancestor (e.g. a table cell in `material-react-table` /
32
+ * `GenericPaginatedGrid`) — **verified working in Storybook on MUI v7**.
31
33
  *
32
- * This keeps the tooltip correctly anchored to the input even when the field
33
- * sits inside an `overflow: hidden` ancestor (e.g. a table cell in
34
- * `material-react-table` / `GenericPaginatedGrid`) — without this
35
- * configuration, MUI's Popper can mis-anchor and pin the tooltip to the
36
- * viewport's top-left corner (0,0) instead of near the input. The fix
37
- * deliberately *keeps* the `Tooltip` (rather than removing it) by letting it
38
- * portal-render to `document.body` and resolving its position against the
39
- * clipping ancestor's boundary. See the "Tooltip inside a clipped container"
40
- * Storybook story for a live repro.
34
+ * However, that fix does not reproduce in consumer apps still pinned to
35
+ * MUI v6 (below our `@mui/material >=7.3.1` peer requirement): the v6
36
+ * Popper/Tooltip runtime ignores the modifier configuration and the tooltip
37
+ * still mis-anchors to the viewport's top-left corner (0,0). Rather than ship
38
+ * a tooltip that only behaves correctly on some MUI majors, we suppress it at
39
+ * the prop level — `open={false}` puts MUI's `Tooltip` into fully-controlled
40
+ * mode so it never opens, while the `<Tooltip>` element (and its modifier
41
+ * config) stays in the tree, deliberately *not removed*. Once the anchoring
42
+ * fix is confirmed across all supported MUI majors, removing `open={false}`
43
+ * re-enables the tooltip with no other changes needed. See the
44
+ * "Tooltip inside a clipped container" Storybook story for the anchoring repro
45
+ * and the `open={false}` suppression in context.
41
46
  */
42
47
  declare const SingleSelectSearchApi: ({ props, variant, minSearchLength, debounceMs, fetchOptions, }: {
43
48
  props: FormRenderProps;
@@ -50,22 +50,27 @@ function useDebounce(value, delayMs) {
50
50
  * Searches fire only after `minSearchLength` chars AND after the `debounceMs`
51
51
  * debounce delay — preventing a request per keystroke.
52
52
  *
53
- * ### Selected-value tooltip & anchoring inside clipped containers
54
- * The input is wrapped in a MUI `Tooltip` showing `resolvedValue.label` on
55
- * hover (useful when a narrow column truncates the label). The `Tooltip` is
56
- * configured with `placement="top-start"`, `arrow`, `disableInteractive`, and
57
- * a `slotProps.popper` block carrying Popper.js modifiers — `offset`,
58
- * `preventOverflow` (`boundary: 'clippingParents'`), and `flip`.
53
+ * ### Selected-value tooltip — currently suppressed via `open={false}`
54
+ * The input is wrapped in a MUI `Tooltip` that would show `resolvedValue.label`
55
+ * on hover (useful when a narrow column truncates the label). It carries
56
+ * `placement="top-start"`, `arrow`, `disableInteractive`, and a
57
+ * `slotProps.popper` block with Popper.js modifiers (`hide` disabled, `offset`,
58
+ * `preventOverflow`, `flip`) that correctly anchor it to the input even inside
59
+ * an `overflow: hidden` ancestor (e.g. a table cell in `material-react-table` /
60
+ * `GenericPaginatedGrid`) — **verified working in Storybook on MUI v7**.
59
61
  *
60
- * This keeps the tooltip correctly anchored to the input even when the field
61
- * sits inside an `overflow: hidden` ancestor (e.g. a table cell in
62
- * `material-react-table` / `GenericPaginatedGrid`) — without this
63
- * configuration, MUI's Popper can mis-anchor and pin the tooltip to the
64
- * viewport's top-left corner (0,0) instead of near the input. The fix
65
- * deliberately *keeps* the `Tooltip` (rather than removing it) by letting it
66
- * portal-render to `document.body` and resolving its position against the
67
- * clipping ancestor's boundary. See the "Tooltip inside a clipped container"
68
- * Storybook story for a live repro.
62
+ * However, that fix does not reproduce in consumer apps still pinned to
63
+ * MUI v6 (below our `@mui/material >=7.3.1` peer requirement): the v6
64
+ * Popper/Tooltip runtime ignores the modifier configuration and the tooltip
65
+ * still mis-anchors to the viewport's top-left corner (0,0). Rather than ship
66
+ * a tooltip that only behaves correctly on some MUI majors, we suppress it at
67
+ * the prop level — `open={false}` puts MUI's `Tooltip` into fully-controlled
68
+ * mode so it never opens, while the `<Tooltip>` element (and its modifier
69
+ * config) stays in the tree, deliberately *not removed*. Once the anchoring
70
+ * fix is confirmed across all supported MUI majors, removing `open={false}`
71
+ * re-enables the tooltip with no other changes needed. See the
72
+ * "Tooltip inside a clipped container" Storybook story for the anchoring repro
73
+ * and the `open={false}` suppression in context.
69
74
  */
70
75
  const SingleSelectSearchApi = ({ props, variant, minSearchLength = 3, debounceMs = 300, fetchOptions, }) => {
71
76
  // ── Stable references from props ──────────────────────────────────────────
@@ -206,30 +211,33 @@ const SingleSelectSearchApi = ({ props, variant, minSearchLength = 3, debounceMs
206
211
  popper: { sx: { zIndex: 20000 } },
207
212
  listbox: { sx: { fontSize: '11px' } },
208
213
  }, PaperComponent: ({ children, ...paperProps }) => (jsx(Paper, { elevation: 4, ...paperProps, children: children })), renderInput: (params) => (jsx(Tooltip, { title: resolvedValue?.label ?? '', placement: "top-start", arrow: true, disableInteractive: true,
209
- // Fixes a Popper mis-anchoring bug where the tooltip rendered
210
- // pinned to the viewport's top-left corner when this field
211
- // sits inside an `overflow: hidden` ancestor (e.g. a table
212
- // cell in material-react-table / GenericPaginatedGrid).
214
+ // ── Tooltip intentionally suppressed via props (kept in the tree) ──
213
215
  //
214
- // ROOT CAUSE: Popper.js ships a built-in `hide` modifier that
215
- // runs `detectOverflow` against the `clippingParents` boundary.
216
- // Because our reference element (the input) sits inside an
217
- // `overflow: hidden` table cell, Popper decides the reference
218
- // is "hidden" relative to that clipping boundary and collapses
219
- // the popper to `inset: 0 auto auto 0` (the body's top-left
220
- // origin) — regardless of `placement`/`offset`/`preventOverflow`
221
- // configuration. That's the (0,0) pin we were seeing.
216
+ // We previously tried to fix a Popper mis-anchoring bug where the
217
+ // tooltip rendered pinned to the viewport's top-left corner
218
+ // (`inset: 0 auto auto 0`) when this field sits inside an
219
+ // `overflow: hidden` ancestor (e.g. a table cell in
220
+ // `material-react-table` / `GenericPaginatedGrid`). The root
221
+ // cause is Popper's built-in `hide` modifier collapsing the
222
+ // popper once it judges the reference element "hidden" relative
223
+ // to the `clippingParents` boundary — and disabling that
224
+ // modifier (`{ name: 'hide', enabled: false }` below) verifiably
225
+ // fixed the anchoring in Storybook (MUI v7), but the SAME build
226
+ // still mis-anchored when consumed live in apps pinned to
227
+ // MUI v6 (`@mui/material@6.x` vs. our `>=7.3.1` peer
228
+ // requirement) — the v6 Popper/Tooltip runtime ignores/overrides
229
+ // the modifier configuration.
222
230
  //
223
- // FIX: explicitly disable the `hide` modifier (we don't want
224
- // "hide when clipped" behaviour — we want the tooltip to
225
- // escape the clipping ancestor instead), and measure overflow
226
- // against the `viewport` (via `rootBoundary`) rather than the
227
- // clipping table cell, since the whole point is to stay
228
- // visible in the viewport even when the anchor is inside a
229
- // clipped container. `disablePortal: false` (the default)
230
- // portal-renders the tooltip to `document.body` so it can
231
- // visually escape the clipping ancestor.
232
- slotProps: {
231
+ // Rather than ship a tooltip that only works on some MUI majors,
232
+ // we suppress it at the component-prop level: `open={false}`
233
+ // forces MUI's Tooltip into fully-controlled mode and it will
234
+ // NEVER open (hover/focus listeners still attach but have no
235
+ // visual effect), so the broken Popper placement can never be
236
+ // observed. The `<Tooltip>` wrapper is deliberately *retained*
237
+ // (not removed) — flipping `open` back to the default
238
+ // (uncontrolled/hover-driven) is a one-line change once the
239
+ // anchoring is fixed for every supported MUI major.
240
+ open: false, slotProps: {
233
241
  popper: {
234
242
  sx: { zIndex: 20001 },
235
243
  modifiers: [
@@ -127,6 +127,16 @@ export interface DynamicFilterBarProps {
127
127
  filterChipFontSize?: number;
128
128
  filterChipMinHeight?: number;
129
129
  filterLabelFontSize?: number;
130
+ /**
131
+ * Current value of MRT's global filter (free-text search across every
132
+ * visible column). When provided together with `onGlobalFilterChange`, a
133
+ * search box is rendered next to "Manage Filters".
134
+ */
135
+ globalFilter?: string;
136
+ /** Called (debounced) as the user types in the global search box. */
137
+ onGlobalFilterChange?: (value: string) => void;
138
+ /** Placeholder text for the global search box. Default: "Search…". */
139
+ globalSearchPlaceholder?: string;
130
140
  }
131
- declare const DynamicFilterBar: ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters, uiDensity, filterChipFontSize, filterChipMinHeight, filterLabelFontSize, }: DynamicFilterBarProps) => import("react/jsx-runtime").JSX.Element;
132
- export default DynamicFilterBar;
141
+ declare const _default: import("react").MemoExoticComponent<({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters, uiDensity, filterChipFontSize, filterChipMinHeight, filterLabelFontSize, globalFilter, onGlobalFilterChange, globalSearchPlaceholder, }: DynamicFilterBarProps) => import("react/jsx-runtime").JSX.Element>;
142
+ export default _default;
@@ -1,5 +1,5 @@
1
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { useState, useEffect } from 'react';
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { memo, useState, useEffect } from 'react';
3
3
  import { useForm } from 'react-hook-form';
4
4
  import dayjs from 'dayjs';
5
5
  import customParseFormat from 'dayjs/plugin/customParseFormat';
@@ -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, Checkbox, Divider } from '@mui/material';
12
+ import { TextField, InputAdornment, IconButton, Box, Typography, Tooltip, Button, Popover, Stack, 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';
@@ -281,6 +281,41 @@ const MultiSelectFilterChip = ({ label, selectedValues, options, onApply, onRemo
281
281
  } })] }, opt.value));
282
282
  }) })] })] }));
283
283
  };
284
+ const GlobalSearchInput = memo(({ value, onChange, chipStyle, placeholder = 'Search…' }) => {
285
+ const [draft, setDraft] = useState(value);
286
+ // Keep local draft in sync when the external value changes elsewhere
287
+ // (e.g. "Clear filters & refresh" resets globalFilter to '').
288
+ useEffect(() => {
289
+ setDraft(value);
290
+ }, [value]);
291
+ // Debounce: push to parent (and trigger MRT's global filter) 250ms after typing stops.
292
+ useEffect(() => {
293
+ if (draft === value)
294
+ return;
295
+ const handle = setTimeout(() => onChange(draft), 250);
296
+ return () => clearTimeout(handle);
297
+ // eslint-disable-next-line react-hooks/exhaustive-deps
298
+ }, [draft]);
299
+ const handleClear = () => {
300
+ setDraft('');
301
+ onChange('');
302
+ };
303
+ return (jsx(TextField, { value: draft, onChange: (e) => setDraft(e.target.value), placeholder: placeholder, size: "small", variant: "outlined", "aria-label": "Search all columns", InputProps: {
304
+ startAdornment: (jsx(InputAdornment, { position: "start", children: jsx(SearchIcon, { sx: { fontSize: 16, color: 'text.disabled' } }) })),
305
+ endAdornment: draft ? (jsx(InputAdornment, { position: "end", children: jsx(IconButton, { size: "small", onClick: handleClear, "aria-label": "Clear search", sx: { p: 0.25 }, children: jsx(CloseIcon, { sx: { fontSize: '14px !important' } }) }) })) : undefined,
306
+ }, sx: {
307
+ minWidth: 160,
308
+ maxWidth: 220,
309
+ flexShrink: 0,
310
+ '& .MuiOutlinedInput-root': {
311
+ borderRadius: 5,
312
+ fontSize: chipStyle.fontSize,
313
+ height: chipStyle.minHeight,
314
+ },
315
+ '& .MuiOutlinedInput-input': { py: 0 },
316
+ } }));
317
+ });
318
+ GlobalSearchInput.displayName = 'GlobalSearchInput';
284
319
  const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, maxVisible = 4, chipStyle, }) => {
285
320
  const [anchor, setAnchor] = useState(null);
286
321
  const atLimit = visibleIds.length >= maxVisible;
@@ -327,7 +362,7 @@ const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, o
327
362
  } }), selected && jsx(CheckIcon, { sx: { fontSize: 16, color: 'primary.main', flexShrink: 0 } })] }) }) }, col.id));
328
363
  }) })] }))] })] })] }));
329
364
  };
330
- const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters = 4, uiDensity = 'default', filterChipFontSize, filterChipMinHeight, filterLabelFontSize, }) => {
365
+ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters = 4, uiDensity = 'default', filterChipFontSize, filterChipMinHeight, filterLabelFontSize, globalFilter, onGlobalFilterChange, globalSearchPlaceholder, }) => {
331
366
  const chipStyle = {
332
367
  fontSize: filterChipFontSize ?? (uiDensity === 'compact' ? 11 : 12),
333
368
  minHeight: filterChipMinHeight ?? (uiDensity === 'compact' ? 26 : 30),
@@ -416,7 +451,8 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
416
451
  }
417
452
  // Default: free-text input
418
453
  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));
419
- }), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter, maxVisible: maxVisibleFilters, chipStyle: chipStyle })] }));
454
+ }), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter, maxVisible: maxVisibleFilters, chipStyle: chipStyle }), onGlobalFilterChange && (jsx(GlobalSearchInput, { value: globalFilter ?? '', onChange: onGlobalFilterChange, chipStyle: chipStyle, placeholder: globalSearchPlaceholder }))] }));
420
455
  };
456
+ var DynamicFilterBar$1 = memo(DynamicFilterBar);
421
457
 
422
- export { DynamicFilterBar as default };
458
+ export { DynamicFilterBar$1 as default };
@@ -91,6 +91,13 @@ export interface GenericPaginatedGridProps<TData extends object> {
91
91
  uiDensityOptions?: GridUiDensityOptions;
92
92
  /** Choices shown in the "Rows per page" footer dropdown. Default: [1 000, 5 000, 10 000]. */
93
93
  pageSizeOptions?: number[];
94
+ /**
95
+ * Show the Global Search input expanded by default (instead of requiring a
96
+ * click on the search icon to reveal it). The icon remains in the top-right
97
+ * toolbar actions and can still be used to collapse/expand the input.
98
+ * Default: `true`.
99
+ */
100
+ showGlobalSearchByDefault?: boolean;
94
101
  /**
95
102
  * Background colour applied to every header cell.
96
103
  * Accepts any valid CSS colour string.
@@ -185,5 +192,5 @@ export interface GenericPaginatedGridProps<TData extends object> {
185
192
  */
186
193
  enableExpanding?: boolean;
187
194
  }
188
- 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, muiTableBodyCellProps: gridBodyCellProps, muiTableHeadCellProps: gridHeadCellProps, renderDetailPanel, enableExpanding, }: GenericPaginatedGridProps<TData>): import("react/jsx-runtime").JSX.Element;
195
+ declare function GenericPaginatedGrid<TData extends object>({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize, exportFilename, tableHeight, tableMinHeight, tableMaxHeight, autoFillViewport, layoutMode, uiDensity, uiDensityOptions, pageSizeOptions, showGlobalSearchByDefault, headerBgColor, headerMinHeight, headerMaxHeight, headerMaxLines, headerFontSize, bodyFontSize, maxVisibleFilters, muiTableBodyCellProps: gridBodyCellProps, muiTableHeadCellProps: gridHeadCellProps, renderDetailPanel, enableExpanding, }: GenericPaginatedGridProps<TData>): import("react/jsx-runtime").JSX.Element;
189
196
  export default GenericPaginatedGrid;
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { useMemo, useState, useRef, useLayoutEffect } from 'react';
3
- import { useMaterialReactTable, MaterialReactTable } from 'material-react-table';
2
+ import { useMemo, useState, useRef, useLayoutEffect, useCallback } from 'react';
3
+ import { useMaterialReactTable, MRT_ToggleGlobalFilterButton, MRT_ShowHideColumnsButton, MRT_ToggleDensePaddingButton, MRT_ToggleFullScreenButton, 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';
6
6
  import RefreshIcon from '@mui/icons-material/Refresh';
@@ -221,7 +221,7 @@ const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoa
221
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 } }) }) }) })] })] }));
222
222
  };
223
223
  // ─── GenericPaginatedGrid ─────────────────────────────────────────────────────
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, muiTableBodyCellProps: gridBodyCellProps, muiTableHeadCellProps: gridHeadCellProps, renderDetailPanel, enableExpanding = !!renderDetailPanel, }) {
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], showGlobalSearchByDefault = true, headerBgColor, headerMinHeight, headerMaxHeight, headerMaxLines, headerFontSize, bodyFontSize, maxVisibleFilters = 4, muiTableBodyCellProps: gridBodyCellProps, muiTableHeadCellProps: gridHeadCellProps, renderDetailPanel, enableExpanding = !!renderDetailPanel, }) {
225
225
  const densityTokens = useMemo(() => ({ ...UI_DENSITY_PRESETS[uiDensity], ...uiDensityOptions }), [uiDensity, uiDensityOptions]);
226
226
  const resolvedHeaderMinHeight = toCssSize(headerMinHeight, uiDensity === 'compact' ? '34px' : '40px');
227
227
  const resolvedHeaderMaxHeight = toCssSize(headerMaxHeight, uiDensity === 'compact' ? '56px' : '72px');
@@ -241,7 +241,11 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
241
241
  // 1. explicit defaultVisibleFilterIds prop (override)
242
242
  // 2. server filters + column filters that have defaultVisible: true
243
243
  // 3. first server filter only (original fallback)
244
- const defaultVisible = defaultVisibleFilterIds
244
+ //
245
+ // Memoized so identity stays stable across re-renders (e.g. while
246
+ // `isFetching` toggles) — used as a `useCallback` dependency below and as
247
+ // the reset target for "Clear All".
248
+ const defaultVisible = useMemo(() => defaultVisibleFilterIds
245
249
  ?? (() => {
246
250
  // Server-side chips with defaultVisible: true
247
251
  const markedServer = serverFilterDefs
@@ -266,7 +270,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
266
270
  return marked.length > 0
267
271
  ? marked
268
272
  : serverFilterDefs.length > 0 ? [serverFilterDefs[0].id] : [];
269
- })();
273
+ })(), [defaultVisibleFilterIds, serverFilterDefs, columns]);
270
274
  // ── State ──────────────────────────────────────────────────────────────────
271
275
  const [filterValues, setFilterValues] = useState(defaultFilters);
272
276
  const [visibleFilterIds, setVisibleFilterIds] = useState(defaultVisible);
@@ -382,24 +386,28 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
382
386
  }));
383
387
  }, [columns, serverFilterDefs]);
384
388
  // ── Handlers ───────────────────────────────────────────────────────────────
385
- const handleFilterValuesChange = (newValues) => {
389
+ // Wrapped in useCallback so stable references can be passed down to the
390
+ // memoized DynamicFilterBar/chip components without forcing re-renders on
391
+ // every parent render (e.g. while `isFetching` toggles).
392
+ const handleFilterValuesChange = useCallback((newValues) => {
386
393
  setFilterValues(newValues);
387
394
  setPagination((p) => ({ ...p, pageIndex: 0 }));
388
- };
389
- const handleToggleFilter = (id) => setVisibleFilterIds((prev) => {
395
+ }, []);
396
+ const handleToggleFilter = useCallback((id) => setVisibleFilterIds((prev) => {
390
397
  if (prev.includes(id))
391
398
  return prev.filter((v) => v !== id); // always allow remove
392
399
  if (prev.length >= maxVisibleFilters)
393
400
  return prev; // cap reached — ignore add
394
401
  return [...prev, id];
395
- });
396
- const handleClearAll = () => {
402
+ }), [maxVisibleFilters]);
403
+ const handleClearAll = useCallback(() => {
397
404
  setFilterValues(defaultFilters);
398
405
  setColumnFilters([]);
399
406
  setVisibleFilterIds(defaultVisible);
407
+ setGlobalFilter('');
400
408
  setPagination((p) => ({ ...p, pageIndex: 0 }));
401
- };
402
- const handleExport = () => {
409
+ }, [defaultFilters, defaultVisible]);
410
+ const handleExport = useCallback(() => {
403
411
  const headers = columns.map((c) => c.header).join(',');
404
412
  const csvRows = rows.map((row) => columns
405
413
  .map((c) => {
@@ -415,7 +423,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
415
423
  a.download = `${exportFilename}-page${pagination.pageIndex + 1}.csv`;
416
424
  a.click();
417
425
  URL.revokeObjectURL(url);
418
- };
426
+ }, [columns, rows, exportFilename, pagination.pageIndex]);
419
427
  // ── Table instance ─────────────────────────────────────────────────────────
420
428
  const table = useMaterialReactTable({
421
429
  columns: processedColumns,
@@ -484,7 +492,15 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
484
492
  columnResizeMode: 'onChange',
485
493
  enableExpanding,
486
494
  renderDetailPanel,
487
- enableFilters: false,
495
+ // Note: `enableFilters` is the master switch for column AND global
496
+ // filtering. Keep it enabled so the Global Search box (which relies on
497
+ // `getCanGlobalFilter()`) actually filters rows. MRT's native per-column
498
+ // filter inputs are hidden separately via `enableColumnFilters: false` —
499
+ // column-level filtering still works because DynamicFilterBar's chips
500
+ // operate on `columnFilters` state directly (column.getFilterFn() is
501
+ // used regardless of this flag).
502
+ enableFilters: true,
503
+ enableColumnFilters: false,
488
504
  enableGlobalFilter: true,
489
505
  enableDensityToggle: true,
490
506
  enableFullScreenToggle: true,
@@ -493,6 +509,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
493
509
  enableRowVirtualization: true,
494
510
  initialState: {
495
511
  density: densityTokens.mrtDensity,
512
+ showGlobalFilter: showGlobalSearchByDefault,
496
513
  },
497
514
  muiTableProps: {
498
515
  sx: { width: '100%' },
@@ -525,6 +542,27 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
525
542
  '& .MuiIconButton-root svg': { fontSize: densityTokens.toolbarIconFontSize === 'small' ? 18 : 20 },
526
543
  },
527
544
  },
545
+ // Align the Global Search field's icon/size with the other toolbar
546
+ // icons (Show/Hide Columns, Density, Fullscreen) so it blends in
547
+ // instead of appearing oversized.
548
+ muiSearchTextFieldProps: {
549
+ size: 'small',
550
+ variant: 'outlined',
551
+ sx: {
552
+ minWidth: uiDensity === 'compact' ? 160 : 200,
553
+ '& .MuiOutlinedInput-root': {
554
+ height: densityTokens.filterChipMinHeight,
555
+ fontSize: densityTokens.filterChipFontSize,
556
+ },
557
+ // The leading search icon is rendered as a plain <SearchIcon />
558
+ // direct child of the input (not inside an IconButton), so size it
559
+ // to match the other top-toolbar action icons. The trailing clear
560
+ // (×) icon is inside an IconButton/InputAdornment.
561
+ '& .MuiOutlinedInput-root > .MuiSvgIcon-root, & .MuiInputAdornment-root .MuiSvgIcon-root': {
562
+ fontSize: densityTokens.toolbarIconFontSize === 'small' ? 18 : 20,
563
+ },
564
+ },
565
+ },
528
566
  muiTableHeadProps: {
529
567
  sx: {
530
568
  position: 'sticky',
@@ -599,6 +637,11 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
599
637
  ? { color: 'error', children: 'Failed to load data. Please try again.' }
600
638
  : undefined,
601
639
  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 }) })] })),
640
+ // Single global search control: MRT's built-in search icon/input in the
641
+ // top-right toolbar actions. The inline search box next to "Manage
642
+ // Filters" is intentionally not rendered (see renderTopToolbarCustomActions)
643
+ // to avoid showing two search controls.
644
+ renderToolbarInternalActions: ({ table: t }) => (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [jsx(MRT_ToggleGlobalFilterButton, { table: t }), jsx(MRT_ShowHideColumnsButton, { table: t }), jsx(MRT_ToggleDensePaddingButton, { table: t }), jsx(MRT_ToggleFullScreenButton, { table: t })] })),
602
645
  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 })),
603
646
  });
604
647
  return (jsxs(Box, { ref: containerRef, className: "tp-gpg-root", sx: {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Mock data + fetch adapters for Storybook sample grids (no API required).
3
3
  */
4
- import type { TPColumnDef } from './types';
4
+ import type { TPColumnDef, InvoiceDetail } from './types';
5
5
  import type { PagedResult } from './GenericPaginatedGrid';
6
6
  import type { ServerFilterDef } from './DynamicFilterBar';
7
7
  export interface EmployeeRow {
@@ -54,6 +54,14 @@ export declare const orderColumns: TPColumnDef<OrderRow>[];
54
54
  export declare const orderFilterDefs: ServerFilterDef[];
55
55
  export declare const orderDefaultFilters: Record<string, string>;
56
56
  export declare function fetchOrderPage(filters: Record<string, string>, pageIndex: number, pageSize?: number): Promise<PagedResult<OrderRow>>;
57
+ /**
58
+ * Mock `fetchPage` adapter for `InvoiceGrid` — pure in-memory data, no API
59
+ * calls. Filters the `ALL_INVOICES` array using the same filter keys
60
+ * (`FromDate`, `ToDate`, `Division`, `Customer`, `DC`, `ProductCode`) that the
61
+ * server filter chips already produce, so `InvoiceGrid.tsx` and its Storybook
62
+ * stories work unchanged.
63
+ */
64
+ export declare function fetchInvoicePage(filters: Record<string, string>, pageIndex: number, pageSize?: number): Promise<PagedResult<InvoiceDetail>>;
57
65
  export interface OrderLineItem {
58
66
  itemId: number;
59
67
  productCode: string;
@@ -107,20 +107,3 @@ export interface InvoiceDetail {
107
107
  changeddate: string;
108
108
  SHIP_TO_PARTY: string;
109
109
  }
110
- export interface InvoiceRequest {
111
- FromDate: string;
112
- ToDate: string;
113
- Division: string;
114
- Customer: string;
115
- DC: string;
116
- ProductCode: string;
117
- PageNumber: number;
118
- }
119
- export interface InvoiceApiResponse {
120
- PageNumber: number;
121
- PageSize: number;
122
- TotalRecords: number;
123
- TotalPages: number;
124
- HasNextPage: boolean;
125
- Data: InvoiceDetail[];
126
- }
@@ -1,4 +1,4 @@
1
- import { __exports as DateRange } from '../../../../../_virtual/index.esm6.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 DateRangePicker } from '../../../../../_virtual/index.esm4.js';
1
+ import { __exports as DateRangePicker } 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 requireDateRange } from '../DateRange/index.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as DefinedRange } from '../../../../../_virtual/index.esm5.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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tp-react-elements",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "React form components library built with React Hook Form and Yup",
@@ -1,2 +0,0 @@
1
- import type { InvoiceRequest, InvoiceApiResponse } from './types';
2
- export declare const fetchInvoiceDetails: (request: InvoiceRequest) => Promise<InvoiceApiResponse>;