tp-react-elements 1.3.12 → 1.3.14

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.
@@ -1,3 +1,3 @@
1
- var DateInput = {};
1
+ var Month = {};
2
2
 
3
- export { DateInput as __exports };
3
+ export { Month as __exports };
@@ -1,3 +1,3 @@
1
- var Month = {};
1
+ var DateInput = {};
2
2
 
3
- export { Month as __exports };
3
+ export { DateInput as __exports };
@@ -1,3 +1,3 @@
1
- var DateRange = {};
1
+ var DefinedRange = {};
2
2
 
3
- export { DateRange as __exports };
3
+ export { DefinedRange as __exports };
@@ -1,3 +1,3 @@
1
- var DefinedRange = {};
1
+ var DateRangePicker = {};
2
2
 
3
- export { DefinedRange as __exports };
3
+ export { DateRangePicker as __exports };
@@ -1,3 +1,3 @@
1
- var DateRangePicker = {};
1
+ var Calendar = {};
2
2
 
3
- export { DateRangePicker as __exports };
3
+ export { Calendar as __exports };
@@ -1,3 +1,3 @@
1
- var Calendar = {};
1
+ var DateRange = {};
2
2
 
3
- export { Calendar as __exports };
3
+ export { DateRange as __exports };
@@ -10,6 +10,11 @@ import type { MRT_ColumnFiltersState } from 'material-react-table';
10
10
  * quickly filter long option lists. Same `filterKey` / `options`
11
11
  * shape as `dropdown`.
12
12
  * - `date` — single date-picker chip. Applied client-side via MRT `columnFilters`.
13
+ * - `multiSelect` — checkbox list that allows picking multiple values at once.
14
+ * Selected values are joined as a comma-separated string in
15
+ * `filterValues[filterKey]` (e.g. `"Pending,Shipped"`).
16
+ * Your `fetchPage` function should split on `","` to build the
17
+ * correct server query.
13
18
  */
14
19
  export type ServerFilterDef = {
15
20
  type: 'dateRange';
@@ -55,6 +60,21 @@ export type ServerFilterDef = {
55
60
  id: string;
56
61
  label: string;
57
62
  columnKey: string;
63
+ } | {
64
+ /**
65
+ * Multi-select checkbox chip — users can pick **multiple** values at once.
66
+ * Selected values are stored as a comma-separated string in
67
+ * `filterValues[filterKey]` (e.g. `"Pending,Shipped,Delivered"`).
68
+ * Split on `","` in your `fetchPage` handler to build the server query.
69
+ */
70
+ type: 'multiSelect';
71
+ id: string;
72
+ label: string;
73
+ filterKey: string;
74
+ options: {
75
+ label: string;
76
+ value: string;
77
+ }[];
58
78
  };
59
79
  interface AvailableColumn {
60
80
  id: string;
@@ -64,7 +84,7 @@ interface AvailableColumn {
64
84
  * Sourced from the `filterType` prop on the column's `TPColumnDef`.
65
85
  * Defaults to `'text'` when omitted.
66
86
  */
67
- filterType?: 'text' | 'date' | 'select' | 'searchable-select' | 'number';
87
+ filterType?: 'text' | 'date' | 'select' | 'searchable-select' | 'number' | 'multi-select';
68
88
  /** Options for `'select'` / `'searchable-select'` chip variants. */
69
89
  filterOptions?: {
70
90
  label: string;
@@ -9,7 +9,7 @@ import { DatePicker } from '@mui/x-date-pickers/DatePicker';
9
9
  import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
10
10
  import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
11
11
  import SingleSelectNonAutoComplete from '../FormComponents/Select/SingleSelectNonAutoComplete.esm.js';
12
- import { Box, Typography, Tooltip, Button, Popover, TextField, Stack, InputAdornment, List, ListItemButton, ListItemText, Divider } from '@mui/material';
12
+ import { Box, Typography, Tooltip, Button, Popover, TextField, Stack, InputAdornment, List, ListItemButton, ListItemText, Checkbox, Divider } from '@mui/material';
13
13
  import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
14
14
  import TuneIcon from '@mui/icons-material/Tune';
15
15
  import CloseIcon from '@mui/icons-material/Close';
@@ -29,6 +29,7 @@ const TYPE_PILL = {
29
29
  text: { label: 'Text', color: '#2e7d32', bg: '#e8f5e9' },
30
30
  dropdown: { label: 'Select', color: '#6a1b9a', bg: '#f3e5f5' },
31
31
  searchableDropdown: { label: 'Search', color: '#bf360c', bg: '#fbe9e7' },
32
+ multiSelect: { label: 'Multi', color: '#0277bd', bg: '#e1f5fe' },
32
33
  };
33
34
  // Column filter type pills (used in COLUMN FILTERS section of Manage Filters)
34
35
  const COLUMN_TYPE_PILL = {
@@ -37,6 +38,7 @@ const COLUMN_TYPE_PILL = {
37
38
  select: { label: 'Select', color: '#bf360c', bg: '#fbe9e7' },
38
39
  'searchable-select': { label: 'Search', color: '#bf360c', bg: '#fbe9e7' },
39
40
  number: { label: 'Number', color: '#e65100', bg: '#fff3e0' },
41
+ 'multi-select': { label: 'Multi', color: '#0277bd', bg: '#e1f5fe' },
40
42
  };
41
43
  const chipSx = (active, opts) => ({
42
44
  borderRadius: 5,
@@ -244,6 +246,41 @@ const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeh
244
246
  } }), isSelected && (jsx(CheckIcon, { sx: { fontSize: 15, color: 'primary.main', flexShrink: 0 } }))] }, opt.value));
