munza-x-data-grid 3.0.0-beta.9 → 3.0.2
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 +471 -187
- package/dist/components/ui/button.d.ts +10 -0
- package/dist/components/ui/checkbox.d.ts +4 -0
- package/dist/components/ui/collapsible.d.ts +5 -0
- package/dist/components/ui/debounced-input.d.ts +7 -0
- package/dist/components/ui/dropdown-menu.d.ts +29 -0
- package/dist/components/ui/empty.d.ts +11 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/label.d.ts +4 -0
- package/dist/components/ui/native-select.d.ts +8 -0
- package/dist/components/ui/skeleton.d.ts +2 -0
- package/dist/components/ui/table.d.ts +10 -0
- package/dist/index.cjs +60 -0
- package/dist/index.css +56 -0
- package/dist/index.d.ts +1 -7
- package/dist/index.js +11184 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/package/components/feedback/NoDataFoundMsg.d.ts +2 -0
- package/dist/package/components/feedback/TableError.d.ts +2 -0
- package/dist/package/components/feedback/TableErrorMsg.d.ts +2 -0
- package/dist/package/components/feedback/TableLeftNoData.d.ts +2 -0
- package/dist/package/components/feedback/TableLeftSkeleton.d.ts +2 -0
- package/dist/package/components/feedback/TableNoData.d.ts +2 -0
- package/dist/package/components/feedback/TableRightNoData.d.ts +2 -0
- package/dist/package/components/feedback/TableRightSkeleton.d.ts +2 -0
- package/dist/package/components/feedback/TableRowSkeleton.d.ts +7 -0
- package/dist/package/components/feedback/TableSkeleton.d.ts +2 -0
- package/dist/package/components/header/HeaderFilter.d.ts +5 -0
- package/dist/package/components/header/HeaderMenu.d.ts +5 -0
- package/dist/package/components/header/HeaderResizing.d.ts +5 -0
- package/dist/package/components/header/HeaderSort.d.ts +5 -0
- package/dist/package/components/pagination/index.d.ts +4 -0
- package/dist/package/components/table/TBody.d.ts +3 -0
- package/dist/package/components/table/TCell.d.ts +6 -0
- package/dist/package/components/table/TGetLeftBody.d.ts +3 -0
- package/dist/package/components/table/TGetLeftHeader.d.ts +2 -0
- package/dist/package/components/table/TGetLeftMain.d.ts +2 -0
- package/dist/package/components/table/TGetRightBody.d.ts +3 -0
- package/dist/package/components/table/TGetRightHeader.d.ts +2 -0
- package/dist/package/components/table/TGetRightMain.d.ts +2 -0
- package/dist/package/components/table/THead.d.ts +6 -0
- package/dist/package/components/table/THeader.d.ts +2 -0
- package/dist/package/components/table/TMain.d.ts +3 -0
- package/dist/package/components/table/TableCellPin.d.ts +6 -0
- package/dist/package/components/table/TableLeftCellPin.d.ts +5 -0
- package/dist/package/components/table/TableRightCellPin.d.ts +5 -0
- package/dist/package/components/toolbar/ToolbarColumns.d.ts +2 -0
- package/dist/package/components/toolbar/ToolbarDnd.d.ts +2 -0
- package/dist/package/components/toolbar/ToolbarFilters.d.ts +3 -0
- package/dist/package/components/toolbar/ToolbarRows.d.ts +2 -0
- package/dist/package/components/toolbar/ToolbarSettings.d.ts +2 -0
- package/dist/package/components/toolbar/index.d.ts +4 -0
- package/dist/package/contexts/GridContext.d.ts +56 -0
- package/dist/package/core/index.d.ts +3 -0
- package/dist/package/features/columnOrder.d.ts +4 -0
- package/dist/package/features/columnPinning.d.ts +5 -0
- package/dist/package/features/columnSizing.d.ts +5 -0
- package/dist/package/features/columnVisibility.d.ts +5 -0
- package/dist/package/features/rowDensity.d.ts +24 -0
- package/dist/package/features/rowPinning.d.ts +5 -0
- package/dist/package/features/splitView.d.ts +4 -0
- package/dist/package/hooks/useGridState.d.ts +19 -0
- package/dist/package/hooks/useQueryArgs.d.ts +12 -0
- package/dist/package/hooks/useSyncScroll.d.ts +7 -0
- package/dist/package/index.d.ts +8 -0
- package/dist/package/types/index.d.ts +303 -0
- package/dist/package/utils/URLSearch.d.ts +21 -0
- package/dist/package/utils/buildQueryString.d.ts +2 -0
- package/dist/package/utils/buildSortString.d.ts +4 -0
- package/dist/package/utils/getPinStyles.d.ts +3 -0
- package/package.json +63 -49
- package/dist/App.d.ts +0 -2
- package/dist/favicon.svg +0 -1
- package/dist/icons.svg +0 -24
- package/dist/main.d.ts +0 -1
- package/dist/munza-x-data-grid.cjs.js +0 -5
- package/dist/munza-x-data-grid.es.js +0 -29
- package/dist/style.css +0 -3
package/README.md
CHANGED
|
@@ -1,288 +1,572 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Grid Component — Usage Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`Grid` is a reusable data-table component built on top of `@tanstack/react-table`, with built-in support for sorting, filtering, pagination, column pinning, row pinning, column resizing, and more.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
5
|
+
## 1. Installation
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
8
|
npm install munza-x-data-grid
|
|
9
|
+
# or
|
|
10
|
+
yarn add munza-x-data-grid
|
|
11
|
+
# or
|
|
12
|
+
pnpm add munza-x-data-grid
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
**Peer dependencies** (must already be installed in your project):
|
|
14
16
|
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
+
```bash
|
|
18
|
+
npm install react@^18 || ^19 react-dom@^18 || ^19
|
|
17
19
|
```
|
|
18
20
|
|
|
19
|
-
###
|
|
21
|
+
### Importing the stylesheet
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
npm install react react-dom
|
|
23
|
-
```
|
|
23
|
+
The package ships with a CSS file that needs to be imported once at your app's entry point (e.g. `main.tsx` or `App.tsx`):
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
```tsx
|
|
26
|
+
import 'munza-x-data-grid/style.css';
|
|
27
|
+
```
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
### Imports
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
Everything — components, hooks, utilities, and types — is exported from the single package entry point:
|
|
30
32
|
|
|
31
33
|
```tsx
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
+
import {
|
|
35
|
+
Grid,
|
|
36
|
+
useGrid,
|
|
37
|
+
useGridState,
|
|
38
|
+
useQueryArgs,
|
|
39
|
+
URLSearch,
|
|
40
|
+
type ColumnDef,
|
|
41
|
+
} from 'munza-x-data-grid';
|
|
42
|
+
```
|
|
34
43
|
|
|
35
|
-
|
|
36
|
-
firstName: string;
|
|
37
|
-
lastName: string;
|
|
38
|
-
age: number;
|
|
39
|
-
};
|
|
44
|
+
> Note: if you're importing from a local source within the same repo (monorepo / linked package), the path may differ, e.g. `./package` or `../package`.
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
{ accessorKey: 'firstName', header: 'First Name' },
|
|
43
|
-
{ accessorKey: 'lastName', header: 'Last Name' },
|
|
44
|
-
{ accessorKey: 'age', header: 'Age' },
|
|
45
|
-
];
|
|
46
|
+
## 2. Basic Usage
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
```tsx
|
|
49
|
+
import { useMemo } from 'react';
|
|
50
|
+
import { Grid, useGridState, type ColumnDef } from 'munza-x-data-grid';
|
|
51
|
+
|
|
52
|
+
const App = () => {
|
|
53
|
+
const columns = useMemo<ColumnDef<MyDataType, unknown>[]>(
|
|
54
|
+
() => [
|
|
55
|
+
{
|
|
56
|
+
id: 'id',
|
|
57
|
+
accessorKey: 'id',
|
|
58
|
+
header: () => <div>ID</div>,
|
|
59
|
+
cell: (info) => info.getValue(),
|
|
60
|
+
meta: { filterVariant: 'text' },
|
|
61
|
+
enableHiding: false,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'name',
|
|
65
|
+
accessorKey: 'name',
|
|
66
|
+
header: () => <div>Name</div>,
|
|
67
|
+
cell: (info) => info.getValue(),
|
|
68
|
+
meta: { filterVariant: 'text' },
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
[],
|
|
72
|
+
);
|
|
48
73
|
|
|
49
|
-
export default function App() {
|
|
50
74
|
const { state, handlers } = useGridState();
|
|
51
75
|
|
|
52
76
|
return (
|
|
53
77
|
<Grid
|
|
78
|
+
payload={{ data: myData, total: 0 }}
|
|
54
79
|
columns={columns}
|
|
55
|
-
|
|
80
|
+
isLoading={false}
|
|
81
|
+
isError={false}
|
|
82
|
+
manualPagination={true}
|
|
56
83
|
state={state}
|
|
57
84
|
{...handlers}
|
|
58
85
|
/>
|
|
59
86
|
);
|
|
60
|
-
}
|
|
61
|
-
```
|
|
87
|
+
};
|
|
62
88
|
|
|
63
|
-
|
|
89
|
+
export default App;
|
|
90
|
+
```
|
|
64
91
|
|
|
65
|
-
##
|
|
92
|
+
## 3. The `useGridState` Hook
|
|
66
93
|
|
|
67
|
-
|
|
94
|
+
Use this hook to manage all table state (`columnFilters`, `pagination`, `sorting`, `rowSelection`, `globalFilter`) and their change handlers in one place.
|
|
68
95
|
|
|
69
|
-
|
|
96
|
+
```tsx
|
|
97
|
+
const { state, handlers, rowSelection } = useGridState();
|
|
70
98
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
| `onSortingChange` | `OnChangeFn<SortingState>` | — | Callback for sort changes |
|
|
79
|
-
| `setGlobalFilter` | `Dispatch<SetStateAction<string>>` | — | Callback to update the global search filter |
|
|
80
|
-
| `isLoading` | `boolean` | — | Displays a loading state |
|
|
81
|
-
| `isError` | `boolean` | — | Displays an error state |
|
|
82
|
-
| `manualPagination` | `boolean` | — | Set `true` for server-side pagination (default: `false`) |
|
|
83
|
-
| `getRowCanExpand` | `(row: Row<T>) => boolean` | — | Controls which rows are expandable |
|
|
84
|
-
| `renderSubComponent` | `(props: { row: Row<T> }) => ReactElement` | — | Renders expanded row content |
|
|
99
|
+
<Grid
|
|
100
|
+
columns={columns}
|
|
101
|
+
payload={{ data, total }}
|
|
102
|
+
state={state}
|
|
103
|
+
{...handlers}
|
|
104
|
+
/>;
|
|
105
|
+
```
|
|
85
106
|
|
|
86
|
-
|
|
107
|
+
`handlers` includes:
|
|
87
108
|
|
|
88
|
-
|
|
109
|
+
- `onColumnFiltersChange`
|
|
110
|
+
- `onPaginationChange`
|
|
111
|
+
- `onSortingChange`
|
|
112
|
+
- `setGlobalFilter`
|
|
113
|
+
- `onRowSelectionChange`
|
|
89
114
|
|
|
90
|
-
|
|
115
|
+
You can also use pieces separately, e.g. tracking selected rows via `rowSelection`:
|
|
91
116
|
|
|
92
117
|
```tsx
|
|
93
|
-
|
|
118
|
+
console.log(rowSelection);
|
|
94
119
|
```
|
|
95
120
|
|
|
96
|
-
|
|
121
|
+
## 4. `Grid` Component Props
|
|
122
|
+
|
|
123
|
+
| Prop | Type | Description |
|
|
124
|
+
| ----------------------- | ------------------------------------------ | ----------------------------------------------------------------- |
|
|
125
|
+
| `columns` | `ColumnDef<T>[]` | Table column definitions (required) |
|
|
126
|
+
| `payload` | `{ data: T[]; total: number }` | Table data and total row count |
|
|
127
|
+
| `state` | `Partial<TableState>` | State obtained from `useGridState` |
|
|
128
|
+
| `onColumnFiltersChange` | `OnChangeFn<ColumnFiltersState>` | Handler for column filter changes |
|
|
129
|
+
| `onPaginationChange` | `OnChangeFn<PaginationState>` | Handler for pagination changes |
|
|
130
|
+
| `onSortingChange` | `OnChangeFn<SortingState>` | Handler for sorting changes |
|
|
131
|
+
| `onRowSelectionChange` | `OnChangeFn<RowSelectionState>` | Handler for row selection changes |
|
|
132
|
+
| `setGlobalFilter` | `Dispatch<SetStateAction<string>>` | Function to set the global search/filter |
|
|
133
|
+
| `manualPagination` | `boolean` | Set to `true` to enable server-side pagination |
|
|
134
|
+
| `enableRowSelection` | `boolean` | Enables/disables row selection (default `true`) |
|
|
135
|
+
| `isLoading` | `boolean` | Shows the loading state |
|
|
136
|
+
| `isError` | `boolean` | Shows the error state |
|
|
137
|
+
| `isFetching` | `boolean` | For a refetching indicator |
|
|
138
|
+
| `refetch` | `() => void` | Function to reload the data |
|
|
139
|
+
| `renderSubComponent` | `(props: { row: Row<T> }) => ReactElement` | Custom content for expandable rows |
|
|
140
|
+
| `getRowCanExpand` | `(row: Row<T>) => boolean` | Determines which rows can be expanded |
|
|
141
|
+
| `height` | `string` | Table height (default `'65vh'`) |
|
|
142
|
+
| `isToolbar` | `boolean` | Whether to show the toolbar (default `true`) |
|
|
143
|
+
| `isPagination` | `boolean` | Whether to show the pagination component (default `true`) |
|
|
144
|
+
| `name` | `string` | Unique grid name, used to persist local state (default `'munza'`) |
|
|
145
|
+
| `children` | `ReactNode` | Renders extra content inside the grid |
|
|
146
|
+
|
|
147
|
+
## 5. Special Column Definition Features
|
|
148
|
+
|
|
149
|
+
### Filter variant
|
|
150
|
+
|
|
151
|
+
Set `meta.filterVariant` on each column to determine the filter type. The following variants are supported:
|
|
152
|
+
|
|
153
|
+
- `'text'`
|
|
154
|
+
- `'number'`
|
|
155
|
+
- `'tel'`
|
|
156
|
+
- `'url'`
|
|
157
|
+
- `'color'`
|
|
158
|
+
- `'range'`
|
|
159
|
+
- `'select'`
|
|
160
|
+
- `'dateRange'`
|
|
161
|
+
- `'date'`
|
|
162
|
+
- `'datetime-local'`
|
|
163
|
+
- `'month'`
|
|
164
|
+
- `'time'`
|
|
165
|
+
- `'week'`
|
|
97
166
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
|
101
|
-
|
|
167
|
+
```tsx
|
|
168
|
+
meta: {
|
|
169
|
+
filterVariant: 'text', // 'number' | 'tel' | 'url' | 'color' | 'range' | 'select' | 'dateRange' | 'date' | 'datetime-local' | 'month' | 'time' | 'week'
|
|
170
|
+
},
|
|
171
|
+
```
|
|
102
172
|
|
|
103
|
-
|
|
173
|
+
Example — a numeric range filter:
|
|
104
174
|
|
|
105
175
|
```tsx
|
|
106
|
-
|
|
176
|
+
{
|
|
177
|
+
id: 'price',
|
|
178
|
+
accessorKey: 'price',
|
|
179
|
+
header: () => <div>Price</div>,
|
|
180
|
+
cell: (info) => info.getValue(),
|
|
181
|
+
meta: { filterVariant: 'range' },
|
|
182
|
+
},
|
|
107
183
|
```
|
|
108
184
|
|
|
109
|
-
|
|
185
|
+
Example — a date filter:
|
|
110
186
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
--color-accent: var(--accent);
|
|
121
|
-
}
|
|
187
|
+
```tsx
|
|
188
|
+
{
|
|
189
|
+
id: 'createdAt',
|
|
190
|
+
accessorKey: 'createdAt',
|
|
191
|
+
header: () => <div>Created At</div>,
|
|
192
|
+
cell: (info) => info.getValue(),
|
|
193
|
+
meta: { filterVariant: 'date' },
|
|
194
|
+
},
|
|
195
|
+
```
|
|
122
196
|
|
|
123
|
-
:
|
|
124
|
-
--background: oklch(1 0 0);
|
|
125
|
-
--foreground: oklch(0.145 0 0);
|
|
126
|
-
--border: oklch(0.922 0 0);
|
|
127
|
-
--muted: oklch(0.97 0 0);
|
|
128
|
-
--accent: oklch(0.97 0 0);
|
|
129
|
-
}
|
|
197
|
+
To disable filtering on a specific column:
|
|
130
198
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
--foreground: oklch(0.985 0 0);
|
|
134
|
-
--border: oklch(1 0 0 / 10%);
|
|
135
|
-
--muted: oklch(0.269 0 0);
|
|
136
|
-
--accent: oklch(0.269 0 0);
|
|
137
|
-
}
|
|
199
|
+
```tsx
|
|
200
|
+
enableColumnFilter: false,
|
|
138
201
|
```
|
|
139
202
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
## Column Definitions
|
|
203
|
+
### Row pinning
|
|
143
204
|
|
|
144
|
-
|
|
205
|
+
Use `row.pin()` and `row.getIsPinned()` to pin/unpin rows:
|
|
145
206
|
|
|
146
|
-
|
|
207
|
+
```tsx
|
|
208
|
+
cell: ({ row }) =>
|
|
209
|
+
row.getIsPinned() ? (
|
|
210
|
+
<button onClick={() => row.pin(false)}>❌</button>
|
|
211
|
+
) : (
|
|
212
|
+
<>
|
|
213
|
+
<button onClick={() => row.pin('top')}>⬆️</button>
|
|
214
|
+
<button onClick={() => row.pin('bottom')}>⬇️</button>
|
|
215
|
+
</>
|
|
216
|
+
),
|
|
217
|
+
```
|
|
147
218
|
|
|
148
|
-
|
|
219
|
+
### Row number column
|
|
149
220
|
|
|
150
221
|
```tsx
|
|
151
222
|
{
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
223
|
+
accessorFn: (_row, index) => index + 1,
|
|
224
|
+
id: 'rowNumber',
|
|
225
|
+
header: '#',
|
|
226
|
+
cell: ({ row, table }) => {
|
|
227
|
+
const { pageIndex, pageSize } = table.getState().pagination;
|
|
228
|
+
return pageIndex * pageSize + row.index + 1;
|
|
156
229
|
},
|
|
157
|
-
}
|
|
230
|
+
},
|
|
231
|
+
```
|
|
158
232
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
},
|
|
165
|
-
}
|
|
233
|
+
### Column size limits
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
size: 52,
|
|
237
|
+
maxSize: 52,
|
|
166
238
|
```
|
|
167
239
|
|
|
168
|
-
###
|
|
240
|
+
### Non-hideable column
|
|
169
241
|
|
|
170
242
|
```tsx
|
|
171
|
-
|
|
172
|
-
accessorFn: (_row, index) => index + 1,
|
|
173
|
-
cell: ({ row }) => row.index + 1,
|
|
174
|
-
id: 'rowNumber',
|
|
175
|
-
header: '',
|
|
176
|
-
size: 54,
|
|
177
|
-
maxSize: 54,
|
|
178
|
-
enableColumnFilter: false,
|
|
179
|
-
}
|
|
243
|
+
enableHiding: false,
|
|
180
244
|
```
|
|
181
245
|
|
|
182
|
-
|
|
246
|
+
## 6. Building Server-Side Queries
|
|
247
|
+
|
|
248
|
+
Use the `useQueryArgs` hook to build query parameters from the table state that can be sent to an API:
|
|
183
249
|
|
|
184
250
|
```tsx
|
|
185
|
-
{
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
<Checkbox
|
|
189
|
-
checked={
|
|
190
|
-
table.getIsAllPageRowsSelected() ||
|
|
191
|
-
(table.getIsSomePageRowsSelected() && 'indeterminate')
|
|
192
|
-
}
|
|
193
|
-
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
|
194
|
-
aria-label="Select all"
|
|
195
|
-
/>
|
|
196
|
-
),
|
|
197
|
-
cell: ({ row }) => (
|
|
198
|
-
<Checkbox
|
|
199
|
-
checked={row.getIsSelected()}
|
|
200
|
-
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
|
201
|
-
aria-label="Select row"
|
|
202
|
-
/>
|
|
203
|
-
),
|
|
204
|
-
size: 40,
|
|
205
|
-
maxSize: 40,
|
|
206
|
-
enableColumnFilter: false,
|
|
207
|
-
}
|
|
251
|
+
import { useQueryArgs } from 'munza-x-data-grid';
|
|
252
|
+
|
|
253
|
+
const { pagination, queryParams, sort, globalFilter } = useQueryArgs(state);
|
|
208
254
|
```
|
|
209
255
|
|
|
210
|
-
|
|
256
|
+
(`useQueryArgs` is exported from the same main entry point, no separate sub-path needed.)
|
|
257
|
+
|
|
258
|
+
It returns:
|
|
259
|
+
|
|
260
|
+
- `pagination` — current page/page size
|
|
261
|
+
- `queryParams` — query parameters built from column filters
|
|
262
|
+
- `sort` — sort string
|
|
263
|
+
- `globalFilter` — global search text
|
|
264
|
+
|
|
265
|
+
## 7. Syncing State to the URL — `URLSearch`
|
|
211
266
|
|
|
212
|
-
|
|
267
|
+
`URLSearch` converts the grid's query state (`QueryArgs`) into a MongoDB-style query string, so you can sync table state to the URL or forward it straight to a REST API that understands Mongo-style filter operators.
|
|
213
268
|
|
|
214
|
-
|
|
269
|
+
### Import
|
|
215
270
|
|
|
216
271
|
```tsx
|
|
217
|
-
|
|
218
|
-
columns={columns}
|
|
219
|
-
payload={{ data: serverData, total: serverTotal }}
|
|
220
|
-
state={state}
|
|
221
|
-
{...handlers}
|
|
222
|
-
manualPagination={true}
|
|
223
|
-
/>
|
|
272
|
+
import { URLSearch } from 'munza-x-data-grid';
|
|
224
273
|
```
|
|
225
274
|
|
|
226
|
-
|
|
275
|
+
### `QueryArgs` shape
|
|
227
276
|
|
|
277
|
+
```ts
|
|
278
|
+
interface ColumnFilter {
|
|
279
|
+
id: string;
|
|
280
|
+
value: unknown;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
interface SortingItem {
|
|
284
|
+
id: string;
|
|
285
|
+
desc: boolean;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
interface Pagination {
|
|
289
|
+
pageIndex: number;
|
|
290
|
+
pageSize: number;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
interface QueryArgs {
|
|
294
|
+
columnFilters: ColumnFilter[];
|
|
295
|
+
globalFilter?: string;
|
|
296
|
+
pagination: Pagination;
|
|
297
|
+
sorting: SortingItem[];
|
|
298
|
+
rowSelection?: Record<string, boolean>;
|
|
299
|
+
}
|
|
228
300
|
```
|
|
229
|
-
|
|
301
|
+
|
|
302
|
+
### Basic usage
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
const { state } = useGridState();
|
|
306
|
+
|
|
307
|
+
console.log(URLSearch(state));
|
|
308
|
+
// e.g. "?page=1&limit=20&sort=-createdAt&q=john"
|
|
230
309
|
```
|
|
231
310
|
|
|
232
|
-
|
|
311
|
+
### What it does
|
|
312
|
+
|
|
313
|
+
`URLSearch(queryArgs: QueryArgs): string` builds a `URLSearchParams` string with the following rules:
|
|
314
|
+
|
|
315
|
+
| State | Resulting query param(s) |
|
|
316
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------- |
|
|
317
|
+
| `pagination.pageIndex` | `page` (1-indexed, so `pageIndex + 1`) |
|
|
318
|
+
| `pagination.pageSize` | `limit` |
|
|
319
|
+
| `sorting` | `sort` — comma-separated column ids, prefixed with `-` for descending (e.g. `sort=-price,name`) |
|
|
320
|
+
| `globalFilter` | `q` — only included if non-empty |
|
|
321
|
+
| `columnFilters` | one or more params per filter, see below |
|
|
322
|
+
|
|
323
|
+
### Column filter handling
|
|
324
|
+
|
|
325
|
+
Each entry in `columnFilters` is passed through `appendColumnFilter`, which converts the filter's `id` into dot-notation (underscores become dots — useful for nested/populated fields) and then branches on the shape of `value`:
|
|
326
|
+
|
|
327
|
+
1. **Range value** — a 2-item array (e.g. from a `range` or `dateRange` filter):
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
{ id: 'price', value: [10, 100] }
|
|
331
|
+
// → price[gte]=10&price[lte]=100
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Empty/`null` bounds are skipped, so `[null, 100]` only sets `price[lte]=100`.
|
|
335
|
+
|
|
336
|
+
2. **Operator object** — a plain object whose keys are MongoDB-style operators:
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
{ id: 'status', value: { in: ['active', 'pending'] } }
|
|
340
|
+
// → status[in]=active,pending
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Only keys present in the supported operator list are included; unsupported keys and empty/`null` values are ignored.
|
|
233
344
|
|
|
234
|
-
|
|
345
|
+
3. **Plain scalar value** — anything else (string, number, boolean):
|
|
346
|
+
```ts
|
|
347
|
+
{ id: 'name', value: 'john' }
|
|
348
|
+
// → name=john
|
|
349
|
+
```
|
|
350
|
+
Empty strings and `null`/`undefined` are skipped entirely.
|
|
351
|
+
|
|
352
|
+
### Supported MongoDB-style operators
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
eq, ne, gt, gte, lt, lte, in, nin, regex, exists, all, size,
|
|
356
|
+
elemMatch, type, mod, not, and, or, nor, text, where,
|
|
357
|
+
geoWithin, geoIntersects, near, nearSphere, expr, jsonSchema,
|
|
358
|
+
bitsAllClear, bitsAllSet, bitsAnyClear, bitsAnySet, rand
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Any operator outside this list is silently dropped from the query string.
|
|
362
|
+
|
|
363
|
+
### Full example
|
|
235
364
|
|
|
236
365
|
```tsx
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
366
|
+
const queryArgs: QueryArgs = {
|
|
367
|
+
pagination: { pageIndex: 0, pageSize: 20 },
|
|
368
|
+
sorting: [{ id: 'createdAt', desc: true }],
|
|
369
|
+
globalFilter: 'john',
|
|
370
|
+
columnFilters: [
|
|
371
|
+
{ id: 'price', value: [10, 100] },
|
|
372
|
+
{ id: 'status', value: { in: ['active', 'pending'] } },
|
|
373
|
+
{ id: 'department_name', value: 'Engineering' },
|
|
374
|
+
],
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
URLSearch(queryArgs);
|
|
378
|
+
// "?page=1&limit=20&price[gte]=10&price[lte]=100&status[in]=active%2Cpending&department.name=Engineering&sort=-createdAt&q=john"
|
|
249
379
|
```
|
|
250
380
|
|
|
251
|
-
|
|
381
|
+
> Note: `department_name` becomes `department.name` — underscores in a column `id` are converted to dots, which is handy for filtering on nested/populated document fields in MongoDB-backed APIs.
|
|
252
382
|
|
|
253
|
-
##
|
|
383
|
+
## 8. Customizing via `useGrid` (inside `<Grid>` children)
|
|
254
384
|
|
|
255
|
-
|
|
385
|
+
`Grid` exposes its internal context — the live `@tanstack/react-table` `table` instance, density, split-view state, column pinning, scroll pane refs, and more — through the `useGrid()` hook. This lets you build custom toolbar buttons, headers, footers, or any other UI that needs access to the table without having to lift state yourself.
|
|
256
386
|
|
|
257
|
-
|
|
387
|
+
> **Important:** `useGrid()` only works **inside** `<Grid>` — i.e. in a component rendered as a _child_ of `<Grid>`. Calling it in the same component that renders `<Grid>` (like your `App` component) will throw, because the context provider wraps `Grid`'s children, not `Grid` itself.
|
|
258
388
|
|
|
259
|
-
|
|
389
|
+
### Import
|
|
260
390
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
| `minSize` | `180px` |
|
|
265
|
-
| `maxSize` | `180px` |
|
|
391
|
+
```tsx
|
|
392
|
+
import { Grid, useGrid } from 'munza-x-data-grid';
|
|
393
|
+
```
|
|
266
394
|
|
|
267
|
-
|
|
395
|
+
### What `useGrid()` returns
|
|
396
|
+
|
|
397
|
+
| Property | Type | Description |
|
|
398
|
+
| -------------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
399
|
+
| `table` | `Table<T>` | The underlying `@tanstack/react-table` instance — full access to rows, columns, filtering/sorting/pagination APIs, etc. |
|
|
400
|
+
| `density` | `DensityState` | Current row density (e.g. compact/comfortable) |
|
|
401
|
+
| `isSplit` / `setIsSplit` | `boolean` / setter | Split-view state, for side-by-side pinned/unpinned panes |
|
|
402
|
+
| `columnPinning` | `ColumnPinningState` | Current left/right pinned columns |
|
|
403
|
+
| `globalFilter` | `string` | The current global search text |
|
|
404
|
+
| `setGlobalFilter` | setter | Update the global search text |
|
|
405
|
+
| `isLoading` / `isError` / `isFetching` | `boolean` | Data-fetch status flags passed down to `Grid` |
|
|
406
|
+
| `refetch` | `() => void` | Re-triggers the data fetch, if provided to `Grid` |
|
|
407
|
+
| `height` | `string` | The table height passed to `Grid` |
|
|
408
|
+
| `renderSubComponent` | function | The expanded-row renderer passed to `Grid` |
|
|
409
|
+
| `gridWrapperRef` | `RefObject<HTMLDivElement>` | Ref to the grid's outer wrapper element |
|
|
410
|
+
| `paneRef1`–`paneRef6` | `RefObject<HTMLDivElement>` | Refs to the internal scroll panes (pinned-left/main/pinned-right, header/body), used for scroll syncing |
|
|
411
|
+
|
|
412
|
+
### Example — a custom toolbar button using `table`
|
|
268
413
|
|
|
269
414
|
```tsx
|
|
270
|
-
{
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
415
|
+
import { Grid, useGrid, useGridState } from 'munza-x-data-grid';
|
|
416
|
+
|
|
417
|
+
const ResetFiltersButton = () => {
|
|
418
|
+
const { table } = useGrid();
|
|
419
|
+
|
|
420
|
+
return (
|
|
421
|
+
<button onClick={() => table.resetColumnFilters()}>Clear filters</button>
|
|
422
|
+
);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const App = () => {
|
|
426
|
+
const { state, handlers } = useGridState();
|
|
427
|
+
|
|
428
|
+
return (
|
|
429
|
+
<Grid
|
|
430
|
+
payload={{ data, total }}
|
|
431
|
+
columns={columns}
|
|
432
|
+
state={state}
|
|
433
|
+
{...handlers}
|
|
434
|
+
>
|
|
435
|
+
<ResetFiltersButton />
|
|
436
|
+
</Grid>
|
|
437
|
+
);
|
|
438
|
+
};
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Example — reading selected rows from `table`
|
|
442
|
+
|
|
443
|
+
```tsx
|
|
444
|
+
const SelectionSummary = () => {
|
|
445
|
+
const { table } = useGrid();
|
|
446
|
+
const selectedCount = table.getSelectedRowModel().rows.length;
|
|
447
|
+
|
|
448
|
+
return <span>{selectedCount} row(s) selected</span>;
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
<Grid columns={columns} payload={{ data, total }} state={state} {...handlers}>
|
|
452
|
+
<SelectionSummary />
|
|
453
|
+
</Grid>;
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Example — toggling density or split view
|
|
457
|
+
|
|
458
|
+
```tsx
|
|
459
|
+
const ViewControls = () => {
|
|
460
|
+
const { density, isSplit, setIsSplit } = useGrid();
|
|
461
|
+
|
|
462
|
+
return (
|
|
463
|
+
<div>
|
|
464
|
+
<span>Density: {density}</span>
|
|
465
|
+
<button onClick={() => setIsSplit((prev) => !prev)}>
|
|
466
|
+
{isSplit ? 'Unsplit view' : 'Split view'}
|
|
467
|
+
</button>
|
|
468
|
+
</div>
|
|
469
|
+
);
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
<Grid columns={columns} payload={{ data, total }} state={state} {...handlers}>
|
|
473
|
+
<ViewControls />
|
|
474
|
+
</Grid>;
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## 9. Theming
|
|
478
|
+
|
|
479
|
+
`Grid` is styled with CSS variables, so you can restyle it (including light/dark mode) without touching component code. Define these variables in your global stylesheet — the `:root` block sets the light theme, and the `.dark` block overrides it for dark mode (e.g. toggled via a `dark` class on `<html>` or `<body>`).
|
|
480
|
+
|
|
481
|
+
```css
|
|
482
|
+
:root {
|
|
483
|
+
--background: oklch(100% 0 0);
|
|
484
|
+
--foreground: oklch(14.5% 0 0);
|
|
485
|
+
--primary: oklch(20.5% 0 0);
|
|
486
|
+
--primary-foreground: oklch(98.5% 0 0);
|
|
487
|
+
--secondary: oklch(97% 0 0);
|
|
488
|
+
--secondary-foreground: oklch(20.5% 0 0);
|
|
489
|
+
--muted: oklch(97% 0 0);
|
|
490
|
+
--muted-foreground: oklch(55.6% 0 0);
|
|
491
|
+
--accent: oklch(97% 0 0);
|
|
492
|
+
--accent-foreground: oklch(20.5% 0 0);
|
|
493
|
+
--destructive: oklch(57.7% 0.245 27.325);
|
|
494
|
+
--border: oklch(92.2% 0 0);
|
|
495
|
+
--input: oklch(92.2% 0 0);
|
|
496
|
+
--ring: oklch(70.8% 0 0);
|
|
497
|
+
--radius: 0.625rem;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.dark {
|
|
501
|
+
--background: oklch(14.5% 0 0);
|
|
502
|
+
--foreground: oklch(98.5% 0 0);
|
|
503
|
+
--primary: oklch(92.2% 0 0);
|
|
504
|
+
--primary-foreground: oklch(20.5% 0 0);
|
|
505
|
+
--secondary: oklch(26.9% 0 0);
|
|
506
|
+
--secondary-foreground: oklch(98.5% 0 0);
|
|
507
|
+
--muted: oklch(26.9% 0 0);
|
|
508
|
+
--muted-foreground: oklch(70.8% 0 0);
|
|
509
|
+
--accent: oklch(26.9% 0 0);
|
|
510
|
+
--accent-foreground: oklch(98.5% 0 0);
|
|
511
|
+
--destructive: oklch(70.4% 0.191 22.216);
|
|
512
|
+
--border: oklch(100% 0 0/0.1);
|
|
513
|
+
--input: oklch(100% 0 0/0.15);
|
|
514
|
+
--ring: oklch(55.6% 0 0);
|
|
275
515
|
}
|
|
276
516
|
```
|
|
277
517
|
|
|
278
|
-
|
|
518
|
+
### Where to put it
|
|
279
519
|
|
|
280
|
-
|
|
520
|
+
Add this CSS **after** the package stylesheet import, so your custom values override the defaults:
|
|
281
521
|
|
|
282
|
-
|
|
522
|
+
```tsx
|
|
523
|
+
import 'munza-x-data-grid/style.css';
|
|
524
|
+
import './theme.css'; // your theme variables above
|
|
525
|
+
```
|
|
283
526
|
|
|
284
|
-
|
|
285
|
-
|
|
527
|
+
### Variable reference
|
|
528
|
+
|
|
529
|
+
| Variable | Used for |
|
|
530
|
+
| ------------------------ | ------------------------------------------------------------ |
|
|
531
|
+
| `--background` | Base grid/page background |
|
|
532
|
+
| `--foreground` | Default text color |
|
|
533
|
+
| `--primary` | Primary buttons/actions, active states |
|
|
534
|
+
| `--primary-foreground` | Text/icon color on top of `--primary` |
|
|
535
|
+
| `--secondary` | Secondary buttons, toolbar surfaces |
|
|
536
|
+
| `--secondary-foreground` | Text/icon color on top of `--secondary` |
|
|
537
|
+
| `--muted` | Subtle backgrounds (e.g. striped rows, disabled states) |
|
|
538
|
+
| `--muted-foreground` | Secondary/lower-emphasis text |
|
|
539
|
+
| `--accent` | Hover/highlight backgrounds |
|
|
540
|
+
| `--accent-foreground` | Text/icon color on top of `--accent` |
|
|
541
|
+
| `--destructive` | Error states, destructive actions (e.g. delete) |
|
|
542
|
+
| `--border` | Table/cell/input borders |
|
|
543
|
+
| `--input` | Input field borders/backgrounds |
|
|
544
|
+
| `--ring` | Focus ring color |
|
|
545
|
+
| `--radius` | Base border radius, used to derive component corner rounding |
|
|
546
|
+
|
|
547
|
+
### Toggling dark mode
|
|
548
|
+
|
|
549
|
+
Add/remove the `dark` class on a parent element (commonly `<html>`):
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
document.documentElement.classList.toggle('dark');
|
|
286
553
|
```
|
|
287
554
|
|
|
288
|
-
|
|
555
|
+
## 10. Example: Full Flow
|
|
556
|
+
|
|
557
|
+
```tsx
|
|
558
|
+
const { state, handlers } = useGridState();
|
|
559
|
+
const queryArgs = useQueryArgs(state);
|
|
560
|
+
|
|
561
|
+
// Call your API with queryArgs, then pass the result as payload
|
|
562
|
+
<Grid
|
|
563
|
+
payload={{ data: apiResponse.data, total: apiResponse.total }}
|
|
564
|
+
columns={columns}
|
|
565
|
+
manualPagination
|
|
566
|
+
isLoading={isFetching}
|
|
567
|
+
isError={isError}
|
|
568
|
+
refetch={refetch}
|
|
569
|
+
state={state}
|
|
570
|
+
{...handlers}
|
|
571
|
+
/>;
|
|
572
|
+
```
|