@izumisy-tailor/tailor-data-viewer 0.1.33 → 0.1.35
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 +16 -0
- package/docs/API.md +0 -318
- package/docs/README.md +23 -0
- package/docs/compositional-api.md +28 -0
- package/docs/custom-renderers.md +230 -0
- package/docs/data-table.md +250 -0
- package/docs/fetcher.md +74 -0
- package/docs/labels.md +157 -0
- package/package.json +1 -1
- package/src/component/column-selector.test.tsx +1 -1
- package/src/component/column-selector.tsx +19 -10
- package/src/component/contexts/data-viewer-context.tsx +8 -1
- package/src/component/contexts/table-data-context.tsx +5 -1
- package/src/component/create-data-viewer.tsx +6 -2
- package/src/component/csv-button.tsx +7 -3
- package/src/component/data-table.test.tsx +306 -1
- package/src/component/data-table.tsx +145 -52
- package/src/component/data-view-tab-content.tsx +33 -29
- package/src/component/data-viewer.tsx +24 -11
- package/src/component/hooks/use-labels.ts +89 -0
- package/src/component/index.ts +18 -0
- package/src/component/label-resolver.test.ts +73 -0
- package/src/component/label-resolver.ts +55 -0
- package/src/component/refresh-button.tsx +3 -1
- package/src/component/relation-content.tsx +48 -44
- package/src/component/search-filter.tsx +27 -23
- package/src/component/single-record-tab-content.tsx +74 -74
- package/src/component/types.ts +109 -0
- package/src/component/ui-labels.ts +66 -0
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ A React component library for building data exploration interfaces with GraphQL
|
|
|
13
13
|
- **Sorting & Pagination**: Full cursor-based pagination support
|
|
14
14
|
- **CSV Export**: Download current view as CSV
|
|
15
15
|
- **Single Record View**: Detailed single record view with all relations
|
|
16
|
+
- **Custom Labels**: Internationalization support for field names and UI text
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
18
19
|
|
|
@@ -55,6 +56,11 @@ const fetcher = createDefaultFetcher({
|
|
|
55
56
|
export const DataViewer = createDataViewer({
|
|
56
57
|
metadata: tableMetadata,
|
|
57
58
|
fetcher,
|
|
59
|
+
// Optional: Custom labels for fields and UI text
|
|
60
|
+
labels: {
|
|
61
|
+
"Task:status": "ステータス",
|
|
62
|
+
"$:refresh": "Refresh",
|
|
63
|
+
},
|
|
58
64
|
});
|
|
59
65
|
```
|
|
60
66
|
|
|
@@ -101,6 +107,11 @@ const fetcher = createDefaultFetcher({
|
|
|
101
107
|
export const DataViewer = createDataViewer({
|
|
102
108
|
metadata: tableMetadata,
|
|
103
109
|
fetcher,
|
|
110
|
+
// Optional: Custom labels for fields and UI text
|
|
111
|
+
labels: {
|
|
112
|
+
"orders:status": "Order Status",
|
|
113
|
+
"*:createdAt": "Created Date",
|
|
114
|
+
},
|
|
104
115
|
});
|
|
105
116
|
|
|
106
117
|
export const savedViewStore = createIndexedDBStore();
|
|
@@ -147,6 +158,11 @@ const fetcher = createDefaultFetcher({
|
|
|
147
158
|
export const DataViewer = createDataViewer({
|
|
148
159
|
metadata: tableMetadata,
|
|
149
160
|
fetcher,
|
|
161
|
+
// Optional: Custom labels and renderers
|
|
162
|
+
labels: {
|
|
163
|
+
"User:name": "User Name",
|
|
164
|
+
"*:createdAt": "Created",
|
|
165
|
+
},
|
|
150
166
|
});
|
|
151
167
|
```
|
|
152
168
|
|
package/docs/API.md
CHANGED
|
@@ -25,81 +25,6 @@ const DataViewer = createDataViewer({
|
|
|
25
25
|
// - DataViewer.fetcher - The fetcher passed to createDataViewer
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
## GraphQL Fetcher
|
|
29
|
-
|
|
30
|
-
### `GraphQLFetcher`
|
|
31
|
-
|
|
32
|
-
Interface for GraphQL data fetching. Implement this interface to use your own GraphQL client.
|
|
33
|
-
|
|
34
|
-
```tsx
|
|
35
|
-
interface GraphQLFetcher {
|
|
36
|
-
execute: <T = unknown>(
|
|
37
|
-
query: string,
|
|
38
|
-
variables?: Record<string, unknown>,
|
|
39
|
-
options?: GraphQLFetcherOptions,
|
|
40
|
-
) => Promise<GraphQLFetcherResult<T>>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface GraphQLFetcherOptions {
|
|
44
|
-
signal?: AbortSignal;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface GraphQLFetcherResult<T> {
|
|
48
|
-
data: T | null;
|
|
49
|
-
errors?: GraphQLError[];
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### `createDefaultFetcher`
|
|
54
|
-
|
|
55
|
-
Creates a default GraphQL fetcher using `graphql-request`.
|
|
56
|
-
|
|
57
|
-
```tsx
|
|
58
|
-
import { createDefaultFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
59
|
-
|
|
60
|
-
const fetcher = createDefaultFetcher({
|
|
61
|
-
endpoint: "https://your-app.tailor.tech/graphql",
|
|
62
|
-
headers: { "X-Custom-Header": "value" }, // optional
|
|
63
|
-
credentials: "include", // optional, default: "include"
|
|
64
|
-
});
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### `createUrqlFetcher`
|
|
68
|
-
|
|
69
|
-
Creates a GraphQL fetcher from an existing urql Client.
|
|
70
|
-
|
|
71
|
-
```tsx
|
|
72
|
-
import { createClient } from "@urql/core";
|
|
73
|
-
import { createUrqlFetcher } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
74
|
-
|
|
75
|
-
const urqlClient = createClient({
|
|
76
|
-
url: "https://your-app.tailor.tech/graphql",
|
|
77
|
-
fetchOptions: {
|
|
78
|
-
headers: { "Authorization": "Bearer token" },
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const fetcher = createUrqlFetcher({ client: urqlClient });
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Custom Fetcher Implementation
|
|
86
|
-
|
|
87
|
-
You can implement your own fetcher for any GraphQL client:
|
|
88
|
-
|
|
89
|
-
```tsx
|
|
90
|
-
const customFetcher: GraphQLFetcher = {
|
|
91
|
-
execute: async (query, variables, options) => {
|
|
92
|
-
const response = await fetch("/graphql", {
|
|
93
|
-
method: "POST",
|
|
94
|
-
headers: { "Content-Type": "application/json" },
|
|
95
|
-
body: JSON.stringify({ query, variables }),
|
|
96
|
-
signal: options?.signal,
|
|
97
|
-
});
|
|
98
|
-
return response.json();
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
```
|
|
102
|
-
|
|
103
28
|
## Components
|
|
104
29
|
|
|
105
30
|
### `<DataViewer />`
|
|
@@ -234,246 +159,3 @@ interface RelationMetadata {
|
|
|
234
159
|
}
|
|
235
160
|
```
|
|
236
161
|
|
|
237
|
-
## Custom Cell Renderers
|
|
238
|
-
|
|
239
|
-
### Overview
|
|
240
|
-
|
|
241
|
-
Custom cell renderers allow you to customize how cells are displayed in the DataTable. You can define renderers by field name (with optional wildcard matching) or by field type.
|
|
242
|
-
|
|
243
|
-
### Types
|
|
244
|
-
|
|
245
|
-
```tsx
|
|
246
|
-
interface CellRendererProps {
|
|
247
|
-
value: unknown;
|
|
248
|
-
field: FieldMetadata;
|
|
249
|
-
row: Record<string, unknown>;
|
|
250
|
-
rowIndex: number;
|
|
251
|
-
tableName: string;
|
|
252
|
-
tableMetadata: TableMetadata;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
type CellRenderer = (props: CellRendererProps) => ReactNode;
|
|
256
|
-
|
|
257
|
-
interface CellRenderers {
|
|
258
|
-
/** Renderers by "tableName:fieldName" pattern */
|
|
259
|
-
byFieldName?: Record<string, CellRenderer>;
|
|
260
|
-
/** Renderers by field type (fallback) */
|
|
261
|
-
byFieldType?: Partial<Record<FieldType, CellRenderer>>;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
interface Renderers {
|
|
265
|
-
cell?: CellRenderers;
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
### Key Format
|
|
270
|
-
|
|
271
|
-
The `byFieldName` object uses a `tableName:fieldName` key format:
|
|
272
|
-
|
|
273
|
-
| Key | Description | Matches |
|
|
274
|
-
|-----|-------------|---------|
|
|
275
|
-
| `orders:status` | Exact match | orders.status |
|
|
276
|
-
| `orders:*Email` | Suffix match (camelCase) | orders.supplierEmail, orders.buyerEmail |
|
|
277
|
-
| `orders:*.email` | Suffix match (dot) | orders.supplier.email |
|
|
278
|
-
| `*:status` | Cross-table exact | *.status |
|
|
279
|
-
| `*:*Email` | Cross-table suffix | *.supplierEmail, *.buyerEmail |
|
|
280
|
-
|
|
281
|
-
### Resolution Priority
|
|
282
|
-
|
|
283
|
-
1. `byFieldName["orders:supplier.email"]` — table-specific + exact match (highest)
|
|
284
|
-
2. `byFieldName["orders:*.email"]` — table-specific + suffix match
|
|
285
|
-
3. `byFieldName["*:supplier.email"]` — cross-table + exact match
|
|
286
|
-
4. `byFieldName["*:*.email"]` — cross-table + suffix match
|
|
287
|
-
5. `byFieldType["string"]` — type-based fallback
|
|
288
|
-
6. Default formatting (lowest)
|
|
289
|
-
|
|
290
|
-
### Usage with createDataViewer (Recommended)
|
|
291
|
-
|
|
292
|
-
Set renderers at the `createDataViewer` level to apply them across all tables:
|
|
293
|
-
|
|
294
|
-
```tsx
|
|
295
|
-
import { createDataViewer, createDefaultFetcher, builtInRenderers } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
296
|
-
import { tableMetadata } from "./generated/metadata";
|
|
297
|
-
|
|
298
|
-
const { EmailLink, StatusBadge, BooleanIcon, RelativeTime, TruncatedId } = builtInRenderers;
|
|
299
|
-
|
|
300
|
-
const fetcher = createDefaultFetcher({
|
|
301
|
-
endpoint: "https://your-app.tailor.tech",
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
export const DataViewer = createDataViewer({
|
|
305
|
-
metadata: tableMetadata,
|
|
306
|
-
fetcher,
|
|
307
|
-
// Renderers are applied across all tables
|
|
308
|
-
renderers: {
|
|
309
|
-
cell: {
|
|
310
|
-
byFieldName: {
|
|
311
|
-
"*:*Email": EmailLink,
|
|
312
|
-
"*:createdAt": RelativeTime,
|
|
313
|
-
"*:id": TruncatedId,
|
|
314
|
-
"orders:status": StatusBadge({
|
|
315
|
-
colorMap: { pending: "yellow", approved: "green", rejected: "red" },
|
|
316
|
-
}),
|
|
317
|
-
},
|
|
318
|
-
byFieldType: {
|
|
319
|
-
boolean: BooleanIcon,
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
},
|
|
323
|
-
});
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
### Usage with DataTable Props
|
|
327
|
-
|
|
328
|
-
You can also pass renderers directly to `DataTable` to override or add renderers for specific pages:
|
|
329
|
-
|
|
330
|
-
```tsx
|
|
331
|
-
import { DataTable, builtInRenderers } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
332
|
-
|
|
333
|
-
const { EmailLink, StatusBadge, BooleanIcon, RelativeTime, TruncatedId } = builtInRenderers;
|
|
334
|
-
|
|
335
|
-
<DataTable
|
|
336
|
-
renderers={{
|
|
337
|
-
cell: {
|
|
338
|
-
byFieldName: {
|
|
339
|
-
// Table-specific renderers
|
|
340
|
-
"orders:status": StatusBadge({
|
|
341
|
-
colorMap: {
|
|
342
|
-
"pending": "yellow",
|
|
343
|
-
"approved": "green",
|
|
344
|
-
"rejected": "red",
|
|
345
|
-
},
|
|
346
|
-
}),
|
|
347
|
-
"invoices:status": StatusBadge({
|
|
348
|
-
colorMap: {
|
|
349
|
-
"draft": "gray",
|
|
350
|
-
"sent": "blue",
|
|
351
|
-
"paid": "green",
|
|
352
|
-
},
|
|
353
|
-
}),
|
|
354
|
-
|
|
355
|
-
// Cross-table suffix match (all *Email fields)
|
|
356
|
-
"*:*Email": EmailLink,
|
|
357
|
-
|
|
358
|
-
// Cross-table exact match
|
|
359
|
-
"*:createdAt": RelativeTime,
|
|
360
|
-
"*:id": TruncatedId,
|
|
361
|
-
|
|
362
|
-
// Custom renderer with click handler
|
|
363
|
-
"orders:orderId": ({ value, row }) => (
|
|
364
|
-
<button
|
|
365
|
-
onClick={() => navigate(`/orders/${value}`)}
|
|
366
|
-
className="text-blue-600 hover:underline"
|
|
367
|
-
>
|
|
368
|
-
{value}
|
|
369
|
-
</button>
|
|
370
|
-
),
|
|
371
|
-
},
|
|
372
|
-
byFieldType: {
|
|
373
|
-
boolean: BooleanIcon,
|
|
374
|
-
datetime: RelativeTime,
|
|
375
|
-
},
|
|
376
|
-
},
|
|
377
|
-
}}
|
|
378
|
-
/>
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
## Built-in Renderers
|
|
382
|
-
|
|
383
|
-
The library provides several built-in cell renderers for common use cases.
|
|
384
|
-
|
|
385
|
-
```tsx
|
|
386
|
-
import { builtInRenderers } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
387
|
-
|
|
388
|
-
const { EmailLink, BooleanIcon, RelativeTime, TruncatedId, ArrayBadges, StatusBadge } = builtInRenderers;
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### EmailLink
|
|
392
|
-
|
|
393
|
-
Renders email values as `mailto:` links. Empty/null values display as "-".
|
|
394
|
-
|
|
395
|
-
```tsx
|
|
396
|
-
"*:*Email": EmailLink
|
|
397
|
-
// supplierEmail → <a href="mailto:supplier@example.com">supplier@example.com</a>
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
### BooleanIcon
|
|
401
|
-
|
|
402
|
-
Renders boolean values as icons: ✓ (green) for true, ✗ (gray) for false.
|
|
403
|
-
|
|
404
|
-
```tsx
|
|
405
|
-
byFieldType: {
|
|
406
|
-
boolean: BooleanIcon
|
|
407
|
-
}
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### RelativeTime
|
|
411
|
-
|
|
412
|
-
Renders datetime values as relative time (e.g., "3日前"). Hover shows absolute datetime. Auto-updates every minute.
|
|
413
|
-
|
|
414
|
-
```tsx
|
|
415
|
-
"*:createdAt": RelativeTime
|
|
416
|
-
// 2024-01-01T10:00:00Z → "3日前" (with tooltip showing "2024/01/01 10:00:00")
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
### TruncatedId
|
|
420
|
-
|
|
421
|
-
Renders long IDs (UUIDs) truncated to 8 characters with "...". Hover shows full ID. Click copies to clipboard.
|
|
422
|
-
|
|
423
|
-
```tsx
|
|
424
|
-
"*:id": TruncatedId
|
|
425
|
-
// "550e8400-e29b-41d4-a716-446655440000" → "550e8400..." (click to copy)
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
### ArrayBadges
|
|
429
|
-
|
|
430
|
-
Renders array values as a horizontal list of badges. Shows "+N" when more than 5 items.
|
|
431
|
-
|
|
432
|
-
```tsx
|
|
433
|
-
byFieldType: {
|
|
434
|
-
array: ArrayBadges
|
|
435
|
-
}
|
|
436
|
-
// ["tag1", "tag2", "tag3"] → [tag1] [tag2] [tag3]
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
### StatusBadge
|
|
440
|
-
|
|
441
|
-
Factory function that creates a colored badge renderer based on value-to-color mapping.
|
|
442
|
-
|
|
443
|
-
```tsx
|
|
444
|
-
StatusBadge({
|
|
445
|
-
colorMap: {
|
|
446
|
-
"pending": "yellow", // Preset color
|
|
447
|
-
"approved": "green",
|
|
448
|
-
"rejected": "red",
|
|
449
|
-
"pending*": "yellow", // Prefix match: pending, pending_review
|
|
450
|
-
"*approved": "green", // Suffix match: manager_approved
|
|
451
|
-
"*error*": "red", // Contains match: validation_error
|
|
452
|
-
},
|
|
453
|
-
defaultColor: "gray", // Default for unmatched values
|
|
454
|
-
})
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
**Preset Colors:**
|
|
458
|
-
- `gray`: #F3F4F6 bg, #374151 text
|
|
459
|
-
- `red`: #FEE2E2 bg, #991B1B text
|
|
460
|
-
- `yellow`: #FEF3C7 bg, #92400E text
|
|
461
|
-
- `green`: #D1FAE5 bg, #065F46 text
|
|
462
|
-
- `blue`: #DBEAFE bg, #1E40AF text
|
|
463
|
-
|
|
464
|
-
**Custom Colors:**
|
|
465
|
-
```tsx
|
|
466
|
-
StatusBadge({
|
|
467
|
-
colorMap: {
|
|
468
|
-
"custom_status": { bg: "#E0F2FE", text: "#0369A1" },
|
|
469
|
-
},
|
|
470
|
-
})
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
**Pattern Priority:**
|
|
474
|
-
1. Exact match (`"approved"`)
|
|
475
|
-
2. Prefix match (`"approved*"`)
|
|
476
|
-
3. Suffix match (`"*approved"`)
|
|
477
|
-
4. Contains match (`"*approved*"`)
|
|
478
|
-
5. `defaultColor`
|
|
479
|
-
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
Documentation for `@izumisy-tailor/tailor-data-viewer`.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
- [API Reference](API.md) - Core API reference (Factory Function, Components, Hooks, Types)
|
|
8
|
+
|
|
9
|
+
## Components
|
|
10
|
+
|
|
11
|
+
- [DataTable](data-table.md) - Main data table component with row actions and click handlers
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
- [GraphQL Fetcher](fetcher.md) - GraphQL fetcher configuration and custom implementation
|
|
16
|
+
- [Custom Labels](labels.md) - Customizing field names, table names, and UI text (i18n support)
|
|
17
|
+
- [Custom Renderers](custom-renderers.md) - Custom cell renderers and built-in renderers
|
|
18
|
+
|
|
19
|
+
## Advanced Usage
|
|
20
|
+
|
|
21
|
+
- [Compositional API](compositional-api.md) - Building flexible UIs by composing components
|
|
22
|
+
- [Saved View Store](saved-view-store.md) - View persistence and restoration
|
|
23
|
+
- [AppShell Module](app-shell-module.md) - AppShell integration module
|
|
@@ -28,6 +28,11 @@ const fetcher = createDefaultFetcher({
|
|
|
28
28
|
export const DataViewer = createDataViewer({
|
|
29
29
|
metadata: tableMetadata,
|
|
30
30
|
fetcher,
|
|
31
|
+
// Optional: Custom labels for fields and UI text
|
|
32
|
+
labels: {
|
|
33
|
+
"Task:status": "ステータス",
|
|
34
|
+
"Task:createdAt": "作成日時",
|
|
35
|
+
},
|
|
31
36
|
});
|
|
32
37
|
```
|
|
33
38
|
|
|
@@ -86,6 +91,8 @@ const fetcher = createDefaultFetcher({
|
|
|
86
91
|
const DataViewer = createDataViewer({
|
|
87
92
|
metadata: tableMetadata, // Generated from TailorDB schema
|
|
88
93
|
fetcher,
|
|
94
|
+
labels: { ... }, // Optional: Custom labels
|
|
95
|
+
renderers: { ... }, // Optional: Custom cell renderers
|
|
89
96
|
});
|
|
90
97
|
|
|
91
98
|
// Returns:
|
|
@@ -93,6 +100,7 @@ const DataViewer = createDataViewer({
|
|
|
93
100
|
// - DataViewer.ToolbarProvider - Toolbar state provider
|
|
94
101
|
// - DataViewer.metadata - The metadata passed to createDataViewer
|
|
95
102
|
// - DataViewer.fetcher - The fetcher passed to createDataViewer
|
|
103
|
+
// - DataViewer.labels - The labels passed to createDataViewer
|
|
96
104
|
```
|
|
97
105
|
|
|
98
106
|
### DataViewer.TableDataProvider
|
|
@@ -254,6 +262,26 @@ const {
|
|
|
254
262
|
} = useDataViewer();
|
|
255
263
|
```
|
|
256
264
|
|
|
265
|
+
### useLabels
|
|
266
|
+
|
|
267
|
+
Access label resolution for fields and UI text.
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
const { getLabel, labels } = useLabels();
|
|
271
|
+
|
|
272
|
+
// Field label (with wildcard fallback)
|
|
273
|
+
const statusLabel = getLabel("orders:status");
|
|
274
|
+
// → labels["orders:status"] → labels["*:status"] → "status"
|
|
275
|
+
|
|
276
|
+
// Table label
|
|
277
|
+
const tableLabel = getLabel("orders");
|
|
278
|
+
// → labels["orders"] → "orders"
|
|
279
|
+
|
|
280
|
+
// UI label (with DEFAULT_UI_LABELS fallback)
|
|
281
|
+
const refreshLabel = getLabel("$:refresh");
|
|
282
|
+
// → labels["$:refresh"] → DEFAULT_UI_LABELS["$:refresh"] → "$:refresh"
|
|
283
|
+
```
|
|
284
|
+
|
|
257
285
|
### useTableDataContext
|
|
258
286
|
|
|
259
287
|
Access data fetching state and pagination controls.
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Custom Cell Renderers
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Custom cell renderers allow you to customize how cells are displayed in the DataTable. You can define renderers by field name (with optional wildcard matching) or by field type.
|
|
6
|
+
|
|
7
|
+
## Types
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
interface CellRendererProps {
|
|
11
|
+
value: unknown;
|
|
12
|
+
field: FieldMetadata;
|
|
13
|
+
row: Record<string, unknown>;
|
|
14
|
+
rowIndex: number;
|
|
15
|
+
tableName: string;
|
|
16
|
+
tableMetadata: TableMetadata;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type CellRenderer = (props: CellRendererProps) => ReactNode;
|
|
20
|
+
|
|
21
|
+
interface CellRenderers {
|
|
22
|
+
/** Renderers by "tableName:fieldName" pattern */
|
|
23
|
+
byFieldName?: Record<string, CellRenderer>;
|
|
24
|
+
/** Renderers by field type (fallback) */
|
|
25
|
+
byFieldType?: Partial<Record<FieldType, CellRenderer>>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Renderers {
|
|
29
|
+
cell?: CellRenderers;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Key Format
|
|
34
|
+
|
|
35
|
+
The `byFieldName` object uses a `tableName:fieldName` key format:
|
|
36
|
+
|
|
37
|
+
| Key | Description | Matches |
|
|
38
|
+
|-----|-------------|---------|
|
|
39
|
+
| `orders:status` | Exact match | orders.status |
|
|
40
|
+
| `orders:*Email` | Suffix match (camelCase) | orders.supplierEmail, orders.buyerEmail |
|
|
41
|
+
| `orders:*.email` | Suffix match (dot) | orders.supplier.email |
|
|
42
|
+
| `*:status` | Cross-table exact | *.status |
|
|
43
|
+
| `*:*Email` | Cross-table suffix | *.supplierEmail, *.buyerEmail |
|
|
44
|
+
|
|
45
|
+
## Resolution Priority
|
|
46
|
+
|
|
47
|
+
1. `byFieldName["orders:supplier.email"]` — table-specific + exact match (highest)
|
|
48
|
+
2. `byFieldName["orders:*.email"]` — table-specific + suffix match
|
|
49
|
+
3. `byFieldName["*:supplier.email"]` — cross-table + exact match
|
|
50
|
+
4. `byFieldName["*:*.email"]` — cross-table + suffix match
|
|
51
|
+
5. `byFieldType["string"]` — type-based fallback
|
|
52
|
+
6. Default formatting (lowest)
|
|
53
|
+
|
|
54
|
+
## Usage with createDataViewer (Recommended)
|
|
55
|
+
|
|
56
|
+
Set renderers at the `createDataViewer` level to apply them across all tables:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { createDataViewer, createDefaultFetcher, builtInRenderers } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
60
|
+
import { tableMetadata } from "./generated/metadata";
|
|
61
|
+
|
|
62
|
+
const { EmailLink, StatusBadge, BooleanIcon, TruncatedId } = builtInRenderers;
|
|
63
|
+
|
|
64
|
+
const fetcher = createDefaultFetcher({
|
|
65
|
+
endpoint: "https://your-app.tailor.tech",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const DataViewer = createDataViewer({
|
|
69
|
+
metadata: tableMetadata,
|
|
70
|
+
fetcher,
|
|
71
|
+
// Renderers are applied across all tables
|
|
72
|
+
renderers: {
|
|
73
|
+
cell: {
|
|
74
|
+
byFieldName: {
|
|
75
|
+
"*:*Email": EmailLink,
|
|
76
|
+
"*:id": TruncatedId,
|
|
77
|
+
"orders:status": StatusBadge({
|
|
78
|
+
colorMap: { pending: "yellow", approved: "green", rejected: "red" },
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
byFieldType: {
|
|
82
|
+
boolean: BooleanIcon,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Usage with DataTable Props
|
|
90
|
+
|
|
91
|
+
You can also pass renderers directly to `DataTable` to override or add renderers for specific pages:
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import { DataTable, builtInRenderers } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
95
|
+
|
|
96
|
+
const { EmailLink, StatusBadge, BooleanIcon, TruncatedId } = builtInRenderers;
|
|
97
|
+
|
|
98
|
+
<DataTable
|
|
99
|
+
renderers={{
|
|
100
|
+
cell: {
|
|
101
|
+
byFieldName: {
|
|
102
|
+
// Table-specific renderers
|
|
103
|
+
"orders:status": StatusBadge({
|
|
104
|
+
colorMap: {
|
|
105
|
+
"pending": "yellow",
|
|
106
|
+
"approved": "green",
|
|
107
|
+
"rejected": "red",
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
"invoices:status": StatusBadge({
|
|
111
|
+
colorMap: {
|
|
112
|
+
"draft": "gray",
|
|
113
|
+
"sent": "blue",
|
|
114
|
+
"paid": "green",
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
|
|
118
|
+
// Cross-table suffix match (all *Email fields)
|
|
119
|
+
"*:*Email": EmailLink,
|
|
120
|
+
|
|
121
|
+
// Cross-table exact match
|
|
122
|
+
"*:id": TruncatedId,
|
|
123
|
+
|
|
124
|
+
// Custom renderer with click handler
|
|
125
|
+
"orders:orderId": ({ value, row }) => (
|
|
126
|
+
<button
|
|
127
|
+
onClick={() => navigate(`/orders/${value}`)}
|
|
128
|
+
className="text-blue-600 hover:underline"
|
|
129
|
+
>
|
|
130
|
+
{value}
|
|
131
|
+
</button>
|
|
132
|
+
),
|
|
133
|
+
},
|
|
134
|
+
byFieldType: {
|
|
135
|
+
boolean: BooleanIcon,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Built-in Renderers
|
|
143
|
+
|
|
144
|
+
The library provides several built-in cell renderers for common use cases.
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import { builtInRenderers } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
148
|
+
|
|
149
|
+
const { EmailLink, BooleanIcon, TruncatedId, ArrayBadges, StatusBadge } = builtInRenderers;
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### EmailLink
|
|
153
|
+
|
|
154
|
+
Renders email values as `mailto:` links. Empty/null values display as "-".
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
"*:*Email": EmailLink
|
|
158
|
+
// supplierEmail → <a href="mailto:supplier@example.com">supplier@example.com</a>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### BooleanIcon
|
|
162
|
+
|
|
163
|
+
Renders boolean values as icons: ✓ (green) for true, ✗ (gray) for false.
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
byFieldType: {
|
|
167
|
+
boolean: BooleanIcon
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### TruncatedId
|
|
172
|
+
|
|
173
|
+
Renders long IDs (UUIDs) truncated to 8 characters with "...". Hover shows full ID. Click copies to clipboard.
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
"*:id": TruncatedId
|
|
177
|
+
// "550e8400-e29b-41d4-a716-446655440000" → "550e8400..." (click to copy)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### ArrayBadges
|
|
181
|
+
|
|
182
|
+
Renders array values as a horizontal list of badges. Shows "+N" when more than 5 items.
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
byFieldType: {
|
|
186
|
+
array: ArrayBadges
|
|
187
|
+
}
|
|
188
|
+
// ["tag1", "tag2", "tag3"] → [tag1] [tag2] [tag3]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### StatusBadge
|
|
192
|
+
|
|
193
|
+
Factory function that creates a colored badge renderer based on value-to-color mapping.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
StatusBadge({
|
|
197
|
+
colorMap: {
|
|
198
|
+
"pending": "yellow", // Preset color
|
|
199
|
+
"approved": "green",
|
|
200
|
+
"rejected": "red",
|
|
201
|
+
"pending*": "yellow", // Prefix match: pending, pending_review
|
|
202
|
+
"*approved": "green", // Suffix match: manager_approved
|
|
203
|
+
"*error*": "red", // Contains match: validation_error
|
|
204
|
+
},
|
|
205
|
+
defaultColor: "gray", // Default for unmatched values
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Preset Colors:**
|
|
210
|
+
- `gray`: #F3F4F6 bg, #374151 text
|
|
211
|
+
- `red`: #FEE2E2 bg, #991B1B text
|
|
212
|
+
- `yellow`: #FEF3C7 bg, #92400E text
|
|
213
|
+
- `green`: #D1FAE5 bg, #065F46 text
|
|
214
|
+
- `blue`: #DBEAFE bg, #1E40AF text
|
|
215
|
+
|
|
216
|
+
**Custom Colors:**
|
|
217
|
+
```tsx
|
|
218
|
+
StatusBadge({
|
|
219
|
+
colorMap: {
|
|
220
|
+
"custom_status": { bg: "#E0F2FE", text: "#0369A1" },
|
|
221
|
+
},
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Pattern Priority:**
|
|
226
|
+
1. Exact match (`"approved"`)
|
|
227
|
+
2. Prefix match (`"approved*"`)
|
|
228
|
+
3. Suffix match (`"*approved"`)
|
|
229
|
+
4. Contains match (`"*approved*"`)
|
|
230
|
+
5. `defaultColor`
|