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,39 @@
1
+ import React from 'react';
2
+ import { IconButton } from '@mui/material';
3
+ import { KeyboardArrowRight as KeyboardArrowRightIcon } from '@mui/icons-material';
4
+ import { Remove as RemoveIcon } from '@mui/icons-material';
5
+
6
+ const ExpandButton = ({
7
+ areAllRowsExpanded,
8
+ buttonClass,
9
+ expandableRowsHeader,
10
+ expandedRows,
11
+ iconClass,
12
+ iconIndeterminateClass,
13
+ isHeaderCell,
14
+ onExpand,
15
+ }) => {
16
+ return (
17
+ <>
18
+ {isHeaderCell && !areAllRowsExpanded() && areAllRowsExpanded && expandedRows.data.length > 0 ? (
19
+ <IconButton
20
+ onClick={onExpand}
21
+ style={{ padding: 0 }}
22
+ disabled={expandableRowsHeader === false}
23
+ className={buttonClass}>
24
+ <RemoveIcon id="expandable-button" className={iconIndeterminateClass} />
25
+ </IconButton>
26
+ ) : (
27
+ <IconButton
28
+ onClick={onExpand}
29
+ style={{ padding: 0 }}
30
+ disabled={expandableRowsHeader === false}
31
+ className={buttonClass}>
32
+ <KeyboardArrowRightIcon id="expandable-button" className={iconClass} />
33
+ </IconButton>
34
+ )}
35
+ </>
36
+ );
37
+ };
38
+
39
+ export default ExpandButton;
@@ -0,0 +1,96 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { InputBase } from '@mui/material';
4
+ import { MenuItem } from '@mui/material';
5
+ import { Select } from '@mui/material';
6
+ import { Toolbar } from '@mui/material';
7
+ import { Typography } from '@mui/material';
8
+ import { makeStyles } from 'tss-react/mui';
9
+ import { getPageValue } from '../utils.js';
10
+ import clsx from 'clsx';
11
+
12
+ const useStyles = makeStyles({ name: 'MUIDataTableJumpToPage' })((theme) => ({
13
+ root: {
14
+ color: theme.palette.text.primary,
15
+ },
16
+ caption: {
17
+ flexShrink: 0,
18
+ },
19
+ /*  Styles applied to the Select component root element */
20
+ selectRoot: {
21
+ marginRight: 32,
22
+ marginLeft: 8,
23
+ },
24
+ select: {
25
+ paddingTop: 6,
26
+ paddingBottom: 7,
27
+ paddingLeft: 8,
28
+ paddingRight: 24,
29
+ textAlign: 'right',
30
+ textAlignLast: 'right',
31
+ fontSize: theme.typography.pxToRem(14),
32
+ },
33
+ /* Styles applied to Select component icon class */
34
+ selectIcon: {},
35
+ /* Styles applied to InputBase component */
36
+ input: {
37
+ color: 'inhert',
38
+ fontSize: 'inhert',
39
+ flexShrink: 0,
40
+ },
41
+ }));
42
+
43
+ function JumpToPage(props) {
44
+ const { classes } = useStyles();
45
+
46
+ const handlePageChange = (event) => {
47
+ props.changePage(parseInt(event.target.value, 10));
48
+ };
49
+
50
+ const { count, textLabels, rowsPerPage, page, changePage } = props;
51
+
52
+ const textLabel = textLabels.pagination.jumpToPage;
53
+
54
+ let pages = [];
55
+ let lastPage = Math.min(1000, getPageValue(count, rowsPerPage, 1000000));
56
+
57
+ for (let ii = 0; ii <= lastPage; ii++) {
58
+ pages.push(ii);
59
+ }
60
+ const MenuItemComponent = MenuItem;
61
+
62
+ let myStyle = {
63
+ display: 'flex',
64
+ minHeight: '52px',
65
+ alignItems: 'center',
66
+ };
67
+
68
+ return (
69
+ <Toolbar style={myStyle} className={classes.root}>
70
+ <Typography color="inherit" variant="body2" className={classes.caption}>
71
+ {textLabel}
72
+ </Typography>
73
+ <Select
74
+ classes={{ select: classes.select, icon: classes.selectIcon }}
75
+ input={<InputBase className={clsx(classes.input, classes.selectRoot)} />}
76
+ value={getPageValue(count, rowsPerPage, page)}
77
+ onChange={handlePageChange}
78
+ style={{ marginRight: 0 }}>
79
+ {pages.map((pageVal) => (
80
+ <MenuItemComponent className={classes.menuItem} key={pageVal} value={pageVal}>
81
+ {pageVal + 1}
82
+ </MenuItemComponent>
83
+ ))}
84
+ </Select>
85
+ </Toolbar>
86
+ );
87
+ }
88
+
89
+ JumpToPage.propTypes = {
90
+ count: PropTypes.number.isRequired,
91
+ page: PropTypes.number.isRequired,
92
+ rowsPerPage: PropTypes.number.isRequired,
93
+ textLabels: PropTypes.object.isRequired,
94
+ };
95
+
96
+ export default JumpToPage;
@@ -0,0 +1,89 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import MuiPopover from '@mui/material/Popover';
4
+ import { IconButton } from '@mui/material';
5
+ import { Close as CloseIcon } from '@mui/icons-material';
6
+
7
+ const Popover = ({ className, trigger, refExit, hide, content, ...providedProps }) => {
8
+ const [isOpen, open] = useState(false);
9
+ const anchorEl = useRef(null);
10
+
11
+ useEffect(() => {
12
+ if (isOpen) {
13
+ const shouldHide = typeof hide === 'boolean' ? hide : false;
14
+ if (shouldHide) {
15
+ open(false);
16
+ }
17
+ }
18
+ }, [hide, isOpen, open]);
19
+
20
+ const handleClick = (event) => {
21
+ anchorEl.current = event.currentTarget;
22
+ open(true);
23
+ };
24
+
25
+ const handleRequestClose = () => {
26
+ open(false);
27
+ };
28
+
29
+ const closeIconClass = providedProps.classes.closeIcon;
30
+ delete providedProps.classes.closeIcon; // remove non-standard class from being passed to the popover component
31
+
32
+ const transformOriginSpecs = {
33
+ vertical: 'top',
34
+ horizontal: 'center',
35
+ };
36
+
37
+ const anchorOriginSpecs = {
38
+ vertical: 'bottom',
39
+ horizontal: 'center',
40
+ };
41
+
42
+ const handleOnExit = () => {
43
+ if (refExit) {
44
+ refExit();
45
+ }
46
+ };
47
+
48
+ const triggerProps = {
49
+ onClick: (event) => {
50
+ if (trigger.props.onClick) trigger.props.onClick();
51
+ handleClick(event);
52
+ },
53
+ };
54
+
55
+ return (
56
+ <>
57
+ <span key="content" {...triggerProps}>
58
+ {trigger}
59
+ </span>
60
+ <MuiPopover
61
+ elevation={2}
62
+ open={isOpen}
63
+ TransitionProps={{ onExited: handleOnExit }}
64
+ onClose={handleRequestClose}
65
+ anchorEl={anchorEl.current}
66
+ anchorOrigin={anchorOriginSpecs}
67
+ transformOrigin={transformOriginSpecs}
68
+ {...providedProps}>
69
+ <IconButton
70
+ aria-label="Close"
71
+ onClick={handleRequestClose}
72
+ className={closeIconClass}
73
+ style={{ position: 'absolute', right: '4px', top: '4px', zIndex: '1000' }}>
74
+ <CloseIcon />
75
+ </IconButton>
76
+ {content}
77
+ </MuiPopover>
78
+ </>
79
+ );
80
+ };
81
+
82
+ Popover.propTypes = {
83
+ refExit: PropTypes.func,
84
+ trigger: PropTypes.node.isRequired,
85
+ content: PropTypes.node.isRequired,
86
+ hide: PropTypes.bool,
87
+ };
88
+
89
+ export default Popover;
@@ -0,0 +1,338 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Typography } from '@mui/material';
4
+ import { TableBody as MuiTableBody } from '@mui/material';
5
+ import TableBodyCell from './TableBodyCell';
6
+ import TableBodyRow from './TableBodyRow';
7
+ import TableSelectCell from './TableSelectCell';
8
+ import { withStyles } from 'tss-react/mui';
9
+ import cloneDeep from 'lodash.clonedeep';
10
+ import { getPageValue } from '../utils';
11
+ import clsx from 'clsx';
12
+
13
+ const defaultBodyStyles = (theme) => ({
14
+ root: {},
15
+ emptyTitle: {
16
+ textAlign: 'center',
17
+ },
18
+ lastStackedCell: {
19
+ [theme.breakpoints.down('md')]: {
20
+ '& td:last-child': {
21
+ borderBottom: 'none',
22
+ },
23
+ },
24
+ },
25
+ lastSimpleCell: {
26
+ [theme.breakpoints.down('sm')]: {
27
+ '& td:last-child': {
28
+ borderBottom: 'none',
29
+ },
30
+ },
31
+ },
32
+ });
33
+
34
+ class TableBody extends React.Component {
35
+ static propTypes = {
36
+ /** Data used to describe table */
37
+ data: PropTypes.array.isRequired,
38
+ /** Total number of data rows */
39
+ count: PropTypes.number.isRequired,
40
+ /** Columns used to describe table */
41
+ columns: PropTypes.array.isRequired,
42
+ /** Options used to describe table */
43
+ options: PropTypes.object.isRequired,
44
+ /** Data used to filter table against */
45
+ filterList: PropTypes.array,
46
+ /** Callback to execute when row is clicked */
47
+ onRowClick: PropTypes.func,
48
+ /** Table rows expanded */
49
+ expandedRows: PropTypes.object,
50
+ /** Table rows selected */
51
+ selectedRows: PropTypes.object,
52
+ /** Callback to trigger table row select */
53
+ selectRowUpdate: PropTypes.func,
54
+ /** The most recent row to have been selected/unselected */
55
+ previousSelectedRow: PropTypes.object,
56
+ /** Data used to search table against */
57
+ searchText: PropTypes.string,
58
+ /** Toggle row expandable */
59
+ toggleExpandRow: PropTypes.func,
60
+ /** Extend the style applied to components */
61
+ classes: PropTypes.object,
62
+ };
63
+
64
+ static defaultProps = {
65
+ toggleExpandRow: () => {},
66
+ };
67
+
68
+ buildRows() {
69
+ const { data, page, rowsPerPage, count } = this.props;
70
+
71
+ if (this.props.options.serverSide) return data.length ? data : null;
72
+
73
+ let rows = [];
74
+ const highestPageInRange = getPageValue(count, rowsPerPage, page);
75
+ const fromIndex = highestPageInRange === 0 ? 0 : highestPageInRange * rowsPerPage;
76
+ const toIndex = Math.min(count, (highestPageInRange + 1) * rowsPerPage);
77
+
78
+ if (page > highestPageInRange) {
79
+ console.warn('Current page is out of range, using the highest page that is in range instead.');
80
+ }
81
+
82
+ for (let rowIndex = fromIndex; rowIndex < count && rowIndex < toIndex; rowIndex++) {
83
+ if (data[rowIndex] !== undefined) rows.push(data[rowIndex]);
84
+ }
85
+
86
+ return rows.length ? rows : null;
87
+ }
88
+
89
+ getRowIndex(index) {
90
+ const { page, rowsPerPage, options } = this.props;
91
+
92
+ if (options.serverSide) {
93
+ return index;
94
+ }
95
+
96
+ const startIndex = page === 0 ? 0 : page * rowsPerPage;
97
+ return startIndex + index;
98
+ }
99
+
100
+ isRowSelected(dataIndex) {
101
+ const { selectedRows } = this.props;
102
+ return selectedRows.lookup && selectedRows.lookup[dataIndex] ? true : false;
103
+ }
104
+
105
+ isRowExpanded(dataIndex) {
106
+ const { expandedRows } = this.props;
107
+ return expandedRows.lookup && expandedRows.lookup[dataIndex] ? true : false;
108
+ }
109
+
110
+ isRowSelectable(dataIndex, selectedRows) {
111
+ const { options } = this.props;
112
+ selectedRows = selectedRows || this.props.selectedRows;
113
+
114
+ if (options.isRowSelectable) {
115
+ return options.isRowSelectable(dataIndex, selectedRows);
116
+ } else {
117
+ return true;
118
+ }
119
+ }
120
+
121
+ isRowExpandable(dataIndex) {
122
+ const { options, expandedRows } = this.props;
123
+ if (options.isRowExpandable) {
124
+ return options.isRowExpandable(dataIndex, expandedRows);
125
+ } else {
126
+ return true;
127
+ }
128
+ }
129
+
130
+ handleRowSelect = (data, event) => {
131
+ let shiftKey = event && event.nativeEvent ? event.nativeEvent.shiftKey : false;
132
+ let shiftAdjacentRows = [];
133
+ let previousSelectedRow = this.props.previousSelectedRow;
134
+
135
+ // If the user is pressing shift and has previously clicked another row.
136
+ if (shiftKey && previousSelectedRow && previousSelectedRow.index < this.props.data.length) {
137
+ let curIndex = previousSelectedRow.index;
138
+
139
+ // Create a copy of the selectedRows object. This will be used and modified
140
+ // below when we see if we can add adjacent rows.
141
+ let selectedRows = cloneDeep(this.props.selectedRows);
142
+
143
+ // Add the clicked on row to our copy of selectedRows (if it isn't already present).
144
+ let clickedDataIndex = this.props.data[data.index].dataIndex;
145
+ if (selectedRows.data.filter((d) => d.dataIndex === clickedDataIndex).length === 0) {
146
+ selectedRows.data.push({
147
+ index: data.index,
148
+ dataIndex: clickedDataIndex,
149
+ });
150
+ selectedRows.lookup[clickedDataIndex] = true;
151
+ }
152
+
153
+ while (curIndex !== data.index) {
154
+ let dataIndex = this.props.data[curIndex].dataIndex;
155
+
156
+ if (this.isRowSelectable(dataIndex, selectedRows)) {
157
+ let lookup = {
158
+ index: curIndex,
159
+ dataIndex: dataIndex,
160
+ };
161
+
162
+ // Add adjacent row to temp selectedRow object if it isn't present.
163
+ if (selectedRows.data.filter((d) => d.dataIndex === dataIndex).length === 0) {
164
+ selectedRows.data.push(lookup);
165
+ selectedRows.lookup[dataIndex] = true;
166
+ }
167
+
168
+ shiftAdjacentRows.push(lookup);
169
+ }
170
+ curIndex = data.index > curIndex ? curIndex + 1 : curIndex - 1;
171
+ }
172
+ }
173
+ this.props.selectRowUpdate('cell', data, shiftAdjacentRows);
174
+ };
175
+
176
+ handleRowClick = (row, data, event) => {
177
+ // Don't trigger onRowClick if the event was actually the expandable icon.
178
+ if (
179
+ event.target.id === 'expandable-button' ||
180
+ (event.target.nodeName === 'path' && event.target.parentNode.id === 'expandable-button')
181
+ ) {
182
+ return;
183
+ }
184
+
185
+ // Don't trigger onRowClick if the event was actually a row selection via checkbox
186
+ if (event.target.id && event.target.id.startsWith('MUIDataTableSelectCell')) return;
187
+
188
+ // Check if we should toggle row select when row is clicked anywhere
189
+ if (
190
+ this.props.options.selectableRowsOnClick &&
191
+ this.props.options.selectableRows !== 'none' &&
192
+ this.isRowSelectable(data.dataIndex, this.props.selectedRows)
193
+ ) {
194
+ const selectRow = { index: data.rowIndex, dataIndex: data.dataIndex };
195
+ this.handleRowSelect(selectRow, event);
196
+ }
197
+ // Check if we should trigger row expand when row is clicked anywhere
198
+ if (
199
+ this.props.options.expandableRowsOnClick &&
200
+ this.props.options.expandableRows &&
201
+ this.isRowExpandable(data.dataIndex, this.props.expandedRows)
202
+ ) {
203
+ const expandRow = { index: data.rowIndex, dataIndex: data.dataIndex };
204
+ this.props.toggleExpandRow(expandRow);
205
+ }
206
+
207
+ // Don't trigger onRowClick if the event was actually a row selection via click
208
+ if (this.props.options.selectableRowsOnClick) return;
209
+
210
+ this.props.options.onRowClick && this.props.options.onRowClick(row, data, event);
211
+ };
212
+
213
+ processRow = (row, columnOrder) => {
214
+ let ret = [];
215
+ for (let ii = 0; ii < row.length; ii++) {
216
+ ret.push({
217
+ value: row[columnOrder[ii]],
218
+ index: columnOrder[ii],
219
+ });
220
+ }
221
+ return ret;
222
+ };
223
+
224
+ render() {
225
+ const {
226
+ classes,
227
+ columns,
228
+ toggleExpandRow,
229
+ options,
230
+ columnOrder = this.props.columns.map((item, idx) => idx),
231
+ components = {},
232
+ tableId,
233
+ } = this.props;
234
+ const tableRows = this.buildRows();
235
+ const visibleColCnt = columns.filter((c) => c.display === 'true').length;
236
+
237
+ return (
238
+ <MuiTableBody>
239
+ {tableRows && tableRows.length > 0 ? (
240
+ tableRows.map((data, rowIndex) => {
241
+ const { data: row, dataIndex } = data;
242
+
243
+ if (options.customRowRender) {
244
+ return options.customRowRender(row, dataIndex, rowIndex);
245
+ }
246
+
247
+ let isRowSelected = options.selectableRows !== 'none' ? this.isRowSelected(dataIndex) : false;
248
+ let isRowSelectable = this.isRowSelectable(dataIndex);
249
+ let bodyClasses = options.setRowProps ? options.setRowProps(row, dataIndex, rowIndex) || {} : {};
250
+
251
+ const processedRow = this.processRow(row, columnOrder);
252
+
253
+ return (
254
+ <React.Fragment key={rowIndex}>
255
+ <TableBodyRow
256
+ {...bodyClasses}
257
+ options={options}
258
+ rowSelected={isRowSelected}
259
+ isRowSelectable={isRowSelectable}
260
+ onClick={this.handleRowClick.bind(null, row, { rowIndex, dataIndex })}
261
+ className={clsx({
262
+ [classes.lastStackedCell]:
263
+ options.responsive === 'vertical' ||
264
+ options.responsive === 'stacked' ||
265
+ options.responsive === 'stackedFullWidth',
266
+ [classes.lastSimpleCell]: options.responsive === 'simple',
267
+ [bodyClasses.className]: bodyClasses.className,
268
+ })}
269
+ data-testid={'MUIDataTableBodyRow-' + dataIndex}
270
+ id={`MUIDataTableBodyRow-${tableId}-${dataIndex}`}>
271
+ <TableSelectCell
272
+ onChange={this.handleRowSelect.bind(null, {
273
+ index: this.getRowIndex(rowIndex),
274
+ dataIndex: dataIndex,
275
+ })}
276
+ onExpand={toggleExpandRow.bind(null, {
277
+ index: this.getRowIndex(rowIndex),
278
+ dataIndex: dataIndex,
279
+ })}
280
+ fixedHeader={options.fixedHeader}
281
+ fixedSelectColumn={options.fixedSelectColumn}
282
+ checked={isRowSelected}
283
+ expandableOn={options.expandableRows}
284
+ // When rows are expandable, but this particular row isn't expandable, set this to true.
285
+ // This will add a new class to the toggle button, MUIDataTableSelectCell-expandDisabled.
286
+ hideExpandButton={!this.isRowExpandable(dataIndex) && options.expandableRows}
287
+ selectableOn={options.selectableRows}
288
+ selectableRowsHideCheckboxes={options.selectableRowsHideCheckboxes}
289
+ isRowExpanded={this.isRowExpanded(dataIndex)}
290
+ isRowSelectable={isRowSelectable}
291
+ dataIndex={dataIndex}
292
+ id={`MUIDataTableSelectCell-${tableId}-${dataIndex}`}
293
+ components={components}
294
+ />
295
+ {processedRow.map(
296
+ (column) =>
297
+ columns[column.index].display === 'true' && (
298
+ <TableBodyCell
299
+ {...(columns[column.index].setCellProps
300
+ ? columns[column.index].setCellProps(column.value, dataIndex, column.index) || {}
301
+ : {})}
302
+ data-testid={`MuiDataTableBodyCell-${column.index}-${rowIndex}`}
303
+ dataIndex={dataIndex}
304
+ rowIndex={rowIndex}
305
+ colIndex={column.index}
306
+ columnHeader={columns[column.index].label}
307
+ print={columns[column.index].print}
308
+ options={options}
309
+ tableId={tableId}
310
+ key={column.index}>
311
+ {column.value}
312
+ </TableBodyCell>
313
+ ),
314
+ )}
315
+ </TableBodyRow>
316
+ {this.isRowExpanded(dataIndex) && options.renderExpandableRow(row, { rowIndex, dataIndex })}
317
+ </React.Fragment>
318
+ );
319
+ })
320
+ ) : (
321
+ <TableBodyRow options={options}>
322
+ <TableBodyCell
323
+ colSpan={options.selectableRows !== 'none' || options.expandableRows ? visibleColCnt + 1 : visibleColCnt}
324
+ options={options}
325
+ colIndex={0}
326
+ rowIndex={0}>
327
+ <Typography variant="body1" className={classes.emptyTitle} component={'div'}>
328
+ {options.textLabels.body.noMatch}
329
+ </Typography>
330
+ </TableBodyCell>
331
+ </TableBodyRow>
332
+ )}
333
+ </MuiTableBody>
334
+ );
335
+ }
336
+ }
337
+
338
+ export default withStyles(TableBody, defaultBodyStyles, { name: 'MUIDataTableBody' });