245
247
  }))] })] })] }));
246
248
  };
249
+ const MultiSelectFilterChip = ({ label, selectedValues, options, onApply, onRemove, chipStyle, }) => {
250
+ const [anchor, setAnchor] = useState(null);
251
+ const active = selectedValues.length > 0;
252
+ const toggle = (value) => {
253
+ const next = selectedValues.includes(value)
254
+ ? selectedValues.filter((v) => v !== value)
255
+ : [...selectedValues, value];
256
+ onApply(next);
257
+ };
258
+ const chipText = (() => {
259
+ if (!active)
260
+ return label;
261
+ if (selectedValues.length === 1) {
262
+ const found = options.find((o) => o.value === selectedValues[0]);
263
+ return `${label}: ${found?.label ?? selectedValues[0]}`;
264
+ }
265
+ if (selectedValues.length === 2) {
266
+ const names = selectedValues.map((v) => options.find((o) => o.value === v)?.label ?? v);
267
+ return `${label}: ${names.join(', ')}`;
268
+ }
269
+ return `${label}: ${selectedValues.length} selected`;
270
+ })();
271
+ return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: (e) => setAnchor(e.currentTarget), startIcon: jsx(FilterListIcon, { sx: { fontSize: '14px !important', mr: -0.25 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, width: 260 } }, children: [active && (jsxs(Box, { sx: {
272
+ px: 2, py: 0.75,
273
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
274
+ borderBottom: '1px solid', borderColor: 'divider',
275
+ }, children: [jsxs(Typography, { sx: { fontSize: 11, color: 'primary.main', fontWeight: 600 }, children: [selectedValues.length, " selected"] }), jsx(Button, { size: "small", color: "inherit", sx: { fontSize: 11, py: 0, textTransform: 'none', minWidth: 'auto' }, onClick: () => onApply([]), children: "Clear all" })] })), jsx(List, { dense: true, disablePadding: true, sx: { maxHeight: 300, overflowY: 'auto', py: 0.5 }, children: options.map((opt) => {
276
+ const checked = selectedValues.includes(opt.value);
277
+ return (jsxs(ListItemButton, { onClick: () => toggle(opt.value), sx: { px: 1, py: 0.25 }, children: [jsx(Checkbox, { checked: checked, size: "small", disableRipple: true, sx: { p: 0.5, mr: 0.5, '& .MuiSvgIcon-root': { fontSize: 16 } } }), jsx(ListItemText, { primary: opt.label, primaryTypographyProps: {
278
+ fontSize: 13,
279
+ color: checked ? 'primary.main' : 'text.primary',
280
+ fontWeight: checked ? 600 : 400,
281
+ } })] }, opt.value));
282
+ }) })] })] }));
283
+ };
247
284
  const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, maxVisible = 4, chipStyle, }) => {
248
285
  const [anchor, setAnchor] = useState(null);
249
286
  const atLimit = visibleIds.length >= maxVisible;
@@ -296,11 +333,21 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
296
333
  minHeight: filterChipMinHeight ?? (uiDensity === 'compact' ? 26 : 30),
297
334
  };
