@mini_7/material-react-table-mcp 4.0.0 → 4.0.1
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/README.md +1 -1
- package/data/examples/advanced.js +215 -0
- package/data/examples/aggregation-and-grouping.js +116 -0
- package/data/examples/aggregation-multi.js +89 -0
- package/data/examples/alternate-column-filtering.js +38 -0
- package/data/examples/alternate-detail-panel.js +65 -0
- package/data/examples/alternate-pagination.js +17 -0
- package/data/examples/basic.js +86 -0
- package/data/examples/chart-detail-panel.js +69 -0
- package/data/examples/column-actions-space.js +34 -0
- package/data/examples/column-alignment.js +58 -0
- package/data/examples/custom-column-actions.js +74 -0
- package/data/examples/custom-column-filtering-ui.js +59 -0
- package/data/examples/custom-headless.js +85 -0
- package/data/examples/custom-top-toolbar.js +60 -0
- package/data/examples/customize-display-columns.js +72 -0
- package/data/examples/customize-filter-components.js +78 -0
- package/data/examples/customize-filter-modes.js +59 -0
- package/data/examples/customize-filter-variants.js +98 -0
- package/data/examples/customize-global-filter-component.js +37 -0
- package/data/examples/customize-remove-column-grouping.js +75 -0
- package/data/examples/customize-row-selection.js +43 -0
- package/data/examples/customize-table-styles.js +81 -0
- package/data/examples/disable-column-actions.js +26 -0
- package/data/examples/disable-column-hiding.js +74 -0
- package/data/examples/disable-density-toggle.js +37 -0
- package/data/examples/dynamic-columns.js +124 -0
- package/data/examples/editing-crud-cell.js +288 -0
- package/data/examples/editing-crud-modal.js +275 -0
- package/data/examples/editing-crud-row.js +255 -0
- package/data/examples/editing-crud-table.js +280 -0
- package/data/examples/editing-crud-tree.js +328 -0
- package/data/examples/enable-cell-actions.js +52 -0
- package/data/examples/enable-click-to-copy.js +33 -0
- package/data/examples/enable-column-grouping.js +69 -0
- package/data/examples/enable-column-ordering.js +37 -0
- package/data/examples/enable-column-pinning.js +58 -0
- package/data/examples/enable-column-resizing.js +45 -0
- package/data/examples/enable-column-virtualization.js +19 -0
- package/data/examples/enable-detail-panel-conditionally.js +61 -0
- package/data/examples/enable-detail-panel-virtualized.js +64 -0
- package/data/examples/enable-expanding-tree.js +87 -0
- package/data/examples/enable-filter-facet-values.js +52 -0
- package/data/examples/enable-row-dragging.js +102 -0
- package/data/examples/enable-row-numbers-original.js +37 -0
- package/data/examples/enable-row-numbers-static.js +37 -0
- package/data/examples/enable-row-ordering.js +40 -0
- package/data/examples/enable-row-pinning-select.js +64 -0
- package/data/examples/enable-row-pinning-static.js +39 -0
- package/data/examples/enable-row-pinning-sticky.js +57 -0
- package/data/examples/enable-row-selection.js +71 -0
- package/data/examples/enable-row-virtualization.js +85 -0
- package/data/examples/enable-sticky-header.js +47 -0
- package/data/examples/expanding-tree-expanded.js +98 -0
- package/data/examples/expanding-tree-flat-parse.js +102 -0
- package/data/examples/expanding-tree-root-expanded.js +120 -0
- package/data/examples/export-to-csv.js +84 -0
- package/data/examples/export-to-pdf.js +76 -0
- package/data/examples/external-toolbar.js +82 -0
- package/data/examples/font-awesome-icons.js +62 -0
- package/data/examples/infinite-scrolling.js +137 -0
- package/data/examples/lazy-detail-panel.js +164 -0
- package/data/examples/lazy-sub-rows.js +118 -0
- package/data/examples/linear-progress.js +48 -0
- package/data/examples/loading.js +34 -0
- package/data/examples/localization-i18n-ar.js +40 -0
- package/data/examples/localization-i18n-az.js +38 -0
- package/data/examples/localization-i18n-bg.js +38 -0
- package/data/examples/localization-i18n-cs.js +38 -0
- package/data/examples/localization-i18n-da.js +38 -0
- package/data/examples/localization-i18n-de.js +38 -0
- package/data/examples/localization-i18n-el.js +38 -0
- package/data/examples/localization-i18n-en.js +38 -0
- package/data/examples/localization-i18n-es.js +38 -0
- package/data/examples/localization-i18n-et.js +38 -0
- package/data/examples/localization-i18n-fa.js +40 -0
- package/data/examples/localization-i18n-fi.js +38 -0
- package/data/examples/localization-i18n-fr.js +38 -0
- package/data/examples/localization-i18n-he.js +40 -0
- package/data/examples/localization-i18n-hr.js +38 -0
- package/data/examples/localization-i18n-hu.js +38 -0
- package/data/examples/localization-i18n-hy.js +40 -0
- package/data/examples/localization-i18n-id.js +38 -0
- package/data/examples/localization-i18n-it.js +38 -0
- package/data/examples/localization-i18n-ja.js +38 -0
- package/data/examples/localization-i18n-ko.js +38 -0
- package/data/examples/localization-i18n-nl.js +38 -0
- package/data/examples/localization-i18n-no.js +38 -0
- package/data/examples/localization-i18n-np.js +38 -0
- package/data/examples/localization-i18n-pl.js +38 -0
- package/data/examples/localization-i18n-pt-BR.js +38 -0
- package/data/examples/localization-i18n-pt.js +38 -0
- package/data/examples/localization-i18n-ro.js +38 -0
- package/data/examples/localization-i18n-ru.js +38 -0
- package/data/examples/localization-i18n-sk.js +38 -0
- package/data/examples/localization-i18n-sr-Cyrl-RS.js +38 -0
- package/data/examples/localization-i18n-sr-Latn-RS.js +38 -0
- package/data/examples/localization-i18n-sv.js +38 -0
- package/data/examples/localization-i18n-tr.js +38 -0
- package/data/examples/localization-i18n-uk.js +38 -0
- package/data/examples/localization-i18n-vi.js +38 -0
- package/data/examples/localization-i18n-zh-hans.js +38 -0
- package/data/examples/localization-i18n-zh-hant.js +38 -0
- package/data/examples/manual-selection.js +81 -0
- package/data/examples/minimal.js +66 -0
- package/data/examples/mui-theme.js +109 -0
- package/data/examples/multi-sorting.js +48 -0
- package/data/examples/persistent-state.js +126 -0
- package/data/examples/react-query.js +122 -0
- package/data/examples/remote.js +113 -0
- package/data/examples/row-actions-buttons.js +54 -0
- package/data/examples/row-actions-menu-items.js +36 -0
- package/data/examples/single-row-selection.js +74 -0
- package/data/examples/virtualized.js +122 -0
- package/dist/data.js +31 -9
- package/dist/index.js +18 -6
- package/package.json +1 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { lazy, Suspense, useMemo, useState } from 'react';
|
|
2
|
+
import { MaterialReactTable, useMaterialReactTable, } from 'material-react-table';
|
|
3
|
+
import { IconButton, Tooltip } from '@mui/material';
|
|
4
|
+
import RefreshIcon from '@mui/icons-material/Refresh';
|
|
5
|
+
import { QueryClient, QueryClientProvider, keepPreviousData, useQuery, } from '@tanstack/react-query'; //note: this is TanStack React Query V5
|
|
6
|
+
const Example = () => {
|
|
7
|
+
//manage our own state for stuff we want to pass to the API
|
|
8
|
+
const [columnFilters, setColumnFilters] = useState([]);
|
|
9
|
+
const [globalFilter, setGlobalFilter] = useState('');
|
|
10
|
+
const [sorting, setSorting] = useState([]);
|
|
11
|
+
const [pagination, setPagination] = useState({
|
|
12
|
+
pageIndex: 0,
|
|
13
|
+
pageSize: 10,
|
|
14
|
+
});
|
|
15
|
+
//consider storing this code in a custom hook (i.e useFetchUsers)
|
|
16
|
+
const { data: { data = [], meta } = {}, //your data and api response will probably be different
|
|
17
|
+
isError, isRefetching, isLoading, refetch, } = useQuery({
|
|
18
|
+
queryKey: [
|
|
19
|
+
'users-list',
|
|
20
|
+
{
|
|
21
|
+
columnFilters, //refetch when columnFilters changes
|
|
22
|
+
globalFilter, //refetch when globalFilter changes
|
|
23
|
+
pagination, //refetch when pagination changes
|
|
24
|
+
sorting, //refetch when sorting changes
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
queryFn: async () => {
|
|
28
|
+
const fetchURL = new URL('/api/data', location.origin);
|
|
29
|
+
//read our state and pass it to the API as query params
|
|
30
|
+
fetchURL.searchParams.set('start', `${pagination.pageIndex * pagination.pageSize}`);
|
|
31
|
+
fetchURL.searchParams.set('size', `${pagination.pageSize}`);
|
|
32
|
+
fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));
|
|
33
|
+
fetchURL.searchParams.set('globalFilter', globalFilter ?? '');
|
|
34
|
+
fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));
|
|
35
|
+
//use whatever fetch library you want, fetch, axios, etc
|
|
36
|
+
const response = await fetch(fetchURL.href);
|
|
37
|
+
const json = (await response.json());
|
|
38
|
+
return json;
|
|
39
|
+
},
|
|
40
|
+
placeholderData: keepPreviousData, //don't go to 0 rows when refetching or paginating to next page
|
|
41
|
+
});
|
|
42
|
+
const columns = useMemo(
|
|
43
|
+
//column definitions...
|
|
44
|
+
() => [
|
|
45
|
+
{
|
|
46
|
+
accessorKey: 'firstName',
|
|
47
|
+
header: 'First Name',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
accessorKey: 'lastName',
|
|
51
|
+
header: 'Last Name',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
accessorKey: 'address',
|
|
55
|
+
header: 'Address',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
accessorKey: 'state',
|
|
59
|
+
header: 'State',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
accessorKey: 'phoneNumber',
|
|
63
|
+
header: 'Phone Number',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
accessorFn: (row) => new Date(row.lastLogin),
|
|
67
|
+
id: 'lastLogin',
|
|
68
|
+
header: 'Last Login',
|
|
69
|
+
Cell: ({ cell }) => new Date(cell.getValue()).toLocaleString(),
|
|
70
|
+
filterFn: 'greaterThan',
|
|
71
|
+
filterVariant: 'date',
|
|
72
|
+
enableGlobalFilter: false,
|
|
73
|
+
},
|
|
74
|
+
], []);
|
|
75
|
+
const table = useMaterialReactTable({
|
|
76
|
+
columns,
|
|
77
|
+
data,
|
|
78
|
+
initialState: { showColumnFilters: true },
|
|
79
|
+
manualFiltering: true, //turn off built-in client-side filtering
|
|
80
|
+
manualPagination: true, //turn off built-in client-side pagination
|
|
81
|
+
manualSorting: true, //turn off built-in client-side sorting
|
|
82
|
+
muiToolbarAlertBannerProps: isError
|
|
83
|
+
? {
|
|
84
|
+
color: 'error',
|
|
85
|
+
children: 'Error loading data',
|
|
86
|
+
}
|
|
87
|
+
: undefined,
|
|
88
|
+
onColumnFiltersChange: setColumnFilters,
|
|
89
|
+
onGlobalFilterChange: setGlobalFilter,
|
|
90
|
+
onPaginationChange: setPagination,
|
|
91
|
+
onSortingChange: setSorting,
|
|
92
|
+
renderTopToolbarCustomActions: () => (<Tooltip arrow title="Refresh Data">
|
|
93
|
+
<IconButton onClick={() => refetch()}>
|
|
94
|
+
<RefreshIcon />
|
|
95
|
+
</IconButton>
|
|
96
|
+
</Tooltip>),
|
|
97
|
+
rowCount: meta?.totalRowCount ?? 0,
|
|
98
|
+
state: {
|
|
99
|
+
columnFilters,
|
|
100
|
+
globalFilter,
|
|
101
|
+
isLoading,
|
|
102
|
+
pagination,
|
|
103
|
+
showAlertBanner: isError,
|
|
104
|
+
showProgressBars: isRefetching,
|
|
105
|
+
sorting,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
return <MaterialReactTable table={table}/>;
|
|
109
|
+
};
|
|
110
|
+
//react query setup in App.tsx
|
|
111
|
+
const ReactQueryDevtoolsProduction = lazy(() => import('@tanstack/react-query-devtools/build/modern/production.js').then((d) => ({
|
|
112
|
+
default: d.ReactQueryDevtools,
|
|
113
|
+
})));
|
|
114
|
+
const queryClient = new QueryClient();
|
|
115
|
+
export default function App() {
|
|
116
|
+
return (<QueryClientProvider client={queryClient}>
|
|
117
|
+
<Example />
|
|
118
|
+
<Suspense fallback={null}>
|
|
119
|
+
<ReactQueryDevtoolsProduction />
|
|
120
|
+
</Suspense>
|
|
121
|
+
</QueryClientProvider>);
|
|
122
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { MaterialReactTable, useMaterialReactTable, } from 'material-react-table';
|
|
3
|
+
const Example = () => {
|
|
4
|
+
//data and fetching state
|
|
5
|
+
const [data, setData] = useState([]);
|
|
6
|
+
const [isError, setIsError] = useState(false);
|
|
7
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
8
|
+
const [isRefetching, setIsRefetching] = useState(false);
|
|
9
|
+
const [rowCount, setRowCount] = useState(0);
|
|
10
|
+
//table state
|
|
11
|
+
const [columnFilters, setColumnFilters] = useState([]);
|
|
12
|
+
const [globalFilter, setGlobalFilter] = useState('');
|
|
13
|
+
const [sorting, setSorting] = useState([]);
|
|
14
|
+
const [pagination, setPagination] = useState({
|
|
15
|
+
pageIndex: 0,
|
|
16
|
+
pageSize: 10,
|
|
17
|
+
});
|
|
18
|
+
//if you want to avoid useEffect, look at the React Query example instead
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const fetchData = async () => {
|
|
21
|
+
if (!data.length) {
|
|
22
|
+
setIsLoading(true);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
setIsRefetching(true);
|
|
26
|
+
}
|
|
27
|
+
const url = new URL('/api/data', location.origin);
|
|
28
|
+
url.searchParams.set('start', `${pagination.pageIndex * pagination.pageSize}`);
|
|
29
|
+
url.searchParams.set('size', `${pagination.pageSize}`);
|
|
30
|
+
url.searchParams.set('filters', JSON.stringify(columnFilters ?? []));
|
|
31
|
+
url.searchParams.set('globalFilter', globalFilter ?? '');
|
|
32
|
+
url.searchParams.set('sorting', JSON.stringify(sorting ?? []));
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(url.href);
|
|
35
|
+
const json = (await response.json());
|
|
36
|
+
setData(json.data);
|
|
37
|
+
setRowCount(json.meta.totalRowCount);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
setIsError(true);
|
|
41
|
+
console.error(error);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
setIsError(false);
|
|
45
|
+
setIsLoading(false);
|
|
46
|
+
setIsRefetching(false);
|
|
47
|
+
};
|
|
48
|
+
fetchData();
|
|
49
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
50
|
+
}, [
|
|
51
|
+
columnFilters, //re-fetch when column filters change
|
|
52
|
+
globalFilter, //re-fetch when global filter changes
|
|
53
|
+
pagination.pageIndex, //re-fetch when page index changes
|
|
54
|
+
pagination.pageSize, //re-fetch when page size changes
|
|
55
|
+
sorting, //re-fetch when sorting changes
|
|
56
|
+
]);
|
|
57
|
+
const columns = useMemo(() => [
|
|
58
|
+
{
|
|
59
|
+
accessorKey: 'firstName',
|
|
60
|
+
header: 'First Name',
|
|
61
|
+
},
|
|
62
|
+
//column definitions...
|
|
63
|
+
{
|
|
64
|
+
accessorKey: 'lastName',
|
|
65
|
+
header: 'Last Name',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
accessorKey: 'address',
|
|
69
|
+
header: 'Address',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
accessorKey: 'state',
|
|
73
|
+
header: 'State',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
accessorKey: 'phoneNumber',
|
|
77
|
+
header: 'Phone Number',
|
|
78
|
+
},
|
|
79
|
+
//end
|
|
80
|
+
], []);
|
|
81
|
+
const table = useMaterialReactTable({
|
|
82
|
+
columns,
|
|
83
|
+
data,
|
|
84
|
+
enableRowSelection: true,
|
|
85
|
+
getRowId: (row) => row.phoneNumber,
|
|
86
|
+
initialState: { showColumnFilters: true },
|
|
87
|
+
manualFiltering: true,
|
|
88
|
+
manualPagination: true,
|
|
89
|
+
manualSorting: true,
|
|
90
|
+
muiToolbarAlertBannerProps: isError
|
|
91
|
+
? {
|
|
92
|
+
color: 'error',
|
|
93
|
+
children: 'Error loading data',
|
|
94
|
+
}
|
|
95
|
+
: undefined,
|
|
96
|
+
onColumnFiltersChange: setColumnFilters,
|
|
97
|
+
onGlobalFilterChange: setGlobalFilter,
|
|
98
|
+
onPaginationChange: setPagination,
|
|
99
|
+
onSortingChange: setSorting,
|
|
100
|
+
rowCount,
|
|
101
|
+
state: {
|
|
102
|
+
columnFilters,
|
|
103
|
+
globalFilter,
|
|
104
|
+
isLoading,
|
|
105
|
+
pagination,
|
|
106
|
+
showAlertBanner: isError,
|
|
107
|
+
showProgressBars: isRefetching,
|
|
108
|
+
sorting,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
return <MaterialReactTable table={table}/>;
|
|
112
|
+
};
|
|
113
|
+
export default Example;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import { MaterialReactTable } from 'material-react-table';
|
|
3
|
+
import { Box, IconButton } from '@mui/material';
|
|
4
|
+
import { Edit as EditIcon, Delete as DeleteIcon, Email as EmailIcon, } from '@mui/icons-material';
|
|
5
|
+
import { data as initialData } from './makeData';
|
|
6
|
+
export const Example = () => {
|
|
7
|
+
const columns = useMemo(
|
|
8
|
+
//column definitions...
|
|
9
|
+
() => [
|
|
10
|
+
{
|
|
11
|
+
accessorKey: 'firstName',
|
|
12
|
+
header: 'First Name',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
accessorKey: 'lastName',
|
|
16
|
+
header: 'Last Name',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
accessorKey: 'address',
|
|
20
|
+
header: 'Address',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
accessorKey: 'city',
|
|
24
|
+
header: 'City',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
accessorKey: 'state',
|
|
28
|
+
header: 'State',
|
|
29
|
+
},
|
|
30
|
+
], []);
|
|
31
|
+
const [data, setData] = useState(initialData);
|
|
32
|
+
return (<MaterialReactTable columns={columns} data={data} layoutMode="grid" displayColumnDefOptions={{
|
|
33
|
+
'mrt-row-actions': {
|
|
34
|
+
size: 180, //if using layoutMode that is not 'semantic', the columns will not auto-size, so you need to set the size manually
|
|
35
|
+
grow: false,
|
|
36
|
+
},
|
|
37
|
+
}} enableRowActions renderRowActions={({ row, table }) => (<Box sx={{ display: 'flex', flexWrap: 'nowrap', gap: '8px' }}>
|
|
38
|
+
<IconButton color="primary" onClick={() => window.open(`mailto:kevinvandy@mailinator.com?subject=Hello ${row.original.firstName}!`)}>
|
|
39
|
+
<EmailIcon />
|
|
40
|
+
</IconButton>
|
|
41
|
+
<IconButton color="secondary" onClick={() => {
|
|
42
|
+
table.setEditingRow(row);
|
|
43
|
+
}}>
|
|
44
|
+
<EditIcon />
|
|
45
|
+
</IconButton>
|
|
46
|
+
<IconButton color="error" onClick={() => {
|
|
47
|
+
data.splice(row.index, 1); //assuming simple data table
|
|
48
|
+
setData([...data]);
|
|
49
|
+
}}>
|
|
50
|
+
<DeleteIcon />
|
|
51
|
+
</IconButton>
|
|
52
|
+
</Box>)}/>);
|
|
53
|
+
};
|
|
54
|
+
export default Example;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { MaterialReactTable, MRT_ActionMenuItem, } from 'material-react-table';
|
|
3
|
+
import { data } from './makeData';
|
|
4
|
+
import { Edit, Delete } from '@mui/icons-material';
|
|
5
|
+
export const Example = () => {
|
|
6
|
+
const columns = useMemo(
|
|
7
|
+
//column definitions...
|
|
8
|
+
() => [
|
|
9
|
+
{
|
|
10
|
+
accessorKey: 'firstName',
|
|
11
|
+
header: 'First Name',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
accessorKey: 'lastName',
|
|
15
|
+
header: 'Last Name',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
accessorKey: 'address',
|
|
19
|
+
header: 'Address',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
accessorKey: 'city',
|
|
23
|
+
header: 'City',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
accessorKey: 'state',
|
|
27
|
+
header: 'State',
|
|
28
|
+
},
|
|
29
|
+
], []);
|
|
30
|
+
return (<MaterialReactTable columns={columns} data={data} enableRowActions renderRowActionMenuItems={({ row, table }) => [
|
|
31
|
+
<MRT_ActionMenuItem //or just use a normal MUI MenuItem component
|
|
32
|
+
icon={<Edit />} key="edit" label="Edit" onClick={() => console.info('Edit')} table={table}/>,
|
|
33
|
+
<MRT_ActionMenuItem icon={<Delete />} key="delete" label="Delete" onClick={() => console.info('Delete')} table={table}/>,
|
|
34
|
+
]}/>);
|
|
35
|
+
};
|
|
36
|
+
export default Example;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import { MaterialReactTable, useMaterialReactTable, } from 'material-react-table';
|
|
3
|
+
//end
|
|
4
|
+
const data = [
|
|
5
|
+
//data definitions...
|
|
6
|
+
{
|
|
7
|
+
userId: '3f25309c-8fa1-470f-811e-cdb082ab9017', //we'll use this as a unique row id
|
|
8
|
+
firstName: 'Dylan',
|
|
9
|
+
lastName: 'Murray',
|
|
10
|
+
age: 22,
|
|
11
|
+
address: '261 Erdman Ford',
|
|
12
|
+
city: 'East Daphne',
|
|
13
|
+
state: 'Kentucky',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
userId: 'be731030-df83-419c-b3d6-9ef04e7f4a9f',
|
|
17
|
+
firstName: 'Raquel',
|
|
18
|
+
lastName: 'Kohler',
|
|
19
|
+
age: 18,
|
|
20
|
+
address: '769 Dominic Grove',
|
|
21
|
+
city: 'Columbus',
|
|
22
|
+
state: 'Ohio',
|
|
23
|
+
},
|
|
24
|
+
//end
|
|
25
|
+
];
|
|
26
|
+
const Example = () => {
|
|
27
|
+
const columns = useMemo(
|
|
28
|
+
//column definitions...
|
|
29
|
+
() => [
|
|
30
|
+
{
|
|
31
|
+
accessorKey: 'firstName',
|
|
32
|
+
header: 'First Name',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
accessorKey: 'lastName',
|
|
36
|
+
header: 'Last Name',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
accessorKey: 'age',
|
|
40
|
+
header: 'Age',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
accessorKey: 'address',
|
|
44
|
+
header: 'Address',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
accessorKey: 'city',
|
|
48
|
+
header: 'City',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
accessorKey: 'state',
|
|
52
|
+
header: 'State',
|
|
53
|
+
},
|
|
54
|
+
], []);
|
|
55
|
+
//optionally, you can manage the row selection state yourself
|
|
56
|
+
const [rowSelection, setRowSelection] = useState({});
|
|
57
|
+
const table = useMaterialReactTable({
|
|
58
|
+
columns,
|
|
59
|
+
data,
|
|
60
|
+
enableRowSelection: true,
|
|
61
|
+
enableMultiRowSelection: false, //use radio buttons instead of checkboxes
|
|
62
|
+
getRowId: (row) => row.userId, //give each row a more useful id
|
|
63
|
+
muiTableBodyRowProps: ({ row }) => ({
|
|
64
|
+
//add onClick to row to select upon clicking anywhere in the row
|
|
65
|
+
onClick: row.getToggleSelectedHandler(),
|
|
66
|
+
sx: { cursor: 'pointer' },
|
|
67
|
+
}),
|
|
68
|
+
positionToolbarAlertBanner: 'bottom', //move the alert banner to the bottom
|
|
69
|
+
onRowSelectionChange: setRowSelection, //connect internal row selection state to your own
|
|
70
|
+
state: { rowSelection }, //pass our managed row selection state to the table to use
|
|
71
|
+
});
|
|
72
|
+
return <MaterialReactTable table={table}/>;
|
|
73
|
+
};
|
|
74
|
+
export default Example;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { MaterialReactTable, useMaterialReactTable, } from 'material-react-table';
|
|
3
|
+
import { makeData } from './makeData';
|
|
4
|
+
const Example = () => {
|
|
5
|
+
const columns = useMemo(
|
|
6
|
+
//column definitions...
|
|
7
|
+
() => [
|
|
8
|
+
{
|
|
9
|
+
accessorKey: 'firstName',
|
|
10
|
+
header: 'First Name',
|
|
11
|
+
size: 150,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
accessorKey: 'middleName',
|
|
15
|
+
header: 'Middle Name',
|
|
16
|
+
size: 170,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
accessorKey: 'lastName',
|
|
20
|
+
header: 'Last Name',
|
|
21
|
+
size: 150,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
accessorKey: 'email',
|
|
25
|
+
header: 'Email Address',
|
|
26
|
+
size: 300,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
accessorKey: 'phoneNumber',
|
|
30
|
+
header: 'Phone Number',
|
|
31
|
+
size: 250,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
accessorKey: 'address',
|
|
35
|
+
header: 'Address',
|
|
36
|
+
size: 300,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
accessorKey: 'zipCode',
|
|
40
|
+
header: 'Zip Code',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
accessorKey: 'city',
|
|
44
|
+
header: 'City',
|
|
45
|
+
size: 220,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
accessorKey: 'state',
|
|
49
|
+
header: 'State',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
accessorKey: 'country',
|
|
53
|
+
header: 'Country',
|
|
54
|
+
size: 350,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
accessorKey: 'petName',
|
|
58
|
+
header: 'Pet Name',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
accessorKey: 'age',
|
|
62
|
+
header: 'Age',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
accessorKey: 'salary',
|
|
66
|
+
header: 'Salary',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
accessorKey: 'dateOfBirth',
|
|
70
|
+
header: 'Date of Birth',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
accessorKey: 'dateOfJoining',
|
|
74
|
+
header: 'Date of Joining',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
accessorKey: 'isActive',
|
|
78
|
+
header: 'Is Active',
|
|
79
|
+
},
|
|
80
|
+
], []);
|
|
81
|
+
//optionally access the underlying virtualizer instance
|
|
82
|
+
const rowVirtualizerInstanceRef = useRef(null);
|
|
83
|
+
const [data, setData] = useState([]);
|
|
84
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
85
|
+
const [sorting, setSorting] = useState([]);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (typeof window !== 'undefined') {
|
|
88
|
+
setData(makeData(10000));
|
|
89
|
+
setIsLoading(false);
|
|
90
|
+
}
|
|
91
|
+
}, []);
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
//scroll to the top of the table when the sorting changes
|
|
94
|
+
try {
|
|
95
|
+
rowVirtualizerInstanceRef.current?.scrollToIndex?.(0);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error(error);
|
|
99
|
+
}
|
|
100
|
+
}, [sorting]);
|
|
101
|
+
const table = useMaterialReactTable({
|
|
102
|
+
columns,
|
|
103
|
+
data, //10,000 rows
|
|
104
|
+
defaultDisplayColumn: { enableResizing: true },
|
|
105
|
+
enableBottomToolbar: false,
|
|
106
|
+
enableColumnResizing: true,
|
|
107
|
+
enableColumnVirtualization: true,
|
|
108
|
+
enableGlobalFilterModes: true,
|
|
109
|
+
enablePagination: false,
|
|
110
|
+
enableColumnPinning: true,
|
|
111
|
+
enableRowNumbers: true,
|
|
112
|
+
enableRowVirtualization: true,
|
|
113
|
+
muiTableContainerProps: { sx: { maxHeight: '600px' } },
|
|
114
|
+
onSortingChange: setSorting,
|
|
115
|
+
state: { isLoading, sorting },
|
|
116
|
+
rowVirtualizerInstanceRef, //optional
|
|
117
|
+
rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer
|
|
118
|
+
columnVirtualizerOptions: { overscan: 2 }, //optionally customize the column virtualizer
|
|
119
|
+
});
|
|
120
|
+
return <MaterialReactTable table={table}/>;
|
|
121
|
+
};
|
|
122
|
+
export default Example;
|
package/dist/data.js
CHANGED
|
@@ -36,13 +36,28 @@ export const loadReference = (name) => {
|
|
|
36
36
|
return undefined;
|
|
37
37
|
return readFileSync(file, 'utf8');
|
|
38
38
|
};
|
|
39
|
-
export const loadExample = (id) => {
|
|
40
|
-
const
|
|
39
|
+
export const loadExample = (id, language = 'ts') => {
|
|
40
|
+
const ext = language === 'js' ? 'js' : 'tsx';
|
|
41
|
+
const file = join(dataDir, 'examples', `${id}.${ext}`);
|
|
41
42
|
if (!safeName.test(id) || !existsSync(file))
|
|
42
43
|
return undefined;
|
|
43
44
|
return readFileSync(file, 'utf8');
|
|
44
45
|
};
|
|
45
46
|
const tokenize = (query) => query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
47
|
+
const STEM_SUFFIXES = ['ings', 'ing', 'ed', 'es', 's'];
|
|
48
|
+
const MIN_STEM_LENGTH = 3;
|
|
49
|
+
/**
|
|
50
|
+
* Strips a common English suffix so e.g. "filtering" and "filters" meet in the
|
|
51
|
+
* middle at "filter". Leaves words too short to keep a meaningful stem
|
|
52
|
+
* untouched (e.g. "is", "us").
|
|
53
|
+
*/
|
|
54
|
+
const stem = (word) => {
|
|
55
|
+
for (const suffix of STEM_SUFFIXES) {
|
|
56
|
+
if (word.length - suffix.length >= MIN_STEM_LENGTH && word.endsWith(suffix))
|
|
57
|
+
return word.slice(0, -suffix.length);
|
|
58
|
+
}
|
|
59
|
+
return word;
|
|
60
|
+
};
|
|
46
61
|
/** Ranks name matches above description matches; exact and prefix matches first. */
|
|
47
62
|
export const searchApi = (api, query, categories = CATEGORIES, limit = 20) => {
|
|
48
63
|
const terms = tokenize(query);
|
|
@@ -76,9 +91,12 @@ export const searchApi = (api, query, categories = CATEGORIES, limit = 20) => {
|
|
|
76
91
|
.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
|
|
77
92
|
.slice(0, limit);
|
|
78
93
|
};
|
|
79
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* Every term must match the name, title, headings, or description of a document.
|
|
96
|
+
* Terms and indexed text are lightly stemmed so e.g. "filters" matches "filtering".
|
|
97
|
+
*/
|
|
80
98
|
export const searchDocs = (index, query, limit = 20, kinds = ['guide', 'reference', 'skill', 'example']) => {
|
|
81
|
-
const terms = tokenize(query);
|
|
99
|
+
const terms = tokenize(query).map((term) => ({ term, termStem: stem(term) }));
|
|
82
100
|
if (terms.length === 0)
|
|
83
101
|
return [];
|
|
84
102
|
const docs = [];
|
|
@@ -108,15 +126,19 @@ export const searchDocs = (index, query, limit = 20, kinds = ['guide', 'referenc
|
|
|
108
126
|
if (!kinds.includes(doc.kind))
|
|
109
127
|
continue;
|
|
110
128
|
const name = doc.name.toLowerCase();
|
|
129
|
+
const nameParts = name.split('-');
|
|
130
|
+
const nameStem = stem(name);
|
|
131
|
+
const namePartStems = nameParts.map(stem);
|
|
132
|
+
const textStem = doc.text.split(/\s+/).map(stem).join(' ');
|
|
111
133
|
let score = 0;
|
|
112
|
-
for (const term of terms) {
|
|
113
|
-
if (name === term)
|
|
134
|
+
for (const { term, termStem } of terms) {
|
|
135
|
+
if (name === term || nameStem === termStem)
|
|
114
136
|
score += 100;
|
|
115
|
-
else if (
|
|
137
|
+
else if (nameParts.includes(term) || namePartStems.includes(termStem))
|
|
116
138
|
score += 60;
|
|
117
|
-
else if (name.includes(term))
|
|
139
|
+
else if (name.includes(term) || nameStem.includes(termStem))
|
|
118
140
|
score += 40;
|
|
119
|
-
else if (doc.text.includes(term))
|
|
141
|
+
else if (doc.text.includes(term) || textStem.includes(termStem))
|
|
120
142
|
score += 10;
|
|
121
143
|
else {
|
|
122
144
|
score = 0;
|
package/dist/index.js
CHANGED
|
@@ -201,14 +201,18 @@ server.registerTool('get_mrt_reference', {
|
|
|
201
201
|
}, async ({ page }) => text(loadReference(page) ?? `Unknown page "${page}".`));
|
|
202
202
|
server.registerTool('get_mrt_example', {
|
|
203
203
|
title: 'Get a Material React Table example',
|
|
204
|
-
description: `Return the
|
|
204
|
+
description: `Return the source of one of the ${docs.examples.length} runnable docs examples, as TypeScript or JavaScript. Call without an id to list every example with the guides that embed it.`,
|
|
205
205
|
inputSchema: {
|
|
206
206
|
id: z
|
|
207
207
|
.string()
|
|
208
208
|
.optional()
|
|
209
209
|
.describe('Example id, e.g. "basic", "editing-crud-modal", "virtualized", "remote". Omit to list all examples.'),
|
|
210
|
+
language: z
|
|
211
|
+
.enum(['ts', 'js'])
|
|
212
|
+
.optional()
|
|
213
|
+
.describe('Source language to return, "ts" or "js". Defaults to "ts".'),
|
|
210
214
|
},
|
|
211
|
-
}, async ({ id }) => {
|
|
215
|
+
}, async ({ id, language }) => {
|
|
212
216
|
if (!id) {
|
|
213
217
|
const lines = docs.examples.map((example) => {
|
|
214
218
|
const where = [
|
|
@@ -221,12 +225,20 @@ server.registerTool('get_mrt_example', {
|
|
|
221
225
|
});
|
|
222
226
|
return text(`Examples:\n${lines.join('\n')}`);
|
|
223
227
|
}
|
|
224
|
-
const
|
|
228
|
+
const lang = language ?? 'ts';
|
|
229
|
+
const source = loadExample(id, lang);
|
|
225
230
|
if (!source) {
|
|
226
|
-
const
|
|
227
|
-
|
|
231
|
+
const known = docs.examples.some((example) => example.id === id);
|
|
232
|
+
if (!known) {
|
|
233
|
+
const suggestions = searchDocs(docs, id, 5, ['example']).map((hit) => hit.name);
|
|
234
|
+
return text(`Unknown example "${id}".${suggestions.length ? ` Did you mean: ${suggestions.join(', ')}?` : ''} Call get_mrt_example without an id to list them.`);
|
|
235
|
+
}
|
|
236
|
+
return text(`No ${lang} source is available for example "${id}".`);
|
|
228
237
|
}
|
|
229
|
-
|
|
238
|
+
const sourceNote = lang === 'js'
|
|
239
|
+
? `// examples/${id}/sandbox/src/TS.tsx (JavaScript, types stripped)`
|
|
240
|
+
: `// examples/${id}/sandbox/src/TS.tsx`;
|
|
241
|
+
return text(`${sourceNote}\n${source}`);
|
|
230
242
|
});
|
|
231
243
|
const transport = new StdioServerTransport();
|
|
232
244
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mini_7/material-react-table-mcp",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "MCP server that lets AI coding agents look up Material React Table V4 table options, column options, state, instance APIs, the V3 to V4 migration guide, and the bundled agent skills.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Tyler Lee",
|