mui-table-2026 1.0.0

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.
@@ -0,0 +1,421 @@
1
+ import { Button } from '@mui/material';
2
+ import { Checkbox } from '@mui/material';
3
+ import { FormControl } from '@mui/material';
4
+ import { FormControlLabel } from '@mui/material';
5
+ import { FormGroup } from '@mui/material';
6
+ import { Grid } from '@mui/material';
7
+ import { Input } from '@mui/material';
8
+ import { InputLabel } from '@mui/material';
9
+ import { ListItemText } from '@mui/material';
10
+ import { MenuItem } from '@mui/material';
11
+ import PropTypes from 'prop-types';
12
+ import React from 'react';
13
+ import { Select } from '@mui/material';
14
+ import { TextField } from '@mui/material';
15
+ import { Typography } from '@mui/material';
16
+ import clsx from 'clsx';
17
+ import { withStyles } from 'tss-react/mui';
18
+ import cloneDeep from 'lodash.clonedeep';
19
+
20
+ export const defaultFilterStyles = theme => ({
21
+ root: {
22
+ backgroundColor: theme.palette.background.default,
23
+ padding: '24px 24px 36px 24px',
24
+ fontFamily: 'Roboto',
25
+ },
26
+ header: {
27
+ flex: '0 0 auto',
28
+ marginBottom: '16px',
29
+ width: '100%',
30
+ display: 'flex',
31
+ justifyContent: 'space-between',
32
+ },
33
+ title: {
34
+ display: 'inline-block',
35
+ marginLeft: '7px',
36
+ color: theme.palette.text.primary,
37
+ fontSize: '14px',
38
+ fontWeight: 500,
39
+ },
40
+ noMargin: {
41
+ marginLeft: '0px',
42
+ },
43
+ reset: {
44
+ alignSelf: 'left',
45
+ },
46
+ resetLink: {
47
+ marginLeft: '16px',
48
+ fontSize: '12px',
49
+ cursor: 'pointer',
50
+ },
51
+ filtersSelected: {
52
+ alignSelf: 'right',
53
+ },
54
+ /* checkbox */
55
+ checkboxListTitle: {
56
+ marginLeft: '7px',
57
+ marginBottom: '8px',
58
+ fontSize: '14px',
59
+ color: theme.palette.text.secondary,
60
+ textAlign: 'left',
61
+ fontWeight: 500,
62
+ },
63
+ checkboxFormGroup: {
64
+ marginTop: '8px',
65
+ },
66
+ checkboxFormControl: {
67
+ margin: '0px',
68
+ },
69
+ checkboxFormControlLabel: {
70
+ fontSize: '15px',
71
+ marginLeft: '8px',
72
+ color: theme.palette.text.primary,
73
+ },
74
+ checkboxIcon: {
75
+ width: '32px',
76
+ height: '32px',
77
+ },
78
+ checkbox: {},
79
+ checked: {},
80
+ gridListTile: {
81
+ marginTop: '16px',
82
+ },
83
+ });
84
+
85
+ class TableFilter extends React.Component {
86
+ static propTypes = {
87
+ /** Data used to populate filter dropdown/checkbox */
88
+ filterData: PropTypes.array.isRequired,
89
+ /** Data selected to be filtered against dropdown/checkbox */
90
+ filterList: PropTypes.array.isRequired,
91
+ /** Options used to describe table */
92
+ options: PropTypes.object.isRequired,
93
+ /** Callback to trigger filter update */
94
+ onFilterUpdate: PropTypes.func,
95
+ /** Callback to trigger filter reset */
96
+ onFilterReset: PropTypes.func,
97
+ /** Extend the style applied to components */
98
+ classes: PropTypes.object,
99
+ };
100
+
101
+ constructor(props) {
102
+ super(props);
103
+ this.state = {
104
+ filterList: cloneDeep(props.filterList),
105
+ };
106
+ }
107
+
108
+ filterUpdate = (index, value, column, type, customUpdate) => {
109
+ let newFilterList = this.state.filterList.slice(0);
110
+
111
+ this.props.updateFilterByType(newFilterList, index, value, type, customUpdate);
112
+ this.setState({
113
+ filterList: newFilterList,
114
+ });
115
+ };
116
+
117
+ handleCheckboxChange = (index, value, column) => {
118
+ this.filterUpdate(index, value, column, 'checkbox');
119
+
120
+ if (this.props.options.confirmFilters !== true) {
121
+ this.props.onFilterUpdate(index, value, column, 'checkbox');
122
+ }
123
+ };
124
+
125
+ handleDropdownChange = (event, index, column) => {
126
+ const labelFilterAll = this.props.options.textLabels.filter.all;
127
+ const value = event.target.value === labelFilterAll ? [] : [event.target.value];
128
+ this.filterUpdate(index, value, column, 'dropdown');
129
+
130
+ if (this.props.options.confirmFilters !== true) {
131
+ this.props.onFilterUpdate(index, value, column, 'dropdown');
132
+ }
133
+ };
134
+
135
+ handleMultiselectChange = (index, value, column) => {
136
+ this.filterUpdate(index, value, column, 'multiselect');
137
+
138
+ if (this.props.options.confirmFilters !== true) {
139
+ this.props.onFilterUpdate(index, value, column, 'multiselect');
140
+ }
141
+ };
142
+
143
+ handleTextFieldChange = (event, index, column) => {
144
+ this.filterUpdate(index, event.target.value, column, 'textField');
145
+
146
+ if (this.props.options.confirmFilters !== true) {
147
+ this.props.onFilterUpdate(index, event.target.value, column, 'textField');
148
+ }
149
+ };
150
+
151
+ handleCustomChange = (value, index, column) => {
152
+ this.filterUpdate(index, value, column.name, column.filterType);
153
+
154
+ if (this.props.options.confirmFilters !== true) {
155
+ this.props.onFilterUpdate(index, value, column.name, column.filterType);
156
+ }
157
+ };
158
+
159
+ renderCheckbox(column, index, components = {}) {
160
+ const CheckboxComponent = components.Checkbox || Checkbox;
161
+
162
+ const { classes, filterData } = this.props;
163
+ const { filterList } = this.state;
164
+ const renderItem =
165
+ column.filterOptions && column.filterOptions.renderValue ? column.filterOptions.renderValue : v => v;
166
+
167
+ return (
168
+ <Grid item key={index} xs={6}>
169
+ <FormGroup>
170
+ <Grid item xs={12}>
171
+ <Typography variant="body2" className={classes.checkboxListTitle}>
172
+ {column.label}
173
+ </Typography>
174
+ </Grid>
175
+ <Grid container>
176
+ {filterData[index].map((filterValue, filterIndex) => (
177
+ <Grid item key={filterIndex}>
178
+ <FormControlLabel
179
+ key={filterIndex}
180
+ classes={{
181
+ root: classes.checkboxFormControl,
182
+ label: classes.checkboxFormControlLabel,
183
+ }}
184
+ control={
185
+ <CheckboxComponent
186
+ data-description="table-filter"
187
+ color="primary"
188
+ className={classes.checkboxIcon}
189
+ onChange={this.handleCheckboxChange.bind(null, index, filterValue, column.name)}
190
+ checked={filterList[index].indexOf(filterValue) >= 0}
191
+ classes={{
192
+ root: classes.checkbox,
193
+ checked: classes.checked,
194
+ }}
195
+ value={filterValue != null ? filterValue.toString() : ''}
196
+ />
197
+ }
198
+ label={renderItem(filterValue)}
199
+ />
200
+ </Grid>
201
+ ))}
202
+ </Grid>
203
+ </FormGroup>
204
+ </Grid>
205
+ );
206
+ }
207
+
208
+ renderSelect(column, index) {
209
+ const { classes, filterData, options } = this.props;
210
+ const { filterList } = this.state;
211
+ const textLabels = options.textLabels.filter;
212
+ const renderItem =
213
+ column.filterOptions && column.filterOptions.renderValue
214
+ ? column.filterOptions.renderValue
215
+ : v => (v != null ? v.toString() : '');
216
+ const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6;
217
+
218
+ return (
219
+ <Grid
220
+ item
221
+ key={index}
222
+ xs={width}
223
+ classes={{ 'grid-xs-12': classes.gridListTile, 'grid-xs-6': classes.gridListTile }}>
224
+ <FormControl key={index} variant={'standard'} fullWidth>
225
+ <InputLabel htmlFor={column.name}>{column.label}</InputLabel>
226
+ <Select
227
+ fullWidth
228
+ value={filterList[index].length ? filterList[index].toString() : textLabels.all}
229
+ name={column.name}
230
+ onChange={event => this.handleDropdownChange(event, index, column.name)}
231
+ input={<Input name={column.name} id={column.name} />}>
232
+ <MenuItem value={textLabels.all} key={0}>
233
+ {textLabels.all}
234
+ </MenuItem>
235
+ {filterData[index].map((filterValue, filterIndex) => (
236
+ <MenuItem value={filterValue} key={filterIndex + 1}>
237
+ {renderItem(filterValue)}
238
+ </MenuItem>
239
+ ))}
240
+ </Select>
241
+ </FormControl>
242
+ </Grid>
243
+ );
244
+ }
245
+
246
+ renderTextField(column, index) {
247
+ const { classes } = this.props;
248
+ const { filterList } = this.state;
249
+ if (column.filterOptions && column.filterOptions.renderValue) {
250
+ console.warn('Custom renderValue not supported for textField filters');
251
+ }
252
+ const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6;
253
+
254
+ return (
255
+ <Grid
256
+ item
257
+ key={index}
258
+ xs={width}
259
+ classes={{ 'grid-xs-12': classes.gridListTile, 'grid-xs-6': classes.gridListTile }}>
260
+ <FormControl key={index} fullWidth>
261
+ <TextField
262
+ fullWidth
263
+ variant={'standard'}
264
+ label={column.label}
265
+ value={filterList[index].toString() || ''}
266
+ data-testid={'filtertextfield-' + column.name}
267
+ onChange={event => this.handleTextFieldChange(event, index, column.name)}
268
+ />
269
+ </FormControl>
270
+ </Grid>
271
+ );
272
+ }
273
+
274
+ renderMultiselect(column, index, components = {}) {
275
+ const CheckboxComponent = components.Checkbox || Checkbox;
276
+
277
+ const { classes, filterData } = this.props;
278
+ const { filterList } = this.state;
279
+ const renderItem =
280
+ column.filterOptions && column.filterOptions.renderValue ? column.filterOptions.renderValue : v => v;
281
+ const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6;
282
+ return (
283
+ <Grid
284
+ item
285
+ key={index}
286
+ xs={width}
287
+ classes={{ 'grid-xs-12': classes.gridListTile, 'grid-xs-6': classes.gridListTile }}>
288
+ <FormControl key={index} variant={'standard'} fullWidth>
289
+ <InputLabel htmlFor={column.name}>{column.label}</InputLabel>
290
+ <Select
291
+ multiple
292
+ fullWidth
293
+ value={filterList[index] || []}
294
+ renderValue={selected => selected.map(renderItem).join(', ')}
295
+ name={column.name}
296
+ onChange={event => this.handleMultiselectChange(index, event.target.value, column.name)}
297
+ input={<Input name={column.name} id={column.name} />}>
298
+ {filterData[index].map((filterValue, filterIndex) => (
299
+ <MenuItem value={filterValue} key={filterIndex + 1}>
300
+ <CheckboxComponent
301
+ data-description="table-filter"
302
+ color="primary"
303
+ checked={filterList[index].indexOf(filterValue) >= 0}
304
+ value={filterValue != null ? filterValue.toString() : ''}
305
+ className={classes.checkboxIcon}
306
+ classes={{
307
+ root: classes.checkbox,
308
+ checked: classes.checked,
309
+ }}
310
+ />
311
+ <ListItemText primary={renderItem(filterValue)} />
312
+ </MenuItem>
313
+ ))}
314
+ </Select>
315
+ </FormControl>
316
+ </Grid>
317
+ );
318
+ }
319
+
320
+ renderCustomField(column, index) {
321
+ const { classes, filterData, options } = this.props;
322
+ const { filterList } = this.state;
323
+ const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6;
324
+ const display =
325
+ (column.filterOptions && column.filterOptions.display) ||
326
+ (options.filterOptions && options.filterOptions.display);
327
+
328
+ if (!display) {
329
+ console.error('Property "display" is required when using custom filter type.');
330
+ return;
331
+ }
332
+ if (column.filterListOptions && column.filterListOptions.renderValue) {
333
+ console.warning('"renderValue" is ignored for custom filter fields');
334
+ }
335
+
336
+ return (
337
+ <Grid
338
+ item
339
+ key={index}
340
+ xs={width}
341
+ classes={{ 'grid-xs-12': classes.gridListTile, 'grid-xs-6': classes.gridListTile }}>
342
+ <FormControl key={index} fullWidth>
343
+ {display(filterList, this.handleCustomChange, index, column, filterData)}
344
+ </FormControl>
345
+ </Grid>
346
+ );
347
+ }
348
+
349
+ applyFilters = () => {
350
+ this.state.filterList.forEach((filter, index) => {
351
+ this.props.onFilterUpdate(index, filter, this.props.columns[index], 'custom');
352
+ });
353
+
354
+ this.props.handleClose(); // close filter dialog popover
355
+
356
+ if (this.props.options.onFilterConfirm) {
357
+ this.props.options.onFilterConfirm(this.state.filterList);
358
+ }
359
+
360
+ return this.state.filterList;
361
+ };
362
+
363
+ resetFilters = () => {
364
+ this.setState({
365
+ filterList: this.props.columns.map(() => []),
366
+ });
367
+ if (this.props.options.confirmFilters !== true) {
368
+ this.props.onFilterReset();
369
+ }
370
+ };
371
+
372
+ render() {
373
+ const { classes, columns, options, customFooter, filterList, components = {} } = this.props;
374
+ const textLabels = options.textLabels.filter;
375
+
376
+ return (
377
+ <div className={classes.root}>
378
+ <div className={classes.header}>
379
+ <div className={classes.reset}>
380
+ <Typography
381
+ variant="body2"
382
+ className={clsx({
383
+ [classes.title]: true,
384
+ })}>
385
+ {textLabels.title}
386
+ </Typography>
387
+ <Button
388
+ color="primary"
389
+ className={classes.resetLink}
390
+ tabIndex={0}
391
+ aria-label={textLabels.reset}
392
+ data-testid={'filterReset-button'}
393
+ onClick={this.resetFilters}>
394
+ {textLabels.reset}
395
+ </Button>
396
+ </div>
397
+ <div className={classes.filtersSelected} />
398
+ </div>
399
+ <Grid container direction="row" justifyContent="flex-start" alignItems="center" spacing={4}>
400
+ {columns.map((column, index) => {
401
+ if (column.filter) {
402
+ const filterType = column.filterType || options.filterType;
403
+ return filterType === 'checkbox'
404
+ ? this.renderCheckbox(column, index, components)
405
+ : filterType === 'multiselect'
406
+ ? this.renderMultiselect(column, index, components)
407
+ : filterType === 'textField'
408
+ ? this.renderTextField(column, index)
409
+ : filterType === 'custom'
410
+ ? this.renderCustomField(column, index)
411
+ : this.renderSelect(column, index);
412
+ }
413
+ })}
414
+ </Grid>
415
+ {customFooter ? customFooter(filterList, this.applyFilters) : ''}
416
+ </div>
417
+ );
418
+ }
419
+ }
420
+
421
+ export default withStyles(TableFilter, defaultFilterStyles, { name: 'MUIDataTableFilter' });
@@ -0,0 +1,136 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+ import PropTypes from 'prop-types';
3
+ import React from 'react';
4
+ import TableFilterListItem from './TableFilterListItem';
5
+
6
+ const useStyles = makeStyles({ name: 'MUIDataTableFilterList' })(() => ({
7
+ root: {
8
+ display: 'flex',
9
+ justifyContent: 'left',
10
+ flexWrap: 'wrap',
11
+ margin: '0px 16px 0px 16px',
12
+ },
13
+ chip: {
14
+ margin: '8px 8px 0px 0px',
15
+ },
16
+ }));
17
+
18
+ const TableFilterList = ({
19
+ options,
20
+ filterList,
21
+ filterUpdate,
22
+ filterListRenderers,
23
+ columnNames,
24
+ serverSideFilterList,
25
+ customFilterListUpdate,
26
+ ItemComponent = TableFilterListItem,
27
+ }) => {
28
+ const { classes } = useStyles();
29
+ const { serverSide } = options;
30
+
31
+ const removeFilter = (index, filterValue, columnName, filterType, customFilterListUpdate = null) => {
32
+ let removedFilter = filterValue;
33
+ if (Array.isArray(removedFilter) && removedFilter.length === 0) {
34
+ removedFilter = filterList[index];
35
+ }
36
+
37
+ filterUpdate(index, filterValue, columnName, filterType, customFilterListUpdate, filterList => {
38
+ if (options.onFilterChipClose) {
39
+ options.onFilterChipClose(index, removedFilter, filterList);
40
+ }
41
+ });
42
+ };
43
+ const customFilterChip = (customFilterItem, index, customFilterItemIndex, item, isArray) => {
44
+ let type;
45
+ // If our custom filter list is an array, we need to check for custom update functions to determine
46
+ // default type. Otherwise we use the supplied type in options.
47
+ if (isArray) {
48
+ type = customFilterListUpdate[index] ? 'custom' : 'chip';
49
+ } else {
50
+ type = columnNames[index].filterType;
51
+ }
52
+
53
+ return (
54
+ <ItemComponent
55
+ label={customFilterItem}
56
+ key={customFilterItemIndex}
57
+ onDelete={() =>
58
+ removeFilter(
59
+ index,
60
+ item[customFilterItemIndex] || [],
61
+ columnNames[index].name,
62
+ type,
63
+ customFilterListUpdate[index],
64
+ )
65
+ }
66
+ className={classes.chip}
67
+ itemKey={customFilterItemIndex}
68
+ index={index}
69
+ data={item}
70
+ columnNames={columnNames}
71
+ filterProps={
72
+ options.setFilterChipProps
73
+ ? options.setFilterChipProps(index, columnNames[index].name, item[customFilterItemIndex] || [])
74
+ : {}
75
+ }
76
+ />
77
+ );
78
+ };
79
+
80
+ const filterChip = (index, data, colIndex) => (
81
+ <ItemComponent
82
+ label={filterListRenderers[index](data)}
83
+ key={colIndex}
84
+ onDelete={() => removeFilter(index, data, columnNames[index].name, 'chip')}
85
+ className={classes.chip}
86
+ itemKey={colIndex}
87
+ index={index}
88
+ data={data}
89
+ columnNames={columnNames}
90
+ filterProps={options.setFilterChipProps ? options.setFilterChipProps(index, columnNames[index].name, data) : {}}
91
+ />
92
+ );
93
+
94
+ const getFilterList = filterList => {
95
+ return filterList.map((item, index) => {
96
+ if (columnNames[index].filterType === 'custom' && filterList[index].length) {
97
+ const filterListRenderersValue = filterListRenderers[index](item);
98
+
99
+ if (Array.isArray(filterListRenderersValue)) {
100
+ return filterListRenderersValue.map((customFilterItem, customFilterItemIndex) =>
101
+ customFilterChip(customFilterItem, index, customFilterItemIndex, item, true),
102
+ );
103
+ } else {
104
+ return customFilterChip(filterListRenderersValue, index, index, item, false);
105
+ }
106
+ }
107
+
108
+ return item.map((data, colIndex) => filterChip(index, data, colIndex));
109
+ });
110
+ };
111
+
112
+ return (
113
+ <div className={classes.root}>
114
+ {serverSide && serverSideFilterList ? getFilterList(serverSideFilterList) : getFilterList(filterList)}
115
+ </div>
116
+ );
117
+ };
118
+
119
+ TableFilterList.propTypes = {
120
+ /** Data used to filter table against */
121
+ filterList: PropTypes.array.isRequired,
122
+ /** Filter List value renderers */
123
+ filterListRenderers: PropTypes.array.isRequired,
124
+ /** Columns used to describe table */
125
+ columnNames: PropTypes.arrayOf(
126
+ PropTypes.oneOfType([
127
+ PropTypes.string,
128
+ PropTypes.shape({ name: PropTypes.string.isRequired, filterType: PropTypes.string }),
129
+ ]),
130
+ ).isRequired,
131
+ /** Callback to trigger filter update */
132
+ onFilterUpdate: PropTypes.func,
133
+ ItemComponent: PropTypes.any,
134
+ };
135
+
136
+ export default TableFilterList;
@@ -0,0 +1,20 @@
1
+ import { Chip } from '@mui/material';
2
+ import PropTypes from 'prop-types';
3
+ import React from 'react';
4
+ import clsx from 'clsx';
5
+
6
+ const TableFilterListItem = ({ label, onDelete, className, filterProps }) => {
7
+ filterProps = filterProps || {};
8
+ if (filterProps.className) {
9
+ className = clsx(className, filterProps.className);
10
+ }
11
+ return <Chip label={label} onDelete={onDelete} className={className} {...filterProps} />;
12
+ };
13
+
14
+ TableFilterListItem.propTypes = {
15
+ label: PropTypes.node,
16
+ onDelete: PropTypes.func.isRequired,
17
+ className: PropTypes.string.isRequired,
18
+ };
19
+
20
+ export default TableFilterListItem;
@@ -0,0 +1,74 @@
1
+ import React from 'react';
2
+ import MuiTable from '@mui/material/Table';
3
+ import TablePagination from './TablePagination';
4
+ import { makeStyles } from 'tss-react/mui';
5
+ import PropTypes from 'prop-types';
6
+
7
+ const useStyles = makeStyles({ name: 'MUIDataTableFooter' })(() => ({
8
+ root: {
9
+ '@media print': {
10
+ display: 'none',
11
+ },
12
+ },
13
+ }));
14
+
15
+ const TableFooter = ({ options, rowCount, page, rowsPerPage, changeRowsPerPage, changePage }) => {
16
+ const { classes } = useStyles();
17
+ const { customFooter, pagination = true } = options;
18
+
19
+ if (customFooter) {
20
+ return (
21
+ <MuiTable className={classes.root}>
22
+ {options.customFooter(
23
+ rowCount,
24
+ page,
25
+ rowsPerPage,
26
+ changeRowsPerPage,
27
+ changePage,
28
+ options.textLabels.pagination,
29
+ )}
30
+ </MuiTable>
31
+ );
32
+ }
33
+
34
+ if (pagination) {
35
+ return (
36
+ <MuiTable className={classes.root}>
37
+ <TablePagination
38
+ count={rowCount}
39
+ page={page}
40
+ rowsPerPage={rowsPerPage}
41
+ changeRowsPerPage={changeRowsPerPage}
42
+ changePage={changePage}
43
+ component={'div'}
44
+ options={options}
45
+ />
46
+ </MuiTable>
47
+ );
48
+ }
49
+
50
+ return null;
51
+ };
52
+
53
+ TableFooter.propTypes = {
54
+ /** Total number of table rows */
55
+ rowCount: PropTypes.number.isRequired,
56
+ /** Options used to describe table */
57
+ options: PropTypes.shape({
58
+ customFooter: PropTypes.func,
59
+ pagination: PropTypes.bool,
60
+ textLabels: PropTypes.shape({
61
+ pagination: PropTypes.object,
62
+ }),
63
+ }),
64
+ /** Current page index */
65
+ page: PropTypes.number.isRequired,
66
+ /** Total number allowed of rows per page */
67
+ rowsPerPage: PropTypes.number.isRequired,
68
+ /** Callback to trigger rows per page change */
69
+ changeRowsPerPage: PropTypes.func.isRequired,
70
+ /** Callback to trigger page change */
71
+ changePage: PropTypes.func.isRequired,
72
+ };
73
+
74
+ export default TableFooter;