298
335
  const labelFontSize = filterLabelFontSize ?? (uiDensity === 'compact' ? 11 : 12);
336
+ // ── Single-value column filter helpers ──────────────────────────────────────
299
337
  const getColValue = (id) => columnFilters.find((f) => f.id === id)?.value ?? '';
300
338
  const applyColFilter = (id, value) => {
301
339
  const rest = columnFilters.filter((f) => f.id !== id);
302
340
  onColumnFilterChange(value ? [...rest, { id, value }] : rest);
303
341
  };
342
+ // ── Multi-value column filter helpers ────────────────────────────────────────
343
+ const getColValues = (id) => {
344
+ const val = columnFilters.find((f) => f.id === id)?.value;
345
+ return Array.isArray(val) ? val : [];
346
+ };
347
+ const applyColMultiFilter = (id, values) => {
348
+ const rest = columnFilters.filter((f) => f.id !== id);
349
+ onColumnFilterChange(values.length > 0 ? [...rest, { id, value: values }] : rest);
350
+ };
304
351
  const removeFilter = (id) => {
305
352
  const def = serverFilterDefs.find((d) => d.id === id);
306
353
  if (def?.type === 'dateRange') {
@@ -308,14 +355,22 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
308
355
  }
309
356
  else if (def?.type === 'text' ||
310
357
  def?.type === 'dropdown' ||
311
- def?.type === 'searchableDropdown') {
358
+ def?.type === 'searchableDropdown' ||
359
+ def?.type === 'multiSelect') {
312
360
  onFilterValuesChange({ ...filterValues, [def.filterKey]: '' });
313
361
  }
314
362
  else if (def?.type === 'date') {
315
363
  applyColFilter(def.columnKey, '');
316
364
  }
317
365
  else {
318
- applyColFilter(id, '');
366
+ // Column filter — multi-select stores string[], others store string
367
+ const colDef = availableColumns.find((c) => c.id === id);
368
+ if (colDef?.filterType === 'multi-select') {
369
+ applyColMultiFilter(id, []);
370
+ }
371
+ else {
372
+ applyColFilter(id, '');
373
+ }
319
374
  }
320
375
  onToggleFilter(id);
321
376
  };
@@ -333,6 +388,16 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
333
388
  if (def?.type === 'searchableDropdown') {
334
389
  return (jsx(SearchableSelectChip, { label: def.label, value: filterValues[def.filterKey] ?? '', options: def.options, onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), placeholder: def.placeholder ?? 'Search…', chipStyle: chipStyle }, id));
335
390
  }
