@izumisy-tailor/tailor-data-viewer 0.2.0 → 0.2.2
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 +315 -189
- package/package.json +1 -1
- package/src/component/collection-params/collection-params-provider.tsx +12 -3
- package/src/component/{use-collection-params.test.ts → collection-params/use-collection-params.test.ts} +127 -1
- package/src/component/collection-params/use-collection-params.ts +67 -4
- package/src/component/{use-data-table.test.ts → data-table/use-data-table.test.ts} +2 -2
- package/src/component/field-helpers.test.ts +99 -33
- package/src/component/field-helpers.ts +60 -19
- package/src/component/index.ts +2 -1
- package/src/component/types.ts +47 -15
package/README.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
# Tailor Data Viewer
|
|
2
2
|
|
|
3
|
-
A React component library for building data
|
|
3
|
+
A low-level React component library for building data table interfaces with Tailor Platform (GraphQL) backends. Provides composable hooks and compound components for query parameter management, table rendering, filtering, sorting, and pagination.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
7
|
+
- **Separation of Concerns**: Data fetching, query parameter management, and UI are fully decoupled
|
|
8
|
+
- **`useCollectionParams` Hook**: Manages filter, sort, and pagination state; outputs Tailor Platform-compatible GraphQL variables
|
|
9
|
+
- **`CollectionParams.Provider`**: Shares query parameters via React Context across sibling components
|
|
10
|
+
- **`Table.*` Compound Components**: Static, unstyled table primitives (`<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>`)
|
|
11
|
+
- **`DataTable.*` Compound Components**: Data-bound table with sort indicators, cell renderers, and `useDataTable` integration
|
|
12
|
+
- **`useDataTable` Hook**: Integrates data, column visibility, row operations (optimistic updates), and props generators
|
|
13
|
+
- **Column Definition Helpers**: `field()` for data columns with sort/filter, `display()` for render-only columns
|
|
14
|
+
- **Metadata-based Inference**: `inferColumnHelper()` auto-derives sort/filter config from generated table metadata
|
|
15
|
+
- **Utility Components**: `ColumnSelector`, `CsvButton`, `SearchFilterForm`, `Pagination` — all props-based, spreadable from hooks
|
|
16
|
+
- **Multi-sort Support**: Multiple simultaneous sort fields
|
|
17
|
+
- **Optimistic Updates**: `updateRow`, `deleteRow`, `insertRow` with rollback
|
|
18
|
+
- **Presentation Agnostic**: Same `useCollectionParams` can drive tables, kanbans, calendars, etc.
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
@@ -39,209 +39,156 @@ This provides AI-optimized documentation for better code generation and assistan
|
|
|
39
39
|
|
|
40
40
|
### Peer Dependencies
|
|
41
41
|
|
|
42
|
-
This library requires the following peer dependencies:
|
|
43
|
-
|
|
44
42
|
```bash
|
|
45
43
|
npm install react react-dom
|
|
46
44
|
```
|
|
47
45
|
|
|
48
|
-
##
|
|
49
|
-
|
|
50
|
-
There are two ways to use Data Viewer:
|
|
51
|
-
|
|
52
|
-
1. **AppShell Module** - Recommended for Tailor Platform apps
|
|
53
|
-
2. **Standalone Component** - For custom React apps
|
|
54
|
-
|
|
55
|
-
### AppShell Module (Recommended)
|
|
56
|
-
|
|
57
|
-
For Tailor Platform apps using `@tailor-platform/app-shell`:
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
// src/shared/data-viewer.ts
|
|
61
|
-
import { createDataViewer, createDefaultFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
62
|
-
import { tableMetadata } from "./generated/table-metadata";
|
|
63
|
-
|
|
64
|
-
const fetcher = createDefaultFetcher({
|
|
65
|
-
endpoint: import.meta.env.VITE_APP_URL,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
export const DataViewer = createDataViewer({
|
|
69
|
-
metadata: tableMetadata,
|
|
70
|
-
fetcher,
|
|
71
|
-
// Optional: Custom labels for fields and UI text
|
|
72
|
-
labels: {
|
|
73
|
-
"Task:status": "ステータス",
|
|
74
|
-
"$:refresh": "Refresh",
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
// src/pages/data-view/index.ts
|
|
81
|
-
import { createDataViewModule } from "@izumisy-tailor/tailor-data-viewer/app-shell";
|
|
82
|
-
import { createIndexedDBStore } from "@izumisy-tailor/tailor-data-viewer/store/indexeddb";
|
|
83
|
-
import { DataViewer } from "../../shared/data-viewer";
|
|
84
|
-
|
|
85
|
-
export const dataViewModule = createDataViewModule({
|
|
86
|
-
dataViewer: DataViewer,
|
|
87
|
-
savedViewStore: createIndexedDBStore(),
|
|
88
|
-
});
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
// src/app.tsx
|
|
93
|
-
import { AppShell } from "@tailor-platform/app-shell";
|
|
94
|
-
import { dataViewModule } from "./pages/data-view";
|
|
95
|
-
|
|
96
|
-
export const App = () => (
|
|
97
|
-
<AppShell
|
|
98
|
-
modules={[dataViewModule]}
|
|
99
|
-
/>
|
|
100
|
-
);
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
For detailed options, see [AppShell Module Integration](./docs/app-shell-module.md).
|
|
104
|
-
|
|
105
|
-
### Standalone Explorer View
|
|
106
|
-
|
|
107
|
-
For custom React apps without AppShell, use `DataViewer.ExplorerView`. This provides a tab-based UI where users can select tables, create filters, and save views:
|
|
46
|
+
## Quick Start
|
|
108
47
|
|
|
109
48
|
```tsx
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
metadata: tableMetadata,
|
|
121
|
-
fetcher,
|
|
122
|
-
// Optional: Custom labels for fields and UI text
|
|
123
|
-
labels: {
|
|
124
|
-
"orders:status": "Order Status",
|
|
125
|
-
"*:createdAt": "Created Date",
|
|
126
|
-
},
|
|
127
|
-
});
|
|
49
|
+
import {
|
|
50
|
+
useCollectionParams,
|
|
51
|
+
useDataTable,
|
|
52
|
+
CollectionParams,
|
|
53
|
+
DataTable,
|
|
54
|
+
Pagination,
|
|
55
|
+
field,
|
|
56
|
+
display,
|
|
57
|
+
} from "@izumisy-tailor/tailor-data-viewer/component";
|
|
58
|
+
import "@izumisy-tailor/tailor-data-viewer/styles/theme.css";
|
|
128
59
|
|
|
129
|
-
|
|
130
|
-
|
|
60
|
+
// 1. Define columns
|
|
61
|
+
const columns = [
|
|
62
|
+
field<Order>("name", {
|
|
63
|
+
label: "Name",
|
|
64
|
+
sort: { type: "string" },
|
|
65
|
+
filter: { type: "string" },
|
|
66
|
+
}),
|
|
67
|
+
field<Order>("amount", {
|
|
68
|
+
label: "Amount",
|
|
69
|
+
sort: { type: "number" },
|
|
70
|
+
filter: { type: "number" },
|
|
71
|
+
}),
|
|
72
|
+
field<Order>("status", {
|
|
73
|
+
label: "Status",
|
|
74
|
+
filter: {
|
|
75
|
+
type: "enum",
|
|
76
|
+
options: [
|
|
77
|
+
{ value: "DRAFT", label: "Draft" },
|
|
78
|
+
{ value: "APPROVED", label: "Approved" },
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
display<Order>("actions", {
|
|
83
|
+
width: 50,
|
|
84
|
+
render: (row) => <button onClick={() => handleEdit(row)}>Edit</button>,
|
|
85
|
+
}),
|
|
86
|
+
];
|
|
131
87
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
88
|
+
// 2. Build a page
|
|
89
|
+
function OrdersPage() {
|
|
90
|
+
const params = useCollectionParams({ pageSize: 20 });
|
|
91
|
+
const [result] = useQuery({ query: GET_ORDERS, variables: params.variables });
|
|
136
92
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
93
|
+
const table = useDataTable<Order>({
|
|
94
|
+
columns,
|
|
95
|
+
data: result.data?.orders,
|
|
96
|
+
loading: result.fetching,
|
|
97
|
+
collectionParams: params,
|
|
98
|
+
});
|
|
142
99
|
|
|
143
|
-
// With initial view loaded from URL
|
|
144
|
-
function AppWithViewId() {
|
|
145
|
-
const viewId = useSearchParams().get("viewId") ?? undefined;
|
|
146
100
|
return (
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
101
|
+
<CollectionParams.Provider value={params}>
|
|
102
|
+
<DataTable.Root {...table.rootProps}>
|
|
103
|
+
<DataTable.Headers />
|
|
104
|
+
<DataTable.Body />
|
|
105
|
+
</DataTable.Root>
|
|
106
|
+
<Pagination {...table} />
|
|
107
|
+
</CollectionParams.Provider>
|
|
151
108
|
);
|
|
152
109
|
}
|
|
153
110
|
```
|
|
154
111
|
|
|
155
|
-
|
|
112
|
+
## API Overview
|
|
156
113
|
|
|
157
|
-
###
|
|
114
|
+
### `useCollectionParams(options?)`
|
|
158
115
|
|
|
159
|
-
|
|
116
|
+
Manages filter, sort, and pagination state. Returns Tailor Platform-compatible `variables` for GraphQL queries.
|
|
160
117
|
|
|
161
118
|
```tsx
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const fetcher = createDefaultFetcher({
|
|
167
|
-
endpoint: "https://your-app.tailor.tech",
|
|
119
|
+
const params = useCollectionParams({
|
|
120
|
+
pageSize: 20,
|
|
121
|
+
initialSort: [{ field: "createdAt", direction: "Desc" }],
|
|
168
122
|
});
|
|
169
123
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
fetcher,
|
|
173
|
-
// Optional: Custom labels and renderers
|
|
174
|
-
labels: {
|
|
175
|
-
"User:name": "User Name",
|
|
176
|
-
"*:createdAt": "Created",
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
```
|
|
124
|
+
// Pass variables to any GraphQL client
|
|
125
|
+
const [result] = useQuery({ query: GET_ORDERS, variables: params.variables });
|
|
180
126
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
ColumnSelector,
|
|
187
|
-
SearchFilterForm,
|
|
188
|
-
CsvButton,
|
|
189
|
-
RefreshButton,
|
|
190
|
-
useDataViewer,
|
|
191
|
-
useTableDataContext,
|
|
192
|
-
} from "@izumisy-tailor/tailor-data-viewer/component";
|
|
127
|
+
// Filter operations
|
|
128
|
+
params.addFilter("status", "ACTIVE", "eq");
|
|
129
|
+
params.setFilters([{ field: "status", fieldType: "enum", operator: "eq", value: "ACTIVE" }]);
|
|
130
|
+
params.removeFilter("status");
|
|
131
|
+
params.clearFilters();
|
|
193
132
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
<DataViewer.ToolbarProvider>
|
|
204
|
-
<ColumnSelector />
|
|
205
|
-
<SearchFilterForm />
|
|
206
|
-
<CsvButton />
|
|
207
|
-
<RefreshButton />
|
|
208
|
-
</DataViewer.ToolbarProvider>
|
|
209
|
-
<DataTable onRecordClick={(id) => console.log(id)} />
|
|
210
|
-
</DataViewer.TableDataProvider>
|
|
211
|
-
);
|
|
212
|
-
}
|
|
133
|
+
// Sort operations
|
|
134
|
+
params.setSort("createdAt", "Desc");
|
|
135
|
+
params.setSort("name", "Asc", true); // append for multi-sort
|
|
136
|
+
params.clearSort();
|
|
137
|
+
|
|
138
|
+
// Pagination
|
|
139
|
+
params.nextPage(endCursor);
|
|
140
|
+
params.prevPage();
|
|
141
|
+
params.resetPage();
|
|
213
142
|
```
|
|
214
143
|
|
|
215
|
-
|
|
216
|
-
- Customize the layout and styling
|
|
217
|
-
- Add your own components alongside Data Viewer components
|
|
218
|
-
- Access internal state via hooks (`useDataViewer`, `useTableDataContext`)
|
|
219
|
-
- Share the same DataViewer instance across multiple pages
|
|
144
|
+
### `CollectionParams.Provider` / `useCollectionParamsContext()`
|
|
220
145
|
|
|
221
|
-
|
|
146
|
+
Shares `useCollectionParams` return value via Context. Child components access it with `useCollectionParamsContext()`.
|
|
222
147
|
|
|
223
|
-
|
|
148
|
+
```tsx
|
|
149
|
+
<CollectionParams.Provider value={params}>
|
|
150
|
+
<StatusFilter /> {/* useCollectionParamsContext() inside */}
|
|
151
|
+
<DataTable.Root {...table.rootProps}>
|
|
152
|
+
<DataTable.Headers />
|
|
153
|
+
<DataTable.Body />
|
|
154
|
+
</DataTable.Root>
|
|
155
|
+
<Pagination {...table} />
|
|
156
|
+
</CollectionParams.Provider>
|
|
157
|
+
```
|
|
224
158
|
|
|
225
|
-
|
|
159
|
+
Provider is optional — for simple cases, pass params directly via props.
|
|
226
160
|
|
|
227
|
-
|
|
228
|
-
- **TailorDB Store** (`createTailorDBStore`): Stores data on the server side. Ideal for sharing views across teams
|
|
161
|
+
### Column Definition Helpers
|
|
229
162
|
|
|
230
|
-
|
|
163
|
+
#### `field(dataKey, options?)`
|
|
231
164
|
|
|
232
|
-
|
|
165
|
+
Defines a data-bound column. Supports sort and filter configuration.
|
|
233
166
|
|
|
234
|
-
|
|
167
|
+
```tsx
|
|
168
|
+
field("name", {
|
|
169
|
+
label: "Name",
|
|
170
|
+
sort: { type: "string" },
|
|
171
|
+
filter: { type: "string" },
|
|
172
|
+
renderer: ({ value }) => <strong>{value}</strong>,
|
|
173
|
+
})
|
|
174
|
+
```
|
|
235
175
|
|
|
236
|
-
####
|
|
176
|
+
#### `display(id, options)`
|
|
237
177
|
|
|
238
|
-
|
|
178
|
+
Defines a render-only column (no sort/filter).
|
|
239
179
|
|
|
240
|
-
```
|
|
241
|
-
|
|
180
|
+
```tsx
|
|
181
|
+
display("actions", {
|
|
182
|
+
width: 50,
|
|
183
|
+
render: (row) => <ActionMenu row={row} />,
|
|
184
|
+
})
|
|
242
185
|
```
|
|
243
186
|
|
|
244
|
-
|
|
187
|
+
### Table Metadata Generator
|
|
188
|
+
|
|
189
|
+
This library includes a metadata generator for [Tailor Platform SDK](https://www.npmjs.com/package/@tailor-platform/sdk) that produces type-safe table metadata with `as const` assertions. The generated metadata is used by `inferColumnHelper()` for automatic sort/filter configuration.
|
|
190
|
+
|
|
191
|
+
1. Configure the generator in your `tailor.config.ts`:
|
|
245
192
|
|
|
246
193
|
```typescript
|
|
247
194
|
import { defineConfig, defineGenerators } from "@tailor-platform/sdk";
|
|
@@ -251,7 +198,6 @@ export const generators = defineGenerators(
|
|
|
251
198
|
dataViewerMetadataGenerator({
|
|
252
199
|
distPath: "src/generated/data-viewer-metadata.generated.ts",
|
|
253
200
|
}),
|
|
254
|
-
// ... other generators
|
|
255
201
|
);
|
|
256
202
|
|
|
257
203
|
export default defineConfig({
|
|
@@ -260,30 +206,202 @@ export default defineConfig({
|
|
|
260
206
|
});
|
|
261
207
|
```
|
|
262
208
|
|
|
263
|
-
|
|
209
|
+
2. Run the generator:
|
|
264
210
|
|
|
265
211
|
```bash
|
|
266
212
|
tailor-sdk generate
|
|
267
213
|
```
|
|
268
214
|
|
|
269
|
-
|
|
215
|
+
### `inferColumnHelper(metadata, tableName)`
|
|
216
|
+
|
|
217
|
+
`field()` requires manually specifying `sort`/`filter` type configs and enum `options` for every column. `inferColumnHelper()` eliminates this boilerplate by automatically deriving these from the generated table metadata. Based on each field's type (string, number, date, enum, etc.), the appropriate `SortConfig` / `FilterConfig` is set automatically, and enum fields get their options populated from the schema.
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
import { inferColumnHelper, display } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
221
|
+
import { tableMetadata } from "./generated/data-viewer-metadata.generated";
|
|
222
|
+
|
|
223
|
+
const { column, columns } = inferColumnHelper(tableMetadata, "task");
|
|
224
|
+
|
|
225
|
+
const taskColumns = [
|
|
226
|
+
column("title"), // sort/filter auto-configured from metadata
|
|
227
|
+
column("status"), // enum options auto-derived
|
|
228
|
+
column("dueDate"), // date type auto-detected
|
|
229
|
+
column("priority", { sort: false }), // override: disable sort
|
|
230
|
+
...columns(["name", "email"]), // batch definition
|
|
231
|
+
display("actions", {
|
|
232
|
+
render: (row) => <ActionMenu row={row} />,
|
|
233
|
+
}),
|
|
234
|
+
];
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`inferColumnHelper()` can be freely mixed with manual `field()` / `display()` definitions. Use `field()` for fields not in the metadata or those requiring custom configuration, and `inferColumnHelper()` for everything else.
|
|
238
|
+
|
|
239
|
+
### `useDataTable(options)`
|
|
240
|
+
|
|
241
|
+
Integrates data, column visibility, row operations, and props generators.
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
const table = useDataTable<Order>({
|
|
245
|
+
columns,
|
|
246
|
+
data: result.data?.orders, // CollectionResult<Order>
|
|
247
|
+
loading: result.fetching,
|
|
248
|
+
error: result.error,
|
|
249
|
+
collectionParams: params,
|
|
250
|
+
});
|
|
270
251
|
|
|
271
|
-
|
|
252
|
+
// Spread props to components
|
|
253
|
+
<DataTable.Root {...table.rootProps}>...</DataTable.Root>
|
|
254
|
+
<ColumnSelector {...table} />
|
|
255
|
+
<CsvButton {...table} />
|
|
256
|
+
<Pagination {...table} />
|
|
257
|
+
|
|
258
|
+
// Column visibility
|
|
259
|
+
table.toggleColumn("amount");
|
|
260
|
+
table.showAllColumns();
|
|
261
|
+
table.hideAllColumns();
|
|
262
|
+
table.isColumnVisible("amount");
|
|
263
|
+
|
|
264
|
+
// Optimistic updates
|
|
265
|
+
const { rollback } = table.updateRow(rowId, { status: "APPROVED" });
|
|
266
|
+
const { rollback, deletedRow } = table.deleteRow(rowId);
|
|
267
|
+
const { rollback } = table.insertRow(newRow);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### `Table.*` — Static Table Components
|
|
271
|
+
|
|
272
|
+
Low-level table primitives without data binding. Use for fully custom layouts or skeleton loading.
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
<Table.Root>
|
|
276
|
+
<Table.Headers>
|
|
277
|
+
<Table.HeaderRow>
|
|
278
|
+
<Table.HeaderCell>Name</Table.HeaderCell>
|
|
279
|
+
<Table.HeaderCell>Status</Table.HeaderCell>
|
|
280
|
+
</Table.HeaderRow>
|
|
281
|
+
</Table.Headers>
|
|
282
|
+
<Table.Body>
|
|
283
|
+
<Table.Row>
|
|
284
|
+
<Table.Cell>Order A</Table.Cell>
|
|
285
|
+
<Table.Cell>Approved</Table.Cell>
|
|
286
|
+
</Table.Row>
|
|
287
|
+
</Table.Body>
|
|
288
|
+
</Table.Root>
|
|
289
|
+
```
|
|
272
290
|
|
|
273
|
-
|
|
291
|
+
### `DataTable.*` — Data-bound Table Components
|
|
292
|
+
|
|
293
|
+
Pair with `useDataTable` for automatic header sorting, cell rendering, and row operations.
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
// Basic usage (spread rootProps)
|
|
297
|
+
<DataTable.Root {...table.rootProps}>
|
|
298
|
+
<DataTable.Headers />
|
|
299
|
+
<DataTable.Body />
|
|
300
|
+
</DataTable.Root>
|
|
301
|
+
|
|
302
|
+
// Custom row rendering
|
|
303
|
+
<DataTable.Root {...table.rootProps}>
|
|
304
|
+
<DataTable.Headers />
|
|
305
|
+
<DataTable.Body>
|
|
306
|
+
{table.rows.map((row, rowIndex) => (
|
|
307
|
+
<DataTable.Row
|
|
308
|
+
key={row.id}
|
|
309
|
+
{...table.getRowProps(row)}
|
|
310
|
+
onClick={() => navigate(`/orders/${row.id}`)}
|
|
311
|
+
>
|
|
312
|
+
{table.visibleColumns.map((col) => (
|
|
313
|
+
<DataTable.Cell key={col.id} {...table.getCellProps(row, col, rowIndex)} />
|
|
314
|
+
))}
|
|
315
|
+
</DataTable.Row>
|
|
316
|
+
))}
|
|
317
|
+
</DataTable.Body>
|
|
318
|
+
</DataTable.Root>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Utility Components
|
|
322
|
+
|
|
323
|
+
All utility components are props-based and designed to be used with spread from `useDataTable` / `useCollectionParams`.
|
|
324
|
+
|
|
325
|
+
| Component | Spread from | Description |
|
|
326
|
+
|-----------|------------|-------------|
|
|
327
|
+
| `ColumnSelector` | `{...table}` | Column visibility toggle UI |
|
|
328
|
+
| `CsvButton` | `{...table}` | Export visible data as CSV |
|
|
329
|
+
| `SearchFilterForm` | `{...table, ...params}` | Multi-field filter form with operator selection |
|
|
330
|
+
| `Pagination` | `{...table}` | Previous/Next page controls |
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
<SearchFilterForm {...table} {...params} />
|
|
334
|
+
<ColumnSelector {...table} />
|
|
335
|
+
<CsvButton {...table} filename="orders-export" />
|
|
336
|
+
<Pagination {...table} />
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Optimistic Updates in Cell Renderers
|
|
340
|
+
|
|
341
|
+
Use `useDataTableContext()` inside `DataTable.Root` to access row operations from custom cell renderers.
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
const StatusEditor: CellRenderer<Order> = ({ value, row }) => {
|
|
345
|
+
const { updateRow } = useDataTableContext<Order>();
|
|
346
|
+
const [updateOrder] = useMutation(UPDATE_ORDER);
|
|
347
|
+
|
|
348
|
+
const handleChange = async (newStatus: string) => {
|
|
349
|
+
const { rollback } = updateRow(row.id, { status: newStatus });
|
|
350
|
+
try {
|
|
351
|
+
await updateOrder({ id: row.id, input: { status: newStatus } });
|
|
352
|
+
} catch (error) {
|
|
353
|
+
rollback();
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
return <StatusSelect value={value} onChange={handleChange} />;
|
|
358
|
+
};
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Relation Fields
|
|
362
|
+
|
|
363
|
+
Use `display()` with GraphQL query expansion to show relation data.
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
// Include relations in your GraphQL query
|
|
367
|
+
const GET_MEMBERSHIPS = graphql(`
|
|
368
|
+
query SupplierGroupMemberships {
|
|
369
|
+
supplierGroupMemberships {
|
|
370
|
+
edges {
|
|
371
|
+
node {
|
|
372
|
+
id
|
|
373
|
+
supplier { companyName, contactName }
|
|
374
|
+
addedBy { name }
|
|
375
|
+
createdAt
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
`);
|
|
381
|
+
|
|
382
|
+
// Display relation fields with display()
|
|
383
|
+
const columns = [
|
|
384
|
+
display("supplierCompanyName", {
|
|
385
|
+
label: "Company",
|
|
386
|
+
render: (row) => row.supplier?.companyName ?? "-",
|
|
387
|
+
}),
|
|
388
|
+
display("addedByName", {
|
|
389
|
+
label: "Added By",
|
|
390
|
+
render: (row) => row.addedBy?.name ?? "-",
|
|
391
|
+
}),
|
|
392
|
+
field("createdAt", { label: "Created", sort: { type: "date" } }),
|
|
393
|
+
];
|
|
394
|
+
```
|
|
274
395
|
|
|
275
396
|
## Styling
|
|
276
397
|
|
|
277
|
-
The library uses Tailwind CSS classes. Import the
|
|
398
|
+
The library uses Tailwind CSS classes. Import the included theme or provide your own CSS variables:
|
|
278
399
|
|
|
279
400
|
```css
|
|
280
401
|
/* Option 1: Import included theme */
|
|
281
402
|
@import "@izumisy-tailor/tailor-data-viewer/styles/theme.css";
|
|
282
403
|
|
|
283
|
-
/* Option 2:
|
|
284
|
-
@import "@tailor-platform/app-shell/theme.css";
|
|
285
|
-
|
|
286
|
-
/* Option 3: Define your own CSS variables */
|
|
404
|
+
/* Option 2: Define your own CSS variables */
|
|
287
405
|
:root {
|
|
288
406
|
--background: 0 0% 100%;
|
|
289
407
|
--foreground: 222.2 84% 4.9%;
|
|
@@ -292,6 +410,14 @@ The library uses Tailwind CSS classes. Import the base theme or provide your own
|
|
|
292
410
|
}
|
|
293
411
|
```
|
|
294
412
|
|
|
413
|
+
## Exports
|
|
414
|
+
|
|
415
|
+
| Entry Point | Description |
|
|
416
|
+
|-------------|-------------|
|
|
417
|
+
| `@izumisy-tailor/tailor-data-viewer/component` | Hooks, components, helpers, and types |
|
|
418
|
+
| `@izumisy-tailor/tailor-data-viewer/generator` | Metadata generator for Tailor SDK |
|
|
419
|
+
| `@izumisy-tailor/tailor-data-viewer/styles` | CSS theme file |
|
|
420
|
+
|
|
295
421
|
## Development
|
|
296
422
|
|
|
297
423
|
See the [Development Guide](./DEVELOPMENT.md) for setup and contribution instructions.
|
package/package.json
CHANGED
|
@@ -36,7 +36,10 @@ export function CollectionParamsProvider({
|
|
|
36
36
|
/**
|
|
37
37
|
* Hook to access collection params from the nearest `CollectionParams.Provider`.
|
|
38
38
|
*
|
|
39
|
-
* Returns the same interface as `useCollectionParams()`.
|
|
39
|
+
* Returns the same interface as `useCollectionParams()`. Pass a `TFieldName`
|
|
40
|
+
* type parameter to narrow method arguments like `addFilter` / `setSort`.
|
|
41
|
+
*
|
|
42
|
+
* @typeParam TFieldName - Union of allowed field name strings (default: `string`).
|
|
40
43
|
*
|
|
41
44
|
* @throws Error if used outside of `CollectionParams.Provider`.
|
|
42
45
|
*
|
|
@@ -46,16 +49,22 @@ export function CollectionParamsProvider({
|
|
|
46
49
|
* const { filters, addFilter, removeFilter } = useCollectionParamsContext();
|
|
47
50
|
* // ...
|
|
48
51
|
* }
|
|
52
|
+
*
|
|
53
|
+
* // With typed field names:
|
|
54
|
+
* type TaskField = FieldName<typeof tableMetadata, "task">;
|
|
55
|
+
* const { addFilter } = useCollectionParamsContext<TaskField>();
|
|
49
56
|
* ```
|
|
50
57
|
*/
|
|
51
|
-
export function useCollectionParamsContext
|
|
58
|
+
export function useCollectionParamsContext<
|
|
59
|
+
TFieldName extends string = string,
|
|
60
|
+
>(): UseCollectionParamsReturn<TFieldName> {
|
|
52
61
|
const ctx = useContext(CollectionParamsContext);
|
|
53
62
|
if (!ctx) {
|
|
54
63
|
throw new Error(
|
|
55
64
|
"useCollectionParamsContext must be used within <CollectionParams.Provider>",
|
|
56
65
|
);
|
|
57
66
|
}
|
|
58
|
-
return ctx
|
|
67
|
+
return ctx as UseCollectionParamsReturn<TFieldName>;
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
/**
|