@izumisy-tailor/tailor-data-viewer 0.2.6 → 0.2.8
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 +39 -36
- package/package.json +1 -1
- package/src/component/collection/collection-provider.tsx +90 -0
- package/src/component/{collection-params/use-collection-params.test.ts → collection/use-collection.test.ts} +97 -145
- package/src/component/{collection-params/use-collection-params.ts → collection/use-collection.ts} +66 -67
- package/src/component/data-table/index.tsx +1 -1
- package/src/component/data-table/use-data-table.ts +14 -14
- package/src/component/index.ts +9 -8
- package/src/component/search-filter-form.tsx +4 -2
- package/src/component/types.ts +121 -45
- package/src/component/collection-params/collection-params-provider.tsx +0 -83
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
|
+
- **`useCollection` Hook**: Manages filter, sort, and pagination state; outputs Tailor Platform-compatible GraphQL variables
|
|
9
|
+
- **`Collection.Provider`**: 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 `useCollection` can drive tables, kanbans, calendars, etc.
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
@@ -47,9 +47,9 @@ npm install react react-dom
|
|
|
47
47
|
|
|
48
48
|
```tsx
|
|
49
49
|
import {
|
|
50
|
-
|
|
50
|
+
useCollection,
|
|
51
51
|
useDataTable,
|
|
52
|
-
|
|
52
|
+
Collection,
|
|
53
53
|
DataTable,
|
|
54
54
|
Pagination,
|
|
55
55
|
field,
|
|
@@ -87,73 +87,76 @@ const columns = [
|
|
|
87
87
|
|
|
88
88
|
// 2. Build a page
|
|
89
89
|
function OrdersPage() {
|
|
90
|
-
const
|
|
91
|
-
const [result] = useQuery({
|
|
90
|
+
const collection = useCollection({ query: GET_ORDERS, params: { pageSize: 20 } });
|
|
91
|
+
const [result] = useQuery({ ...collection.toQueryArgs() });
|
|
92
92
|
|
|
93
93
|
const table = useDataTable<Order>({
|
|
94
94
|
columns,
|
|
95
95
|
data: result.data?.orders,
|
|
96
96
|
loading: result.fetching,
|
|
97
|
-
|
|
97
|
+
collection,
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
return (
|
|
101
|
-
<
|
|
101
|
+
<Collection.Provider value={collection}>
|
|
102
102
|
<DataTable.Root {...table.rootProps}>
|
|
103
103
|
<DataTable.Headers />
|
|
104
104
|
<DataTable.Body />
|
|
105
105
|
</DataTable.Root>
|
|
106
106
|
<Pagination {...table} />
|
|
107
|
-
</
|
|
107
|
+
</Collection.Provider>
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
## API Overview
|
|
113
113
|
|
|
114
|
-
### `
|
|
114
|
+
### `useCollection(options)`
|
|
115
115
|
|
|
116
|
-
Manages filter, sort, and pagination state. Returns Tailor Platform-compatible
|
|
116
|
+
Manages filter, sort, and pagination state. Returns `toQueryArgs()` which produces Tailor Platform-compatible arguments for `useQuery()`.
|
|
117
117
|
|
|
118
118
|
```tsx
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
const collection = useCollection({
|
|
120
|
+
query: GET_ORDERS,
|
|
121
|
+
params: {
|
|
122
|
+
pageSize: 20,
|
|
123
|
+
initialSort: [{ field: "createdAt", direction: "Desc" }],
|
|
124
|
+
},
|
|
122
125
|
});
|
|
123
126
|
|
|
124
|
-
//
|
|
125
|
-
const [result] = useQuery({
|
|
127
|
+
// Spread toQueryArgs() into useQuery
|
|
128
|
+
const [result] = useQuery({ ...collection.toQueryArgs() });
|
|
126
129
|
|
|
127
130
|
// Filter operations
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
collection.addFilter("status", "ACTIVE", "eq");
|
|
132
|
+
collection.setFilters([{ field: "status", fieldType: "enum", operator: "eq", value: "ACTIVE" }]);
|
|
133
|
+
collection.removeFilter("status");
|
|
134
|
+
collection.clearFilters();
|
|
132
135
|
|
|
133
136
|
// Sort operations
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
collection.setSort("createdAt", "Desc");
|
|
138
|
+
collection.setSort("name", "Asc", true); // append for multi-sort
|
|
139
|
+
collection.clearSort();
|
|
137
140
|
|
|
138
141
|
// Pagination
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
collection.nextPage(endCursor);
|
|
143
|
+
collection.prevPage();
|
|
144
|
+
collection.resetPage();
|
|
142
145
|
```
|
|
143
146
|
|
|
144
|
-
### `
|
|
147
|
+
### `Collection.Provider` / `useCollectionContext()`
|
|
145
148
|
|
|
146
|
-
Shares `
|
|
149
|
+
Shares `useCollection` return value via Context. Child components access it with `useCollectionContext()`.
|
|
147
150
|
|
|
148
151
|
```tsx
|
|
149
|
-
<
|
|
150
|
-
<StatusFilter /> {/*
|
|
152
|
+
<Collection.Provider value={collection}>
|
|
153
|
+
<StatusFilter /> {/* useCollectionContext() inside */}
|
|
151
154
|
<DataTable.Root {...table.rootProps}>
|
|
152
155
|
<DataTable.Headers />
|
|
153
156
|
<DataTable.Body />
|
|
154
157
|
</DataTable.Root>
|
|
155
158
|
<Pagination {...table} />
|
|
156
|
-
</
|
|
159
|
+
</Collection.Provider>
|
|
157
160
|
```
|
|
158
161
|
|
|
159
162
|
Provider is optional — for simple cases, pass params directly via props.
|
|
@@ -246,7 +249,7 @@ const table = useDataTable<Order>({
|
|
|
246
249
|
data: result.data?.orders, // CollectionResult<Order>
|
|
247
250
|
loading: result.fetching,
|
|
248
251
|
error: result.error,
|
|
249
|
-
|
|
252
|
+
collection,
|
|
250
253
|
});
|
|
251
254
|
|
|
252
255
|
// Spread props to components
|
|
@@ -320,17 +323,17 @@ Pair with `useDataTable` for automatic header sorting, cell rendering, and row o
|
|
|
320
323
|
|
|
321
324
|
### Utility Components
|
|
322
325
|
|
|
323
|
-
All utility components are props-based and designed to be used with spread from `useDataTable` / `
|
|
326
|
+
All utility components are props-based and designed to be used with spread from `useDataTable` / `useCollection`.
|
|
324
327
|
|
|
325
328
|
| Component | Spread from | Description |
|
|
326
329
|
|-----------|------------|-------------|
|
|
327
330
|
| `ColumnSelector` | `{...table}` | Column visibility toggle UI |
|
|
328
331
|
| `CsvButton` | `{...table}` | Export visible data as CSV |
|
|
329
|
-
| `SearchFilterForm` | `{...table, ...
|
|
332
|
+
| `SearchFilterForm` | `{...table, ...collection}` | Multi-field filter form with operator selection |
|
|
330
333
|
| `Pagination` | `{...table}` | Previous/Next page controls |
|
|
331
334
|
|
|
332
335
|
```tsx
|
|
333
|
-
<SearchFilterForm {...table} {...
|
|
336
|
+
<SearchFilterForm {...table} {...collection} />
|
|
334
337
|
<ColumnSelector {...table} />
|
|
335
338
|
<CsvButton {...table} filename="orders-export" />
|
|
336
339
|
<Pagination {...table} />
|
package/package.json
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
+
import type { UseCollectionReturn } from "../types";
|
|
3
|
+
|
|
4
|
+
const CollectionContext = createContext<UseCollectionReturn<
|
|
5
|
+
string,
|
|
6
|
+
unknown,
|
|
7
|
+
unknown
|
|
8
|
+
> | null>(null);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Provider that shares collection query parameters via React Context.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const collection = useCollection({ params: { pageSize: 20 } });
|
|
16
|
+
*
|
|
17
|
+
* <CollectionProvider value={collection}>
|
|
18
|
+
* <FilterPanel />
|
|
19
|
+
* <DataTable.Root {...table.rootProps}>...</DataTable.Root>
|
|
20
|
+
* <Pagination {...table} />
|
|
21
|
+
* </CollectionProvider>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function CollectionProvider({
|
|
25
|
+
value,
|
|
26
|
+
children,
|
|
27
|
+
}: {
|
|
28
|
+
value: UseCollectionReturn<string, unknown, unknown>;
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
}) {
|
|
31
|
+
return (
|
|
32
|
+
<CollectionContext.Provider value={value}>
|
|
33
|
+
{children}
|
|
34
|
+
</CollectionContext.Provider>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Hook to access collection state from the nearest `Collection.Provider`.
|
|
42
|
+
*
|
|
43
|
+
* Returns the same interface as `useCollection()`. Pass a `TFieldName`
|
|
44
|
+
* type parameter to narrow method arguments like `addFilter` / `setSort`.
|
|
45
|
+
*
|
|
46
|
+
* @typeParam TFieldName - Union of allowed field name strings (default: `string`).
|
|
47
|
+
*
|
|
48
|
+
* @throws Error if used outside of `Collection.Provider`.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* function StatusFilter() {
|
|
53
|
+
* const { filters, addFilter, removeFilter } = useCollectionContext();
|
|
54
|
+
* // ...
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* // With typed field names:
|
|
58
|
+
* type TaskField = FieldName<typeof tableMetadata, "task">;
|
|
59
|
+
* const { addFilter } = useCollectionContext<TaskField>();
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function useCollectionContext<
|
|
63
|
+
TFieldName extends string = string,
|
|
64
|
+
>(): UseCollectionReturn<TFieldName> {
|
|
65
|
+
const ctx = useContext(CollectionContext);
|
|
66
|
+
if (!ctx) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"useCollectionContext must be used within <Collection.Provider>",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return ctx as UseCollectionReturn<TFieldName>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* `Collection` namespace object providing the Provider component.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```tsx
|
|
81
|
+
* <Collection.Provider value={collection}>
|
|
82
|
+
* ...
|
|
83
|
+
* </Collection.Provider>
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export const Collection = {
|
|
87
|
+
Provider: CollectionProvider,
|
|
88
|
+
} as const;
|
|
89
|
+
|
|
90
|
+
|