391
+ if (def?.type === 'multiSelect') {
392
+ // filterValues[key] stores comma-separated values: "Pending,Shipped"
393
+ const selected = (filterValues[def.filterKey] ?? '')
394
+ .split(',')
395
+ .filter(Boolean);
396
+ return (jsx(MultiSelectFilterChip, { label: def.label, selectedValues: selected, options: def.options, onApply: (vals) => onFilterValuesChange({
397
+ ...filterValues,
398
+ [def.filterKey]: vals.join(','),
399
+ }), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
400
+ }
336
401
  if (def?.type === 'date') {
337
402
  return (jsx(DatePickerChip, { label: def.label, value: getColValue(def.columnKey), onApply: (v) => applyColFilter(def.columnKey, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
338
403
  }
@@ -346,6 +411,9 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
346
411
  if (col.filterType === 'select' || col.filterType === 'searchable-select') {
347
412
  return (jsx(SearchableSelectChip, { label: col.label, value: getColValue(id), options: col.filterOptions ?? [], onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
348
413
  }
414
+ if (col.filterType === 'multi-select') {
415
+ return (jsx(MultiSelectFilterChip, { label: col.label, selectedValues: getColValues(id), options: col.filterOptions ?? [], onApply: (vals) => applyColMultiFilter(id, vals), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
416
+ }
349
417
  // Default: free-text input
350
418
  return (jsx(TextFilterChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), placeholder: `Filter by ${col.label}`, chipStyle: chipStyle }, id));
351
419
  }), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter, maxVisible: maxVisibleFilters, chipStyle: chipStyle })] }));
@@ -315,6 +315,13 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
315
315
  ? row.getValue(columnId) === filterValue
316
316
  : true;
317
317
  }
318
+ else if (filterType === 'multi-select') {
319
+ // Inclusion check: pass when the cell value is one of the selected values.
320
+ // filterValue is string[] (stored directly in MRT columnFilters).
321
+ mrtCol.filterFn = (row, columnId, filterValue) => !filterValue || filterValue.length === 0
322
+ ? true
323
+ : filterValue.includes(row.getValue(columnId) ?? '');
324
+ }
318
325
  // 'text' and 'number' use MRT's built-in client-side filtering
319
326
  }
320
327
  return mrtCol;
@@ -31,3 +31,24 @@ export declare const productColumns: TPColumnDef<ProductRow>[];
31
31
  export declare const productFilterDefs: ServerFilterDef[];
32
32
  export declare const productDefaultFilters: Record<string, string>;
33
33
  export declare function fetchProductPage(filters: Record<string, string>, pageIndex: number, pageSize?: number): Promise<PagedResult<ProductRow>>;
34
+ export interface OrderRow {
35
+ orderId: string;
36
+ customer: string;
37
+ category: string;
38
+ status: string;
39
+ priority: string;
40
+ amount: number;
41
+ qty: number;
42
+ orderDate: string;
43
+ }
44
+ export declare const orderColumns: TPColumnDef<OrderRow>[];
45
+ /**
46
+ * Server filter definitions for the Orders demo grid.
47
+ *
48
+ * `multiSelect` chips for Status and Category send comma-separated values
49
+ * in `filterValues` (e.g. `status: "Pending,Shipped"`).
50
+ * `fetchOrderPage` splits on `","` to apply the server-side filter.
51
+ */
52
+ export declare const orderFilterDefs: ServerFilterDef[];
53
+ export declare const orderDefaultFilters: Record<string, string>;
54
+ export declare function fetchOrderPage(filters: Record<string, string>, pageIndex: number, pageSize?: number): Promise<PagedResult<OrderRow>>;
@@ -2,56 +2,53 @@ import type { MRT_ColumnDef } from 'material-react-table';
2
2
  /**
3
3
  * Filter chip variant shown in Manage Filters → Column Filters.
4
4
  *
5
- * | Value | Chip rendered |
6
- * |---------------------|---------------------------|
7
- * | `'text'` | Free-text input (default) |
8
- * | `'date'` | Calendar date picker |
9
- * | `'select'` | Searchable option list |
10
- * | `'searchable-select'` | Searchable option list |
11
- * | `'number'` | Numeric input |
5
+ * | Value | Chip rendered |
6
+ * |---------------------|------------------------------------------------------|
7
+ * | `'text'` | Free-text input (default) |
8
+ * | `'date'` | Calendar date picker |
9
+ * | `'select'` | Searchable single-select option list |
10
+ * | `'searchable-select'` | Searchable single-select option list |
11
+ * | `'number'` | Numeric input |
12
+ * | `'multi-select'` | Checkbox list — pick many values, filters client-side|
12
13
  *
13
14
  * Set `enableColumnFilter: false` to hide the column from Manage Filters
14
15
  * entirely (e.g. for computed or numeric columns that don't need filtering).
15
16
  */
16
- export type ColumnFilterType = 'text' | 'date' | 'select' | 'searchable-select' | 'number';
17
+ export type ColumnFilterType = 'text' | 'date' | 'select' | 'searchable-select' | 'number' | 'multi-select';
17
18
  /**
18
- * Drop-in replacement for `MRT_ColumnDef` that adds top-level
19
- * `filterType` and `options` so you never have to write `meta` blocks
20
- * or custom `filterFn` — the grid handles both automatically.
19
+ * `TValue` (second generic) is passed straight through to
20
+ * `MRT_ColumnDef<TData, TValue>` and **defaults to `any`** so that
21
+ * `cell.getValue()` returns `any` inside `Cell`, `muiTableBodyCellProps`,
22
+ * and all other MRT callbacks — exactly as you'd expect from a plain
23
+ * `MRT_ColumnDef` column. Set `TValue` explicitly only when you want
24
+ * strict return-type checking on `cell.getValue()`.
21
25
  *
22
- * @example
23
26
  * ```ts
24
- * import type { TPColumnDef } from './types';
25
- *
26
- * const columns: TPColumnDef<MyRow>[] = [
27
- * // Date picker chip
28
- * { accessorKey: 'invoiceDate', header: 'Invoice Date', filterType: 'date' },
29
- *
30
- * // Searchable option list chip
27
+ * // Default — cell.getValue() is `any` (no cast needed)
28
+ * const cols: TPColumnDef<MyRow>[] = [
31
29
  * {
32
- * accessorKey: 'status',
33
- * header: 'Status',
34
- * filterType: 'searchable-select',
35
- * options: [
36
- * { label: 'Active', value: 'A' },
37
- * { label: 'Inactive', value: 'I' },
38
- * ],
30
+ * accessorKey: 'name',
31
+ * muiTableBodyCellProps: ({ cell }) => ({ title: cell.getValue(), className: 'body_cell' }),
39
32
  * },
40
- *
41
- * // Hidden from Manage Filters (numeric column)
42
- * { accessorKey: 'qty', header: 'Qty', enableColumnFilter: false },
43
33
  * ];
34
+ *
35
+ * // Strict — cell.getValue() is `number`
36
+ * const strictCol: TPColumnDef<MyRow, number> = {
37
+ * accessorKey: 'qty',
38
+ * Cell: ({ cell }) => <b>{cell.getValue().toFixed(2)}</b>,
39
+ * };
44
40
  * ```
45
41
  */
46
- export type TPColumnDef<TData extends object = object> = MRT_ColumnDef<TData> & {
42
+ export type TPColumnDef<TData extends object = object, TValue = any> = MRT_ColumnDef<TData, TValue> & {
47
43
  /**
48
44
  * Which filter chip to show in Manage Filters → Column Filters.
49
45
  * Omit (or set to `'text'`) for a plain text-input chip.
50
46
  */
51
47
  filterType?: ColumnFilterType;
52
48
  /**
53
- * Option pairs for `'select'` and `'searchable-select'` filter types.
54
- * Each entry needs `label` (display text) and `value` (filter value sent).
49
+ * Option pairs for `'select'`, `'searchable-select'`, and `'multi-select'`
50
+ * filter types. Each entry needs `label` (display text) and `value` (filter
51
+ * value sent to the server / used for client-side matching).
55
52
  */
56
53
  options?: {
57
54
  label: string;
@@ -1,4 +1,4 @@
1
- import { __exports as Calendar } from '../../../../../_virtual/index.esm7.js';
1
+ import { __exports as Calendar } 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 requireDayCell } from '../DayCell/index.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as DateInput } from '../../../../../_virtual/index.esm13.js';
1
+ import { __exports as DateInput } from '../../../../../_virtual/index.esm14.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
  import { __require as requireClassnames } from '../../../../classnames/index.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as DateRange } from '../../../../../_virtual/index.esm4.js';
1
+ import { __exports as DateRange } from '../../../../../_virtual/index.esm7.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
  import { __require as requireCalendar } from '../Calendar/index.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as DateRangePicker } from '../../../../../_virtual/index.esm6.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.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 requireStyles } from '../../styles.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as Month } from '../../../../../_virtual/index.esm14.js';
1
+ import { __exports as Month } from '../../../../../_virtual/index.esm13.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
  import { __require as requireDayCell } from '../DayCell/index.esm.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tp-react-elements",
3
- "version": "1.3.12",
3
+ "version": "1.3.14",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "React form components library built with React Hook Form and Yup",