@payfit/unity-components 2.55.14 → 2.55.16
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/package.json +17 -17
- package/skills/unity-data-table/SKILL.md +24 -240
- package/skills/unity-data-table/references/patterns.md +237 -0
- package/skills/unity-find-component/SKILL.md +20 -105
- package/skills/unity-find-component/references/component-catalog.md +94 -0
- package/skills/unity-layout-and-styling/SKILL.md +14 -141
- package/skills/unity-layout-and-styling/references/patterns.md +139 -0
- package/skills/unity-migrate-from-midnight/SKILL.md +9 -9
- package/skills/unity-navigation/SKILL.md +12 -116
- package/skills/unity-navigation/references/patterns.md +115 -0
- package/skills/unity-overlays/SKILL.md +11 -132
- package/skills/unity-overlays/references/patterns.md +131 -0
- package/skills/unity-setup-feature-plugin/SKILL.md +4 -3
- package/skills/unity-tanstack-form/SKILL.md +15 -102
- package/skills/unity-tanstack-form/references/patterns.md +102 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payfit/unity-components",
|
|
3
|
-
"version": "2.55.
|
|
3
|
+
"version": "2.55.16",
|
|
4
4
|
"module": "./dist/esm/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -89,11 +89,11 @@
|
|
|
89
89
|
"react-international-phone": "4.5.0",
|
|
90
90
|
"react-intl": "7.1.14",
|
|
91
91
|
"react-stately": "3.48.0",
|
|
92
|
-
"storybook": "10.
|
|
92
|
+
"storybook": "10.5.0",
|
|
93
93
|
"tailwind-variants": "3.2.2",
|
|
94
94
|
"usehooks-ts": "3.1.1",
|
|
95
95
|
"zod": "4.4.3",
|
|
96
|
-
"@payfit/unity-illustrations": "2.55.
|
|
96
|
+
"@payfit/unity-illustrations": "2.55.16"
|
|
97
97
|
},
|
|
98
98
|
"peerDependencies": {
|
|
99
99
|
"@hookform/devtools": "^4",
|
|
@@ -105,22 +105,22 @@
|
|
|
105
105
|
"react-hook-form": "^7",
|
|
106
106
|
"react-router-dom": "^5",
|
|
107
107
|
"zod": "^3 || ^4",
|
|
108
|
-
"@payfit/unity-icons": "2.55.
|
|
109
|
-
"@payfit/unity-themes": "2.55.
|
|
108
|
+
"@payfit/unity-icons": "2.55.16",
|
|
109
|
+
"@payfit/unity-themes": "2.55.16"
|
|
110
110
|
},
|
|
111
111
|
"devDependencies": {
|
|
112
112
|
"@figma/code-connect": "1.4.8",
|
|
113
113
|
"@github-ui/storybook-addon-performance-panel": "1.1.4",
|
|
114
114
|
"@hookform/devtools": "4.4.0",
|
|
115
115
|
"@internationalized/date": "3.12.2",
|
|
116
|
-
"@storybook/addon-a11y": "10.
|
|
116
|
+
"@storybook/addon-a11y": "10.5.0",
|
|
117
117
|
"@storybook/addon-designs": "11.1.4",
|
|
118
|
-
"@storybook/addon-docs": "10.
|
|
119
|
-
"@storybook/addon-links": "10.
|
|
118
|
+
"@storybook/addon-docs": "10.5.0",
|
|
119
|
+
"@storybook/addon-links": "10.5.0",
|
|
120
120
|
"@storybook/addon-mcp": "0.6.0",
|
|
121
|
-
"@storybook/addon-themes": "10.
|
|
122
|
-
"@storybook/addon-vitest": "10.
|
|
123
|
-
"@storybook/react-vite": "10.
|
|
121
|
+
"@storybook/addon-themes": "10.5.0",
|
|
122
|
+
"@storybook/addon-vitest": "10.5.0",
|
|
123
|
+
"@storybook/react-vite": "10.5.0",
|
|
124
124
|
"@tanstack/intent": "0.3.0",
|
|
125
125
|
"@tanstack/react-devtools": "0.10.8",
|
|
126
126
|
"@tanstack/react-form-devtools": "0.2.30",
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
"prettier": "3.8.3",
|
|
143
143
|
"react-docgen-typescript": "2.2.2",
|
|
144
144
|
"react-hot-toast": "2.5.1",
|
|
145
|
-
"storybook": "10.
|
|
145
|
+
"storybook": "10.5.0",
|
|
146
146
|
"storybook-addon-pseudo-states": "10.4.6",
|
|
147
147
|
"storybook-addon-tag-badges": "3.1.0",
|
|
148
148
|
"storybook-addon-test-codegen": "3.0.1",
|
|
@@ -152,14 +152,14 @@
|
|
|
152
152
|
"vite": "8.1.4",
|
|
153
153
|
"vite-plugin-node-polyfills": "0.28.0",
|
|
154
154
|
"vitest": "4.1.10",
|
|
155
|
-
"@payfit/unity-icons": "2.55.14",
|
|
156
|
-
"@payfit/unity-illustrations": "2.55.14",
|
|
157
|
-
"@payfit/unity-themes": "2.55.14",
|
|
158
|
-
"@payfit/vite-configs": "0.0.0-use.local",
|
|
159
155
|
"@payfit/hr-app-eslint": "0.0.0-use.local",
|
|
160
156
|
"@payfit/hr-apps-tsconfigs": "0.0.0-use.local",
|
|
161
157
|
"@payfit/storybook-addon-console-errors": "0.0.0-use.local",
|
|
162
|
-
"@payfit/storybook-config": "0.0.0-use.local"
|
|
158
|
+
"@payfit/storybook-config": "0.0.0-use.local",
|
|
159
|
+
"@payfit/unity-icons": "2.55.16",
|
|
160
|
+
"@payfit/unity-illustrations": "2.55.16",
|
|
161
|
+
"@payfit/unity-themes": "2.55.16",
|
|
162
|
+
"@payfit/vite-configs": "0.0.0-use.local"
|
|
163
163
|
},
|
|
164
164
|
"peerDependenciesMeta": {
|
|
165
165
|
"@hookform/devtools": {
|
|
@@ -4,9 +4,10 @@ description: >
|
|
|
4
4
|
Load when rendering tabular data with Unity. Use it to choose DataTable for
|
|
5
5
|
managed table state or primitive Table for static/custom markup, including
|
|
6
6
|
filtering, pagination, virtualization, or bulk actions.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
metadata:
|
|
8
|
+
type: core
|
|
9
|
+
library: '@payfit/unity-components'
|
|
10
|
+
library_version: '2.x'
|
|
10
11
|
sources:
|
|
11
12
|
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/data-table/DataTable.tsx'
|
|
12
13
|
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/data-table/parts/DataTableRoot.tsx'
|
|
@@ -23,7 +24,9 @@ table state to manage.
|
|
|
23
24
|
|
|
24
25
|
## Setup
|
|
25
26
|
|
|
26
|
-
Minimum working client-side DataTable.
|
|
27
|
+
Minimum working client-side DataTable. Keep `columns` and `data` references
|
|
28
|
+
stable across renders. Define static columns outside the component or memoize
|
|
29
|
+
them; memoize derived data when its source would otherwise create a new array.
|
|
27
30
|
|
|
28
31
|
```tsx
|
|
29
32
|
import { useMemo, useState } from 'react'
|
|
@@ -98,241 +101,17 @@ export function EmployeeTable({ employees }: { employees: Employee[] }) {
|
|
|
98
101
|
|
|
99
102
|
## Core Patterns
|
|
100
103
|
|
|
101
|
-
|
|
104
|
+
- Define columns with `createColumnHelper` and use `ColumnMeta` for row
|
|
105
|
+
headers, focus behavior, helper text, and fixed-layout widths.
|
|
106
|
+
- For server pagination, pass only the current page, set `manualPagination`,
|
|
107
|
+
and derive `pageCount` from the server total.
|
|
108
|
+
- Map `FilterToolbar.onChange` into TanStack Table's column/global filter
|
|
109
|
+
state.
|
|
110
|
+
- Put `DataTableBulkActions` beside `DataTable` inside `DataTableRoot` and
|
|
111
|
+
drive it from row-selection state.
|
|
102
112
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
checkboxes), `helperText` (renders a tooltip next to the header), and
|
|
106
|
-
`headerClassName` (required when `layout="fixed"`).
|
|
107
|
-
|
|
108
|
-
```tsx
|
|
109
|
-
import { Badge } from '@payfit/unity-components'
|
|
110
|
-
import { createColumnHelper } from '@tanstack/react-table'
|
|
111
|
-
|
|
112
|
-
const columnHelper = createColumnHelper<Employee>()
|
|
113
|
-
|
|
114
|
-
export const employeeColumns = [
|
|
115
|
-
columnHelper.accessor('name', {
|
|
116
|
-
id: 'employee',
|
|
117
|
-
header: 'Employee',
|
|
118
|
-
enableSorting: true,
|
|
119
|
-
meta: { isRowHeader: true, headerClassName: 'uy:w-[260px]' },
|
|
120
|
-
}),
|
|
121
|
-
columnHelper.accessor('status', {
|
|
122
|
-
header: 'Status',
|
|
123
|
-
enableColumnFilter: true,
|
|
124
|
-
filterFn: 'arrIncludesSome',
|
|
125
|
-
cell: info => {
|
|
126
|
-
const status = info.getValue()
|
|
127
|
-
return (
|
|
128
|
-
<Badge variant={status === 'active' ? 'success' : 'neutral'}>
|
|
129
|
-
{status}
|
|
130
|
-
</Badge>
|
|
131
|
-
)
|
|
132
|
-
},
|
|
133
|
-
meta: { helperText: 'Active employees can sign in.' },
|
|
134
|
-
}),
|
|
135
|
-
columnHelper.display({
|
|
136
|
-
id: 'actions',
|
|
137
|
-
header: '',
|
|
138
|
-
cell: ({ row }) => <RowMenu row={row.original} />,
|
|
139
|
-
meta: { isFocusable: false },
|
|
140
|
-
}),
|
|
141
|
-
]
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Server-side pagination
|
|
145
|
-
|
|
146
|
-
`manualPagination: true` disables Tanstack's internal slicing. Pass only the
|
|
147
|
-
current page's slice as `data` and supply `pageCount`.
|
|
148
|
-
|
|
149
|
-
```tsx
|
|
150
|
-
import { useMemo, useState } from 'react'
|
|
151
|
-
|
|
152
|
-
import { getCoreRowModel, useReactTable } from '@tanstack/react-table'
|
|
153
|
-
|
|
154
|
-
export function ServerTable({ totalCount, fetchPage }: Props) {
|
|
155
|
-
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 20 })
|
|
156
|
-
const { data: pageRows = [] } = useQuery({
|
|
157
|
-
queryKey: ['employees', pagination],
|
|
158
|
-
queryFn: () => fetchPage(pagination.pageIndex, pagination.pageSize),
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
const columns = useMemo(() => employeeColumns, [])
|
|
162
|
-
const data = useMemo(() => pageRows, [pageRows])
|
|
163
|
-
|
|
164
|
-
const table = useReactTable({
|
|
165
|
-
data,
|
|
166
|
-
columns,
|
|
167
|
-
manualPagination: true,
|
|
168
|
-
pageCount: Math.ceil(totalCount / pagination.pageSize),
|
|
169
|
-
state: { pagination },
|
|
170
|
-
onPaginationChange: setPagination,
|
|
171
|
-
getCoreRowModel: getCoreRowModel(),
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
return (
|
|
175
|
-
<DataTableRoot>
|
|
176
|
-
<DataTable table={table}>
|
|
177
|
-
{row => (
|
|
178
|
-
<TableRow key={row.id}>
|
|
179
|
-
{row.getVisibleCells().map(cell => (
|
|
180
|
-
<TableCell key={cell.id}>
|
|
181
|
-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
182
|
-
</TableCell>
|
|
183
|
-
))}
|
|
184
|
-
</TableRow>
|
|
185
|
-
)}
|
|
186
|
-
</DataTable>
|
|
187
|
-
</DataTableRoot>
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Filtering with FilterToolbar
|
|
193
|
-
|
|
194
|
-
`FilterToolbar.onChange` emits `SerializableAppliedFilter[]`. Map that onto
|
|
195
|
-
`table.setColumnFilters` / `table.setGlobalFilter`. `renderControl` takes any
|
|
196
|
-
control — it is a render function on purpose so you can drop in date pickers,
|
|
197
|
-
multi-selects, text fields, etc.
|
|
198
|
-
|
|
199
|
-
```tsx
|
|
200
|
-
import type { FilterDef } from '@payfit/unity-components'
|
|
201
|
-
|
|
202
|
-
import { FilterToolbar, Select, SelectItem } from '@payfit/unity-components'
|
|
203
|
-
import { getFilteredRowModel } from '@tanstack/react-table'
|
|
204
|
-
|
|
205
|
-
const filterDefs: FilterDef[] = [
|
|
206
|
-
{
|
|
207
|
-
id: 'status',
|
|
208
|
-
label: 'Status',
|
|
209
|
-
renderControl: (value, onChange) => (
|
|
210
|
-
<Select selectedKey={value as string} onSelectionChange={onChange}>
|
|
211
|
-
<SelectItem id="active">Active</SelectItem>
|
|
212
|
-
<SelectItem id="inactive">Inactive</SelectItem>
|
|
213
|
-
</Select>
|
|
214
|
-
),
|
|
215
|
-
renderLabel: value => String(value),
|
|
216
|
-
},
|
|
217
|
-
]
|
|
218
|
-
|
|
219
|
-
export function FilteredTable() {
|
|
220
|
-
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
221
|
-
|
|
222
|
-
const table = useReactTable({
|
|
223
|
-
data,
|
|
224
|
-
columns,
|
|
225
|
-
state: { columnFilters, pagination },
|
|
226
|
-
onColumnFiltersChange: setColumnFilters,
|
|
227
|
-
onPaginationChange: setPagination,
|
|
228
|
-
getCoreRowModel: getCoreRowModel(),
|
|
229
|
-
getFilteredRowModel: getFilteredRowModel(),
|
|
230
|
-
getPaginationRowModel: getPaginationRowModel(),
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
return (
|
|
234
|
-
<>
|
|
235
|
-
<FilterToolbar
|
|
236
|
-
filterDefs={filterDefs}
|
|
237
|
-
onChange={applied =>
|
|
238
|
-
setColumnFilters(applied.map(f => ({ id: f.id, value: f.value })))
|
|
239
|
-
}
|
|
240
|
-
/>
|
|
241
|
-
<DataTableRoot>
|
|
242
|
-
<DataTable table={table}>
|
|
243
|
-
{row => (
|
|
244
|
-
<TableRow key={row.id}>
|
|
245
|
-
{row.getVisibleCells().map(cell => (
|
|
246
|
-
<TableCell key={cell.id}>
|
|
247
|
-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
248
|
-
</TableCell>
|
|
249
|
-
))}
|
|
250
|
-
</TableRow>
|
|
251
|
-
)}
|
|
252
|
-
</DataTable>
|
|
253
|
-
</DataTableRoot>
|
|
254
|
-
</>
|
|
255
|
-
)
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### Bulk actions with row selection
|
|
260
|
-
|
|
261
|
-
`DataTableBulkActions` lives next to `DataTable` inside `DataTableRoot`.
|
|
262
|
-
It auto-shows when rows are selected and provides the F6 focus trap. Use a
|
|
263
|
-
display column with `meta.isFocusable: false` for the checkbox.
|
|
264
|
-
|
|
265
|
-
```tsx
|
|
266
|
-
import {
|
|
267
|
-
CheckboxStandalone,
|
|
268
|
-
DataTable,
|
|
269
|
-
DataTableBulkActions,
|
|
270
|
-
DataTableRoot,
|
|
271
|
-
} from '@payfit/unity-components'
|
|
272
|
-
|
|
273
|
-
const checkboxColumn = columnHelper.display({
|
|
274
|
-
id: 'select',
|
|
275
|
-
header: ({ table }) => (
|
|
276
|
-
<CheckboxStandalone
|
|
277
|
-
isSelected={table.getIsAllPageRowsSelected()}
|
|
278
|
-
isIndeterminate={table.getIsSomePageRowsSelected()}
|
|
279
|
-
onChange={value => table.toggleAllPageRowsSelected(value)}
|
|
280
|
-
slot="selection"
|
|
281
|
-
>
|
|
282
|
-
Select all
|
|
283
|
-
</CheckboxStandalone>
|
|
284
|
-
),
|
|
285
|
-
cell: ({ row }) => (
|
|
286
|
-
<CheckboxStandalone
|
|
287
|
-
isSelected={row.getIsSelected()}
|
|
288
|
-
onChange={value => row.toggleSelected(value)}
|
|
289
|
-
slot="selection"
|
|
290
|
-
>
|
|
291
|
-
Select row
|
|
292
|
-
</CheckboxStandalone>
|
|
293
|
-
),
|
|
294
|
-
enableSorting: false,
|
|
295
|
-
meta: { isFocusable: false },
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
export function BulkTable() {
|
|
299
|
-
const [rowSelection, setRowSelection] = useState({})
|
|
300
|
-
const columns = useMemo(() => [checkboxColumn, ...employeeColumns], [])
|
|
301
|
-
const table = useReactTable({
|
|
302
|
-
data,
|
|
303
|
-
columns,
|
|
304
|
-
state: { rowSelection, pagination },
|
|
305
|
-
onRowSelectionChange: setRowSelection,
|
|
306
|
-
onPaginationChange: setPagination,
|
|
307
|
-
enableRowSelection: true,
|
|
308
|
-
getCoreRowModel: getCoreRowModel(),
|
|
309
|
-
getPaginationRowModel: getPaginationRowModel(),
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
return (
|
|
313
|
-
<DataTableRoot>
|
|
314
|
-
<DataTable table={table}>
|
|
315
|
-
{row => (
|
|
316
|
-
<TableRow key={row.id} isSelected={row.getIsSelected()}>
|
|
317
|
-
{row.getVisibleCells().map(cell => (
|
|
318
|
-
<TableCell key={cell.id}>
|
|
319
|
-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
320
|
-
</TableCell>
|
|
321
|
-
))}
|
|
322
|
-
</TableRow>
|
|
323
|
-
)}
|
|
324
|
-
</DataTable>
|
|
325
|
-
<DataTableBulkActions
|
|
326
|
-
table={table}
|
|
327
|
-
actions={[
|
|
328
|
-
{ id: 'archive', label: 'Archive', onAction: rows => archive(rows) },
|
|
329
|
-
{ id: 'delete', label: 'Delete', onAction: rows => remove(rows) },
|
|
330
|
-
]}
|
|
331
|
-
/>
|
|
332
|
-
</DataTableRoot>
|
|
333
|
-
)
|
|
334
|
-
}
|
|
335
|
-
```
|
|
113
|
+
Read [references/patterns.md](references/patterns.md) when implementing one of
|
|
114
|
+
these patterns.
|
|
336
115
|
|
|
337
116
|
## Common Mistakes
|
|
338
117
|
|
|
@@ -435,7 +214,7 @@ const table = useReactTable({ /* … */ state: { columnFilters, globalFilter },
|
|
|
435
214
|
|
|
436
215
|
FilterToolbar.onChange emits AppliedFilter[]. Agent stores them locally without mapping to table.setGlobalFilter / setColumnFilters, so rows do not actually filter.
|
|
437
216
|
|
|
438
|
-
###
|
|
217
|
+
### HIGH Pass every local row with manual pagination
|
|
439
218
|
|
|
440
219
|
Wrong:
|
|
441
220
|
|
|
@@ -447,7 +226,7 @@ useReactTable({
|
|
|
447
226
|
})
|
|
448
227
|
```
|
|
449
228
|
|
|
450
|
-
Correct:
|
|
229
|
+
Correct for locally held data:
|
|
451
230
|
|
|
452
231
|
```tsx
|
|
453
232
|
const currentData = useMemo(() => {
|
|
@@ -465,6 +244,11 @@ useReactTable({
|
|
|
465
244
|
|
|
466
245
|
manualPagination: true tells Tanstack not to slice. Agents pass the full dataset and expect TanStack to paginate anyway.
|
|
467
246
|
|
|
247
|
+
For server-side pagination, fetch and pass only the current server page and
|
|
248
|
+
set `pageCount` from the server's total count. When all rows are local and
|
|
249
|
+
TanStack should paginate them automatically, omit `manualPagination` and use
|
|
250
|
+
`getPaginationRowModel()` instead.
|
|
251
|
+
|
|
468
252
|
### MEDIUM Render large datasets without enableVirtualization
|
|
469
253
|
|
|
470
254
|
Wrong:
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Unity DataTable patterns
|
|
2
|
+
|
|
3
|
+
### Define columns with the column helper and ColumnMeta
|
|
4
|
+
|
|
5
|
+
ColumnMeta drives accessibility (`isRowHeader`), keyboard navigation
|
|
6
|
+
(`isFocusable: false` for cells whose children are themselves focusable like
|
|
7
|
+
checkboxes), `helperText` (renders a tooltip next to the header), and
|
|
8
|
+
`headerClassName` (required when `layout="fixed"`).
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { Badge } from '@payfit/unity-components'
|
|
12
|
+
import { createColumnHelper } from '@tanstack/react-table'
|
|
13
|
+
|
|
14
|
+
const columnHelper = createColumnHelper<Employee>()
|
|
15
|
+
|
|
16
|
+
export const employeeColumns = [
|
|
17
|
+
columnHelper.accessor('name', {
|
|
18
|
+
id: 'employee',
|
|
19
|
+
header: 'Employee',
|
|
20
|
+
enableSorting: true,
|
|
21
|
+
meta: { isRowHeader: true, headerClassName: 'uy:w-[260px]' },
|
|
22
|
+
}),
|
|
23
|
+
columnHelper.accessor('status', {
|
|
24
|
+
header: 'Status',
|
|
25
|
+
enableColumnFilter: true,
|
|
26
|
+
filterFn: 'arrIncludesSome',
|
|
27
|
+
cell: info => {
|
|
28
|
+
const status = info.getValue()
|
|
29
|
+
return (
|
|
30
|
+
<Badge variant={status === 'active' ? 'success' : 'neutral'}>
|
|
31
|
+
{status}
|
|
32
|
+
</Badge>
|
|
33
|
+
)
|
|
34
|
+
},
|
|
35
|
+
meta: { helperText: 'Active employees can sign in.' },
|
|
36
|
+
}),
|
|
37
|
+
columnHelper.display({
|
|
38
|
+
id: 'actions',
|
|
39
|
+
header: '',
|
|
40
|
+
cell: ({ row }) => <RowMenu row={row.original} />,
|
|
41
|
+
meta: { isFocusable: false },
|
|
42
|
+
}),
|
|
43
|
+
]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Server-side pagination
|
|
47
|
+
|
|
48
|
+
`manualPagination: true` disables Tanstack's internal slicing. Pass only the
|
|
49
|
+
current page's slice as `data` and supply `pageCount`.
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { useMemo, useState } from 'react'
|
|
53
|
+
|
|
54
|
+
import { getCoreRowModel, useReactTable } from '@tanstack/react-table'
|
|
55
|
+
|
|
56
|
+
export function ServerTable({ totalCount, fetchPage }: Props) {
|
|
57
|
+
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 20 })
|
|
58
|
+
const { data: pageRows = [] } = useQuery({
|
|
59
|
+
queryKey: ['employees', pagination],
|
|
60
|
+
queryFn: () => fetchPage(pagination.pageIndex, pagination.pageSize),
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const columns = useMemo(() => employeeColumns, [])
|
|
64
|
+
const data = useMemo(() => pageRows, [pageRows])
|
|
65
|
+
|
|
66
|
+
const table = useReactTable({
|
|
67
|
+
data,
|
|
68
|
+
columns,
|
|
69
|
+
manualPagination: true,
|
|
70
|
+
pageCount: Math.ceil(totalCount / pagination.pageSize),
|
|
71
|
+
state: { pagination },
|
|
72
|
+
onPaginationChange: setPagination,
|
|
73
|
+
getCoreRowModel: getCoreRowModel(),
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<DataTableRoot>
|
|
78
|
+
<DataTable table={table}>
|
|
79
|
+
{row => (
|
|
80
|
+
<TableRow key={row.id}>
|
|
81
|
+
{row.getVisibleCells().map(cell => (
|
|
82
|
+
<TableCell key={cell.id}>
|
|
83
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
84
|
+
</TableCell>
|
|
85
|
+
))}
|
|
86
|
+
</TableRow>
|
|
87
|
+
)}
|
|
88
|
+
</DataTable>
|
|
89
|
+
</DataTableRoot>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Filtering with FilterToolbar
|
|
95
|
+
|
|
96
|
+
`FilterToolbar.onChange` emits `SerializableAppliedFilter[]`. Map that onto
|
|
97
|
+
`table.setColumnFilters` / `table.setGlobalFilter`. `renderControl` takes any
|
|
98
|
+
control — it is a render function on purpose so you can drop in date pickers,
|
|
99
|
+
multi-selects, text fields, etc.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import type { FilterDef } from '@payfit/unity-components'
|
|
103
|
+
|
|
104
|
+
import { FilterToolbar, Select, SelectItem } from '@payfit/unity-components'
|
|
105
|
+
import { getFilteredRowModel } from '@tanstack/react-table'
|
|
106
|
+
|
|
107
|
+
const filterDefs: FilterDef[] = [
|
|
108
|
+
{
|
|
109
|
+
id: 'status',
|
|
110
|
+
label: 'Status',
|
|
111
|
+
renderControl: (value, onChange) => (
|
|
112
|
+
<Select selectedKey={value as string} onSelectionChange={onChange}>
|
|
113
|
+
<SelectItem id="active">Active</SelectItem>
|
|
114
|
+
<SelectItem id="inactive">Inactive</SelectItem>
|
|
115
|
+
</Select>
|
|
116
|
+
),
|
|
117
|
+
renderLabel: value => String(value),
|
|
118
|
+
},
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
export function FilteredTable() {
|
|
122
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
123
|
+
|
|
124
|
+
const table = useReactTable({
|
|
125
|
+
data,
|
|
126
|
+
columns,
|
|
127
|
+
state: { columnFilters, pagination },
|
|
128
|
+
onColumnFiltersChange: setColumnFilters,
|
|
129
|
+
onPaginationChange: setPagination,
|
|
130
|
+
getCoreRowModel: getCoreRowModel(),
|
|
131
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
132
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<>
|
|
137
|
+
<FilterToolbar
|
|
138
|
+
filterDefs={filterDefs}
|
|
139
|
+
onChange={applied =>
|
|
140
|
+
setColumnFilters(applied.map(f => ({ id: f.id, value: f.value })))
|
|
141
|
+
}
|
|
142
|
+
/>
|
|
143
|
+
<DataTableRoot>
|
|
144
|
+
<DataTable table={table}>
|
|
145
|
+
{row => (
|
|
146
|
+
<TableRow key={row.id}>
|
|
147
|
+
{row.getVisibleCells().map(cell => (
|
|
148
|
+
<TableCell key={cell.id}>
|
|
149
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
150
|
+
</TableCell>
|
|
151
|
+
))}
|
|
152
|
+
</TableRow>
|
|
153
|
+
)}
|
|
154
|
+
</DataTable>
|
|
155
|
+
</DataTableRoot>
|
|
156
|
+
</>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Bulk actions with row selection
|
|
162
|
+
|
|
163
|
+
`DataTableBulkActions` lives next to `DataTable` inside `DataTableRoot`.
|
|
164
|
+
It auto-shows when rows are selected and provides the F6 focus trap. Use a
|
|
165
|
+
display column with `meta.isFocusable: false` for the checkbox.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import {
|
|
169
|
+
CheckboxStandalone,
|
|
170
|
+
DataTable,
|
|
171
|
+
DataTableBulkActions,
|
|
172
|
+
DataTableRoot,
|
|
173
|
+
} from '@payfit/unity-components'
|
|
174
|
+
|
|
175
|
+
const checkboxColumn = columnHelper.display({
|
|
176
|
+
id: 'select',
|
|
177
|
+
header: ({ table }) => (
|
|
178
|
+
<CheckboxStandalone
|
|
179
|
+
isSelected={table.getIsAllPageRowsSelected()}
|
|
180
|
+
isIndeterminate={table.getIsSomePageRowsSelected()}
|
|
181
|
+
onChange={value => table.toggleAllPageRowsSelected(value)}
|
|
182
|
+
slot="selection"
|
|
183
|
+
>
|
|
184
|
+
Select all
|
|
185
|
+
</CheckboxStandalone>
|
|
186
|
+
),
|
|
187
|
+
cell: ({ row }) => (
|
|
188
|
+
<CheckboxStandalone
|
|
189
|
+
isSelected={row.getIsSelected()}
|
|
190
|
+
onChange={value => row.toggleSelected(value)}
|
|
191
|
+
slot="selection"
|
|
192
|
+
>
|
|
193
|
+
Select row
|
|
194
|
+
</CheckboxStandalone>
|
|
195
|
+
),
|
|
196
|
+
enableSorting: false,
|
|
197
|
+
meta: { isFocusable: false },
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
export function BulkTable() {
|
|
201
|
+
const [rowSelection, setRowSelection] = useState({})
|
|
202
|
+
const columns = useMemo(() => [checkboxColumn, ...employeeColumns], [])
|
|
203
|
+
const table = useReactTable({
|
|
204
|
+
data,
|
|
205
|
+
columns,
|
|
206
|
+
state: { rowSelection, pagination },
|
|
207
|
+
onRowSelectionChange: setRowSelection,
|
|
208
|
+
onPaginationChange: setPagination,
|
|
209
|
+
enableRowSelection: true,
|
|
210
|
+
getCoreRowModel: getCoreRowModel(),
|
|
211
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<DataTableRoot>
|
|
216
|
+
<DataTable table={table}>
|
|
217
|
+
{row => (
|
|
218
|
+
<TableRow key={row.id} isSelected={row.getIsSelected()}>
|
|
219
|
+
{row.getVisibleCells().map(cell => (
|
|
220
|
+
<TableCell key={cell.id}>
|
|
221
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
222
|
+
</TableCell>
|
|
223
|
+
))}
|
|
224
|
+
</TableRow>
|
|
225
|
+
)}
|
|
226
|
+
</DataTable>
|
|
227
|
+
<DataTableBulkActions
|
|
228
|
+
table={table}
|
|
229
|
+
actions={[
|
|
230
|
+
{ id: 'archive', label: 'Archive', onAction: rows => archive(rows) },
|
|
231
|
+
{ id: 'delete', label: 'Delete', onAction: rows => remove(rows) },
|
|
232
|
+
]}
|
|
233
|
+
/>
|
|
234
|
+
</DataTableRoot>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
```
|