@izumisy-tailor/tailor-data-viewer 0.3.0-preview.0 → 0.3.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 +24 -17
- package/package.json +1 -1
- package/src/component/collection/collection-provider.tsx +19 -29
- package/src/component/collection/use-collection.test.ts +27 -27
- package/src/component/collection/use-collection.ts +7 -7
- package/src/component/data-table/data-table.tsx +8 -6
- package/src/component/data-table/pagination.tsx +3 -3
- package/src/component/data-table/search-filter-form.tsx +3 -3
- package/src/component/data-table/use-data-table.ts +2 -2
- package/src/component/index.ts +3 -4
- package/src/component/types.ts +6 -6
- package/src/tests/helpers.tsx +4 -4
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@ A low-level React component library for building data table interfaces with Tail
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Separation of Concerns**: Data fetching, query parameter management, and UI are fully decoupled
|
|
8
|
-
- **`
|
|
9
|
-
- **`
|
|
8
|
+
- **`useCollectionVariables` Hook**: Manages filter, sort, and pagination state; outputs Tailor Platform-compatible GraphQL variables
|
|
9
|
+
- **`CollectionVariablesProvider`**: Shares query parameters via React Context across sibling components
|
|
10
10
|
- **`Table.*` Compound Components**: Static, unstyled table primitives (`<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>`)
|
|
11
11
|
- **`DataTable.*` Compound Components**: Data-bound table with sort indicators, cell renderers, and `useDataTable` integration
|
|
12
12
|
- **`useDataTable` Hook**: Integrates data, column visibility, row operations (optimistic updates), and props generators
|
|
@@ -15,7 +15,7 @@ A low-level React component library for building data table interfaces with Tail
|
|
|
15
15
|
- **Utility Components**: `ColumnSelector`, `CsvButton`, `SearchFilterForm`, `Pagination` — all props-based, spreadable from hooks
|
|
16
16
|
- **Multi-sort Support**: Multiple simultaneous sort fields
|
|
17
17
|
- **Optimistic Updates**: `updateRow`, `deleteRow`, `insertRow` with rollback
|
|
18
|
-
- **Presentation Agnostic**: Same `
|
|
18
|
+
- **Presentation Agnostic**: Same `useCollectionVariables` can drive tables, kanbans, calendars, etc.
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
@@ -47,7 +47,7 @@ npm install react react-dom
|
|
|
47
47
|
|
|
48
48
|
```tsx
|
|
49
49
|
import {
|
|
50
|
-
|
|
50
|
+
useCollectionVariables,
|
|
51
51
|
useDataTable,
|
|
52
52
|
DataTable,
|
|
53
53
|
Pagination,
|
|
@@ -95,8 +95,15 @@ const columns = [
|
|
|
95
95
|
|
|
96
96
|
// 2. Build a page
|
|
97
97
|
function OrdersPage() {
|
|
98
|
-
const
|
|
99
|
-
const [result] = useQuery({
|
|
98
|
+
const { variables } = useCollectionVariables({ params: { pageSize: 20 } });
|
|
99
|
+
const [result] = useQuery({
|
|
100
|
+
query: GET_ORDERS,
|
|
101
|
+
variables: {
|
|
102
|
+
...variables.pagination,
|
|
103
|
+
query: variables.query,
|
|
104
|
+
order: variables.order
|
|
105
|
+
}
|
|
106
|
+
});
|
|
100
107
|
|
|
101
108
|
const table = useDataTable<Order>({
|
|
102
109
|
columns,
|
|
@@ -119,12 +126,12 @@ function OrdersPage() {
|
|
|
119
126
|
|
|
120
127
|
## API Overview
|
|
121
128
|
|
|
122
|
-
### `
|
|
129
|
+
### `useCollectionVariables(options)`
|
|
123
130
|
|
|
124
131
|
Manages filter, sort, and pagination state. Returns `variables` containing `query`, `order`, and `pagination` sub-properties in Tailor Platform-compatible format.
|
|
125
132
|
|
|
126
133
|
```tsx
|
|
127
|
-
const collection =
|
|
134
|
+
const { variables, ...collection } = useCollectionVariables({
|
|
128
135
|
params: {
|
|
129
136
|
pageSize: 20,
|
|
130
137
|
initialSort: [{ field: "createdAt", direction: "Desc" }],
|
|
@@ -135,9 +142,9 @@ const collection = useCollection({
|
|
|
135
142
|
const [result] = useQuery({
|
|
136
143
|
query: GET_ORDERS,
|
|
137
144
|
variables: {
|
|
138
|
-
...
|
|
139
|
-
query:
|
|
140
|
-
order:
|
|
145
|
+
...variables.pagination,
|
|
146
|
+
query: variables.query,
|
|
147
|
+
order: variables.order,
|
|
141
148
|
},
|
|
142
149
|
});
|
|
143
150
|
|
|
@@ -163,15 +170,15 @@ collection.hasPrevPage; // boolean
|
|
|
163
170
|
collection.hasNextPage; // boolean
|
|
164
171
|
```
|
|
165
172
|
|
|
166
|
-
### `DataTable.Provider` / `useDataTableContext()` / `
|
|
173
|
+
### `DataTable.Provider` / `useDataTableContext()` / `useCollectionVariablesContext()`
|
|
167
174
|
|
|
168
175
|
`DataTable.Provider` wraps the table UI and provides both data table and collection context. All utility components (`Pagination`, `ColumnSelector`, `CsvButton`, `SearchFilterForm`) read from this context — no prop spreading needed.
|
|
169
176
|
|
|
170
|
-
When `collection` is passed to `useDataTable`, `DataTable.Provider` automatically wraps a `
|
|
177
|
+
When `collection` is passed to `useDataTable`, `DataTable.Provider` automatically wraps a `CollectionVariablesProvider` so child components can use `useCollectionVariablesContext()`.
|
|
171
178
|
|
|
172
179
|
```tsx
|
|
173
180
|
<DataTable.Provider value={table}>
|
|
174
|
-
<StatusFilter /> {/*
|
|
181
|
+
<StatusFilter /> {/* useCollectionVariablesContext() inside */}
|
|
175
182
|
<DataTable.Root>
|
|
176
183
|
<DataTable.Headers />
|
|
177
184
|
<DataTable.Body />
|
|
@@ -180,13 +187,13 @@ When `collection` is passed to `useDataTable`, `DataTable.Provider` automaticall
|
|
|
180
187
|
</DataTable.Provider>
|
|
181
188
|
```
|
|
182
189
|
|
|
183
|
-
For cases where you need `
|
|
190
|
+
For cases where you need `CollectionVariablesProvider` without `DataTable.Provider` (e.g., non-table UIs), you can use it standalone:
|
|
184
191
|
|
|
185
192
|
```tsx
|
|
186
|
-
<
|
|
193
|
+
<CollectionVariablesProvider value={collection}>
|
|
187
194
|
<StatusFilter />
|
|
188
195
|
<CustomKanbanBoard />
|
|
189
|
-
</
|
|
196
|
+
</CollectionVariablesProvider>
|
|
190
197
|
```
|
|
191
198
|
|
|
192
199
|
### Column Definition Helper
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createContext, useContext, type ReactNode } from "react";
|
|
2
2
|
import type { UseCollectionReturn } from "../types";
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const CollectionVariablesContext = createContext<UseCollectionReturn<
|
|
5
5
|
string,
|
|
6
6
|
unknown
|
|
7
7
|
> | null>(null);
|
|
@@ -9,14 +9,18 @@ const CollectionContext = createContext<UseCollectionReturn<
|
|
|
9
9
|
/**
|
|
10
10
|
* Provider that shares collection query parameters via React Context.
|
|
11
11
|
*
|
|
12
|
-
* @example
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const { variables, ...collection } = useCollectionVariables({ params: { pageSize: 20 } });
|
|
15
|
+
*
|
|
16
|
+
* <CollectionVariablesProvider value={collection}>
|
|
13
17
|
* <FilterPanel />
|
|
14
18
|
* <DataTable.Root>...</DataTable.Root>
|
|
15
|
-
* <Pagination
|
|
16
|
-
* </
|
|
19
|
+
* <Pagination />
|
|
20
|
+
* </CollectionVariablesProvider>
|
|
17
21
|
* ```
|
|
18
22
|
*/
|
|
19
|
-
export function
|
|
23
|
+
export function CollectionVariablesProvider({
|
|
20
24
|
value,
|
|
21
25
|
children,
|
|
22
26
|
}: {
|
|
@@ -24,56 +28,42 @@ export function CollectionProvider({
|
|
|
24
28
|
children: ReactNode;
|
|
25
29
|
}) {
|
|
26
30
|
return (
|
|
27
|
-
<
|
|
31
|
+
<CollectionVariablesContext.Provider value={value}>
|
|
28
32
|
{children}
|
|
29
|
-
</
|
|
33
|
+
</CollectionVariablesContext.Provider>
|
|
30
34
|
);
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/**
|
|
34
|
-
* Hook to access collection state from the nearest `
|
|
38
|
+
* Hook to access collection state from the nearest `CollectionVariablesProvider`.
|
|
35
39
|
*
|
|
36
|
-
* Returns the same interface as `
|
|
40
|
+
* Returns the same interface as `useCollectionVariables()`. Pass a `TFieldName`
|
|
37
41
|
* type parameter to narrow method arguments like `addFilter` / `setSort`.
|
|
38
42
|
*
|
|
39
43
|
* @typeParam TFieldName - Union of allowed field name strings (default: `string`).
|
|
40
44
|
*
|
|
41
|
-
* @throws Error if used outside of `
|
|
45
|
+
* @throws Error if used outside of `CollectionVariablesProvider`.
|
|
42
46
|
*
|
|
43
47
|
* @example
|
|
44
48
|
* ```tsx
|
|
45
49
|
* function StatusFilter() {
|
|
46
|
-
* const { filters, addFilter, removeFilter } =
|
|
50
|
+
* const { filters, addFilter, removeFilter } = useCollectionVariablesContext();
|
|
47
51
|
* // ...
|
|
48
52
|
* }
|
|
49
53
|
*
|
|
50
54
|
* // With typed field names:
|
|
51
55
|
* type TaskField = FieldName<typeof tableMetadata, "task">;
|
|
52
|
-
* const { addFilter } =
|
|
56
|
+
* const { addFilter } = useCollectionVariablesContext<TaskField>();
|
|
53
57
|
* ```
|
|
54
58
|
*/
|
|
55
|
-
export function
|
|
59
|
+
export function useCollectionVariablesContext<
|
|
56
60
|
TFieldName extends string = string,
|
|
57
61
|
>(): UseCollectionReturn<TFieldName> {
|
|
58
|
-
const ctx = useContext(
|
|
62
|
+
const ctx = useContext(CollectionVariablesContext);
|
|
59
63
|
if (!ctx) {
|
|
60
64
|
throw new Error(
|
|
61
|
-
"
|
|
65
|
+
"useCollectionVariablesContext must be used within <CollectionVariablesProvider>",
|
|
62
66
|
);
|
|
63
67
|
}
|
|
64
68
|
return ctx as UseCollectionReturn<TFieldName>;
|
|
65
69
|
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* `Collection` namespace object providing the Provider component.
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```tsx
|
|
72
|
-
* <Collection.Provider value={collection}>
|
|
73
|
-
* ...
|
|
74
|
-
* </Collection.Provider>
|
|
75
|
-
* ```
|
|
76
|
-
*/
|
|
77
|
-
export const Collection = {
|
|
78
|
-
Provider: CollectionProvider,
|
|
79
|
-
} as const;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { renderHook, act } from "@testing-library/react";
|
|
2
2
|
import { describe, it, expect } from "vitest";
|
|
3
3
|
import type { TableMetadataMap } from "../../generator/metadata-generator";
|
|
4
|
-
import {
|
|
4
|
+
import { useCollectionVariables } from "./use-collection";
|
|
5
5
|
|
|
6
|
-
describe("
|
|
6
|
+
describe("useCollectionVariables", () => {
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
// Initial state
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
describe("initial state", () => {
|
|
11
11
|
it("returns default variables with pageSize 20", () => {
|
|
12
|
-
const { result } = renderHook(() =>
|
|
12
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
13
13
|
expect(result.current.variables.pagination).toEqual({ first: 20 });
|
|
14
14
|
expect(result.current.variables.query).toBeUndefined();
|
|
15
15
|
expect(result.current.variables.order).toBeUndefined();
|
|
@@ -21,14 +21,14 @@ describe("useCollection", () => {
|
|
|
21
21
|
|
|
22
22
|
it("uses custom pageSize", () => {
|
|
23
23
|
const { result } = renderHook(() =>
|
|
24
|
-
|
|
24
|
+
useCollectionVariables({ params: { pageSize: 50 } }),
|
|
25
25
|
);
|
|
26
26
|
expect(result.current.variables.pagination.first).toBe(50);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
it("applies initial sort", () => {
|
|
30
30
|
const { result } = renderHook(() =>
|
|
31
|
-
|
|
31
|
+
useCollectionVariables({
|
|
32
32
|
params: {
|
|
33
33
|
initialSort: [{ field: "createdAt", direction: "Desc" }],
|
|
34
34
|
},
|
|
@@ -44,7 +44,7 @@ describe("useCollection", () => {
|
|
|
44
44
|
|
|
45
45
|
it("applies initial filters", () => {
|
|
46
46
|
const { result } = renderHook(() =>
|
|
47
|
-
|
|
47
|
+
useCollectionVariables({
|
|
48
48
|
params: {
|
|
49
49
|
initialFilters: [
|
|
50
50
|
{
|
|
@@ -68,7 +68,7 @@ describe("useCollection", () => {
|
|
|
68
68
|
// ---------------------------------------------------------------------------
|
|
69
69
|
describe("filter operations", () => {
|
|
70
70
|
it("adds a filter", () => {
|
|
71
|
-
const { result } = renderHook(() =>
|
|
71
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
72
72
|
|
|
73
73
|
act(() => {
|
|
74
74
|
result.current.addFilter("status", "eq", "ACTIVE");
|
|
@@ -86,7 +86,7 @@ describe("useCollection", () => {
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it("replaces filter for same field", () => {
|
|
89
|
-
const { result } = renderHook(() =>
|
|
89
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
90
90
|
|
|
91
91
|
act(() => {
|
|
92
92
|
result.current.addFilter("status", "eq", "ACTIVE");
|
|
@@ -100,7 +100,7 @@ describe("useCollection", () => {
|
|
|
100
100
|
});
|
|
101
101
|
|
|
102
102
|
it("sets filters in bulk", () => {
|
|
103
|
-
const { result } = renderHook(() =>
|
|
103
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
104
104
|
|
|
105
105
|
act(() => {
|
|
106
106
|
result.current.setFilters([
|
|
@@ -125,7 +125,7 @@ describe("useCollection", () => {
|
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
it("removes a filter", () => {
|
|
128
|
-
const { result } = renderHook(() =>
|
|
128
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
129
129
|
|
|
130
130
|
act(() => {
|
|
131
131
|
result.current.addFilter("status", "eq", "ACTIVE");
|
|
@@ -140,7 +140,7 @@ describe("useCollection", () => {
|
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
it("clears all filters", () => {
|
|
143
|
-
const { result } = renderHook(() =>
|
|
143
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
144
144
|
|
|
145
145
|
act(() => {
|
|
146
146
|
result.current.addFilter("status", "eq", "ACTIVE");
|
|
@@ -155,7 +155,7 @@ describe("useCollection", () => {
|
|
|
155
155
|
});
|
|
156
156
|
|
|
157
157
|
it("resets pagination when filters change", () => {
|
|
158
|
-
const { result } = renderHook(() =>
|
|
158
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
159
159
|
|
|
160
160
|
// Navigate to next page
|
|
161
161
|
act(() => {
|
|
@@ -177,7 +177,7 @@ describe("useCollection", () => {
|
|
|
177
177
|
// ---------------------------------------------------------------------------
|
|
178
178
|
describe("sort operations", () => {
|
|
179
179
|
it("sets sort", () => {
|
|
180
|
-
const { result } = renderHook(() =>
|
|
180
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
181
181
|
|
|
182
182
|
act(() => {
|
|
183
183
|
result.current.setSort("createdAt", "Desc");
|
|
@@ -192,7 +192,7 @@ describe("useCollection", () => {
|
|
|
192
192
|
});
|
|
193
193
|
|
|
194
194
|
it("appends sort for different fields", () => {
|
|
195
|
-
const { result } = renderHook(() =>
|
|
195
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
196
196
|
|
|
197
197
|
act(() => {
|
|
198
198
|
result.current.setSort("createdAt", "Desc");
|
|
@@ -208,7 +208,7 @@ describe("useCollection", () => {
|
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
it("replaces direction for existing field", () => {
|
|
211
|
-
const { result } = renderHook(() =>
|
|
211
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
212
212
|
|
|
213
213
|
act(() => {
|
|
214
214
|
result.current.setSort("createdAt", "Desc");
|
|
@@ -227,7 +227,7 @@ describe("useCollection", () => {
|
|
|
227
227
|
});
|
|
228
228
|
|
|
229
229
|
it("removes sort when direction is undefined", () => {
|
|
230
|
-
const { result } = renderHook(() =>
|
|
230
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
231
231
|
|
|
232
232
|
act(() => {
|
|
233
233
|
result.current.setSort("createdAt", "Desc");
|
|
@@ -245,7 +245,7 @@ describe("useCollection", () => {
|
|
|
245
245
|
});
|
|
246
246
|
|
|
247
247
|
it("clears sort", () => {
|
|
248
|
-
const { result } = renderHook(() =>
|
|
248
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
249
249
|
|
|
250
250
|
act(() => {
|
|
251
251
|
result.current.setSort("createdAt", "Desc");
|
|
@@ -264,7 +264,7 @@ describe("useCollection", () => {
|
|
|
264
264
|
// ---------------------------------------------------------------------------
|
|
265
265
|
describe("pagination operations", () => {
|
|
266
266
|
it("navigates to next page (forward)", () => {
|
|
267
|
-
const { result } = renderHook(() =>
|
|
267
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
268
268
|
|
|
269
269
|
act(() => {
|
|
270
270
|
result.current.nextPage("cursor1");
|
|
@@ -279,7 +279,7 @@ describe("useCollection", () => {
|
|
|
279
279
|
});
|
|
280
280
|
|
|
281
281
|
it("navigates to previous page (backward)", () => {
|
|
282
|
-
const { result } = renderHook(() =>
|
|
282
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
283
283
|
|
|
284
284
|
act(() => {
|
|
285
285
|
result.current.prevPage("cursor1");
|
|
@@ -294,7 +294,7 @@ describe("useCollection", () => {
|
|
|
294
294
|
});
|
|
295
295
|
|
|
296
296
|
it("switches direction on nextPage after prevPage", () => {
|
|
297
|
-
const { result } = renderHook(() =>
|
|
297
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
298
298
|
|
|
299
299
|
act(() => {
|
|
300
300
|
result.current.prevPage("cursorB");
|
|
@@ -310,7 +310,7 @@ describe("useCollection", () => {
|
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
it("resets page to forward direction", () => {
|
|
313
|
-
const { result } = renderHook(() =>
|
|
313
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
314
314
|
|
|
315
315
|
act(() => {
|
|
316
316
|
result.current.prevPage("cursor1");
|
|
@@ -324,7 +324,7 @@ describe("useCollection", () => {
|
|
|
324
324
|
});
|
|
325
325
|
|
|
326
326
|
it("tracks hasPrevPage from currentPage and hasNextPage from setPageInfo", () => {
|
|
327
|
-
const { result } = renderHook(() =>
|
|
327
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
328
328
|
|
|
329
329
|
// Initially on page 1: no prev, no next
|
|
330
330
|
expect(result.current.hasPrevPage).toBe(false);
|
|
@@ -357,7 +357,7 @@ describe("useCollection", () => {
|
|
|
357
357
|
describe("variables", () => {
|
|
358
358
|
it("generates complete variables with filters, sort, and cursor", () => {
|
|
359
359
|
const { result } = renderHook(() =>
|
|
360
|
-
|
|
360
|
+
useCollectionVariables({
|
|
361
361
|
params: {
|
|
362
362
|
pageSize: 10,
|
|
363
363
|
initialFilters: [
|
|
@@ -387,7 +387,7 @@ describe("useCollection", () => {
|
|
|
387
387
|
});
|
|
388
388
|
|
|
389
389
|
it("omits undefined fields from pagination", () => {
|
|
390
|
-
const { result } = renderHook(() =>
|
|
390
|
+
const { result } = renderHook(() => useCollectionVariables({}));
|
|
391
391
|
const { pagination } = result.current.variables;
|
|
392
392
|
expect(pagination).toEqual({ first: 20 });
|
|
393
393
|
expect("after" in pagination).toBe(false);
|
|
@@ -397,7 +397,7 @@ describe("useCollection", () => {
|
|
|
397
397
|
|
|
398
398
|
it("returns undefined for query and order when empty", () => {
|
|
399
399
|
const { result } = renderHook(() =>
|
|
400
|
-
|
|
400
|
+
useCollectionVariables({ params: { pageSize: 10 } }),
|
|
401
401
|
);
|
|
402
402
|
|
|
403
403
|
expect(result.current.variables.query).toBeUndefined();
|
|
@@ -432,7 +432,7 @@ describe("useCollection", () => {
|
|
|
432
432
|
|
|
433
433
|
it("works with tableMetadata", () => {
|
|
434
434
|
const { result } = renderHook(() =>
|
|
435
|
-
|
|
435
|
+
useCollectionVariables({
|
|
436
436
|
tableMetadata: testMetadata.task,
|
|
437
437
|
params: { pageSize: 10 },
|
|
438
438
|
}),
|
|
@@ -442,7 +442,7 @@ describe("useCollection", () => {
|
|
|
442
442
|
|
|
443
443
|
it("applies typed initialSort", () => {
|
|
444
444
|
const { result } = renderHook(() =>
|
|
445
|
-
|
|
445
|
+
useCollectionVariables({
|
|
446
446
|
tableMetadata: testMetadata.task,
|
|
447
447
|
params: {
|
|
448
448
|
initialSort: [{ field: "dueDate", direction: "Desc" }],
|
|
@@ -29,18 +29,18 @@ import type { TableFieldName, TableOrderableFieldName } from "../types";
|
|
|
29
29
|
* ```tsx
|
|
30
30
|
* import { tableMetadata } from "./generated/data-viewer-metadata.generated";
|
|
31
31
|
*
|
|
32
|
-
* const
|
|
32
|
+
* const { variables } = useCollectionVariables({
|
|
33
33
|
* tableMetadata: tableMetadata.task,
|
|
34
34
|
* params: { pageSize: 20 },
|
|
35
35
|
* });
|
|
36
|
-
* const { query, order, pagination } =
|
|
36
|
+
* const { query, order, pagination } = variables;
|
|
37
37
|
* const [result] = useQuery({
|
|
38
38
|
* query: GET_TASKS,
|
|
39
39
|
* variables: { ...pagination, query, order },
|
|
40
40
|
* });
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
|
-
export function
|
|
43
|
+
export function useCollectionVariables<const TTable extends TableMetadata>(
|
|
44
44
|
options: UseCollectionOptions<
|
|
45
45
|
TableFieldName<TTable>,
|
|
46
46
|
TableMetadataFilter<TTable>
|
|
@@ -70,15 +70,15 @@ export function useCollection<const TTable extends TableMetadata>(
|
|
|
70
70
|
*
|
|
71
71
|
* @example
|
|
72
72
|
* ```tsx
|
|
73
|
-
* const
|
|
74
|
-
* const { query, order, pagination } =
|
|
73
|
+
* const { variables } = useCollectionVariables({ params: { pageSize: 20 } });
|
|
74
|
+
* const { query, order, pagination } = variables;
|
|
75
75
|
* const [result] = useQuery({
|
|
76
76
|
* query: GET_ORDERS,
|
|
77
77
|
* variables: { ...pagination, query, order },
|
|
78
78
|
* });
|
|
79
79
|
* ```
|
|
80
80
|
*/
|
|
81
|
-
export function
|
|
81
|
+
export function useCollectionVariables(
|
|
82
82
|
options: UseCollectionOptions & {
|
|
83
83
|
tableMetadata?: never;
|
|
84
84
|
},
|
|
@@ -87,7 +87,7 @@ export function useCollection(
|
|
|
87
87
|
// -----------------------------------------------------------------------------
|
|
88
88
|
// Implementation
|
|
89
89
|
// -----------------------------------------------------------------------------
|
|
90
|
-
export function
|
|
90
|
+
export function useCollectionVariables(
|
|
91
91
|
options: UseCollectionOptions & {
|
|
92
92
|
tableMetadata?: TableMetadata;
|
|
93
93
|
},
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type ReactNode,
|
|
9
9
|
} from "react";
|
|
10
10
|
import { cn } from "../lib/utils";
|
|
11
|
-
import {
|
|
11
|
+
import { CollectionVariablesProvider } from "../collection/collection-provider";
|
|
12
12
|
import { Table } from "../table";
|
|
13
13
|
import type { RowAction, SortConfig, UseDataTableReturn } from "../types";
|
|
14
14
|
import {
|
|
@@ -53,14 +53,14 @@ function DataTableRoot({
|
|
|
53
53
|
/**
|
|
54
54
|
* Provider that shares `useDataTable()` state via React Context.
|
|
55
55
|
*
|
|
56
|
-
* Internally provides both `DataTableContext` and `
|
|
56
|
+
* Internally provides both `DataTableContext` and `CollectionVariablesContext`
|
|
57
57
|
* so that utility components (`Pagination`, `ColumnSelector`,
|
|
58
58
|
* `SearchFilterForm`, `CsvButton`) can consume data without explicit props.
|
|
59
59
|
*
|
|
60
60
|
* @example
|
|
61
61
|
* ```tsx
|
|
62
|
-
* const collection =
|
|
63
|
-
* const [result] = useQuery({ query: GET_ORDERS, variables: { ...
|
|
62
|
+
* const { variables, ...collection } = useCollectionVariables({ params: { pageSize: 20 } });
|
|
63
|
+
* const [result] = useQuery({ query: GET_ORDERS, variables: { ...variables.pagination, query: variables.query, order: variables.order } });
|
|
64
64
|
* const table = useDataTable({ columns, data: result.data?.orders, collection });
|
|
65
65
|
*
|
|
66
66
|
* <DataTable.Provider value={table}>
|
|
@@ -110,10 +110,12 @@ function DataTableProviderComponent<TRow extends Record<string, unknown>>({
|
|
|
110
110
|
</DataTableContext.Provider>
|
|
111
111
|
);
|
|
112
112
|
|
|
113
|
-
// Wrap with
|
|
113
|
+
// Wrap with CollectionVariablesContext when collection is available
|
|
114
114
|
if (collectionValue) {
|
|
115
115
|
return (
|
|
116
|
-
<
|
|
116
|
+
<CollectionVariablesProvider value={collectionValue}>
|
|
117
|
+
{inner}
|
|
118
|
+
</CollectionVariablesProvider>
|
|
117
119
|
);
|
|
118
120
|
}
|
|
119
121
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useDataTableContext } from "./data-table-context";
|
|
2
|
-
import {
|
|
2
|
+
import { useCollectionVariablesContext } from "../collection/collection-provider";
|
|
3
3
|
import { getLabels } from "./i18n";
|
|
4
4
|
|
|
5
5
|
// =============================================================================
|
|
@@ -112,7 +112,7 @@ const btnClass =
|
|
|
112
112
|
/**
|
|
113
113
|
* Pagination controls with first/prev/next/last navigation and page indicator.
|
|
114
114
|
*
|
|
115
|
-
* Reads pagination state from `DataTableContext` and `
|
|
115
|
+
* Reads pagination state from `DataTableContext` and `CollectionVariablesContext`.
|
|
116
116
|
* Must be rendered inside `DataTable.Provider` but **outside** `DataTable.Root`.
|
|
117
117
|
*
|
|
118
118
|
* @example
|
|
@@ -139,7 +139,7 @@ export function Pagination({ labels, pageSizeOptions }: PaginationProps = {}) {
|
|
|
139
139
|
goToLastPage,
|
|
140
140
|
pageSize,
|
|
141
141
|
setPageSize,
|
|
142
|
-
} =
|
|
142
|
+
} = useCollectionVariablesContext();
|
|
143
143
|
|
|
144
144
|
const pl = getLabels(locale).pagination;
|
|
145
145
|
const firstLabel = labels?.first ?? pl.first;
|
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
} from "../types";
|
|
15
15
|
import { OPERATORS_BY_FILTER_TYPE } from "../types";
|
|
16
16
|
import { useDataTableContext } from "./data-table-context";
|
|
17
|
-
import {
|
|
17
|
+
import { useCollectionVariablesContext } from "../collection/collection-provider";
|
|
18
18
|
import { getLabels } from "./i18n";
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -22,7 +22,7 @@ import { getLabels } from "./i18n";
|
|
|
22
22
|
*
|
|
23
23
|
* Renders a dropdown panel with type-specific filter inputs, operator
|
|
24
24
|
* selectors, and active filter badges. Reads `columns` from
|
|
25
|
-
* `DataTableContext` and filter state from `
|
|
25
|
+
* `DataTableContext` and filter state from `CollectionVariablesContext`.
|
|
26
26
|
* Must be rendered inside `DataTable.Provider`.
|
|
27
27
|
*
|
|
28
28
|
* All text is customisable through the optional `labels` prop (defaults
|
|
@@ -46,7 +46,7 @@ export function SearchFilterForm({
|
|
|
46
46
|
} = {}) {
|
|
47
47
|
const { columns, locale } = useDataTableContext();
|
|
48
48
|
const { filters, addFilter, removeFilter, clearFilters } =
|
|
49
|
-
|
|
49
|
+
useCollectionVariablesContext();
|
|
50
50
|
const sf = getLabels(locale).searchFilter;
|
|
51
51
|
const filterableColumns = columns.filter((col) => !!col.filter);
|
|
52
52
|
|
|
@@ -13,8 +13,8 @@ import type {
|
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
15
|
* ```tsx
|
|
16
|
-
* const collection =
|
|
17
|
-
* const { query, order, pagination } =
|
|
16
|
+
* const { variables, ...collection } = useCollectionVariables({ params: { pageSize: 20 } });
|
|
17
|
+
* const { query, order, pagination } = variables;
|
|
18
18
|
* const [result] = useQuery({
|
|
19
19
|
* query: GET_ORDERS,
|
|
20
20
|
* variables: { ...pagination, query, order },
|
package/src/component/index.ts
CHANGED
|
@@ -42,11 +42,10 @@ export {
|
|
|
42
42
|
} from "./types";
|
|
43
43
|
|
|
44
44
|
// Collection
|
|
45
|
-
export {
|
|
45
|
+
export { useCollectionVariables } from "./collection/use-collection";
|
|
46
46
|
export {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
useCollectionContext,
|
|
47
|
+
CollectionVariablesProvider,
|
|
48
|
+
useCollectionVariablesContext,
|
|
50
49
|
} from "./collection/collection-provider";
|
|
51
50
|
|
|
52
51
|
// Table (static)
|
package/src/component/types.ts
CHANGED
|
@@ -219,13 +219,13 @@ export interface PageInfo {
|
|
|
219
219
|
* GraphQL query variables in Tailor Platform format.
|
|
220
220
|
*
|
|
221
221
|
* Field-level type safety for `query` and `order` is not enforced here.
|
|
222
|
-
* When `
|
|
222
|
+
* When `useCollectionVariables` is called with metadata, `BuildQueryVariables`
|
|
223
223
|
* produces precise per-field filter types at compile time.
|
|
224
224
|
*/
|
|
225
225
|
export interface QueryVariables {
|
|
226
|
-
/** Filter object built at runtime by `
|
|
226
|
+
/** Filter object built at runtime by `useCollectionVariables`. */
|
|
227
227
|
query?: Record<string, unknown>;
|
|
228
|
-
/** Sort order built at runtime by `
|
|
228
|
+
/** Sort order built at runtime by `useCollectionVariables`. */
|
|
229
229
|
order?: { field: string; direction: "Asc" | "Desc" }[];
|
|
230
230
|
/** Forward pagination: number of items to fetch */
|
|
231
231
|
first?: number | null;
|
|
@@ -425,11 +425,11 @@ export type ColumnDefinition<TRow extends Record<string, unknown>> =
|
|
|
425
425
|
Column<TRow>;
|
|
426
426
|
|
|
427
427
|
// =============================================================================
|
|
428
|
-
//
|
|
428
|
+
// useCollectionVariables Types
|
|
429
429
|
// =============================================================================
|
|
430
430
|
|
|
431
431
|
/**
|
|
432
|
-
* Options for `
|
|
432
|
+
* Options for `useCollectionVariables` hook.
|
|
433
433
|
*
|
|
434
434
|
* @typeParam TFieldName - Union of allowed field name strings (default: `string`).
|
|
435
435
|
*/
|
|
@@ -449,7 +449,7 @@ export interface UseCollectionOptions<
|
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
/**
|
|
452
|
-
* Return type of `
|
|
452
|
+
* Return type of `useCollectionVariables` hook.
|
|
453
453
|
*
|
|
454
454
|
* Methods that accept a field name are typed with `TFieldName` so that
|
|
455
455
|
* auto-completion works when a concrete union is supplied.
|
package/src/tests/helpers.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { vi } from "vitest";
|
|
|
3
3
|
import type { Column, UseCollectionReturn } from "../component/types";
|
|
4
4
|
import { DataTableContext } from "../component/data-table/data-table-context";
|
|
5
5
|
import type { DataTableContextValue } from "../component/data-table/data-table-context";
|
|
6
|
-
import {
|
|
6
|
+
import { CollectionVariablesProvider } from "../component/collection/collection-provider";
|
|
7
7
|
|
|
8
8
|
// =============================================================================
|
|
9
9
|
// Mock factory: DataTableContext
|
|
@@ -43,7 +43,7 @@ export function createMockDataTableContext<T extends Record<string, unknown>>(
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// =============================================================================
|
|
46
|
-
// Mock factory:
|
|
46
|
+
// Mock factory: CollectionVariablesContext
|
|
47
47
|
// =============================================================================
|
|
48
48
|
|
|
49
49
|
export function createMockCollectionContext(
|
|
@@ -122,7 +122,7 @@ export function createTestProviders<
|
|
|
122
122
|
collection?: Partial<UseCollectionReturn<string, unknown>>;
|
|
123
123
|
}) {
|
|
124
124
|
return (
|
|
125
|
-
<
|
|
125
|
+
<CollectionVariablesProvider
|
|
126
126
|
value={createMockCollectionContext({
|
|
127
127
|
...defaults.collectionDefaults,
|
|
128
128
|
...collection,
|
|
@@ -136,7 +136,7 @@ export function createTestProviders<
|
|
|
136
136
|
>
|
|
137
137
|
{children}
|
|
138
138
|
</DataTableContext.Provider>
|
|
139
|
-
</
|
|
139
|
+
</CollectionVariablesProvider>
|
|
140
140
|
);
|
|
141
141
|
};
|
|
142
142
|
}
|