@izumisy-tailor/tailor-data-viewer 0.2.19 → 0.2.20
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 +1 -1
- package/src/component/collection/collection-provider.tsx +1 -1
- package/src/component/components.test.tsx +88 -36
- package/src/component/data-table/data-table-context.tsx +7 -5
- package/src/component/data-table/{index.tsx → data-table.tsx} +66 -152
- package/src/component/data-table/use-data-table.test.ts +6 -29
- package/src/component/data-table/use-data-table.ts +22 -56
- package/src/component/index.ts +1 -4
- package/src/component/types.ts +4 -53
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ const CollectionContext = createContext<UseCollectionReturn<
|
|
|
15
15
|
*
|
|
16
16
|
* <CollectionProvider value={collection}>
|
|
17
17
|
* <FilterPanel />
|
|
18
|
-
* <DataTable.Root
|
|
18
|
+
* <DataTable.Root>...</DataTable.Root>
|
|
19
19
|
* <Pagination {...table} />
|
|
20
20
|
* </CollectionProvider>
|
|
21
21
|
* ```
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
2
|
import { describe, it, expect } from "vitest";
|
|
3
3
|
import { Table } from "./table";
|
|
4
|
-
import { DataTable } from "./data-table/
|
|
4
|
+
import { DataTable } from "./data-table/data-table";
|
|
5
|
+
import { DataTableContext } from "./data-table/data-table-context";
|
|
6
|
+
import type { DataTableContextValue } from "./data-table/data-table-context";
|
|
5
7
|
import type { Column } from "./types";
|
|
6
8
|
|
|
7
9
|
describe("Table (static)", () => {
|
|
@@ -65,13 +67,51 @@ const testRows: TestRow[] = [
|
|
|
65
67
|
{ id: "2", name: "Bob", status: "Inactive" },
|
|
66
68
|
];
|
|
67
69
|
|
|
70
|
+
const noopRowOps = {
|
|
71
|
+
updateRow: () => ({ rollback: () => {} }),
|
|
72
|
+
deleteRow: () => ({
|
|
73
|
+
rollback: () => {},
|
|
74
|
+
deletedRow: {} as TestRow,
|
|
75
|
+
}),
|
|
76
|
+
insertRow: () => ({ rollback: () => {} }),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const defaultPageInfo = {
|
|
80
|
+
hasNextPage: false,
|
|
81
|
+
endCursor: null,
|
|
82
|
+
hasPreviousPage: false,
|
|
83
|
+
startCursor: null,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function createCtx(
|
|
87
|
+
overrides: Partial<DataTableContextValue<TestRow>> = {},
|
|
88
|
+
): DataTableContextValue<TestRow> {
|
|
89
|
+
return {
|
|
90
|
+
columns: testColumns,
|
|
91
|
+
rows: testRows,
|
|
92
|
+
loading: false,
|
|
93
|
+
error: null,
|
|
94
|
+
sortStates: [],
|
|
95
|
+
visibleColumns: testColumns,
|
|
96
|
+
isColumnVisible: () => true,
|
|
97
|
+
toggleColumn: () => {},
|
|
98
|
+
showAllColumns: () => {},
|
|
99
|
+
hideAllColumns: () => {},
|
|
100
|
+
pageInfo: defaultPageInfo,
|
|
101
|
+
...noopRowOps,
|
|
102
|
+
...overrides,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
68
106
|
describe("DataTable", () => {
|
|
69
107
|
it("renders data-bound table with auto-generated rows", () => {
|
|
70
108
|
render(
|
|
71
|
-
<
|
|
72
|
-
<DataTable.
|
|
73
|
-
|
|
74
|
-
|
|
109
|
+
<DataTableContext.Provider value={createCtx()}>
|
|
110
|
+
<DataTable.Root>
|
|
111
|
+
<DataTable.Headers />
|
|
112
|
+
<DataTable.Body />
|
|
113
|
+
</DataTable.Root>
|
|
114
|
+
</DataTableContext.Provider>,
|
|
75
115
|
);
|
|
76
116
|
|
|
77
117
|
expect(screen.getByText("Name")).toBeInTheDocument();
|
|
@@ -83,10 +123,12 @@ describe("DataTable", () => {
|
|
|
83
123
|
|
|
84
124
|
it("renders display columns via render function", () => {
|
|
85
125
|
render(
|
|
86
|
-
<
|
|
87
|
-
<DataTable.
|
|
88
|
-
|
|
89
|
-
|
|
126
|
+
<DataTableContext.Provider value={createCtx()}>
|
|
127
|
+
<DataTable.Root>
|
|
128
|
+
<DataTable.Headers />
|
|
129
|
+
<DataTable.Body />
|
|
130
|
+
</DataTable.Root>
|
|
131
|
+
</DataTableContext.Provider>,
|
|
90
132
|
);
|
|
91
133
|
|
|
92
134
|
expect(screen.getByText("Edit Alice")).toBeInTheDocument();
|
|
@@ -95,10 +137,12 @@ describe("DataTable", () => {
|
|
|
95
137
|
|
|
96
138
|
it("shows loading state", () => {
|
|
97
139
|
render(
|
|
98
|
-
<
|
|
99
|
-
<DataTable.
|
|
100
|
-
|
|
101
|
-
|
|
140
|
+
<DataTableContext.Provider value={createCtx({ rows: [], loading: true })}>
|
|
141
|
+
<DataTable.Root>
|
|
142
|
+
<DataTable.Headers />
|
|
143
|
+
<DataTable.Body />
|
|
144
|
+
</DataTable.Root>
|
|
145
|
+
</DataTableContext.Provider>,
|
|
102
146
|
);
|
|
103
147
|
|
|
104
148
|
expect(screen.getByText("Loading...")).toBeInTheDocument();
|
|
@@ -107,10 +151,12 @@ describe("DataTable", () => {
|
|
|
107
151
|
it("shows error state", () => {
|
|
108
152
|
const err = new Error("Something went wrong");
|
|
109
153
|
render(
|
|
110
|
-
<
|
|
111
|
-
<DataTable.
|
|
112
|
-
|
|
113
|
-
|
|
154
|
+
<DataTableContext.Provider value={createCtx({ rows: [], error: err })}>
|
|
155
|
+
<DataTable.Root>
|
|
156
|
+
<DataTable.Headers />
|
|
157
|
+
<DataTable.Body />
|
|
158
|
+
</DataTable.Root>
|
|
159
|
+
</DataTableContext.Provider>,
|
|
114
160
|
);
|
|
115
161
|
|
|
116
162
|
expect(screen.getByText("Error: Something went wrong")).toBeInTheDocument();
|
|
@@ -118,10 +164,12 @@ describe("DataTable", () => {
|
|
|
118
164
|
|
|
119
165
|
it("shows empty state", () => {
|
|
120
166
|
render(
|
|
121
|
-
<
|
|
122
|
-
<DataTable.
|
|
123
|
-
|
|
124
|
-
|
|
167
|
+
<DataTableContext.Provider value={createCtx({ rows: [] })}>
|
|
168
|
+
<DataTable.Root>
|
|
169
|
+
<DataTable.Headers />
|
|
170
|
+
<DataTable.Body />
|
|
171
|
+
</DataTable.Root>
|
|
172
|
+
</DataTableContext.Provider>,
|
|
125
173
|
);
|
|
126
174
|
|
|
127
175
|
expect(screen.getByText("No data")).toBeInTheDocument();
|
|
@@ -129,14 +177,16 @@ describe("DataTable", () => {
|
|
|
129
177
|
|
|
130
178
|
it("renders sort indicator on sorted column", () => {
|
|
131
179
|
render(
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
180
|
+
<DataTableContext.Provider
|
|
181
|
+
value={createCtx({
|
|
182
|
+
sortStates: [{ field: "name", direction: "Asc" }],
|
|
183
|
+
})}
|
|
136
184
|
>
|
|
137
|
-
<DataTable.
|
|
138
|
-
|
|
139
|
-
|
|
185
|
+
<DataTable.Root>
|
|
186
|
+
<DataTable.Headers />
|
|
187
|
+
<DataTable.Body />
|
|
188
|
+
</DataTable.Root>
|
|
189
|
+
</DataTableContext.Provider>,
|
|
140
190
|
);
|
|
141
191
|
|
|
142
192
|
expect(screen.getByText("▲")).toBeInTheDocument();
|
|
@@ -144,14 +194,16 @@ describe("DataTable", () => {
|
|
|
144
194
|
|
|
145
195
|
it("supports custom rendering with children", () => {
|
|
146
196
|
render(
|
|
147
|
-
<
|
|
148
|
-
<DataTable.
|
|
149
|
-
|
|
150
|
-
<DataTable.
|
|
151
|
-
<DataTable.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
197
|
+
<DataTableContext.Provider value={createCtx()}>
|
|
198
|
+
<DataTable.Root>
|
|
199
|
+
<DataTable.Headers />
|
|
200
|
+
<DataTable.Body>
|
|
201
|
+
<DataTable.Row>
|
|
202
|
+
<DataTable.Cell>Custom Cell</DataTable.Cell>
|
|
203
|
+
</DataTable.Row>
|
|
204
|
+
</DataTable.Body>
|
|
205
|
+
</DataTable.Root>
|
|
206
|
+
</DataTableContext.Provider>,
|
|
155
207
|
);
|
|
156
208
|
|
|
157
209
|
expect(screen.getByText("Custom Cell")).toBeInTheDocument();
|
|
@@ -2,7 +2,7 @@ import { createContext, useContext } from "react";
|
|
|
2
2
|
import type { Column, PageInfo, RowOperations, SortState } from "../types";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Context value provided by `DataTable.
|
|
5
|
+
* Context value provided by `DataTable.Provider`.
|
|
6
6
|
*
|
|
7
7
|
* Exposes row operations for optimistic updates, table state, column
|
|
8
8
|
* visibility, and page info so that `DataTable.Headers` / `DataTable.Body`
|
|
@@ -14,7 +14,7 @@ export interface DataTableContextValue<TRow extends Record<string, unknown>> {
|
|
|
14
14
|
deleteRow: RowOperations<TRow>["deleteRow"];
|
|
15
15
|
insertRow: RowOperations<TRow>["insertRow"];
|
|
16
16
|
|
|
17
|
-
// Table state propagated from DataTable.
|
|
17
|
+
// Table state propagated from DataTable.Provider
|
|
18
18
|
columns: Column<TRow>[];
|
|
19
19
|
rows: TRow[];
|
|
20
20
|
loading: boolean;
|
|
@@ -40,9 +40,9 @@ const DataTableContext = createContext<DataTableContextValue<any> | null>(null);
|
|
|
40
40
|
export { DataTableContext };
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* Hook to access row operations from the nearest `DataTable.
|
|
43
|
+
* Hook to access row operations from the nearest `DataTable.Provider`.
|
|
44
44
|
*
|
|
45
|
-
* @throws Error if used outside of `DataTable.
|
|
45
|
+
* @throws Error if used outside of `DataTable.Provider`.
|
|
46
46
|
*
|
|
47
47
|
* @example
|
|
48
48
|
* ```tsx
|
|
@@ -57,7 +57,9 @@ export function useDataTableContext<
|
|
|
57
57
|
>(): DataTableContextValue<TRow> {
|
|
58
58
|
const ctx = useContext(DataTableContext);
|
|
59
59
|
if (!ctx) {
|
|
60
|
-
throw new Error(
|
|
60
|
+
throw new Error(
|
|
61
|
+
"useDataTableContext must be used within <DataTable.Provider>",
|
|
62
|
+
);
|
|
61
63
|
}
|
|
62
64
|
return ctx as DataTableContextValue<TRow>;
|
|
63
65
|
}
|
|
@@ -7,12 +7,7 @@ import {
|
|
|
7
7
|
import { cn } from "../lib/utils";
|
|
8
8
|
import { CollectionProvider } from "../collection/collection-provider";
|
|
9
9
|
import { Table } from "../table";
|
|
10
|
-
import type {
|
|
11
|
-
Column,
|
|
12
|
-
DataTableRootProps,
|
|
13
|
-
PageInfo,
|
|
14
|
-
UseDataTableReturn,
|
|
15
|
-
} from "../types";
|
|
10
|
+
import type { Column, UseDataTableReturn } from "../types";
|
|
16
11
|
import {
|
|
17
12
|
DataTableContext,
|
|
18
13
|
type DataTableContextValue,
|
|
@@ -23,66 +18,19 @@ import {
|
|
|
23
18
|
// =============================================================================
|
|
24
19
|
|
|
25
20
|
/**
|
|
26
|
-
*
|
|
21
|
+
* Root container for the DataTable compound component.
|
|
22
|
+
*
|
|
23
|
+
* Must be used within `DataTable.Provider`.
|
|
24
|
+
* Renders a `<Table.Root>` wrapper; all data is read from context.
|
|
27
25
|
*/
|
|
28
|
-
|
|
29
|
-
updateRow: () => ({ rollback: () => {} }),
|
|
30
|
-
deleteRow: () => ({
|
|
31
|
-
rollback: () => {},
|
|
32
|
-
deletedRow: {} as Record<string, unknown>,
|
|
33
|
-
}),
|
|
34
|
-
insertRow: () => ({ rollback: () => {} }),
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const defaultPageInfo: PageInfo = {
|
|
38
|
-
hasNextPage: false,
|
|
39
|
-
endCursor: null,
|
|
40
|
-
hasPreviousPage: false,
|
|
41
|
-
startCursor: null,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
function DataTableRoot<TRow extends Record<string, unknown>>({
|
|
45
|
-
columns = [] as Column<TRow>[],
|
|
46
|
-
rows = [] as TRow[],
|
|
47
|
-
loading = false,
|
|
48
|
-
error = null,
|
|
49
|
-
sortStates = [],
|
|
50
|
-
onSort,
|
|
51
|
-
rowOperations,
|
|
26
|
+
function DataTableRoot({
|
|
52
27
|
children,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const contextValue: DataTableContextValue<TRow> = {
|
|
60
|
-
updateRow: (rowOperations?.updateRow ??
|
|
61
|
-
noopRowOps.updateRow) as DataTableContextValue<TRow>["updateRow"],
|
|
62
|
-
deleteRow: (rowOperations?.deleteRow ??
|
|
63
|
-
noopRowOps.deleteRow) as DataTableContextValue<TRow>["deleteRow"],
|
|
64
|
-
insertRow: (rowOperations?.insertRow ??
|
|
65
|
-
noopRowOps.insertRow) as DataTableContextValue<TRow>["insertRow"],
|
|
66
|
-
columns,
|
|
67
|
-
rows,
|
|
68
|
-
loading,
|
|
69
|
-
error,
|
|
70
|
-
sortStates,
|
|
71
|
-
onSort,
|
|
72
|
-
// Inherit column visibility & pageInfo from parent context (DataTable.Provider)
|
|
73
|
-
visibleColumns: parentCtx?.visibleColumns ?? columns,
|
|
74
|
-
isColumnVisible: parentCtx?.isColumnVisible ?? (() => true),
|
|
75
|
-
toggleColumn: parentCtx?.toggleColumn ?? (() => {}),
|
|
76
|
-
showAllColumns: parentCtx?.showAllColumns ?? (() => {}),
|
|
77
|
-
hideAllColumns: parentCtx?.hideAllColumns ?? (() => {}),
|
|
78
|
-
pageInfo: parentCtx?.pageInfo ?? defaultPageInfo,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<DataTableContext.Provider value={contextValue}>
|
|
83
|
-
<Table.Root>{children}</Table.Root>
|
|
84
|
-
</DataTableContext.Provider>
|
|
85
|
-
);
|
|
28
|
+
className,
|
|
29
|
+
}: {
|
|
30
|
+
children: ReactNode;
|
|
31
|
+
className?: string;
|
|
32
|
+
}) {
|
|
33
|
+
return <Table.Root className={className}>{children}</Table.Root>;
|
|
86
34
|
}
|
|
87
35
|
|
|
88
36
|
// =============================================================================
|
|
@@ -125,8 +73,8 @@ function DataTableProviderComponent<TRow extends Record<string, unknown>>({
|
|
|
125
73
|
rows: value.rows,
|
|
126
74
|
loading: value.loading,
|
|
127
75
|
error: value.error,
|
|
128
|
-
sortStates: value.
|
|
129
|
-
onSort: value.
|
|
76
|
+
sortStates: value.sortStates ?? [],
|
|
77
|
+
onSort: value.onSort,
|
|
130
78
|
updateRow: value.updateRow,
|
|
131
79
|
deleteRow: value.deleteRow,
|
|
132
80
|
insertRow: value.insertRow,
|
|
@@ -160,14 +108,12 @@ function DataTableProviderComponent<TRow extends Record<string, unknown>>({
|
|
|
160
108
|
// DataTable.Headers
|
|
161
109
|
// =============================================================================
|
|
162
110
|
|
|
163
|
-
|
|
164
|
-
className?: string;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function DataTableHeaders({ className }: DataTableHeadersProps) {
|
|
111
|
+
function DataTableHeaders({ className }: { className?: string }) {
|
|
168
112
|
const ctx = useContext(DataTableContext);
|
|
169
113
|
if (!ctx) {
|
|
170
|
-
throw new Error(
|
|
114
|
+
throw new Error(
|
|
115
|
+
"<DataTable.Headers> must be used within <DataTable.Provider>",
|
|
116
|
+
);
|
|
171
117
|
}
|
|
172
118
|
const { columns, sortStates, onSort } = ctx;
|
|
173
119
|
|
|
@@ -225,15 +171,18 @@ function SortIndicator({ direction }: { direction: "Asc" | "Desc" }) {
|
|
|
225
171
|
// DataTable.Body
|
|
226
172
|
// =============================================================================
|
|
227
173
|
|
|
228
|
-
|
|
174
|
+
function DataTableBody({
|
|
175
|
+
children,
|
|
176
|
+
className,
|
|
177
|
+
}: {
|
|
229
178
|
children?: ReactNode;
|
|
230
179
|
className?: string;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function DataTableBody({ children, className }: DataTableBodyProps) {
|
|
180
|
+
}) {
|
|
234
181
|
const ctx = useContext(DataTableContext);
|
|
235
182
|
if (!ctx) {
|
|
236
|
-
throw new Error(
|
|
183
|
+
throw new Error(
|
|
184
|
+
"<DataTable.Body> must be used within <DataTable.Provider>",
|
|
185
|
+
);
|
|
237
186
|
}
|
|
238
187
|
const { columns, rows, loading, error } = ctx;
|
|
239
188
|
|
|
@@ -276,19 +225,19 @@ function DataTableBody({ children, className }: DataTableBodyProps) {
|
|
|
276
225
|
</Table.Row>
|
|
277
226
|
)}
|
|
278
227
|
{rows?.map((row, rowIndex) => (
|
|
279
|
-
<
|
|
228
|
+
<Table.Row key={rowIndex}>
|
|
280
229
|
{columns?.map((col) => {
|
|
281
230
|
const key = col.kind === "field" ? col.dataKey : col.id;
|
|
282
231
|
return (
|
|
283
|
-
<
|
|
232
|
+
<Table.Cell
|
|
284
233
|
key={key}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
234
|
+
style={col.width ? { width: col.width } : undefined}
|
|
235
|
+
>
|
|
236
|
+
{resolveContent(row, col, rowIndex)}
|
|
237
|
+
</Table.Cell>
|
|
289
238
|
);
|
|
290
239
|
})}
|
|
291
|
-
</
|
|
240
|
+
</Table.Row>
|
|
292
241
|
))}
|
|
293
242
|
</Table.Body>
|
|
294
243
|
);
|
|
@@ -298,82 +247,54 @@ function DataTableBody({ children, className }: DataTableBodyProps) {
|
|
|
298
247
|
// DataTable.Row
|
|
299
248
|
// =============================================================================
|
|
300
249
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function DataTableRow<TRow extends Record<string, unknown>>({
|
|
309
|
-
children,
|
|
310
|
-
...restProps
|
|
311
|
-
}: DataTableRowComponentProps<TRow>) {
|
|
312
|
-
return <Table.Row {...restProps}>{children}</Table.Row>;
|
|
250
|
+
/**
|
|
251
|
+
* Thin wrapper around `Table.Row` for use in custom rendering within
|
|
252
|
+
* `DataTable.Body`.
|
|
253
|
+
*/
|
|
254
|
+
function DataTableRow(props: ComponentProps<"tr">) {
|
|
255
|
+
return <Table.Row {...props} />;
|
|
313
256
|
}
|
|
314
257
|
|
|
315
258
|
// =============================================================================
|
|
316
259
|
// DataTable.Cell
|
|
317
260
|
// =============================================================================
|
|
318
261
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Thin wrapper around `Table.Cell` for use in custom rendering within
|
|
264
|
+
* `DataTable.Body`.
|
|
265
|
+
*/
|
|
266
|
+
function DataTableCell(props: ComponentProps<"td">) {
|
|
267
|
+
return <Table.Cell {...props} />;
|
|
325
268
|
}
|
|
326
269
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
rowIndex = 0,
|
|
331
|
-
children,
|
|
332
|
-
className,
|
|
333
|
-
...restProps
|
|
334
|
-
}: DataTableCellComponentProps<TRow>) {
|
|
335
|
-
const content = resolveContent(children, row, column, rowIndex);
|
|
336
|
-
|
|
337
|
-
return (
|
|
338
|
-
<Table.Cell
|
|
339
|
-
className={className}
|
|
340
|
-
style={column?.width ? { width: column.width } : undefined}
|
|
341
|
-
{...restProps}
|
|
342
|
-
>
|
|
343
|
-
{content}
|
|
344
|
-
</Table.Cell>
|
|
345
|
-
);
|
|
346
|
-
}
|
|
270
|
+
// =============================================================================
|
|
271
|
+
// Helpers
|
|
272
|
+
// =============================================================================
|
|
347
273
|
|
|
348
274
|
/**
|
|
349
|
-
* Resolve cell content from
|
|
275
|
+
* Resolve cell content from row data and column definition.
|
|
350
276
|
*/
|
|
351
277
|
function resolveContent<TRow extends Record<string, unknown>>(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
column: Column<TRow> | undefined,
|
|
278
|
+
row: TRow,
|
|
279
|
+
column: Column<TRow>,
|
|
355
280
|
rowIndex: number,
|
|
356
281
|
): ReactNode {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
return formatValue(value);
|
|
282
|
+
switch (column.kind) {
|
|
283
|
+
case "field": {
|
|
284
|
+
const value = row[column.dataKey];
|
|
285
|
+
if (column.renderer) {
|
|
286
|
+
return createElement(column.renderer, {
|
|
287
|
+
value,
|
|
288
|
+
row,
|
|
289
|
+
rowIndex,
|
|
290
|
+
column,
|
|
291
|
+
});
|
|
370
292
|
}
|
|
371
|
-
|
|
372
|
-
return column.render(row);
|
|
293
|
+
return formatValue(value);
|
|
373
294
|
}
|
|
295
|
+
case "display":
|
|
296
|
+
return column.render(row);
|
|
374
297
|
}
|
|
375
|
-
|
|
376
|
-
return children;
|
|
377
298
|
}
|
|
378
299
|
|
|
379
300
|
/**
|
|
@@ -394,12 +315,11 @@ function formatValue(value: unknown): ReactNode {
|
|
|
394
315
|
/**
|
|
395
316
|
* Data-bound table compound component.
|
|
396
317
|
*
|
|
397
|
-
* Use with `useDataTable()` hook
|
|
398
|
-
*
|
|
318
|
+
* Use with `useDataTable()` hook and wrap with `DataTable.Provider`
|
|
319
|
+
* for context-based data flow.
|
|
399
320
|
*
|
|
400
321
|
* @example
|
|
401
322
|
* ```tsx
|
|
402
|
-
* // Context-based (recommended)
|
|
403
323
|
* const table = useDataTable({ columns, data, loading, collection });
|
|
404
324
|
*
|
|
405
325
|
* <DataTable.Provider value={table}>
|
|
@@ -411,12 +331,6 @@ function formatValue(value: unknown): ReactNode {
|
|
|
411
331
|
* </DataTable.Root>
|
|
412
332
|
* <Pagination />
|
|
413
333
|
* </DataTable.Provider>
|
|
414
|
-
*
|
|
415
|
-
* // Props-based (still supported)
|
|
416
|
-
* <DataTable.Root {...table.rootProps}>
|
|
417
|
-
* <DataTable.Headers />
|
|
418
|
-
* <DataTable.Body />
|
|
419
|
-
* </DataTable.Root>
|
|
420
334
|
* ```
|
|
421
335
|
*/
|
|
422
336
|
export const DataTable = {
|
|
@@ -234,46 +234,23 @@ describe("useDataTable", () => {
|
|
|
234
234
|
});
|
|
235
235
|
|
|
236
236
|
// ---------------------------------------------------------------------------
|
|
237
|
-
//
|
|
237
|
+
// Sort state
|
|
238
238
|
// ---------------------------------------------------------------------------
|
|
239
|
-
describe("
|
|
240
|
-
it("
|
|
241
|
-
const { result } = renderHook(() =>
|
|
242
|
-
useDataTable<TestRow>({
|
|
243
|
-
columns: testColumns,
|
|
244
|
-
data: testData,
|
|
245
|
-
loading: true,
|
|
246
|
-
}),
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
const { rootProps } = result.current;
|
|
250
|
-
expect(rootProps.columns).toHaveLength(4);
|
|
251
|
-
expect(rootProps.rows).toHaveLength(3);
|
|
252
|
-
expect(rootProps.loading).toBe(true);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it("getRowProps returns row", () => {
|
|
239
|
+
describe("sort state", () => {
|
|
240
|
+
it("sortStates is empty when no collection is provided", () => {
|
|
256
241
|
const { result } = renderHook(() =>
|
|
257
242
|
useDataTable<TestRow>({ columns: testColumns, data: testData }),
|
|
258
243
|
);
|
|
259
244
|
|
|
260
|
-
|
|
261
|
-
expect(rowProps.row.name).toBe("Alice");
|
|
245
|
+
expect(result.current.sortStates).toEqual([]);
|
|
262
246
|
});
|
|
263
247
|
|
|
264
|
-
it("
|
|
248
|
+
it("onSort is undefined when no collection is provided", () => {
|
|
265
249
|
const { result } = renderHook(() =>
|
|
266
250
|
useDataTable<TestRow>({ columns: testColumns, data: testData }),
|
|
267
251
|
);
|
|
268
252
|
|
|
269
|
-
|
|
270
|
-
result.current.rows[0],
|
|
271
|
-
testColumns[0],
|
|
272
|
-
0,
|
|
273
|
-
);
|
|
274
|
-
expect(cellProps.row.name).toBe("Alice");
|
|
275
|
-
expect(cellProps.column).toBe(testColumns[0]);
|
|
276
|
-
expect(cellProps.rowIndex).toBe(0);
|
|
253
|
+
expect(result.current.onSort).toBeUndefined();
|
|
277
254
|
});
|
|
278
255
|
});
|
|
279
256
|
});
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import type {
|
|
3
3
|
Column,
|
|
4
|
-
DataTableCellProps,
|
|
5
|
-
DataTableRootProps,
|
|
6
|
-
DataTableRowProps,
|
|
7
4
|
PageInfo,
|
|
5
|
+
SortState,
|
|
8
6
|
UseDataTableOptions,
|
|
9
7
|
UseDataTableReturn,
|
|
10
8
|
} from "../types";
|
|
11
9
|
|
|
12
10
|
/**
|
|
13
11
|
* Hook that integrates data management, column visibility, row operations, and
|
|
14
|
-
*
|
|
12
|
+
* sort/pagination state for the `DataTable.*` compound component.
|
|
15
13
|
*
|
|
16
14
|
* @example
|
|
17
15
|
* ```tsx
|
|
@@ -25,10 +23,12 @@ import type {
|
|
|
25
23
|
* collection,
|
|
26
24
|
* });
|
|
27
25
|
*
|
|
28
|
-
* <DataTable.
|
|
29
|
-
* <DataTable.
|
|
30
|
-
*
|
|
31
|
-
*
|
|
26
|
+
* <DataTable.Provider value={table}>
|
|
27
|
+
* <DataTable.Root>
|
|
28
|
+
* <DataTable.Headers />
|
|
29
|
+
* <DataTable.Body />
|
|
30
|
+
* </DataTable.Root>
|
|
31
|
+
* </DataTable.Provider>
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export function useDataTable<TRow extends Record<string, unknown>>(
|
|
@@ -194,64 +194,30 @@ export function useDataTable<TRow extends Record<string, unknown>>(
|
|
|
194
194
|
);
|
|
195
195
|
|
|
196
196
|
// ---------------------------------------------------------------------------
|
|
197
|
-
//
|
|
197
|
+
// Sort (delegated from collection)
|
|
198
198
|
// ---------------------------------------------------------------------------
|
|
199
|
-
const
|
|
200
|
-
return
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
rowOperations: { updateRow, deleteRow, insertRow },
|
|
211
|
-
children: null,
|
|
212
|
-
};
|
|
213
|
-
}, [
|
|
214
|
-
visibleColumns,
|
|
215
|
-
rows,
|
|
216
|
-
loading,
|
|
217
|
-
error,
|
|
218
|
-
collection,
|
|
219
|
-
updateRow,
|
|
220
|
-
deleteRow,
|
|
221
|
-
insertRow,
|
|
222
|
-
]);
|
|
223
|
-
|
|
224
|
-
const getRowProps = useCallback(
|
|
225
|
-
(row: TRow): DataTableRowProps<TRow> => ({ row }),
|
|
226
|
-
[],
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
const getCellProps = useCallback(
|
|
230
|
-
(
|
|
231
|
-
row: TRow,
|
|
232
|
-
column: Column<TRow>,
|
|
233
|
-
rowIndex: number,
|
|
234
|
-
): DataTableCellProps<TRow> => ({
|
|
235
|
-
row,
|
|
236
|
-
column,
|
|
237
|
-
rowIndex,
|
|
238
|
-
}),
|
|
239
|
-
[],
|
|
240
|
-
);
|
|
199
|
+
const sortStates = useMemo<SortState[]>(() => {
|
|
200
|
+
return collection?.sortStates ?? [];
|
|
201
|
+
}, [collection?.sortStates]);
|
|
202
|
+
|
|
203
|
+
const onSort = useMemo<
|
|
204
|
+
((field: string, direction?: "Asc" | "Desc") => void) | undefined
|
|
205
|
+
>(() => {
|
|
206
|
+
if (!collection) return undefined;
|
|
207
|
+
return (field: string, direction?: "Asc" | "Desc") =>
|
|
208
|
+
collection.setSort(field, direction);
|
|
209
|
+
}, [collection]);
|
|
241
210
|
|
|
242
211
|
// ---------------------------------------------------------------------------
|
|
243
212
|
// Return
|
|
244
213
|
// ---------------------------------------------------------------------------
|
|
245
214
|
return {
|
|
246
|
-
// Props generators
|
|
247
|
-
rootProps,
|
|
248
|
-
getRowProps,
|
|
249
|
-
getCellProps,
|
|
250
|
-
|
|
251
215
|
// Data
|
|
252
216
|
rows,
|
|
253
217
|
loading,
|
|
254
218
|
error,
|
|
219
|
+
sortStates,
|
|
220
|
+
onSort,
|
|
255
221
|
|
|
256
222
|
// Pagination
|
|
257
223
|
pageInfo,
|
package/src/component/index.ts
CHANGED
|
@@ -23,9 +23,6 @@ export type {
|
|
|
23
23
|
UseDataTableOptions,
|
|
24
24
|
UseDataTableReturn,
|
|
25
25
|
RowOperations,
|
|
26
|
-
DataTableRootProps,
|
|
27
|
-
DataTableRowProps,
|
|
28
|
-
DataTableCellProps,
|
|
29
26
|
FieldName,
|
|
30
27
|
TableFieldName,
|
|
31
28
|
OrderableFieldName,
|
|
@@ -62,7 +59,7 @@ export {
|
|
|
62
59
|
export { Table } from "./table";
|
|
63
60
|
|
|
64
61
|
// DataTable (data-bound)
|
|
65
|
-
export { DataTable } from "./data-table/
|
|
62
|
+
export { DataTable } from "./data-table/data-table";
|
|
66
63
|
export { useDataTable } from "./data-table/use-data-table";
|
|
67
64
|
export { useDataTableContext } from "./data-table/data-table-context";
|
|
68
65
|
|
package/src/component/types.ts
CHANGED
|
@@ -511,63 +511,10 @@ export interface RowOperations<TRow extends Record<string, unknown>> {
|
|
|
511
511
|
insertRow: (row: TRow) => { rollback: () => void };
|
|
512
512
|
}
|
|
513
513
|
|
|
514
|
-
/**
|
|
515
|
-
* Props for `DataTable.Root` component (generated by `useDataTable`).
|
|
516
|
-
*/
|
|
517
|
-
export interface DataTableRootProps<TRow extends Record<string, unknown>> {
|
|
518
|
-
/** Visible column definitions */
|
|
519
|
-
columns: Column<TRow>[];
|
|
520
|
-
/** Row data */
|
|
521
|
-
rows: TRow[];
|
|
522
|
-
/** Loading state */
|
|
523
|
-
loading?: boolean;
|
|
524
|
-
/** Error */
|
|
525
|
-
error?: Error | null;
|
|
526
|
-
/** Sort handler (connected to collection) */
|
|
527
|
-
onSort?: (field: string, direction?: "Asc" | "Desc") => void;
|
|
528
|
-
/** Current sort states */
|
|
529
|
-
sortStates?: SortState[];
|
|
530
|
-
/** Row operations (provided to children via Context) */
|
|
531
|
-
rowOperations?: RowOperations<TRow>;
|
|
532
|
-
children: ReactNode;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Props for `DataTable.Row` component.
|
|
537
|
-
*/
|
|
538
|
-
export interface DataTableRowProps<TRow extends Record<string, unknown>> {
|
|
539
|
-
/** The row data */
|
|
540
|
-
row: TRow;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* Props for `DataTable.Cell` component.
|
|
545
|
-
*/
|
|
546
|
-
export interface DataTableCellProps<TRow extends Record<string, unknown>> {
|
|
547
|
-
/** The row data */
|
|
548
|
-
row: TRow;
|
|
549
|
-
/** The column definition */
|
|
550
|
-
column: Column<TRow>;
|
|
551
|
-
/** The row index */
|
|
552
|
-
rowIndex: number;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
514
|
/**
|
|
556
515
|
* Return type of `useDataTable` hook.
|
|
557
516
|
*/
|
|
558
517
|
export interface UseDataTableReturn<TRow extends Record<string, unknown>> {
|
|
559
|
-
// Props generators (for spreading)
|
|
560
|
-
/** Props for DataTable.Root */
|
|
561
|
-
rootProps: DataTableRootProps<TRow>;
|
|
562
|
-
/** Get props for a DataTable.Row */
|
|
563
|
-
getRowProps: (row: TRow) => DataTableRowProps<TRow>;
|
|
564
|
-
/** Get props for a DataTable.Cell */
|
|
565
|
-
getCellProps: (
|
|
566
|
-
row: TRow,
|
|
567
|
-
column: Column<TRow>,
|
|
568
|
-
rowIndex: number,
|
|
569
|
-
) => DataTableCellProps<TRow>;
|
|
570
|
-
|
|
571
518
|
// Data
|
|
572
519
|
/** Row data extracted from collection result */
|
|
573
520
|
rows: TRow[];
|
|
@@ -575,6 +522,10 @@ export interface UseDataTableReturn<TRow extends Record<string, unknown>> {
|
|
|
575
522
|
loading: boolean;
|
|
576
523
|
/** Error */
|
|
577
524
|
error: Error | null;
|
|
525
|
+
/** Current sort states */
|
|
526
|
+
sortStates: SortState[];
|
|
527
|
+
/** Sort handler (connected to collection) */
|
|
528
|
+
onSort?: (field: string, direction?: "Asc" | "Desc") => void;
|
|
578
529
|
|
|
579
530
|
// Pagination (delegated from collection)
|
|
580
531
|
/** Page info from GraphQL response */
|