mui-datatables-updated 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.
- package/LICENSE +21 -0
- package/package.json +55 -0
- package/rollup.config.mjs +23 -0
- package/src/components/MUITable.tsx +320 -0
- package/src/components/TableHead.tsx +82 -0
- package/src/components/Toolbar.tsx +289 -0
- package/src/components/test-data.ts +89 -0
- package/src/components/utils.ts +17 -0
- package/src/index.ts +1 -0
- package/tsconfig.json +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Diego Jacobo Martínez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mui-datatables-updated",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MUI Datatable library inspired by the gregnb/mui-datatables project, featuring an up-to-date implementation with Typescript Support.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.esm.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
+
"build": "rollup -c"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/Djmr5/mui-datatables-updated.git"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"MUI",
|
|
19
|
+
"mui-datatables",
|
|
20
|
+
"datatables",
|
|
21
|
+
"react-table",
|
|
22
|
+
"typescript",
|
|
23
|
+
"react",
|
|
24
|
+
"material-ui",
|
|
25
|
+
"data-table"
|
|
26
|
+
],
|
|
27
|
+
"author": "Diego Jacobo Martínez Djmr5",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/Djmr5/mui-datatables-updated/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/Djmr5/mui-datatables-updated#readme",
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@emotion/react": "^11.0.0",
|
|
35
|
+
"@emotion/styled": "^11.0.0",
|
|
36
|
+
"@mui/icons-material": "^6.0.0",
|
|
37
|
+
"@mui/material": "^6.0.0",
|
|
38
|
+
"@mui/utils": "^6.0.0",
|
|
39
|
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
40
|
+
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@emotion/react": "^11.14.0",
|
|
44
|
+
"@emotion/styled": "^11.14.0",
|
|
45
|
+
"@mui/icons-material": "^6.4.0",
|
|
46
|
+
"@mui/material": "^6.4.0",
|
|
47
|
+
"@mui/utils": "^6.4.0",
|
|
48
|
+
"@types/react": "^19.0.7",
|
|
49
|
+
"@types/react-dom": "^19.0.3",
|
|
50
|
+
"rollup": "^4.30.1",
|
|
51
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
52
|
+
"rollup-plugin-typescript2": "^0.36.0",
|
|
53
|
+
"typescript": "^5.7.3"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import typescript from 'rollup-plugin-typescript2';
|
|
2
|
+
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
input: 'src/index.ts',
|
|
6
|
+
output: [
|
|
7
|
+
{
|
|
8
|
+
file: 'dist/index.js',
|
|
9
|
+
format: 'cjs',
|
|
10
|
+
sourcemap: true,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
file: 'dist/index.esm.js',
|
|
14
|
+
format: 'esm',
|
|
15
|
+
sourcemap: true,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
plugins: [
|
|
19
|
+
peerDepsExternal(),
|
|
20
|
+
typescript(),
|
|
21
|
+
],
|
|
22
|
+
external: ['react', 'react-dom', '@mui/material'],
|
|
23
|
+
};
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import Checkbox from '@mui/material/Checkbox';
|
|
2
|
+
import Paper from '@mui/material/Paper';
|
|
3
|
+
import Table from '@mui/material/Table';
|
|
4
|
+
import TableBody from '@mui/material/TableBody';
|
|
5
|
+
import TableCell from '@mui/material/TableCell';
|
|
6
|
+
import TableContainer from '@mui/material/TableContainer';
|
|
7
|
+
import TablePagination from '@mui/material/TablePagination';
|
|
8
|
+
import TableRow from '@mui/material/TableRow';
|
|
9
|
+
import React, { useCallback } from 'react';
|
|
10
|
+
import { EnhancedTableHead, HeadCell } from './TableHead';
|
|
11
|
+
import { CustomSelectedToolbarProps, EnhancedTableToolbar } from './Toolbar';
|
|
12
|
+
import { getComparator, Order } from './utils';
|
|
13
|
+
|
|
14
|
+
export interface CustomCell<T> {
|
|
15
|
+
id: keyof T;
|
|
16
|
+
render: (row: T, key: keyof T) => React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface EnhancedTableProps<T extends object> extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
|
20
|
+
title: string;
|
|
21
|
+
data: T[];
|
|
22
|
+
headCells?: HeadCell<T>[];
|
|
23
|
+
deactivateSelect?: boolean;
|
|
24
|
+
defaultOrderBy?: string;
|
|
25
|
+
defaultOrder?: Order;
|
|
26
|
+
excludedColumns?: (keyof T)[];
|
|
27
|
+
customCells?: CustomCell<T>[];
|
|
28
|
+
CustomToolbar?: React.FC;
|
|
29
|
+
CustomSelectedToolbar?: React.FC<CustomSelectedToolbarProps<T>>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface EnhancedTableState<T> {
|
|
33
|
+
order: Order;
|
|
34
|
+
orderBy: keyof T;
|
|
35
|
+
selected: readonly T[];
|
|
36
|
+
page: number;
|
|
37
|
+
rowsPerPage: number;
|
|
38
|
+
searchQuery: string;
|
|
39
|
+
filterFunc: (row: T) => boolean;
|
|
40
|
+
filteredData: T[];
|
|
41
|
+
currentData: T[];
|
|
42
|
+
visibleRows: T[];
|
|
43
|
+
emptyRows: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const MUITable = <T extends object>({
|
|
47
|
+
title,
|
|
48
|
+
data,
|
|
49
|
+
headCells: passedHeadCells,
|
|
50
|
+
deactivateSelect,
|
|
51
|
+
defaultOrderBy,
|
|
52
|
+
defaultOrder,
|
|
53
|
+
excludedColumns,
|
|
54
|
+
customCells,
|
|
55
|
+
CustomToolbar,
|
|
56
|
+
CustomSelectedToolbar,
|
|
57
|
+
...rest
|
|
58
|
+
}: EnhancedTableProps<T>) => {
|
|
59
|
+
|
|
60
|
+
const getDefaultOrderByKey = React.useCallback((): keyof T => {
|
|
61
|
+
if (data.length === 0) return "id" as keyof T;
|
|
62
|
+
|
|
63
|
+
const keys = data.length > 0 ? (Object.keys(data[0]) as (keyof T)[]) : [];
|
|
64
|
+
|
|
65
|
+
if (defaultOrderBy) {
|
|
66
|
+
if (keys.includes(defaultOrderBy as keyof T)) {
|
|
67
|
+
return defaultOrderBy as keyof T;
|
|
68
|
+
} else {
|
|
69
|
+
console.warn(`{defaultOrderBy}: "${defaultOrderBy}" not found among the object keys. Falling back to automatic key detection.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (keys.includes("id" as keyof T) ? "id" : keys[0]) as keyof T;
|
|
74
|
+
}, [data, defaultOrderBy]);
|
|
75
|
+
|
|
76
|
+
const [state, setState] = React.useState<EnhancedTableState<T>>(() => {
|
|
77
|
+
const orderByKey = getDefaultOrderByKey();
|
|
78
|
+
return {
|
|
79
|
+
order: defaultOrder || 'asc',
|
|
80
|
+
orderBy: orderByKey,
|
|
81
|
+
selected: [] as readonly T[],
|
|
82
|
+
page: 0,
|
|
83
|
+
rowsPerPage: 5,
|
|
84
|
+
searchQuery: "",
|
|
85
|
+
filterFunc: () => true,
|
|
86
|
+
filteredData: data,
|
|
87
|
+
currentData: data,
|
|
88
|
+
visibleRows: data,
|
|
89
|
+
emptyRows: 0,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const generateHeadCells = React.useCallback((): HeadCell<T>[] => {
|
|
94
|
+
if (data.length === 0) return [];
|
|
95
|
+
return Object.keys(data[0] as object)
|
|
96
|
+
.filter((key) => !(excludedColumns || []).includes(key as keyof T))
|
|
97
|
+
.map((key) => ({
|
|
98
|
+
id: key as keyof T,
|
|
99
|
+
label: key.charAt(0).toUpperCase() + key.slice(1),
|
|
100
|
+
numeric: typeof data[0][key as keyof T] === 'number',
|
|
101
|
+
}));
|
|
102
|
+
}, [data, excludedColumns]);
|
|
103
|
+
|
|
104
|
+
const headCells = passedHeadCells || generateHeadCells();
|
|
105
|
+
|
|
106
|
+
const handleSearch = (query: string) => {
|
|
107
|
+
setState((prevState) => ({ ...prevState, searchQuery: query.toLowerCase() }));
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
React.useEffect(() => {
|
|
111
|
+
const orderByKey = getDefaultOrderByKey();
|
|
112
|
+
setState((prevState) => ({ ...prevState, orderBy: orderByKey }));
|
|
113
|
+
}, [data, getDefaultOrderByKey]);
|
|
114
|
+
|
|
115
|
+
// Filter data based on applied filters
|
|
116
|
+
React.useEffect(() => {
|
|
117
|
+
const newFilteredData = data.filter(state.filterFunc);
|
|
118
|
+
|
|
119
|
+
setState((prevState) => ({
|
|
120
|
+
...prevState,
|
|
121
|
+
filteredData: newFilteredData,
|
|
122
|
+
currentData: newFilteredData,
|
|
123
|
+
}));
|
|
124
|
+
}, [data, state.filterFunc]);
|
|
125
|
+
|
|
126
|
+
// Search data based on search query
|
|
127
|
+
React.useEffect(() => {
|
|
128
|
+
const searchResults = state.filteredData.filter((row) =>
|
|
129
|
+
Object.values(row as Record<string, unknown>).some((value) =>
|
|
130
|
+
typeof value === 'string' && value.toLowerCase().includes(state.searchQuery)
|
|
131
|
+
)
|
|
132
|
+
);
|
|
133
|
+
setState((prevState) => ({ ...prevState, currentData: searchResults }));
|
|
134
|
+
}, [state.filteredData, state.searchQuery]);
|
|
135
|
+
|
|
136
|
+
// Sort and paginate data to display TablePagination and visible rows respectively
|
|
137
|
+
React.useEffect(() => {
|
|
138
|
+
const sortedData = [...state.currentData].sort(
|
|
139
|
+
getComparator<T, keyof T>(state.order, state.orderBy)
|
|
140
|
+
);
|
|
141
|
+
const paginatedData = sortedData.slice(
|
|
142
|
+
state.page * state.rowsPerPage,
|
|
143
|
+
state.page * state.rowsPerPage + state.rowsPerPage
|
|
144
|
+
);
|
|
145
|
+
const calculatedEmptyRows = Math.max(
|
|
146
|
+
0,
|
|
147
|
+
(1 + state.page) * state.rowsPerPage - state.currentData.length
|
|
148
|
+
);
|
|
149
|
+
setState((prevState) => ({
|
|
150
|
+
...prevState,
|
|
151
|
+
visibleRows: paginatedData,
|
|
152
|
+
emptyRows: calculatedEmptyRows,
|
|
153
|
+
}));
|
|
154
|
+
}, [state.currentData, state.order, state.orderBy, state.page, state.rowsPerPage]);
|
|
155
|
+
|
|
156
|
+
// Remove selected rows deleted from the data to prevent stale selected state
|
|
157
|
+
React.useEffect(() => {
|
|
158
|
+
setState((prevState) => ({
|
|
159
|
+
...prevState,
|
|
160
|
+
selected: prevState.selected.filter((selectedRow) =>
|
|
161
|
+
data.some((row) => row === selectedRow)
|
|
162
|
+
),
|
|
163
|
+
}));
|
|
164
|
+
}, [data]);
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
const handleRequestSort = (_event: React.MouseEvent<unknown>, property: keyof T) => {
|
|
168
|
+
const isAsc = state.orderBy === property && state.order === 'asc';
|
|
169
|
+
setState((prevState) => ({
|
|
170
|
+
...prevState,
|
|
171
|
+
order: isAsc ? 'desc' : 'asc',
|
|
172
|
+
orderBy: property,
|
|
173
|
+
}));
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
177
|
+
if (event.target.checked) {
|
|
178
|
+
setState((prevState) => ({ ...prevState, selected: [...state.currentData] }));
|
|
179
|
+
} else {
|
|
180
|
+
setState((prevState) => ({ ...prevState, selected: [] }));
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const handleClick = (_event: React.MouseEvent<unknown>, row: T) => {
|
|
185
|
+
const selectedIndex = state.selected.findIndex((selectedRow) => selectedRow === row);
|
|
186
|
+
let newSelected: readonly T[] = [];
|
|
187
|
+
|
|
188
|
+
if (selectedIndex === -1) {
|
|
189
|
+
newSelected = [...state.selected, row];
|
|
190
|
+
} else if (selectedIndex === 0) {
|
|
191
|
+
newSelected = state.selected.slice(1);
|
|
192
|
+
} else if (selectedIndex === state.selected.length - 1) {
|
|
193
|
+
newSelected = state.selected.slice(0, -1);
|
|
194
|
+
} else if (selectedIndex > 0) {
|
|
195
|
+
newSelected = [
|
|
196
|
+
...state.selected.slice(0, selectedIndex),
|
|
197
|
+
...state.selected.slice(selectedIndex + 1),
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
setState((prevState) => ({ ...prevState, selected: newSelected }));
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const handleChangePage = (_event: unknown, newPage: number) => {
|
|
204
|
+
setState((prevState) => ({ ...prevState, page: newPage }));
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
208
|
+
setState((prevState) => ({
|
|
209
|
+
...prevState,
|
|
210
|
+
rowsPerPage: parseInt(event.target.value, 10),
|
|
211
|
+
page: 0,
|
|
212
|
+
}));
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const handleFilterChange = useCallback((filterFunc: (row: T) => boolean) => {
|
|
216
|
+
setState((prevState) => ({ ...prevState, filterFunc }));
|
|
217
|
+
}, []);
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<div {...rest}>
|
|
221
|
+
<Paper sx={{ width: '100%', mb: 2 }}>
|
|
222
|
+
<EnhancedTableToolbar
|
|
223
|
+
title={title}
|
|
224
|
+
numSelected={state.selected.length}
|
|
225
|
+
selected={state.selected}
|
|
226
|
+
onFilterChange={handleFilterChange}
|
|
227
|
+
onSearch={handleSearch}
|
|
228
|
+
headCells={headCells}
|
|
229
|
+
CustomToolbar={CustomToolbar}
|
|
230
|
+
CustomSelectedToolbar={CustomSelectedToolbar}
|
|
231
|
+
data={data}
|
|
232
|
+
/>
|
|
233
|
+
<TableContainer>
|
|
234
|
+
<Table
|
|
235
|
+
sx={{ minWidth: 750 }}
|
|
236
|
+
aria-labelledby="tableTitle"
|
|
237
|
+
size="small"
|
|
238
|
+
>
|
|
239
|
+
<EnhancedTableHead
|
|
240
|
+
headCells={headCells}
|
|
241
|
+
numSelected={state.selected.length}
|
|
242
|
+
order={state.order}
|
|
243
|
+
orderBy={state.orderBy}
|
|
244
|
+
onSelectAllClick={deactivateSelect ? undefined : handleSelectAllClick}
|
|
245
|
+
onRequestSort={handleRequestSort}
|
|
246
|
+
rowCount={state.currentData.length}
|
|
247
|
+
deactivateSelectAll={deactivateSelect}
|
|
248
|
+
/>
|
|
249
|
+
<TableBody>
|
|
250
|
+
{state.visibleRows.map((row, index) => {
|
|
251
|
+
const selectionKey = state.orderBy;
|
|
252
|
+
const isItemSelected = state.selected.some((selectedRow) => selectedRow === row);
|
|
253
|
+
const labelId = `enhanced-table-checkbox-${index}`;
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<TableRow
|
|
257
|
+
hover
|
|
258
|
+
onClick={deactivateSelect ? undefined : (event) => handleClick(event, row)}
|
|
259
|
+
role="checkbox"
|
|
260
|
+
aria-checked={isItemSelected}
|
|
261
|
+
tabIndex={-1}
|
|
262
|
+
key={String(row[selectionKey])}
|
|
263
|
+
selected={isItemSelected}
|
|
264
|
+
sx={{ cursor: 'pointer' }}
|
|
265
|
+
>
|
|
266
|
+
{!deactivateSelect && (
|
|
267
|
+
<TableCell padding="checkbox">
|
|
268
|
+
<Checkbox
|
|
269
|
+
color="primary"
|
|
270
|
+
checked={isItemSelected}
|
|
271
|
+
inputProps={{
|
|
272
|
+
'aria-labelledby': labelId,
|
|
273
|
+
}}
|
|
274
|
+
/>
|
|
275
|
+
</TableCell>
|
|
276
|
+
)}
|
|
277
|
+
{headCells.map((headCell, cellIndex) => (
|
|
278
|
+
<TableCell
|
|
279
|
+
key={String(headCell.id)}
|
|
280
|
+
component={cellIndex === 0 ? "th" : undefined}
|
|
281
|
+
id={cellIndex === 0 ? labelId : undefined}
|
|
282
|
+
scope={cellIndex === 0 ? "row" : undefined}
|
|
283
|
+
padding="normal"
|
|
284
|
+
>
|
|
285
|
+
{customCells &&
|
|
286
|
+
customCells.some((renderer) => renderer.id === headCell.id)
|
|
287
|
+
? customCells
|
|
288
|
+
.find((renderer) => renderer.id === headCell.id)
|
|
289
|
+
?.render(row, headCell.id)
|
|
290
|
+
: String(row[headCell.id])}
|
|
291
|
+
</TableCell>
|
|
292
|
+
))}
|
|
293
|
+
</TableRow>
|
|
294
|
+
);
|
|
295
|
+
})}
|
|
296
|
+
{state.emptyRows > 0 && (
|
|
297
|
+
<TableRow
|
|
298
|
+
style={{
|
|
299
|
+
height: 33 * state.emptyRows,
|
|
300
|
+
}}
|
|
301
|
+
>
|
|
302
|
+
<TableCell colSpan={headCells.length + 1} />
|
|
303
|
+
</TableRow>
|
|
304
|
+
)}
|
|
305
|
+
</TableBody>
|
|
306
|
+
</Table>
|
|
307
|
+
</TableContainer>
|
|
308
|
+
<TablePagination
|
|
309
|
+
rowsPerPageOptions={[5, 10, 25]}
|
|
310
|
+
component="div"
|
|
311
|
+
count={state.currentData.length}
|
|
312
|
+
rowsPerPage={state.rowsPerPage}
|
|
313
|
+
page={state.page}
|
|
314
|
+
onPageChange={handleChangePage}
|
|
315
|
+
onRowsPerPageChange={handleChangeRowsPerPage}
|
|
316
|
+
/>
|
|
317
|
+
</Paper>
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import Box from '@mui/material/Box';
|
|
2
|
+
import Checkbox from '@mui/material/Checkbox';
|
|
3
|
+
import TableCell from '@mui/material/TableCell';
|
|
4
|
+
import TableHead from '@mui/material/TableHead';
|
|
5
|
+
import TableRow from '@mui/material/TableRow';
|
|
6
|
+
import TableSortLabel from '@mui/material/TableSortLabel';
|
|
7
|
+
import { visuallyHidden } from '@mui/utils';
|
|
8
|
+
import { Order } from './utils';
|
|
9
|
+
|
|
10
|
+
export interface HeadCell<T> {
|
|
11
|
+
id: keyof T;
|
|
12
|
+
label: string;
|
|
13
|
+
numeric: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface EnhancedTableProps<T> {
|
|
17
|
+
numSelected: number;
|
|
18
|
+
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof T) => void;
|
|
19
|
+
onSelectAllClick?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
20
|
+
order: Order;
|
|
21
|
+
orderBy: keyof T;
|
|
22
|
+
rowCount: number;
|
|
23
|
+
headCells: HeadCell<T>[];
|
|
24
|
+
deactivateSelectAll?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function EnhancedTableHead<T>({
|
|
28
|
+
onSelectAllClick,
|
|
29
|
+
order,
|
|
30
|
+
orderBy,
|
|
31
|
+
numSelected,
|
|
32
|
+
rowCount,
|
|
33
|
+
onRequestSort,
|
|
34
|
+
headCells,
|
|
35
|
+
deactivateSelectAll,
|
|
36
|
+
}: EnhancedTableProps<T>) {
|
|
37
|
+
const createSortHandler =
|
|
38
|
+
(property: keyof T) => (event: React.MouseEvent<unknown>) => {
|
|
39
|
+
onRequestSort(event, property);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<TableHead>
|
|
44
|
+
<TableRow>
|
|
45
|
+
{!deactivateSelectAll &&
|
|
46
|
+
<TableCell padding="checkbox">
|
|
47
|
+
<Checkbox
|
|
48
|
+
color="primary"
|
|
49
|
+
indeterminate={numSelected > 0 && numSelected < rowCount}
|
|
50
|
+
checked={rowCount > 0 && numSelected === rowCount}
|
|
51
|
+
onChange={onSelectAllClick}
|
|
52
|
+
inputProps={{
|
|
53
|
+
'aria-label': 'select all items',
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
</TableCell>
|
|
57
|
+
}
|
|
58
|
+
{headCells.map((headCell) => (
|
|
59
|
+
<TableCell
|
|
60
|
+
key={String(headCell.id)}
|
|
61
|
+
padding='normal'
|
|
62
|
+
sortDirection={orderBy === headCell.id ? order : false}
|
|
63
|
+
>
|
|
64
|
+
<TableSortLabel
|
|
65
|
+
active={orderBy === headCell.id}
|
|
66
|
+
direction={orderBy === headCell.id ? order : 'asc'}
|
|
67
|
+
onClick={createSortHandler(headCell.id)}
|
|
68
|
+
sx={{ fontWeight: 'bold' }}
|
|
69
|
+
>
|
|
70
|
+
{headCell.label}
|
|
71
|
+
{orderBy === headCell.id ? (
|
|
72
|
+
<Box component="span" sx={visuallyHidden}>
|
|
73
|
+
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
|
74
|
+
</Box>
|
|
75
|
+
) : null}
|
|
76
|
+
</TableSortLabel>
|
|
77
|
+
</TableCell>
|
|
78
|
+
))}
|
|
79
|
+
</TableRow>
|
|
80
|
+
</TableHead>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import FilterListIcon from "@mui/icons-material/FilterList";
|
|
2
|
+
import SearchIcon from "@mui/icons-material/Search";
|
|
3
|
+
import { Box, Button, Checkbox, IconButton, InputBase, Popover, Slider, Stack, Toolbar, Tooltip, Typography } from "@mui/material";
|
|
4
|
+
import { alpha } from "@mui/material/styles";
|
|
5
|
+
import React, { useEffect, useState } from "react";
|
|
6
|
+
import { Close } from "@mui/icons-material";
|
|
7
|
+
import { HeadCell } from "./TableHead";
|
|
8
|
+
|
|
9
|
+
interface Filter {
|
|
10
|
+
key: string;
|
|
11
|
+
type: "number" | "string" | "boolean";
|
|
12
|
+
value?: string | boolean | number[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface FilterConfig {
|
|
16
|
+
key: string;
|
|
17
|
+
type: "number" | "string" | "boolean";
|
|
18
|
+
min?: number;
|
|
19
|
+
max?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CustomSelectedToolbarProps<T> {
|
|
23
|
+
selected?: readonly T[];
|
|
24
|
+
data?: T[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface EnhancedTableToolbarProps<T> {
|
|
28
|
+
title: string;
|
|
29
|
+
numSelected: number;
|
|
30
|
+
selected: readonly T[];
|
|
31
|
+
onFilterChange: (filterFunc: (row: T) => boolean) => void;
|
|
32
|
+
onSearch: (query: string) => void;
|
|
33
|
+
headCells: HeadCell<T>[];
|
|
34
|
+
CustomToolbar?: React.FC;
|
|
35
|
+
CustomSelectedToolbar?: React.FC<CustomSelectedToolbarProps<T>>;
|
|
36
|
+
data?: T[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function EnhancedTableToolbar<T>(props: EnhancedTableToolbarProps<T>) {
|
|
40
|
+
const { title, numSelected, selected, onFilterChange, onSearch, headCells, CustomToolbar, CustomSelectedToolbar, data } = props;
|
|
41
|
+
|
|
42
|
+
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
|
43
|
+
const [filters, setFilters] = useState<Filter[]>([]);
|
|
44
|
+
const [filterConfig, setFilterConfig] = useState<FilterConfig[]>([]);
|
|
45
|
+
// rest value to force re-render of filters
|
|
46
|
+
const [resetCounter, setResetCounter] = useState(0);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (data && data.length > 0) {
|
|
50
|
+
const inferredConfig = Object.keys(data[0] as object).map((key: string) => {
|
|
51
|
+
const values = data.map((row) => (row as Record<string, any>)[key]);
|
|
52
|
+
const isNumber = values.every((val) => typeof val === "number");
|
|
53
|
+
const inferredType = isNumber ? "number" : typeof values[0] === "boolean" ? "boolean" : "string";
|
|
54
|
+
return {
|
|
55
|
+
key,
|
|
56
|
+
type: inferredType as "number" | "string" | "boolean",
|
|
57
|
+
min: isNumber ? Math.min(...values) : undefined,
|
|
58
|
+
max: isNumber ? Math.max(...values) : undefined,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
setFilterConfig(inferredConfig);
|
|
62
|
+
}
|
|
63
|
+
}, [data]);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const newFilterFunc = (row: T) => {
|
|
67
|
+
if (filters.length === 0) return true;
|
|
68
|
+
return filters.every((filter) => {
|
|
69
|
+
const rowValue = (row as Record<string, any>)[filter.key];
|
|
70
|
+
if (filter.type === "number") {
|
|
71
|
+
const [min, max] = filter.value as number[];
|
|
72
|
+
return rowValue >= min && rowValue <= max;
|
|
73
|
+
}
|
|
74
|
+
if (filter.type === "string") {
|
|
75
|
+
return rowValue.toString().toLowerCase().includes((filter.value as string).toLowerCase());
|
|
76
|
+
}
|
|
77
|
+
if (filter.type === "boolean") {
|
|
78
|
+
return rowValue === filter.value;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
onFilterChange(newFilterFunc);
|
|
84
|
+
}, [filters, onFilterChange]);
|
|
85
|
+
|
|
86
|
+
const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
87
|
+
setAnchorEl(event.currentTarget);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleClose = () => {
|
|
91
|
+
setAnchorEl(null);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const open = Boolean(anchorEl);
|
|
95
|
+
|
|
96
|
+
const getFilter = (key: string): Filter | undefined =>
|
|
97
|
+
filters.find((filter) => filter.key === key);
|
|
98
|
+
|
|
99
|
+
const updateFilter = (updatedFilter: Filter) => {
|
|
100
|
+
setFilters((prevFilters) => {
|
|
101
|
+
const existingIndex = prevFilters.findIndex((filter) => filter.key === updatedFilter.key);
|
|
102
|
+
if (existingIndex !== -1) {
|
|
103
|
+
// Update existing filter
|
|
104
|
+
const newFilters = [...prevFilters];
|
|
105
|
+
newFilters[existingIndex] = updatedFilter;
|
|
106
|
+
return newFilters;
|
|
107
|
+
}
|
|
108
|
+
// Add new filter
|
|
109
|
+
return [...prevFilters, updatedFilter];
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const removeFilter = (key: string) => {
|
|
114
|
+
setFilters((prevFilters) => prevFilters.filter((filter) => filter.key !== key));
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleFilterChange = (key: string, value: any) => {
|
|
118
|
+
if (value === undefined || value === null || value === "") {
|
|
119
|
+
removeFilter(key);
|
|
120
|
+
} else {
|
|
121
|
+
const config = filterConfig.find((config) => config.key === key);
|
|
122
|
+
if (!config) return;
|
|
123
|
+
|
|
124
|
+
updateFilter({
|
|
125
|
+
key,
|
|
126
|
+
type: config.type,
|
|
127
|
+
value,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const resetFilters = () => {
|
|
133
|
+
setFilters([]);
|
|
134
|
+
setResetCounter((prev) => prev + 1);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<Toolbar
|
|
139
|
+
sx={[
|
|
140
|
+
{ pl: { sm: 2 }, pr: { sm: 1 } },
|
|
141
|
+
numSelected > 0 && {
|
|
142
|
+
bgcolor: (theme) =>
|
|
143
|
+
alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity),
|
|
144
|
+
},
|
|
145
|
+
]}
|
|
146
|
+
>
|
|
147
|
+
{numSelected > 0 ? (
|
|
148
|
+
<>
|
|
149
|
+
<Typography
|
|
150
|
+
sx={{ flex: "1 1 100%" }}
|
|
151
|
+
color="inherit"
|
|
152
|
+
variant="subtitle1"
|
|
153
|
+
component="div"
|
|
154
|
+
>
|
|
155
|
+
{numSelected} selected
|
|
156
|
+
</Typography>
|
|
157
|
+
{CustomSelectedToolbar && (
|
|
158
|
+
<CustomSelectedToolbar data={data} selected={selected} />
|
|
159
|
+
)}
|
|
160
|
+
</>
|
|
161
|
+
) : (
|
|
162
|
+
<Stack
|
|
163
|
+
direction="row"
|
|
164
|
+
justifyContent="space-between"
|
|
165
|
+
width="100%"
|
|
166
|
+
alignItems="center"
|
|
167
|
+
>
|
|
168
|
+
<Typography
|
|
169
|
+
sx={{ flex: "1 1 100%", alignContent: "center", paddingLeft: 1 }}
|
|
170
|
+
variant="h6"
|
|
171
|
+
id="tableTitle"
|
|
172
|
+
component="div"
|
|
173
|
+
>
|
|
174
|
+
{title}
|
|
175
|
+
</Typography>
|
|
176
|
+
<Box
|
|
177
|
+
sx={{
|
|
178
|
+
flexGrow: 1,
|
|
179
|
+
display: "flex",
|
|
180
|
+
alignItems: "center",
|
|
181
|
+
border: 1,
|
|
182
|
+
borderColor: "lightgray",
|
|
183
|
+
borderRadius: 1,
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
<InputBase
|
|
187
|
+
placeholder="Search…"
|
|
188
|
+
onChange={(e) => onSearch(e.target.value)}
|
|
189
|
+
sx={{ marginLeft: 2, flex: 1, width: 180 }}
|
|
190
|
+
/>
|
|
191
|
+
<IconButton>
|
|
192
|
+
<SearchIcon />
|
|
193
|
+
</IconButton>
|
|
194
|
+
</Box>
|
|
195
|
+
<Tooltip title="Filter list">
|
|
196
|
+
<IconButton onClick={handleOpen}>
|
|
197
|
+
<FilterListIcon />
|
|
198
|
+
</IconButton>
|
|
199
|
+
</Tooltip>
|
|
200
|
+
{CustomToolbar && <CustomToolbar />}
|
|
201
|
+
</Stack>
|
|
202
|
+
)}
|
|
203
|
+
<Popover
|
|
204
|
+
open={open}
|
|
205
|
+
anchorEl={anchorEl}
|
|
206
|
+
onClose={handleClose}
|
|
207
|
+
anchorOrigin={{
|
|
208
|
+
vertical: "bottom",
|
|
209
|
+
horizontal: "right",
|
|
210
|
+
}}
|
|
211
|
+
transformOrigin={{
|
|
212
|
+
vertical: "top",
|
|
213
|
+
horizontal: "right",
|
|
214
|
+
}}
|
|
215
|
+
slotProps={{
|
|
216
|
+
paper: { sx: { width: "20%", padding: 2, boxShadow: 2 } }
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
<Stack direction="row" justifyContent="space-between">
|
|
220
|
+
<Typography variant="h6" sx={{ marginBottom: 2 }}>
|
|
221
|
+
Filters
|
|
222
|
+
</Typography>
|
|
223
|
+
<Button variant="contained" size="small" sx={{ height: "fit-content" }} onClick={resetFilters}>
|
|
224
|
+
Reset
|
|
225
|
+
</Button>
|
|
226
|
+
</Stack>
|
|
227
|
+
<Stack key={resetCounter} spacing={2}>
|
|
228
|
+
{filterConfig.map(({ key, type, min, max }) => {
|
|
229
|
+
const currentFilter = getFilter(key);
|
|
230
|
+
return (
|
|
231
|
+
<Box key={key}>
|
|
232
|
+
<Typography variant="subtitle1">
|
|
233
|
+
{headCells.find((cell) => cell.id === key)?.label}
|
|
234
|
+
</Typography>
|
|
235
|
+
{type === "number" && min !== undefined && max !== undefined && (
|
|
236
|
+
<Slider
|
|
237
|
+
value={[
|
|
238
|
+
(currentFilter?.value as number[] | undefined)?.[0] ?? min,
|
|
239
|
+
(currentFilter?.value as number[] | undefined)?.[1] ?? max,
|
|
240
|
+
]}
|
|
241
|
+
onChange={(_, newValue) => {
|
|
242
|
+
const [newMin, newMax] = newValue as number[];
|
|
243
|
+
handleFilterChange(key, [newMin, newMax]);
|
|
244
|
+
}}
|
|
245
|
+
valueLabelDisplay="auto"
|
|
246
|
+
min={min || 0}
|
|
247
|
+
max={max || 100}
|
|
248
|
+
step={1}
|
|
249
|
+
/>
|
|
250
|
+
)}
|
|
251
|
+
{type === "string" && (
|
|
252
|
+
<InputBase
|
|
253
|
+
placeholder="Search..."
|
|
254
|
+
value={(currentFilter?.value as string) || ""}
|
|
255
|
+
onChange={(e) => handleFilterChange(key, e.target.value)}
|
|
256
|
+
sx={{
|
|
257
|
+
padding: 1,
|
|
258
|
+
border: 1,
|
|
259
|
+
borderColor: "lightgray",
|
|
260
|
+
borderRadius: 1,
|
|
261
|
+
width: "100%",
|
|
262
|
+
}}
|
|
263
|
+
/>
|
|
264
|
+
)}
|
|
265
|
+
{type === "boolean" && (
|
|
266
|
+
<Box display="flex" alignItems="center">
|
|
267
|
+
<Checkbox
|
|
268
|
+
checked={currentFilter?.value === true}
|
|
269
|
+
onChange={(e) =>
|
|
270
|
+
handleFilterChange(key, e.target.checked)
|
|
271
|
+
}
|
|
272
|
+
/>
|
|
273
|
+
{currentFilter && (
|
|
274
|
+
<Close
|
|
275
|
+
color="error"
|
|
276
|
+
sx={{ cursor: "pointer", fontSize: 15 }}
|
|
277
|
+
onClick={() => handleFilterChange(key, undefined)}
|
|
278
|
+
/>
|
|
279
|
+
)}
|
|
280
|
+
</Box>
|
|
281
|
+
)}
|
|
282
|
+
</Box>
|
|
283
|
+
);
|
|
284
|
+
})}
|
|
285
|
+
</Stack>
|
|
286
|
+
</Popover>
|
|
287
|
+
</Toolbar>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { HeadCell } from "./TableHead";
|
|
2
|
+
export interface Data {
|
|
3
|
+
id: number;
|
|
4
|
+
calories: number;
|
|
5
|
+
carbs: number;
|
|
6
|
+
fat: number;
|
|
7
|
+
name: string;
|
|
8
|
+
protein: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function createData(
|
|
12
|
+
id: number,
|
|
13
|
+
name: string,
|
|
14
|
+
calories: number,
|
|
15
|
+
fat: number,
|
|
16
|
+
carbs: number,
|
|
17
|
+
protein: number,
|
|
18
|
+
): Data {
|
|
19
|
+
return {
|
|
20
|
+
id,
|
|
21
|
+
name,
|
|
22
|
+
calories,
|
|
23
|
+
fat,
|
|
24
|
+
carbs,
|
|
25
|
+
protein,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const rows = [
|
|
30
|
+
createData(1, 'Cupcake', 305, 3.7, 67, 4.3),
|
|
31
|
+
createData(2, 'Donut', 452, 25.0, 51, 4.9),
|
|
32
|
+
createData(3, 'Eclair', 262, 16.0, 24, 6.0),
|
|
33
|
+
createData(4, 'Frozen yoghurt', 159, 6.0, 24, 4.0),
|
|
34
|
+
createData(5, 'Gingerbread', 356, 16.0, 49, 3.9),
|
|
35
|
+
createData(6, 'Honeycomb', 408, 3.2, 87, 6.5),
|
|
36
|
+
createData(7, 'Ice cream sandwich', 237, 9.0, 37, 4.3),
|
|
37
|
+
createData(8, 'Jelly Bean', 375, 0.0, 94, 0.0),
|
|
38
|
+
createData(9, 'KitKat', 518, 26.0, 65, 7.0),
|
|
39
|
+
createData(10, 'Lollipop', 392, 0.2, 98, 0.0),
|
|
40
|
+
createData(11, 'Marshmallow', 318, 0, 81, 2.0),
|
|
41
|
+
createData(12, 'Nougat', 360, 19.0, 9, 37.0),
|
|
42
|
+
createData(13, 'Oreo', 437, 18.0, 63, 4.0),
|
|
43
|
+
createData(14, 'Pie', 305, 3.7, 67, 4.3),
|
|
44
|
+
createData(15, 'Quiche', 452, 25.0, 51, 4.9),
|
|
45
|
+
createData(16, 'Raspberry', 262, 16.0, 24, 6.0),
|
|
46
|
+
createData(17, 'Strawberry', 159, 6.0, 24, 4.0),
|
|
47
|
+
createData(18, 'Tiramisu', 356, 16.0, 49, 3.9),
|
|
48
|
+
createData(19, 'Ugli fruit', 408, 3.2, 87, 6.5),
|
|
49
|
+
createData(20, 'Vanilla', 237, 9.0, 37, 4.3),
|
|
50
|
+
createData(21, 'Waffle', 375, 0.0, 94, 0.0),
|
|
51
|
+
createData(22, 'Xigua', 518, 26.0, 65, 7.0),
|
|
52
|
+
createData(23, 'Yogurt', 392, 0.2, 98, 0.0),
|
|
53
|
+
createData(24, 'Zucchini', 318, 0, 81, 2.0),
|
|
54
|
+
createData(25, 'Apple', 360, 19.0, 9, 37.0),
|
|
55
|
+
createData(26, 'Banana', 437, 18.0, 63, 4.0),
|
|
56
|
+
createData(27, 'Cherry', 305, 3.7, 67, 4.3),
|
|
57
|
+
createData(28, 'Date', 452, 25.0, 51, 4.9),
|
|
58
|
+
createData(29, 'Elderberry', 262, 16.0, 24, 6.0),
|
|
59
|
+
createData(30, 'Fig', 159, 6.0, 24, 4.0),
|
|
60
|
+
createData(31, 'Grape', 356, 16.0, 49, 3.9),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
export const headCells: HeadCell<typeof rows[0]>[] = [
|
|
64
|
+
{
|
|
65
|
+
id: 'name',
|
|
66
|
+
numeric: false,
|
|
67
|
+
label: 'Dessert (100g serving)',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'calories',
|
|
71
|
+
numeric: true,
|
|
72
|
+
label: 'Calories',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'fat',
|
|
76
|
+
numeric: true,
|
|
77
|
+
label: 'Fat (g)',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'carbs',
|
|
81
|
+
numeric: true,
|
|
82
|
+
label: 'Carbs (g)',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'protein',
|
|
86
|
+
numeric: true,
|
|
87
|
+
label: 'Protein (g)',
|
|
88
|
+
},
|
|
89
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type Order = 'asc' | 'desc';
|
|
2
|
+
|
|
3
|
+
export function getComparator<T, Key extends keyof T>(
|
|
4
|
+
order: Order,
|
|
5
|
+
orderBy: Key,
|
|
6
|
+
): (a: T, b: T) => number {
|
|
7
|
+
return order === 'desc'
|
|
8
|
+
? (a, b) => descendingComparator(a[orderBy], b[orderBy])
|
|
9
|
+
: (a, b) => -descendingComparator(a[orderBy], b[orderBy]);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function descendingComparator<T>(a: T, b: T): number {
|
|
13
|
+
if (b < a) return -1;
|
|
14
|
+
if (b > a) return 1;
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MUITable } from './components/MUITable';
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationDir": "./dist/types",
|
|
13
|
+
"outDir": "./dist",
|
|
14
|
+
"isolatedModules": true
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"src/**/*.ts",
|
|
18
|
+
"src/**/*.tsx"
|
|
19
|
+
],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"node_modules",
|
|
22
|
+
"dist"
|
|
23
|
+
]
|
|
24
|
+
}
|