@nexgrid/react 0.1.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/README.md +432 -0
- package/dist/index.cjs +944 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +156 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +943 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
- package/styles.css +931 -0
package/README.md
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# @nexgrid/react
|
|
2
|
+
|
|
3
|
+
A server-driven data grid for React and Next.js.
|
|
4
|
+
|
|
5
|
+
NexGrid renders one page of rows at a time and never holds the dataset. Every
|
|
6
|
+
piece of user intent — page, page size, sort, search, filters — is expressed as
|
|
7
|
+
a single `QueryState` object that **you** own; the grid hands you the next one
|
|
8
|
+
and re-renders when you hand back the matching page. That is the whole contract.
|
|
9
|
+
There is no local sort that quietly reorders 10 rows out of 40,000, and no
|
|
10
|
+
client-side filter that hides records the total still counts.
|
|
11
|
+
|
|
12
|
+
Around that core it provides the things every real admin table ends up needing:
|
|
13
|
+
debounced global search, a sort cycle, column visibility, row density,
|
|
14
|
+
selection, formatted Excel and CSV export, a paginated footer with a page-jump,
|
|
15
|
+
loading / empty / error states, and a card layout for phones — all styled by one
|
|
16
|
+
stylesheet shared with the Angular and vanilla adapters, so the same grid looks
|
|
17
|
+
identical on every platform.
|
|
18
|
+
|
|
19
|
+
- Zero runtime dependencies beyond `@nexgrid/core`. React is a peer dependency.
|
|
20
|
+
- Written for strict TypeScript, generic over your row type.
|
|
21
|
+
- Ships ESM and CJS, with a `"use client"` banner so it drops straight into the
|
|
22
|
+
Next.js App Router.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @nexgrid/react @nexgrid/core
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Import the stylesheet once, anywhere in your app:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import "@nexgrid/react/styles.css";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
The grid is fully controlled. Hold a `QueryState` in state, fetch whenever it
|
|
39
|
+
changes, and pass the result straight through.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
"use client";
|
|
43
|
+
|
|
44
|
+
import { useCallback, useEffect, useState } from "react";
|
|
45
|
+
import {
|
|
46
|
+
NexGrid,
|
|
47
|
+
defaultQuery,
|
|
48
|
+
serializeQuery,
|
|
49
|
+
type NexGridReactColumn,
|
|
50
|
+
type PagedResponse,
|
|
51
|
+
type QueryState,
|
|
52
|
+
} from "@nexgrid/react";
|
|
53
|
+
import "@nexgrid/react/styles.css";
|
|
54
|
+
|
|
55
|
+
interface Student {
|
|
56
|
+
id: number;
|
|
57
|
+
name: string;
|
|
58
|
+
email: string;
|
|
59
|
+
status: "Active" | "Pending" | "Disabled";
|
|
60
|
+
joinedAt: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const columns: NexGridReactColumn<Student>[] = [
|
|
64
|
+
{ accessorKey: "name", header: "Name", meta: { minWidth: 180 } },
|
|
65
|
+
{ accessorKey: "email", header: "Email" },
|
|
66
|
+
{
|
|
67
|
+
accessorKey: "status",
|
|
68
|
+
header: "Status",
|
|
69
|
+
meta: { align: "center", width: 130 },
|
|
70
|
+
cell: ({ getValue }) => {
|
|
71
|
+
const status = String(getValue());
|
|
72
|
+
return <span className={`pill pill--${status.toLowerCase()}`}>{status}</span>;
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
accessorKey: "joinedAt",
|
|
77
|
+
header: "Joined",
|
|
78
|
+
cell: ({ getValue }) => new Date(String(getValue())).toLocaleDateString(),
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
export function StudentsGrid() {
|
|
83
|
+
const [query, setQuery] = useState<QueryState>(defaultQuery());
|
|
84
|
+
const [page, setPage] = useState<PagedResponse<Student> | null>(null);
|
|
85
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
86
|
+
const [error, setError] = useState(false);
|
|
87
|
+
|
|
88
|
+
const load = useCallback(async (next: QueryState) => {
|
|
89
|
+
setIsLoading(true);
|
|
90
|
+
setError(false);
|
|
91
|
+
try {
|
|
92
|
+
// serializeQuery produces ?page=2&pageSize=25&sort=name:asc&q=smith
|
|
93
|
+
const response = await fetch(`/api/students?${serializeQuery(next)}`);
|
|
94
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
95
|
+
setPage((await response.json()) as PagedResponse<Student>);
|
|
96
|
+
} catch {
|
|
97
|
+
setError(true);
|
|
98
|
+
} finally {
|
|
99
|
+
setIsLoading(false);
|
|
100
|
+
}
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
void load(query);
|
|
105
|
+
}, [load, query]);
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<NexGrid
|
|
109
|
+
caption="Students"
|
|
110
|
+
columns={columns}
|
|
111
|
+
data={page?.items ?? []}
|
|
112
|
+
total={page?.total ?? 0}
|
|
113
|
+
query={query}
|
|
114
|
+
onQueryChange={setQuery}
|
|
115
|
+
isLoading={isLoading}
|
|
116
|
+
error={error}
|
|
117
|
+
onRetry={() => void load(query)}
|
|
118
|
+
enableSelection
|
|
119
|
+
onSelectionChange={(ids) => console.log("selected", ids)}
|
|
120
|
+
// Lets an export page in the whole filtered dataset, not just this page.
|
|
121
|
+
fetchEndpoint="/api/students"
|
|
122
|
+
// The grid never renders toasts — forward these to your own.
|
|
123
|
+
onNotify={({ type, message }) => console.info(type, message)}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Your endpoint must answer with a `PagedResponse<T>`:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{ "items": [], "page": 1, "pageSize": 10, "total": 0, "totalPages": 1 }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If your API is ASP.NET Core, `NexGrid.AspNetCore` binds exactly the query string
|
|
136
|
+
`serializeQuery` produces and returns exactly this shape.
|
|
137
|
+
|
|
138
|
+
### Putting the query in the URL
|
|
139
|
+
|
|
140
|
+
`QueryState` round-trips through a query string, so making the grid shareable and
|
|
141
|
+
back-button-friendly is a swap of the state hook:
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
const searchParams = useSearchParams();
|
|
145
|
+
const router = useRouter();
|
|
146
|
+
const query = useMemo(() => parseQuery(searchParams.toString()), [searchParams]);
|
|
147
|
+
|
|
148
|
+
<NexGrid
|
|
149
|
+
query={query}
|
|
150
|
+
onQueryChange={(next) => router.replace(`?${serializeQuery(next)}`)}
|
|
151
|
+
{...rest}
|
|
152
|
+
/>;
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`parseQuery` degrades safely — a bad page becomes 1, an unknown page size
|
|
156
|
+
becomes the default, malformed sort tokens are dropped — so a hand-edited URL can
|
|
157
|
+
never put the grid into an impossible state.
|
|
158
|
+
|
|
159
|
+
## Next.js App Router
|
|
160
|
+
|
|
161
|
+
The published bundle starts with `"use client"`, so `<NexGrid />` can be imported
|
|
162
|
+
directly from a Server Component without a wrapper:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
// app/students/page.tsx — a Server Component
|
|
166
|
+
import { StudentsGrid } from "./students-grid";
|
|
167
|
+
|
|
168
|
+
export default async function Page() {
|
|
169
|
+
return <StudentsGrid />;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Two notes:
|
|
174
|
+
|
|
175
|
+
- Anything you pass through props still crosses the server/client boundary, so
|
|
176
|
+
`columns` (which contains `cell` functions) must be defined in a file marked
|
|
177
|
+
`"use client"`, not in the server page.
|
|
178
|
+
- Import `@nexgrid/react/styles.css` from your root layout, or from the client
|
|
179
|
+
component itself if your setup supports component-level CSS imports.
|
|
180
|
+
|
|
181
|
+
## Props
|
|
182
|
+
|
|
183
|
+
| Prop | Type | Default | Description |
|
|
184
|
+
|------|------|---------|-------------|
|
|
185
|
+
| `columns` | `NexGridReactColumn<TData>[]` | required | Column definitions, in display order. |
|
|
186
|
+
| `data` | `TData[]` | required | The **current page** of rows only. |
|
|
187
|
+
| `total` | `number` | required | Total filtered row count from the server. Drives the pager. |
|
|
188
|
+
| `query` | `QueryState` | required | The query the `data` above answers. |
|
|
189
|
+
| `onQueryChange` | `(next: QueryState) => void` | required | Called with the next query on page / size / sort / search changes. |
|
|
190
|
+
| `caption` | `string` | required | Accessible name for the table; also the default export file name and sheet title. |
|
|
191
|
+
| `density` | `"compact" \| "default" \| "comfortable"` | `"default"` | **Initial** density. The user owns it afterwards. |
|
|
192
|
+
| `isLoading` | `boolean` | `false` | Replaces the rows with a spinner. The toolbar and footer stay usable. |
|
|
193
|
+
| `error` | `boolean` | `false` | Replaces the **whole grid** with an error card. |
|
|
194
|
+
| `onRetry` | `() => void` | — | When set, the error card offers a retry button. |
|
|
195
|
+
| `enableSelection` | `boolean` | `false` | Renders selection checkboxes. |
|
|
196
|
+
| `onSelectionChange` | `(ids: string[], allAcrossSelected: boolean) => void` | — | Fires after each selection change. `allAcrossSelected` is reserved and always `false`. |
|
|
197
|
+
| `enableSearch` | `boolean` | `true` | Shows the debounced global search box. |
|
|
198
|
+
| `searchPlaceholder` | `string` | `locale.searchPlaceholder` | Placeholder text for the search box. |
|
|
199
|
+
| `toolbarActions` | `ReactNode` | — | Rendered at the end of the toolbar, after the export menu. |
|
|
200
|
+
| `onRowClick` | `(row: TData) => void` | — | Row / card click handler. Adds a pointer cursor and makes rows keyboard-activatable. |
|
|
201
|
+
| `getRowId` | `(row: TData) => string` | `String(row.id ?? row)` | Stable row identity, used for selection and React keys. |
|
|
202
|
+
| `className` | `string` | — | Extra class(es) on the grid root. |
|
|
203
|
+
| `showSerialNumber` | `boolean` | `true` | Shows the automatic `S.No.` column, numbered across the whole result set. |
|
|
204
|
+
| `enableExport` | `boolean` | `true` | Shows the export menu. |
|
|
205
|
+
| `exportFileName` | `string` | caption, lower-cased and underscored | File name prefix, without extension. |
|
|
206
|
+
| `onExportAll` | `() => void \| Promise<void>` | — | Takes over exporting entirely; the built-in flow never runs. |
|
|
207
|
+
| `fetchEndpoint` | `string` | — | List endpoint used to page in the rest of the dataset when exporting. |
|
|
208
|
+
| `badgeRules` | `readonly ExcelBadgeRule[]` | core's `DEFAULT_BADGE_RULES` | Value-based cell styling for the Excel export. |
|
|
209
|
+
| `locale` | `Partial<NexGridLocale>` | English defaults | Overrides for any user-facing string. |
|
|
210
|
+
| `onNotify` | `(notice: NexGridNotice) => void` | no-op | Receives `{ type, message }` for export progress, failures, and successes. |
|
|
211
|
+
| `theme` | `"light" \| "dark" \| "auto"` | `"light"` | Adds `.nxg-dark` / `.nxg-auto` to the root. |
|
|
212
|
+
|
|
213
|
+
## Column definitions
|
|
214
|
+
|
|
215
|
+
A column is a plain object, structurally compatible with TanStack Table's
|
|
216
|
+
`ColumnDef` — existing column sets usually work unchanged.
|
|
217
|
+
|
|
218
|
+
| Field | Type | Description |
|
|
219
|
+
|-------|------|-------------|
|
|
220
|
+
| `id` | `string` | Column id. Falls back to `accessorKey`. |
|
|
221
|
+
| `accessorKey` | `string` | The row property this column reads. |
|
|
222
|
+
| `header` | `string \| (ctx) => ReactNode` | Header content. A string is also used for menus and export headers. |
|
|
223
|
+
| `cell` | `(ctx: { row: { original: TData }, getValue(): unknown }) => ReactNode` | Custom cell renderer. Without it the raw value is rendered as text. |
|
|
224
|
+
| `enableSorting` | `boolean` | Sorting is on by default; set `false` to opt out. |
|
|
225
|
+
| `meta` | `NexGridColumnMeta` | Layout and behavior hints — see below. |
|
|
226
|
+
|
|
227
|
+
### `meta`
|
|
228
|
+
|
|
229
|
+
| Field | Type | Description |
|
|
230
|
+
|-------|------|-------------|
|
|
231
|
+
| `width` | `number` | Fixed pixel width. |
|
|
232
|
+
| `minWidth` | `number` | Minimum width in pixels. Defaults to `120` when no `width` is set. |
|
|
233
|
+
| `align` | `"left" \| "center" \| "right"` | Header and cell alignment. |
|
|
234
|
+
| `hidden` | `boolean` | Start hidden. Still listed in the Columns menu. |
|
|
235
|
+
| `hideable` | `boolean` | Set `false` to keep the column out of the Columns menu. |
|
|
236
|
+
| `exportable` | `boolean` | Set `false` to keep the column out of CSV/Excel exports. |
|
|
237
|
+
| `serverFilterable`, `serverFilterField`, `filterOptions` | — | Declare a column as server-filterable (`filter[field]=value`). |
|
|
238
|
+
|
|
239
|
+
Two ids are structural: `select` and `actions`. They are never sortable, never
|
|
240
|
+
hideable, and never exported.
|
|
241
|
+
|
|
242
|
+
### Custom cells
|
|
243
|
+
|
|
244
|
+
`cell` returns any `ReactNode`, and the same renderer is used by the table and
|
|
245
|
+
the mobile card list, so the two can never drift apart.
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
const columns: NexGridReactColumn<Student>[] = [
|
|
249
|
+
// A status pill.
|
|
250
|
+
{
|
|
251
|
+
accessorKey: "status",
|
|
252
|
+
header: "Status",
|
|
253
|
+
meta: { align: "center", width: 130 },
|
|
254
|
+
cell: ({ getValue }) => {
|
|
255
|
+
const status = String(getValue());
|
|
256
|
+
return <span className={`pill pill--${status.toLowerCase()}`}>{status}</span>;
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
// Composed from more than one field — reach through `row.original`.
|
|
261
|
+
{
|
|
262
|
+
id: "student",
|
|
263
|
+
header: "Student",
|
|
264
|
+
cell: ({ row }) => (
|
|
265
|
+
<div className="stack">
|
|
266
|
+
<strong>{row.original.name}</strong>
|
|
267
|
+
<small>{row.original.email}</small>
|
|
268
|
+
</div>
|
|
269
|
+
),
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
// A row action menu. `actions` is structural: unsortable and never exported.
|
|
273
|
+
{
|
|
274
|
+
id: "actions",
|
|
275
|
+
header: "",
|
|
276
|
+
meta: { align: "right", width: 64 },
|
|
277
|
+
cell: ({ row }) => (
|
|
278
|
+
<button type="button" onClick={(event) => event.stopPropagation()}>
|
|
279
|
+
Edit
|
|
280
|
+
</button>
|
|
281
|
+
),
|
|
282
|
+
},
|
|
283
|
+
];
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Two things worth knowing:
|
|
287
|
+
|
|
288
|
+
- Exports read the **underlying row value**, not the rendered cell. A custom cell
|
|
289
|
+
is presentation; the `status` column above exports `Active`, not the markup of
|
|
290
|
+
the pill. Set `meta.exportable: false` on columns that carry no data.
|
|
291
|
+
- When `onRowClick` is set, call `event.stopPropagation()` in interactive cell
|
|
292
|
+
content so a button click does not also open the row. Selection checkboxes
|
|
293
|
+
already do this for you.
|
|
294
|
+
|
|
295
|
+
## Theming
|
|
296
|
+
|
|
297
|
+
Every color and shape in the stylesheet reads a CSS custom property, so you
|
|
298
|
+
re-skin the grid by overriding tokens — no class overrides, no `!important`.
|
|
299
|
+
|
|
300
|
+
```css
|
|
301
|
+
.nxg-root {
|
|
302
|
+
--nxg-primary: #7c3aed;
|
|
303
|
+
--nxg-primary-fg: #ffffff;
|
|
304
|
+
--nxg-radius: 8px;
|
|
305
|
+
--nxg-font: "Inter", system-ui, sans-serif;
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
| Token | Purpose |
|
|
310
|
+
|-------|---------|
|
|
311
|
+
| `--nxg-font`, `--nxg-font-mono` | Body font, and the serial-number font. |
|
|
312
|
+
| `--nxg-bg` | Input and pager background. |
|
|
313
|
+
| `--nxg-card`, `--nxg-card-2` | Panel background, and the table header band. |
|
|
314
|
+
| `--nxg-border` | Every border and divider. |
|
|
315
|
+
| `--nxg-fg`, `--nxg-muted-fg` | Primary and secondary text. |
|
|
316
|
+
| `--nxg-muted` | Hover fills and subtle chips. |
|
|
317
|
+
| `--nxg-primary`, `--nxg-primary-fg` | Accent: sort icons, current page, selection. |
|
|
318
|
+
| `--nxg-danger` | Destructive accents. |
|
|
319
|
+
| `--nxg-radius`, `--nxg-radius-sm` | Panel and control corner radii. |
|
|
320
|
+
| `--nxg-shadow`, `--nxg-focus-ring` | Elevation, and the focus ring. |
|
|
321
|
+
|
|
322
|
+
Dark mode is a class, not a media query, so it can follow whatever your app
|
|
323
|
+
already uses:
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
<NexGrid theme="dark" {...props} /> {/* always dark */}
|
|
327
|
+
<NexGrid theme="auto" {...props} /> {/* follows the OS */}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
`theme="dark"` puts `.nxg-dark` on the grid root. If your app already toggles a
|
|
331
|
+
dark class higher up the tree, add `nxg-dark` alongside it and leave `theme`
|
|
332
|
+
alone — the stylesheet matches `.nxg-dark .nxg-root` as well.
|
|
333
|
+
|
|
334
|
+
Responsive behavior is driven entirely by the stylesheet: the grid renders both a
|
|
335
|
+
table and a card list, and CSS shows the table at ≥ 768px and the cards below it.
|
|
336
|
+
|
|
337
|
+
## Exporting
|
|
338
|
+
|
|
339
|
+
The export menu offers a formatted Excel workbook (`.xls`, with colored status
|
|
340
|
+
badges) and a raw CSV (RFC 4180, UTF-8 BOM, with spreadsheet-formula injection
|
|
341
|
+
neutralized). Both write the **visible** columns, minus anything marked
|
|
342
|
+
`meta.exportable: false`.
|
|
343
|
+
|
|
344
|
+
By default an export contains the current page. Pass `fetchEndpoint` and the grid
|
|
345
|
+
will page through the rest of the filtered dataset first, preserving the active
|
|
346
|
+
search, sort, and filters, at 100 rows per request up to a 2,000-row safety cap.
|
|
347
|
+
If those requests fail it notifies you and falls back to the current page rather
|
|
348
|
+
than producing nothing.
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
<NexGrid
|
|
352
|
+
fetchEndpoint="/api/students"
|
|
353
|
+
exportFileName="student_roster"
|
|
354
|
+
badgeRules={[
|
|
355
|
+
{ values: ["Active"], background: "#dcfce7", color: "#15803d" },
|
|
356
|
+
{ values: ["Disabled"], background: "#fee2e2", color: "#b91c1c" },
|
|
357
|
+
]}
|
|
358
|
+
onNotify={({ type, message }) => toast[type](message)}
|
|
359
|
+
{...props}
|
|
360
|
+
/>
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
To export server-side instead — a real `.xlsx`, a background job, a signed
|
|
364
|
+
download URL — pass `onExportAll`. It replaces the built-in flow completely.
|
|
365
|
+
|
|
366
|
+
## Notifications
|
|
367
|
+
|
|
368
|
+
The grid never renders toasts. A toast belongs to your design system, and two
|
|
369
|
+
competing toast stacks in one page is a worse bug than no toast at all. Anything
|
|
370
|
+
the grid wants to say arrives at `onNotify` as `{ type, message }` where `type`
|
|
371
|
+
is `"info" | "success" | "error"`, ready to forward to whatever you already use.
|
|
372
|
+
|
|
373
|
+
## Localization
|
|
374
|
+
|
|
375
|
+
Every user-facing string comes from a locale object. Override any subset:
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
<NexGrid
|
|
379
|
+
locale={{
|
|
380
|
+
searchPlaceholder: "Rechercher…",
|
|
381
|
+
emptyText: "Aucun enregistrement ne correspond à votre recherche.",
|
|
382
|
+
showingRange: "Affichage de {start} à {end} sur {total} entrées",
|
|
383
|
+
rowsPerPage: "Lignes :",
|
|
384
|
+
}}
|
|
385
|
+
{...props}
|
|
386
|
+
/>
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Templates keep their `{placeholder}` tokens so word order stays yours. Import
|
|
390
|
+
`DEFAULT_LOCALE` from this package to see every key.
|
|
391
|
+
|
|
392
|
+
## Accessibility
|
|
393
|
+
|
|
394
|
+
- `aria-label={caption}` on the table; every icon-only control has an accessible
|
|
395
|
+
name drawn from the locale.
|
|
396
|
+
- Sortable headers are focusable, activate on Enter or Space, and carry
|
|
397
|
+
`aria-sort` (`ascending` / `descending` / `none`).
|
|
398
|
+
- Menus are `role="menu"` with `role="menuitemcheckbox"` and `aria-checked`
|
|
399
|
+
items; they close on outside click and on Escape, which returns focus to the
|
|
400
|
+
trigger.
|
|
401
|
+
- The header checkbox reports the mixed state when only part of a page is
|
|
402
|
+
selected.
|
|
403
|
+
- The current page button carries `aria-current="page"`.
|
|
404
|
+
- When `onRowClick` is set, rows and cards become focusable and activate on
|
|
405
|
+
Enter. Keep genuinely interactive content in a cell rather than relying on the
|
|
406
|
+
row handler alone.
|
|
407
|
+
|
|
408
|
+
## Re-exported from `@nexgrid/core`
|
|
409
|
+
|
|
410
|
+
For convenience, the pieces a host needs to drive a controlled grid are
|
|
411
|
+
re-exported from this package, so most apps never import `@nexgrid/core`
|
|
412
|
+
directly:
|
|
413
|
+
|
|
414
|
+
`defaultQuery`, `parseQuery`, `serializeQuery`, `buildQueryUrl`, `primarySort`,
|
|
415
|
+
`withToggledSort`, `withSort`, `withSearch`, `withPage`, `withPageSize`,
|
|
416
|
+
`withFilter`, `totalPagesFor`, `isPageSize`, `PAGE_SIZES`, `DEFAULT_PAGE_SIZE`,
|
|
417
|
+
`DEFAULT_LOCALE`, `resolveLocale`.
|
|
418
|
+
|
|
419
|
+
Always mutate a `QueryState` through those reducers rather than spreading it by
|
|
420
|
+
hand — they are what guarantee that a search or a page-size change resets to page
|
|
421
|
+
one and that the sort cycle stays `asc → desc → cleared` across every adapter.
|
|
422
|
+
|
|
423
|
+
## Author & Maintainer
|
|
424
|
+
|
|
425
|
+
**Chhagan Sinha**
|
|
426
|
+
- 📧 Contact: [sinhachhagan@outlook.com](mailto:sinhachhagan@outlook.com)
|
|
427
|
+
- 🐙 GitHub: [@ChhaganSinha](https://github.com/ChhaganSinha)
|
|
428
|
+
|
|
429
|
+
## License
|
|
430
|
+
|
|
431
|
+
[MIT](https://github.com/ChhaganSinha/NexGrid/blob/main/LICENSE) © 2026 Chhagan Sinha
|
|
432
|
+
|