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 +20 -12
- package/dist/_virtual/index.esm4.js +2 -2
- package/dist/_virtual/index.esm5.js +2 -2
- package/dist/_virtual/index.esm6.js +2 -2
- package/dist/components/FormComponents/Select/MultiSelect.d.ts +10 -0
- package/dist/components/FormComponents/Select/SingleSelectSearchApi.d.ts +20 -15
- package/dist/components/FormComponents/Select/SingleSelectSearchApi.esm.js +45 -37
- package/dist/components/PaginatedGrid/DynamicFilterBar.d.ts +12 -2
- package/dist/components/PaginatedGrid/DynamicFilterBar.esm.js +42 -6
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.d.ts +8 -1
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.esm.js +57 -14
- package/dist/components/PaginatedGrid/mockData.d.ts +9 -1
- package/dist/components/PaginatedGrid/types.d.ts +0 -17
- package/dist/node_modules/react-date-range/dist/components/DateRange/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DateRangePicker/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DefinedRange/index.esm.js +1 -1
- package/package.json +1 -1
- package/dist/components/PaginatedGrid/invoiceApi.d.ts +0 -2
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`** —
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
`
|
|
19
|
-
|
|
20
|
-
|
|
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`
|
|
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** —
|
|
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
|
|
1
|
+
var DateRange = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateRange as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DateRangePicker = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateRangePicker as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DefinedRange = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
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
|
|
26
|
-
* The input is wrapped in a MUI `Tooltip`
|
|
27
|
-
* hover (useful when a narrow column truncates the label).
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* `preventOverflow
|
|
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
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
|
54
|
-
* The input is wrapped in a MUI `Tooltip`
|
|
55
|
-
* hover (useful when a narrow column truncates the label).
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `preventOverflow
|
|
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
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
// `overflow: hidden`
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
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
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
//
|
|
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
|
|
132
|
-
export default
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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