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,288 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { withStyles } from 'tss-react/mui';
4
+
5
+ const defaultResizeStyles = {
6
+ root: {
7
+ position: 'absolute',
8
+ },
9
+ resizer: {
10
+ position: 'absolute',
11
+ width: '1px',
12
+ height: '100%',
13
+ left: '100px',
14
+ cursor: 'ew-resize',
15
+ border: '0.1px solid rgba(224, 224, 224, 1)',
16
+ },
17
+ };
18
+
19
+ function getParentOffsetLeft(tableEl) {
20
+ let ii = 0,
21
+ parentOffsetLeft = 0,
22
+ offsetParent = tableEl.offsetParent;
23
+ while (offsetParent) {
24
+ parentOffsetLeft = parentOffsetLeft + (offsetParent.offsetLeft || 0) - (offsetParent.scrollLeft || 0);
25
+ offsetParent = offsetParent.offsetParent;
26
+ ii++;
27
+ if (ii > 1000) break;
28
+ }
29
+ return parentOffsetLeft;
30
+ }
31
+
32
+ class TableResize extends React.Component {
33
+ static propTypes = {
34
+ /** Extend the style applied to components */
35
+ classes: PropTypes.object,
36
+ };
37
+
38
+ state = {
39
+ resizeCoords: {},
40
+ priorPosition: {},
41
+ tableWidth: '100%',
42
+ tableHeight: '100%',
43
+ };
44
+
45
+ handleResize = () => {
46
+ if (window.innerWidth !== this.windowWidth) {
47
+ this.windowWidth = window.innerWidth;
48
+ this.setDividers();
49
+ }
50
+ };
51
+
52
+ componentDidMount() {
53
+ this.minWidths = [];
54
+ this.windowWidth = null;
55
+ this.props.setResizeable(this.setCellRefs);
56
+ this.props.updateDividers(() => this.setState({ updateCoords: true }, () => this.updateWidths));
57
+ window.addEventListener('resize', this.handleResize, false);
58
+ }
59
+
60
+ componentWillUnmount() {
61
+ window.removeEventListener('resize', this.handleResize, false);
62
+ }
63
+
64
+ setCellRefs = (cellsRef, tableRef) => {
65
+ this.cellsRef = cellsRef;
66
+ this.tableRef = tableRef;
67
+ this.setDividers();
68
+ };
69
+
70
+ setDividers = () => {
71
+ const tableEl = this.tableRef;
72
+ const { width: tableWidth, height: tableHeight } = tableEl.getBoundingClientRect();
73
+ const { resizeCoords } = this.state;
74
+
75
+ for (let prop in resizeCoords) {
76
+ delete resizeCoords[prop];
77
+ }
78
+
79
+ let parentOffsetLeft = getParentOffsetLeft(tableEl);
80
+ let finalCells = Object.entries(this.cellsRef);
81
+ let cellMinusOne = finalCells.filter((_item, ix) => ix + 1 < finalCells.length);
82
+
83
+ cellMinusOne.forEach(([key, item], idx) => {
84
+ if (!item) return;
85
+ let elRect = item.getBoundingClientRect();
86
+ let left = elRect.left;
87
+ left = (left || 0) - parentOffsetLeft;
88
+ const elStyle = window.getComputedStyle(item, null);
89
+ resizeCoords[key] = { left: left + item.offsetWidth };
90
+ });
91
+ this.setState({ tableWidth, tableHeight, resizeCoords }, this.updateWidths);
92
+ };
93
+
94
+ updateWidths = () => {
95
+ let lastPosition = 0;
96
+ const { resizeCoords, tableWidth } = this.state;
97
+
98
+ Object.entries(resizeCoords).forEach(([key, item]) => {
99
+ let newWidth = Number(((item.left - lastPosition) / tableWidth) * 100);
100
+
101
+ /*
102
+ Using .toFixed(2) causes the columns to jitter when resized. On all browsers I (patrojk) have tested,
103
+ a width with a floating point decimal works fine. It's unclear to me why the numbers were being rouned.
104
+ However, I'm putting in an undocumented escape hatch to use toFixed in case the change introduces a bug.
105
+ The below code will be removed in a later release if no problems with non-rounded widths are reported.
106
+ */
107
+ if (typeof this.props.resizableColumns === 'object' && this.props.resizableColumns.roundWidthPercentages) {
108
+ newWidth = newWidth.toFixed(2);
109
+ }
110
+
111
+ lastPosition = item.left;
112
+
113
+ const thCell = this.cellsRef[key];
114
+ if (thCell) thCell.style.width = newWidth + '%';
115
+ });
116
+ };
117
+
118
+ onResizeStart = (id, e) => {
119
+ const tableEl = this.tableRef;
120
+ const originalWidth = tableEl.style.width;
121
+ let lastColumn = 0;
122
+ tableEl.style.width = '1px';
123
+
124
+ let finalCells = Object.entries(this.cellsRef);
125
+ finalCells.forEach(([key, item], idx) => {
126
+ let elRect = item ? item.getBoundingClientRect() : { width: 0, left: 0 };
127
+ this.minWidths[key] = elRect.width;
128
+ lastColumn = Math.max(key, lastColumn);
129
+ });
130
+ tableEl.style.width = originalWidth;
131
+
132
+ this.setState({ isResize: true, id, lastColumn });
133
+ };
134
+
135
+ onResizeMove = (id, e) => {
136
+ const { isResize, resizeCoords, lastColumn } = this.state;
137
+
138
+ const prevCol = id => {
139
+ let nextId = id - 1;
140
+ while (typeof resizeCoords[nextId] === 'undefined' && nextId >= 0) {
141
+ nextId--;
142
+ }
143
+ return nextId;
144
+ };
145
+ const nextCol = id => {
146
+ let nextId = id + 1;
147
+ let tries = 0;
148
+ while (typeof resizeCoords[nextId] === 'undefined' && tries < 20) {
149
+ nextId++;
150
+ tries++;
151
+ }
152
+ return nextId;
153
+ };
154
+
155
+ const fixedMinWidth1 = this.minWidths[id];
156
+ const fixedMinWidth2 = this.minWidths[nextCol(parseInt(id, 10))] || this.minWidths[id];
157
+ const idNumber = parseInt(id, 10);
158
+ const finalCells = Object.entries(this.cellsRef);
159
+ const tableEl = this.tableRef;
160
+ const { width: tableWidth, height: tableHeight } = tableEl.getBoundingClientRect();
161
+ const { selectableRows } = this.props.options;
162
+
163
+ let parentOffsetLeft = getParentOffsetLeft(tableEl);
164
+
165
+ const nextCoord = id => {
166
+ let nextId = id + 1;
167
+ let tries = 0;
168
+ while (typeof resizeCoords[nextId] === 'undefined' && tries < 20) {
169
+ nextId++;
170
+ tries++;
171
+ }
172
+ return resizeCoords[nextId];
173
+ };
174
+ const prevCoord = id => {
175
+ let nextId = id - 1;
176
+ while (typeof resizeCoords[nextId] === 'undefined' && nextId >= 0) {
177
+ nextId--;
178
+ }
179
+ return resizeCoords[nextId];
180
+ };
181
+
182
+ if (isResize) {
183
+ let leftPos = e.clientX - parentOffsetLeft;
184
+
185
+ const handleMoveRightmostBoundary = (leftPos, tableWidth, fixedMinWidth) => {
186
+ if (leftPos > tableWidth - fixedMinWidth) {
187
+ return tableWidth - fixedMinWidth;
188
+ }
189
+ return leftPos;
190
+ };
191
+
192
+ const handleMoveLeftmostBoundary = (leftPos, fixedMinWidth) => {
193
+ if (leftPos < fixedMinWidth) {
194
+ return fixedMinWidth;
195
+ }
196
+ return leftPos;
197
+ };
198
+
199
+ const handleMoveRight = (leftPos, resizeCoords, id, fixedMinWidth) => {
200
+ if (typeof nextCoord(id) === 'undefined') return leftPos;
201
+ if (leftPos > nextCoord(id).left - fixedMinWidth) {
202
+ return nextCoord(id).left - fixedMinWidth;
203
+ }
204
+ return leftPos;
205
+ };
206
+
207
+ const handleMoveLeft = (leftPos, resizeCoords, id, fixedMinWidth) => {
208
+ if (typeof prevCoord(id) === 'undefined') return leftPos;
209
+ if (leftPos < prevCoord(id).left + fixedMinWidth) {
210
+ return prevCoord(id).left + fixedMinWidth;
211
+ }
212
+ return leftPos;
213
+ };
214
+
215
+ const isFirstColumn = (selectableRows, id) => {
216
+ let firstColumn = 1;
217
+ while (!resizeCoords[firstColumn] && firstColumn < 20) {
218
+ firstColumn++;
219
+ }
220
+
221
+ return (selectableRows !== 'none' && id === 0) || (selectableRows === 'none' && id === firstColumn);
222
+ };
223
+
224
+ const isLastColumn = (id, finalCells) => {
225
+ return id === prevCol(lastColumn);
226
+ };
227
+
228
+ if (isFirstColumn(selectableRows, idNumber) && isLastColumn(idNumber, finalCells)) {
229
+ leftPos = handleMoveLeftmostBoundary(leftPos, fixedMinWidth1);
230
+ leftPos = handleMoveRightmostBoundary(leftPos, tableWidth, fixedMinWidth2);
231
+ } else if (!isFirstColumn(selectableRows, idNumber) && isLastColumn(idNumber, finalCells)) {
232
+ leftPos = handleMoveRightmostBoundary(leftPos, tableWidth, fixedMinWidth2);
233
+ leftPos = handleMoveLeft(leftPos, resizeCoords, idNumber, fixedMinWidth1);
234
+ } else if (isFirstColumn(selectableRows, idNumber) && !isLastColumn(idNumber, finalCells)) {
235
+ leftPos = handleMoveLeftmostBoundary(leftPos, fixedMinWidth1);
236
+ leftPos = handleMoveRight(leftPos, resizeCoords, idNumber, fixedMinWidth2);
237
+ } else if (!isFirstColumn(selectableRows, idNumber) && !isLastColumn(idNumber, finalCells)) {
238
+ leftPos = handleMoveLeft(leftPos, resizeCoords, idNumber, fixedMinWidth1);
239
+ leftPos = handleMoveRight(leftPos, resizeCoords, idNumber, fixedMinWidth2);
240
+ }
241
+
242
+ const curCoord = { ...resizeCoords[id], left: leftPos };
243
+ const newResizeCoords = { ...resizeCoords, [id]: curCoord };
244
+ this.setState({ resizeCoords: newResizeCoords, tableHeight }, this.updateWidths);
245
+ }
246
+ };
247
+
248
+ onResizeEnd = (id, e) => {
249
+ this.setState({ isResize: false, id: null });
250
+ };
251
+
252
+ render() {
253
+ const { classes, tableId } = this.props;
254
+ const { id, isResize, resizeCoords, tableWidth, tableHeight } = this.state;
255
+
256
+ return (
257
+ <div className={classes.root} style={{ width: tableWidth }}>
258
+ {Object.entries(resizeCoords).map(([key, val]) => {
259
+ return (
260
+ <div
261
+ data-divider-index={key}
262
+ data-tableid={tableId}
263
+ aria-hidden="true"
264
+ key={key}
265
+ onMouseMove={this.onResizeMove.bind(null, key)}
266
+ onMouseUp={this.onResizeEnd.bind(null, key)}
267
+ style={{
268
+ width: isResize && id == key ? tableWidth : 'auto',
269
+ position: 'absolute',
270
+ height: tableHeight - 2,
271
+ cursor: 'ew-resize',
272
+ zIndex: 1000,
273
+ }}>
274
+ <div
275
+ aria-hidden="true"
276
+ onMouseDown={this.onResizeStart.bind(null, key)}
277
+ className={classes.resizer}
278
+ style={{ left: val.left }}
279
+ />
280
+ </div>
281
+ );
282
+ })}
283
+ </div>
284
+ );
285
+ }
286
+ }
287
+
288
+ export default withStyles(TableResize, defaultResizeStyles, { name: 'MUIDataTableResize' });
@@ -0,0 +1,89 @@
1
+ import React from "react";
2
+ import { Grow } from "@mui/material";
3
+ import { TextField } from "@mui/material";
4
+ import { Search as SearchIcon } from "@mui/icons-material";
5
+ import { IconButton } from "@mui/material";
6
+ import { Clear as ClearIcon } from "@mui/icons-material";
7
+ import { makeStyles } from "tss-react/mui";
8
+
9
+ const useStyles = makeStyles({ name: "MUIDataTableSearch" })((theme) => ({
10
+ main: {
11
+ display: "flex",
12
+ flex: "1 0 auto",
13
+ alignItems: "center",
14
+ "@media print": {
15
+ display: "none",
16
+ },
17
+ },
18
+ searchIcon: {
19
+ color: theme.palette.text.secondary,
20
+ marginRight: "8px",
21
+ },
22
+ searchText: {
23
+ flex: "0.8 0",
24
+ "& .MuiInputBase-root": {
25
+ borderRadius: "8px",
26
+ backgroundColor: "#f8fafc",
27
+ border: "1px solid #e5e7eb",
28
+ transition: "border-color 0.2s ease, box-shadow 0.2s ease",
29
+ "&:hover": {
30
+ borderColor: "#d1d5db",
31
+ },
32
+ "&.Mui-focused": {
33
+ borderColor: "#3b82f6",
34
+ boxShadow: "0 0 0 3px rgba(59, 130, 246, 0.1)",
35
+ },
36
+ },
37
+ },
38
+ clearIcon: {
39
+ "&:hover": {
40
+ color: theme.palette.error.main,
41
+ },
42
+ },
43
+ }));
44
+
45
+ const TableSearch = ({ options, searchText, onSearch, onHide }) => {
46
+ const { classes } = useStyles();
47
+
48
+ const handleTextChange = (event) => {
49
+ onSearch(event.target.value);
50
+ };
51
+
52
+ const onKeyDown = (event) => {
53
+ if (event.key === "Escape") {
54
+ onHide();
55
+ }
56
+ };
57
+
58
+ const clearIconVisibility = options.searchAlwaysOpen ? "hidden" : "visible";
59
+
60
+ return (
61
+ <Grow appear in={true} timeout={300}>
62
+ <div className={classes.main}>
63
+ <SearchIcon className={classes.searchIcon} />
64
+ <TextField
65
+ className={classes.searchText}
66
+ autoFocus={true}
67
+ variant={"standard"}
68
+ InputProps={{
69
+ "data-test-id": options.textLabels.toolbar.search,
70
+ }}
71
+ inputProps={{
72
+ "aria-label": options.textLabels.toolbar.search,
73
+ }}
74
+ value={searchText ?? ""}
75
+ onKeyDown={onKeyDown}
76
+ onChange={handleTextChange}
77
+ fullWidth={true}
78
+ placeholder={options.searchPlaceholder}
79
+ {...(options.searchProps ? options.searchProps : {})}
80
+ />
81
+ <IconButton className={classes.clearIcon} style={{ visibility: clearIconVisibility }} onClick={onHide}>
82
+ <ClearIcon />
83
+ </IconButton>
84
+ </div>
85
+ </Grow>
86
+ );
87
+ };
88
+
89
+ export default TableSearch;
@@ -0,0 +1,163 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import clsx from 'clsx';
4
+ import { Checkbox } from '@mui/material';
5
+ import { TableCell } from '@mui/material';
6
+ import { makeStyles } from 'tss-react/mui';
7
+ import ExpandButton from './ExpandButton';
8
+
9
+ const useStyles = makeStyles({ name: 'MUIDataTableSelectCell' })(theme => ({
10
+ root: {
11
+ '@media print': {
12
+ display: 'none',
13
+ },
14
+ },
15
+ fixedHeader: {
16
+ position: 'sticky',
17
+ top: '0px',
18
+ zIndex: 100,
19
+ },
20
+ fixedLeft: {
21
+ position: 'sticky',
22
+ left: '0px',
23
+ zIndex: 100,
24
+ },
25
+ icon: {
26
+ cursor: 'pointer',
27
+ transition: 'transform 0.25s',
28
+ },
29
+ expanded: {
30
+ transform: 'rotate(90deg)',
31
+ },
32
+ hide: {
33
+ visibility: 'hidden',
34
+ },
35
+ headerCell: {
36
+ zIndex: 110,
37
+ backgroundColor: theme.palette.background.paper,
38
+ },
39
+ expandDisabled: {},
40
+ checkboxRoot: {},
41
+ checked: {},
42
+ disabled: {},
43
+ }));
44
+
45
+ const TableSelectCell = ({
46
+ fixedHeader,
47
+ fixedSelectColumn,
48
+ isHeaderCell = false,
49
+ expandableOn = false,
50
+ selectableOn = 'none',
51
+ isRowExpanded = false,
52
+ onExpand,
53
+ isRowSelectable,
54
+ selectableRowsHeader,
55
+ hideExpandButton,
56
+ expandableRowsHeader,
57
+ expandedRows,
58
+ areAllRowsExpanded = () => false,
59
+ selectableRowsHideCheckboxes,
60
+ setHeadCellRef,
61
+ dataIndex,
62
+ components = {},
63
+ ...otherProps
64
+ }) => {
65
+ const { classes } = useStyles();
66
+ const CheckboxComponent = components.Checkbox || Checkbox;
67
+ const ExpandButtonComponent = components.ExpandButton || ExpandButton;
68
+
69
+ if (expandableOn === false && (selectableOn === 'none' || selectableRowsHideCheckboxes === true)) {
70
+ return null;
71
+ }
72
+
73
+ const cellClass = clsx({
74
+ [classes.root]: true,
75
+ [classes.fixedHeader]: fixedHeader && isHeaderCell,
76
+ [classes.fixedLeft]: fixedSelectColumn,
77
+ [classes.headerCell]: isHeaderCell,
78
+ });
79
+
80
+ const buttonClass = clsx({
81
+ [classes.expandDisabled]: hideExpandButton,
82
+ });
83
+
84
+ const iconClass = clsx({
85
+ [classes.icon]: true,
86
+ [classes.hide]: isHeaderCell && !expandableRowsHeader,
87
+ [classes.expanded]: isRowExpanded || (isHeaderCell && areAllRowsExpanded()),
88
+ });
89
+ const iconIndeterminateClass = clsx({
90
+ [classes.icon]: true,
91
+ [classes.hide]: isHeaderCell && !expandableRowsHeader,
92
+ });
93
+
94
+ let refProp = {};
95
+ if (setHeadCellRef) {
96
+ refProp.ref = el => {
97
+ setHeadCellRef(0, 0, el);
98
+ };
99
+ }
100
+
101
+ const renderCheckBox = () => {
102
+ if (isHeaderCell && (selectableOn !== 'multiple' || selectableRowsHeader === false)) {
103
+ // only display the header checkbox for multiple selection.
104
+ return null;
105
+ }
106
+ return (
107
+ <CheckboxComponent
108
+ classes={{
109
+ root: classes.checkboxRoot,
110
+ checked: classes.checked,
111
+ disabled: classes.disabled,
112
+ }}
113
+ data-description={isHeaderCell ? 'row-select-header' : 'row-select'}
114
+ data-index={dataIndex || null}
115
+ color="primary"
116
+ disabled={!isRowSelectable}
117
+ {...otherProps}
118
+ />
119
+ );
120
+ };
121
+
122
+ return (
123
+ <TableCell className={cellClass} padding="checkbox" {...refProp}>
124
+ <div style={{ display: 'flex', alignItems: 'center' }}>
125
+ {expandableOn && (
126
+ <ExpandButtonComponent
127
+ isHeaderCell={isHeaderCell}
128
+ areAllRowsExpanded={areAllRowsExpanded}
129
+ expandedRows={expandedRows}
130
+ onExpand={onExpand}
131
+ expandableRowsHeader={expandableRowsHeader}
132
+ buttonClass={buttonClass}
133
+ iconIndeterminateClass={iconIndeterminateClass}
134
+ iconClass={iconClass}
135
+ dataIndex={dataIndex}
136
+ />
137
+ )}
138
+ {selectableOn !== 'none' && selectableRowsHideCheckboxes !== true && renderCheckBox()}
139
+ </div>
140
+ </TableCell>
141
+ );
142
+ };
143
+
144
+ TableSelectCell.propTypes = {
145
+ /** Select cell checked on/off */
146
+ checked: PropTypes.bool.isRequired,
147
+ /** Select cell part of fixed header */
148
+ fixedHeader: PropTypes.bool,
149
+ /** Callback to trigger cell update */
150
+ onChange: PropTypes.func,
151
+ /** Extend the style applied to components */
152
+ classes: PropTypes.object,
153
+ /** Is expandable option enabled */
154
+ expandableOn: PropTypes.bool,
155
+ /** Adds extra class, `expandDisabled` when the row is not expandable. */
156
+ hideExpandButton: PropTypes.bool,
157
+ /** Is selectable option enabled */
158
+ selectableOn: PropTypes.string,
159
+ /** Select cell disabled on/off */
160
+ isRowSelectable: PropTypes.bool,
161
+ };
162
+
163
+ export default TableSelectCell;