tabler-react-2 0.1.153 → 0.1.154
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/dist/index.js +17 -5
- package/dist/table/index.js +143 -35
- package/dist/table/useTable.js +192 -0
- package/dist/table-v2/index.js +328 -0
- package/docs/gatsby-node.js +36 -0
- package/docs/package.json +1 -1
- package/docs/src/components/LoadableTabler.jsx +3 -0
- package/docs/src/components/Tabler.jsx +128 -51
- package/docs/src/config/sidebar.yml +2 -0
- package/docs/src/data/congressPeople.json +88 -0
- package/docs/src/docs/changelog.mdx +4 -0
- package/docs/src/docs/components/table-v2.mdx +367 -0
- package/docs/src/docs/components/tables.mdx +228 -0
- package/package.json +6 -2
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tables v2
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
import { Excerpt } from "../../components/Excerpt.jsx";
|
|
6
|
+
import { TableV2 } from "../../components/LoadableTabler.jsx";
|
|
7
|
+
import congressPeople from "../../data/congressPeople.json";
|
|
8
|
+
|
|
9
|
+
Tables v2 is a new data table built on TanStack Table v8 with Tabler styling. It is fully controlled: you pass the current page of `data`, the total row count, and the active `sorting`. This design fits server-side pagination and ordering out of the box.
|
|
10
|
+
|
|
11
|
+
> Note: live previews on this page may require a package version that exports `TableV2`. If you don’t see live examples, the code snippets still illustrate usage.
|
|
12
|
+
|
|
13
|
+
## Signature
|
|
14
|
+
|
|
15
|
+
```jsx
|
|
16
|
+
import { TableV2 } from "tabler-react-2";
|
|
17
|
+
|
|
18
|
+
<TableV2 {...props} />;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Core props
|
|
22
|
+
|
|
23
|
+
| Prop | Required | Type | Description |
|
|
24
|
+
| ---------------------- | -------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
25
|
+
| `columns` | Yes | TanStack `ColumnDef[]` | Column definitions using `accessorKey`, `header`, and optional `cell` renderers. |
|
|
26
|
+
| `data` | Yes | `any[]` | Current page rows only. |
|
|
27
|
+
| `totalRows` | Yes | `number` | Total available row count (for page count and range text). |
|
|
28
|
+
| `page` | Yes | `number` | Current page (1-based). |
|
|
29
|
+
| `size` | Yes | `number` | Current page size. |
|
|
30
|
+
| `onPageChange` | Yes | `(page:number) => void` | Called with next 1-based page. |
|
|
31
|
+
| `onSizeChange` | Yes | `(size:number) => void` | Called with next page size. Typically also reset page to 1. |
|
|
32
|
+
| `sorting` | Yes | `{ id:string, desc?:boolean }[]` | Active ordering (TanStack format). Empty array for “no sort”. |
|
|
33
|
+
| `onSortingChange` | Yes | `(next) => void` | Called when the user clicks a sortable header. |
|
|
34
|
+
| `loading` | No | `boolean` | Shows a small spinner and disables pager while loading. |
|
|
35
|
+
| `headerSticky` | No | `boolean` | Makes the header stick to the top of the card. |
|
|
36
|
+
| `emptyState` | No | `string \| () => ReactNode` | What to render when there are no rows. |
|
|
37
|
+
| `getRowId` | No | `(row) => string` | Supply when your rows need a stable custom ID. |
|
|
38
|
+
| `rowSelection` | No | `Record<string, boolean>` | Controlled selection map. |
|
|
39
|
+
| `onRowSelectionChange` | No | `(updater) => void` | Controlled selection callback. |
|
|
40
|
+
| `renderToolbarLeft` | No | `({ page,size,totalRows,sorting }) => ReactNode` | Custom left content in the card header. |
|
|
41
|
+
| `renderToolbarRight` | No | same | Custom right content in the card header. |
|
|
42
|
+
|
|
43
|
+
## Basic usage (client or server data)
|
|
44
|
+
|
|
45
|
+
The table is controlled; keep `page`, `size`, and `sorting` in your component state. The example below uses in-memory data, but the same shape works with server APIs.
|
|
46
|
+
|
|
47
|
+
```jsx
|
|
48
|
+
import { useMemo, useState } from "react";
|
|
49
|
+
import { TableV2 } from "tabler-react-2";
|
|
50
|
+
|
|
51
|
+
const allRows = congressPeople; // e.g., from your API
|
|
52
|
+
|
|
53
|
+
export default function Demo() {
|
|
54
|
+
const [page, setPage] = useState(1);
|
|
55
|
+
const [size, setSize] = useState(5);
|
|
56
|
+
const [sorting, setSorting] = useState([]); // [{ id:'name', desc:false }]
|
|
57
|
+
|
|
58
|
+
const columns = useMemo(
|
|
59
|
+
() => [
|
|
60
|
+
{ accessorKey: "name", header: "Name" },
|
|
61
|
+
{ accessorKey: "party", header: "Party" },
|
|
62
|
+
{ accessorKey: "region.state", header: "State" },
|
|
63
|
+
{
|
|
64
|
+
accessorKey: "email",
|
|
65
|
+
header: "Email",
|
|
66
|
+
cell: ({ getValue }) => (
|
|
67
|
+
<a className="text-reset" href={`mailto:${getValue()}`}>
|
|
68
|
+
{getValue()}
|
|
69
|
+
</a>
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
[]
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// client-side slice just for the demo
|
|
77
|
+
const ordered = useMemo(() => {
|
|
78
|
+
if (!sorting.length) return allRows;
|
|
79
|
+
const { id, desc } = sorting[0];
|
|
80
|
+
const val = (row) => id.split(".").reduce((a, k) => a?.[k], row);
|
|
81
|
+
const cmp = (a, b) => (a === b ? 0 : a > b ? 1 : -1);
|
|
82
|
+
const sorted = [...allRows].sort((a, b) => cmp(val(a), val(b)));
|
|
83
|
+
return desc ? sorted.reverse() : sorted;
|
|
84
|
+
}, [sorting]);
|
|
85
|
+
|
|
86
|
+
const start = (page - 1) * size;
|
|
87
|
+
const pageData = ordered.slice(start, start + size);
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<TableV2
|
|
91
|
+
columns={columns}
|
|
92
|
+
data={pageData}
|
|
93
|
+
totalRows={allRows.length}
|
|
94
|
+
page={page}
|
|
95
|
+
size={size}
|
|
96
|
+
onPageChange={setPage}
|
|
97
|
+
onSizeChange={(n) => {
|
|
98
|
+
setPage(1);
|
|
99
|
+
setSize(n);
|
|
100
|
+
}}
|
|
101
|
+
sorting={sorting}
|
|
102
|
+
onSortingChange={(next) => {
|
|
103
|
+
setPage(1);
|
|
104
|
+
setSorting(next);
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
<Excerpt>
|
|
112
|
+
{(() => {
|
|
113
|
+
const React = require("react");
|
|
114
|
+
const { useMemo, useState } = React;
|
|
115
|
+
|
|
116
|
+
// Local, fully functional demo state (client-side slice)
|
|
117
|
+
const allRows = congressPeople;
|
|
118
|
+
const [page, setPage] = useState(1);
|
|
119
|
+
const [size, setSize] = useState(10);
|
|
120
|
+
const [sorting, setSorting] = useState([]); // [{ id:'name', desc:false }]
|
|
121
|
+
|
|
122
|
+
const ordered = useMemo(() => {
|
|
123
|
+
if (!sorting.length) return allRows;
|
|
124
|
+
const { id, desc } = sorting[0];
|
|
125
|
+
const val = (row) => id.split(".").reduce((a, k) => a?.[k], row);
|
|
126
|
+
const cmp = (a, b) => (a === b ? 0 : a > b ? 1 : -1);
|
|
127
|
+
const sorted = [...allRows].sort((a, b) => cmp(val(a), val(b)));
|
|
128
|
+
return desc ? sorted.reverse() : sorted;
|
|
129
|
+
}, [sorting]);
|
|
130
|
+
|
|
131
|
+
const start = (page - 1) * size;
|
|
132
|
+
const pageData = ordered.slice(start, start + size);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<TableV2
|
|
136
|
+
columns={[
|
|
137
|
+
{ accessorKey: "name", header: "Name" },
|
|
138
|
+
{ accessorKey: "party", header: "Party" },
|
|
139
|
+
{ accessorKey: "region.state", header: "State" },
|
|
140
|
+
{
|
|
141
|
+
accessorKey: "email",
|
|
142
|
+
header: "Email",
|
|
143
|
+
cell: ({ getValue }) => (
|
|
144
|
+
<a className="text-reset" href={`mailto:${getValue()}`}>
|
|
145
|
+
{getValue()}
|
|
146
|
+
</a>
|
|
147
|
+
),
|
|
148
|
+
},
|
|
149
|
+
]}
|
|
150
|
+
data={pageData}
|
|
151
|
+
totalRows={allRows.length}
|
|
152
|
+
page={page}
|
|
153
|
+
size={size}
|
|
154
|
+
onPageChange={setPage}
|
|
155
|
+
onSizeChange={(n) => {
|
|
156
|
+
setPage(1);
|
|
157
|
+
setSize(n);
|
|
158
|
+
}}
|
|
159
|
+
sorting={sorting}
|
|
160
|
+
onSortingChange={(next) => {
|
|
161
|
+
setPage(1);
|
|
162
|
+
setSorting(next);
|
|
163
|
+
}}
|
|
164
|
+
loading={false}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
})()}
|
|
168
|
+
|
|
169
|
+
</Excerpt>
|
|
170
|
+
|
|
171
|
+
## Server-side workflow
|
|
172
|
+
|
|
173
|
+
When fetching from an API, keep the same controlled state, but fetch rows for the current `page`, `size`, and `sorting`.
|
|
174
|
+
|
|
175
|
+
```jsx
|
|
176
|
+
const [page, setPage] = useState(1);
|
|
177
|
+
const [size, setSize] = useState(25);
|
|
178
|
+
const [sorting, setSorting] = useState([]);
|
|
179
|
+
const [rows, setRows] = useState([]);
|
|
180
|
+
const [total, setTotal] = useState(0);
|
|
181
|
+
const [loading, setLoading] = useState(false);
|
|
182
|
+
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
let isMounted = true;
|
|
185
|
+
(async () => {
|
|
186
|
+
setLoading(true);
|
|
187
|
+
try {
|
|
188
|
+
const res = await fetchPeople({ page, size, sorting });
|
|
189
|
+
if (!isMounted) return;
|
|
190
|
+
setRows(res.items);
|
|
191
|
+
setTotal(res.total);
|
|
192
|
+
} finally {
|
|
193
|
+
setLoading(false);
|
|
194
|
+
}
|
|
195
|
+
})();
|
|
196
|
+
return () => {
|
|
197
|
+
isMounted = false;
|
|
198
|
+
};
|
|
199
|
+
}, [page, size, sorting]);
|
|
200
|
+
|
|
201
|
+
<TableV2
|
|
202
|
+
columns={columns}
|
|
203
|
+
data={rows}
|
|
204
|
+
totalRows={total}
|
|
205
|
+
page={page}
|
|
206
|
+
size={size}
|
|
207
|
+
onPageChange={setPage}
|
|
208
|
+
onSizeChange={(n) => {
|
|
209
|
+
setPage(1);
|
|
210
|
+
setSize(n);
|
|
211
|
+
}}
|
|
212
|
+
sorting={sorting}
|
|
213
|
+
onSortingChange={(next) => {
|
|
214
|
+
setPage(1);
|
|
215
|
+
setSorting(next);
|
|
216
|
+
}}
|
|
217
|
+
loading={loading}
|
|
218
|
+
/>;
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Live async demo (simulated API)
|
|
222
|
+
|
|
223
|
+
The preview below simulates a server by sorting and slicing in a faux `fetch` with a 1s delay.
|
|
224
|
+
|
|
225
|
+
<Excerpt>
|
|
226
|
+
{(() => {
|
|
227
|
+
const React = require("react");
|
|
228
|
+
const { useEffect, useMemo, useState } = React;
|
|
229
|
+
|
|
230
|
+
// Simulated API that delays 1s and then returns sorted, paged rows
|
|
231
|
+
const fetchPeople = ({ page, size, sorting }) =>
|
|
232
|
+
new Promise((resolve) => {
|
|
233
|
+
setTimeout(() => {
|
|
234
|
+
const all = congressPeople;
|
|
235
|
+
const total = all.length;
|
|
236
|
+
|
|
237
|
+
// server-side order
|
|
238
|
+
let ordered = all;
|
|
239
|
+
if (sorting?.length) {
|
|
240
|
+
const { id, desc } = sorting[0];
|
|
241
|
+
const val = (row) => id.split(".").reduce((a, k) => a?.[k], row);
|
|
242
|
+
const cmp = (a, b) => (a === b ? 0 : a > b ? 1 : -1);
|
|
243
|
+
const sorted = [...all].sort((a, b) => cmp(val(a), val(b)));
|
|
244
|
+
ordered = desc ? sorted.reverse() : sorted;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// server-side page
|
|
248
|
+
const start = (page - 1) * size;
|
|
249
|
+
const items = ordered.slice(start, start + size);
|
|
250
|
+
resolve({ items, total });
|
|
251
|
+
}, 1000);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const columns = useMemo(
|
|
255
|
+
() => [
|
|
256
|
+
{ accessorKey: "name", header: "Name" },
|
|
257
|
+
{ accessorKey: "party", header: "Party" },
|
|
258
|
+
{ accessorKey: "region.state", header: "State" },
|
|
259
|
+
{
|
|
260
|
+
accessorKey: "email",
|
|
261
|
+
header: "Email",
|
|
262
|
+
cell: ({ getValue }) => (
|
|
263
|
+
<a className="text-reset" href={`mailto:${getValue()}`}>
|
|
264
|
+
{getValue()}
|
|
265
|
+
</a>
|
|
266
|
+
),
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
[]
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const [page, setPage] = useState(1);
|
|
273
|
+
const [size, setSize] = useState(10);
|
|
274
|
+
const [sorting, setSorting] = useState([]);
|
|
275
|
+
const [rows, setRows] = useState([]);
|
|
276
|
+
const [total, setTotal] = useState(0);
|
|
277
|
+
const [loading, setLoading] = useState(false);
|
|
278
|
+
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
let isMounted = true;
|
|
281
|
+
setLoading(true);
|
|
282
|
+
fetchPeople({ page, size, sorting }).then((res) => {
|
|
283
|
+
if (!isMounted) return;
|
|
284
|
+
setRows(res.items);
|
|
285
|
+
setTotal(res.total);
|
|
286
|
+
setLoading(false);
|
|
287
|
+
});
|
|
288
|
+
return () => {
|
|
289
|
+
isMounted = false;
|
|
290
|
+
};
|
|
291
|
+
}, [page, size, sorting]);
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<TableV2
|
|
295
|
+
columns={columns}
|
|
296
|
+
data={rows}
|
|
297
|
+
totalRows={total}
|
|
298
|
+
page={page}
|
|
299
|
+
size={size}
|
|
300
|
+
onPageChange={setPage}
|
|
301
|
+
onSizeChange={(n) => {
|
|
302
|
+
setPage(1);
|
|
303
|
+
setSize(n);
|
|
304
|
+
}}
|
|
305
|
+
sorting={sorting}
|
|
306
|
+
onSortingChange={(next) => {
|
|
307
|
+
setPage(1);
|
|
308
|
+
setSorting(next);
|
|
309
|
+
}}
|
|
310
|
+
loading={loading}
|
|
311
|
+
/>
|
|
312
|
+
);
|
|
313
|
+
})()}
|
|
314
|
+
|
|
315
|
+
</Excerpt>
|
|
316
|
+
|
|
317
|
+
## Extras
|
|
318
|
+
|
|
319
|
+
- Sticky header: pass `headerSticky` to keep headers visible while scrolling.
|
|
320
|
+
- Toolbars: use `renderToolbarLeft`/`renderToolbarRight` to add filters or actions.
|
|
321
|
+
- Row selection: control with `rowSelection` and `onRowSelectionChange`.
|
|
322
|
+
|
|
323
|
+
### Sticky header example
|
|
324
|
+
|
|
325
|
+
```jsx
|
|
326
|
+
<TableV2 {...commonProps} headerSticky />
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Toolbar example
|
|
330
|
+
|
|
331
|
+
```jsx
|
|
332
|
+
<TableV2
|
|
333
|
+
{...commonProps}
|
|
334
|
+
renderToolbarLeft={() => (
|
|
335
|
+
<input className="form-control form-control-sm" placeholder="Search" />
|
|
336
|
+
)}
|
|
337
|
+
renderToolbarRight={() => (
|
|
338
|
+
<button className="btn btn-sm btn-primary">Add</button>
|
|
339
|
+
)}
|
|
340
|
+
/>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Row selection (minimal)
|
|
344
|
+
|
|
345
|
+
```jsx
|
|
346
|
+
const [rowSelection, setRowSelection] = useState({});
|
|
347
|
+
|
|
348
|
+
<TableV2
|
|
349
|
+
{...commonProps}
|
|
350
|
+
rowSelection={rowSelection}
|
|
351
|
+
onRowSelectionChange={setRowSelection}
|
|
352
|
+
columns={[
|
|
353
|
+
{
|
|
354
|
+
id: "select",
|
|
355
|
+
header: () => null,
|
|
356
|
+
cell: ({ row }) => (
|
|
357
|
+
<input
|
|
358
|
+
type="checkbox"
|
|
359
|
+
checked={row.getIsSelected()}
|
|
360
|
+
onChange={row.getToggleSelectedHandler()}
|
|
361
|
+
/>
|
|
362
|
+
),
|
|
363
|
+
},
|
|
364
|
+
...columns,
|
|
365
|
+
]}
|
|
366
|
+
/>;
|
|
367
|
+
```
|
|
@@ -454,3 +454,231 @@ const customSortFn = (a, b) => {
|
|
|
454
454
|
```
|
|
455
455
|
|
|
456
456
|
> **Note**: Sorting only works for the data in the table. If, for example, you have thousands of rows and are only rendering a few, you will need to handle the sorting yourself, outside of the table. Incorporating internal pagination that supports sorting is a planned feature but not yet implemented.
|
|
457
|
+
|
|
458
|
+
### External ordering
|
|
459
|
+
|
|
460
|
+
You can control ordering from the parent (e.g., server-side sort) and still use the table’s sortable headers. Provide the props below. When controlled, the table will not sort your `data`; it assumes you already passed rows in the desired order.
|
|
461
|
+
|
|
462
|
+
- orderBy: accessor of the active column
|
|
463
|
+
- order: either `"asc"` or `"desc"`
|
|
464
|
+
- onSetOrder: called with `(orderBy, order)` when the user clicks a header
|
|
465
|
+
|
|
466
|
+
<Excerpt>
|
|
467
|
+
{(() => {
|
|
468
|
+
const React = require("react");
|
|
469
|
+
const [page, setPage] = React.useState(1);
|
|
470
|
+
const [size, setSize] = React.useState(5);
|
|
471
|
+
const [orderBy, setOrderBy] = React.useState("name");
|
|
472
|
+
const [order, setOrder] = React.useState("asc");
|
|
473
|
+
const total = congressPeople.length;
|
|
474
|
+
|
|
475
|
+
const sorted = [...congressPeople].sort((a, b) => {
|
|
476
|
+
const av = orderBy.split('.').reduce((acc, k) => acc?.[k], a);
|
|
477
|
+
const bv = orderBy.split('.').reduce((acc, k) => acc?.[k], b);
|
|
478
|
+
if (av === bv) return 0;
|
|
479
|
+
const cmp = av > bv ? 1 : -1;
|
|
480
|
+
return order === 'asc' ? cmp : -cmp;
|
|
481
|
+
});
|
|
482
|
+
const start = (page - 1) * size;
|
|
483
|
+
const pageData = sorted.slice(start, start + size);
|
|
484
|
+
|
|
485
|
+
return (
|
|
486
|
+
<Table
|
|
487
|
+
columns={[
|
|
488
|
+
{ label: "Name", accessor: "name", sortable: true },
|
|
489
|
+
{ label: "Party", accessor: "party", sortable: true },
|
|
490
|
+
{ label: "State", accessor: "region.state", sortable: true },
|
|
491
|
+
]}
|
|
492
|
+
data={pageData}
|
|
493
|
+
showPagination
|
|
494
|
+
page={page}
|
|
495
|
+
size={size}
|
|
496
|
+
totalRows={total}
|
|
497
|
+
orderBy={orderBy}
|
|
498
|
+
order={order}
|
|
499
|
+
onSetOrder={(by, dir) => { setOrderBy(by); setOrder(dir); }}
|
|
500
|
+
onSetPage={setPage}
|
|
501
|
+
onSetSize={(n) => { setPage(1); setSize(n); }}
|
|
502
|
+
/>
|
|
503
|
+
);
|
|
504
|
+
})()}
|
|
505
|
+
</Excerpt>
|
|
506
|
+
|
|
507
|
+
```jsx
|
|
508
|
+
// Parent controls ordering and pagination
|
|
509
|
+
const [orderBy, setOrderBy] = useState('name');
|
|
510
|
+
const [order, setOrder] = useState('asc');
|
|
511
|
+
const [page, setPage] = useState(1);
|
|
512
|
+
const [size, setSize] = useState(10);
|
|
513
|
+
|
|
514
|
+
const ordered = [...allRows].sort((a, b) => {
|
|
515
|
+
const av = orderBy.split('.').reduce((acc, k) => acc?.[k], a);
|
|
516
|
+
const bv = orderBy.split('.').reduce((acc, k) => acc?.[k], b);
|
|
517
|
+
if (av === bv) return 0;
|
|
518
|
+
const cmp = av > bv ? 1 : -1;
|
|
519
|
+
return order === 'asc' ? cmp : -cmp;
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
const start = (page - 1) * size;
|
|
523
|
+
const currentData = ordered.slice(start, start + size);
|
|
524
|
+
|
|
525
|
+
<Table
|
|
526
|
+
columns={columns}
|
|
527
|
+
data={currentData}
|
|
528
|
+
showPagination
|
|
529
|
+
page={page}
|
|
530
|
+
size={size}
|
|
531
|
+
totalRows={allRows.length}
|
|
532
|
+
orderBy={orderBy}
|
|
533
|
+
order={order}
|
|
534
|
+
onSetOrder={(by, dir) => { setOrderBy(by); setOrder(dir); }}
|
|
535
|
+
onSetPage={setPage}
|
|
536
|
+
onSetSize={(n) => { setPage(1); setSize(n); }}
|
|
537
|
+
/>;
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## Pagination
|
|
541
|
+
|
|
542
|
+
The `Table` supports built-in pagination UI. Enable it with `showPagination`.
|
|
543
|
+
|
|
544
|
+
- showPagination: toggles the pagination controls
|
|
545
|
+
- defaultRowsPerPage: initial rows per page for internal mode
|
|
546
|
+
- pageSizeOptions: selectable page sizes (default `[10, 25, 50, 100]`)
|
|
547
|
+
|
|
548
|
+
<Excerpt>
|
|
549
|
+
<Table
|
|
550
|
+
columns={[
|
|
551
|
+
{ label: "Name", accessor: "name" },
|
|
552
|
+
{ label: "Party", accessor: "party" },
|
|
553
|
+
{ label: "State", accessor: "region.state" },
|
|
554
|
+
]}
|
|
555
|
+
data={congressPeople}
|
|
556
|
+
showPagination
|
|
557
|
+
defaultRowsPerPage={10}
|
|
558
|
+
/>
|
|
559
|
+
</Excerpt>
|
|
560
|
+
|
|
561
|
+
```jsx
|
|
562
|
+
<Table
|
|
563
|
+
columns={columns}
|
|
564
|
+
data={rows}
|
|
565
|
+
showPagination
|
|
566
|
+
defaultRowsPerPage={10}
|
|
567
|
+
pageSizeOptions={[10, 25, 50, 100]}
|
|
568
|
+
/>
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### External pagination
|
|
572
|
+
|
|
573
|
+
You can also fully control pagination from the parent (e.g., server-side paging) while keeping the same UI. Provide the controlled props and callbacks below. When any controlled pagination prop is provided, the table does not slice your `data` and instead assumes `data` already contains the current page.
|
|
574
|
+
|
|
575
|
+
- page: current page (1-based)
|
|
576
|
+
- size: current page size
|
|
577
|
+
- totalRows: total available row count used for page count and the “Showing … of …” text
|
|
578
|
+
- onSetPage: called with next page (1-based)
|
|
579
|
+
- onSetSize: called with next page size
|
|
580
|
+
|
|
581
|
+
Aliases are supported for compatibility: `rowsPerPage`, `onPageChange`, `onRowsPerPageChange`.
|
|
582
|
+
|
|
583
|
+
<Excerpt>
|
|
584
|
+
{(() => {
|
|
585
|
+
// Simulated external pagination
|
|
586
|
+
const React = require("react");
|
|
587
|
+
const [page, setPage] = React.useState(1);
|
|
588
|
+
const [rpp, setRpp] = React.useState(4);
|
|
589
|
+
const total = congressPeople.length;
|
|
590
|
+
const start = (page - 1) * rpp;
|
|
591
|
+
const pageData = congressPeople.slice(start, start + rpp);
|
|
592
|
+
return (
|
|
593
|
+
<Table
|
|
594
|
+
columns={[
|
|
595
|
+
{ label: "Name", accessor: "name" },
|
|
596
|
+
{ label: "Party", accessor: "party" },
|
|
597
|
+
{ label: "State", accessor: "region.state" },
|
|
598
|
+
]}
|
|
599
|
+
data={pageData}
|
|
600
|
+
showPagination
|
|
601
|
+
page={page}
|
|
602
|
+
size={rpp}
|
|
603
|
+
totalRows={total}
|
|
604
|
+
onSetPage={setPage}
|
|
605
|
+
onSetSize={(n) => {
|
|
606
|
+
setPage(1);
|
|
607
|
+
setRpp(n);
|
|
608
|
+
}}
|
|
609
|
+
/>
|
|
610
|
+
);
|
|
611
|
+
})()}
|
|
612
|
+
</Excerpt>
|
|
613
|
+
|
|
614
|
+
```jsx
|
|
615
|
+
// Parent controls the table
|
|
616
|
+
const [page, setPage] = useState(1);
|
|
617
|
+
const [rowsPerPage, setRowsPerPage] = useState(10);
|
|
618
|
+
const totalRows = allRows.length; // from your API
|
|
619
|
+
|
|
620
|
+
// Only pass the current slice to the table
|
|
621
|
+
const start = (page - 1) * rowsPerPage;
|
|
622
|
+
const currentData = allRows.slice(start, start + rowsPerPage);
|
|
623
|
+
|
|
624
|
+
<Table
|
|
625
|
+
columns={columns}
|
|
626
|
+
data={currentData}
|
|
627
|
+
showPagination
|
|
628
|
+
page={page}
|
|
629
|
+
size={rowsPerPage}
|
|
630
|
+
totalRows={totalRows}
|
|
631
|
+
onSetPage={setPage}
|
|
632
|
+
onSetSize={(n) => { setPage(1); setRowsPerPage(n); }}
|
|
633
|
+
/>;
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
If you don’t supply these controlled props, the table falls back to its internal pagination state, preserving backwards compatibility.
|
|
637
|
+
|
|
638
|
+
## useTable hook
|
|
639
|
+
|
|
640
|
+
For async workflows, use the helper hook to centralize loading, pagination, count, and ordering while preserving the Table’s controlled API.
|
|
641
|
+
|
|
642
|
+
```jsx
|
|
643
|
+
import { useEffect, useState } from 'react';
|
|
644
|
+
import { Table, useTable } from 'tabler-react-2';
|
|
645
|
+
|
|
646
|
+
function PeopleTable() {
|
|
647
|
+
const [rows, setRows] = useState([]);
|
|
648
|
+
const { tableProps, setLoading, setPage, setCount, setOrder } = useTable({
|
|
649
|
+
autoLoading: false,
|
|
650
|
+
onPageChange: async (page, size, sort) => {
|
|
651
|
+
setLoading(true);
|
|
652
|
+
try {
|
|
653
|
+
// fetch your data here using page, size, and sort
|
|
654
|
+
const res = await fetchPeople({ page, size, sort });
|
|
655
|
+
setRows(res.items);
|
|
656
|
+
setCount(res.total);
|
|
657
|
+
} finally {
|
|
658
|
+
setLoading(false);
|
|
659
|
+
}
|
|
660
|
+
},
|
|
661
|
+
onSortChange: async (by, dir) => {
|
|
662
|
+
// optionally kick off a fetch based on new sort
|
|
663
|
+
// setOrder(by, dir) is already called for you
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
useEffect(() => {
|
|
668
|
+
// initial load
|
|
669
|
+
setPage(1);
|
|
670
|
+
}, [setPage]);
|
|
671
|
+
|
|
672
|
+
return <Table {...tableProps} columns={[
|
|
673
|
+
{ label: 'Name', accessor: 'name', sortable: true },
|
|
674
|
+
{ label: 'Party', accessor: 'party', sortable: true },
|
|
675
|
+
{ label: 'State', accessor: 'region.state', sortable: true },
|
|
676
|
+
]} data={rows} />;
|
|
677
|
+
}
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
The hook returns:
|
|
681
|
+
|
|
682
|
+
- tableProps: spread onto `<Table />` to enable controlled pagination, ordering, and loading
|
|
683
|
+
- setLoading, setPage, setCount: imperative helpers for async flows
|
|
684
|
+
- Also available: page, size, count, orderBy, order, setSize, setOrder
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tabler-react-2",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.154",
|
|
4
4
|
"description": "A react implementation of Tabler ui",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "cd demo && yarn start",
|
|
8
8
|
"build:css": "postcss src/**/*.css --dir dist",
|
|
9
|
-
"build": "babel src --out-dir dist && npm run build:css"
|
|
9
|
+
"build": "babel src --out-dir dist && npm run build:css",
|
|
10
|
+
"docs:vanilla": "yarn --cwd docs start",
|
|
11
|
+
"docs": "cpulimit -l 20 -- yarn --cwd docs start"
|
|
10
12
|
},
|
|
11
13
|
"author": "Jack Crane",
|
|
12
14
|
"license": "MIT",
|
|
@@ -15,6 +17,8 @@
|
|
|
15
17
|
"@babel/core": "^7.24.8",
|
|
16
18
|
"@babel/preset-env": "^7.24.8",
|
|
17
19
|
"@babel/preset-react": "^7.24.7",
|
|
20
|
+
"@emotion/react": "^11.14.0",
|
|
21
|
+
"@tanstack/react-table": "^8.21.3",
|
|
18
22
|
"classnames": "^2.5.1",
|
|
19
23
|
"prop-types": "^15.8.1",
|
|
20
24
|
"react": "^18.3.1",
|