@izumisy-tailor/tailor-data-viewer 0.1.41 → 0.1.42

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 CHANGED
@@ -183,8 +183,8 @@ function CustomDataViewer() {
183
183
  return (
184
184
  <DataViewer.TableDataProvider
185
185
  tableName="User"
186
+ columns={["id", "name", "email"]}
186
187
  initialData={{
187
- selectedFields: ["id", "name", "email"],
188
188
  sort: { field: "name", direction: "Asc" },
189
189
  }}
190
190
  >
package/docs/README.md CHANGED
@@ -12,6 +12,7 @@ Documentation for `@izumisy-tailor/tailor-data-viewer`.
12
12
 
13
13
  ## Configuration
14
14
 
15
+ - [Column Definition API](columns.md) - Flexible column definition with width, renderers, and relation fields
15
16
  - [GraphQL Fetcher](fetcher.md) - GraphQL fetcher configuration and custom implementation
16
17
  - [Custom Labels](labels.md) - Customizing field names, table names, and UI text (i18n support)
17
18
  - [Custom Renderers](custom-renderers.md) - Custom cell renderers and built-in renderers
@@ -0,0 +1,276 @@
1
+ # Column Definition API
2
+
3
+ The Column Definition API provides a flexible way to define columns displayed in DataViewer. It supports various levels of definition, from simple field name arrays to detailed configurations including width and renderers.
4
+
5
+ ## Overview
6
+
7
+ Use the `columns` property to specify which columns to display and their settings.
8
+
9
+ ```tsx
10
+ const { TableDataProvider, ToolbarProvider, DataTable } = createDataViewer({
11
+ metadata,
12
+ fetcher,
13
+ });
14
+
15
+ // Simple usage
16
+ <TableDataProvider tableName="task" columns={["title", "status", "createdAt"]}>
17
+ ...
18
+ </TableDataProvider>
19
+
20
+ // Detailed configuration
21
+ <TableDataProvider
22
+ tableName="task"
23
+ columns={[
24
+ "title",
25
+ ["status", { width: 100 }],
26
+ ["assignee.name", { label: "Assignee" }],
27
+ { field: "description", width: 300, renderer: MyCustomRenderer },
28
+ ]}
29
+ >
30
+ ...
31
+ </TableDataProvider>
32
+ ```
33
+
34
+ ## ColumnDef Type
35
+
36
+ Column definitions support four levels:
37
+
38
+ ### Level 1: Field Path (String)
39
+
40
+ The simplest form. Specify the field name as a string.
41
+
42
+ ```typescript
43
+ type ColumnDef = string;
44
+
45
+ // Example
46
+ const columns = ["id", "title", "status"];
47
+ ```
48
+
49
+ ### Level 2a: Tuple (Field Path + Renderer)
50
+
51
+ Use when specifying a custom renderer.
52
+
53
+ ```typescript
54
+ type ColumnDef = [FieldPath, CellRenderer];
55
+
56
+ // Example
57
+ const columns = [
58
+ ["status", StatusBadgeRenderer],
59
+ ["priority", PriorityRenderer],
60
+ ];
61
+ ```
62
+
63
+ ### Level 2b: Tuple (Field Path + Options)
64
+
65
+ Use when specifying options like width or label.
66
+
67
+ ```typescript
68
+ type ColumnDef = [FieldPath, ColumnOptions];
69
+
70
+ // Example
71
+ const columns = [
72
+ ["title", { width: 200, label: "Title" }],
73
+ ["description", { minWidth: 100, maxWidth: 400 }],
74
+ ];
75
+ ```
76
+
77
+ ### Level 3: Object Form
78
+
79
+ The complete form that allows explicit specification of all options.
80
+
81
+ ```typescript
82
+ type ColumnDef = ColumnDefinition;
83
+
84
+ interface ColumnDefinition {
85
+ /** Field path (can specify relation fields using dot notation) */
86
+ field: FieldPath;
87
+ /** Column width (number or string) */
88
+ width?: number | string;
89
+ /** Minimum width */
90
+ minWidth?: number | string;
91
+ /** Maximum width */
92
+ maxWidth?: number | string;
93
+ /** Custom cell renderer */
94
+ renderer?: CellRenderer;
95
+ /** Column header label */
96
+ label?: string;
97
+ }
98
+
99
+ // Example
100
+ const columns = [
101
+ { field: "title", width: 200, label: "Title" },
102
+ { field: "status", renderer: StatusBadgeRenderer, width: 100 },
103
+ ];
104
+ ```
105
+
106
+ ## Relation Fields
107
+
108
+ Use dot notation to directly specify fields from relations.
109
+
110
+ ```typescript
111
+ const columns = [
112
+ "title",
113
+ "assignee.name", // name field from assignee relation
114
+ "assignee.email", // email field from assignee relation
115
+ "project.name", // name field from project relation
116
+ ];
117
+ ```
118
+
119
+ This displays the following columns in the table:
120
+ - title
121
+ - assignee.name (label is "assignee.name" or can be overridden with labels)
122
+ - assignee.email
123
+ - project.name
124
+
125
+ ## Options Reference
126
+
127
+ ### width
128
+
129
+ Specifies the column width. Can be a number (pixels) or string (CSS width value).
130
+
131
+ ```typescript
132
+ { width: 200 } // 200px
133
+ { width: "20%" } // 20%
134
+ { width: "auto" } // auto
135
+ ```
136
+
137
+ ### minWidth / maxWidth
138
+
139
+ Specifies the minimum and maximum width of the column.
140
+
141
+ ```typescript
142
+ { minWidth: 100, maxWidth: 400 }
143
+ ```
144
+
145
+ ### renderer
146
+
147
+ Specifies a custom cell renderer.
148
+
149
+ ```typescript
150
+ const StatusRenderer: CellRenderer = ({ value, row }) => (
151
+ <Badge variant={value === "active" ? "success" : "secondary"}>
152
+ {value}
153
+ </Badge>
154
+ );
155
+
156
+ const columns = [
157
+ ["status", StatusRenderer],
158
+ // or
159
+ { field: "status", renderer: StatusRenderer },
160
+ ];
161
+ ```
162
+
163
+ ### label
164
+
165
+ Overrides the column header label.
166
+
167
+ ```typescript
168
+ const columns = [
169
+ ["assignee.name", { label: "Assignee" }],
170
+ { field: "createdAt", label: "Created At" },
171
+ ];
172
+ ```
173
+
174
+ ## Type Definitions
175
+
176
+ ```typescript
177
+ /** Field path (can specify relation fields using dot notation) */
178
+ type FieldPath = string;
179
+
180
+ /** Column options */
181
+ interface ColumnOptions {
182
+ width?: number | string;
183
+ minWidth?: number | string;
184
+ maxWidth?: number | string;
185
+ renderer?: CellRenderer;
186
+ label?: string;
187
+ }
188
+
189
+ /** Column definition (Level 3) */
190
+ interface ColumnDefinition extends ColumnOptions {
191
+ field: FieldPath;
192
+ }
193
+
194
+ /** Column definition (all levels) */
195
+ type ColumnDef =
196
+ | FieldPath // Level 1
197
+ | [FieldPath, CellRenderer] // Level 2a
198
+ | [FieldPath, ColumnOptions] // Level 2b
199
+ | ColumnDefinition; // Level 3
200
+ ```
201
+
202
+ ## Normalized Form
203
+
204
+ Internally, all ColumnDef values are normalized to the following form:
205
+
206
+ ```typescript
207
+ interface NormalizedColumnDefinition {
208
+ /** Original field path */
209
+ field: FieldPath;
210
+ /** Column width */
211
+ width?: number | string;
212
+ /** Minimum width */
213
+ minWidth?: number | string;
214
+ /** Maximum width */
215
+ maxWidth?: number | string;
216
+ /** Custom renderer */
217
+ renderer?: CellRenderer;
218
+ /** Column label */
219
+ label?: string;
220
+ /** Whether this is a relation field */
221
+ isRelationField: boolean;
222
+ /** Base field name (before the dot) */
223
+ baseField: string;
224
+ /** Nested field name (after the dot, if any) */
225
+ nestedField?: string;
226
+ }
227
+ ```
228
+
229
+ ## Example: Complete Configuration
230
+
231
+ ```tsx
232
+ import { createDataViewer, builtInRenderers } from "@tailor/data-viewer";
233
+
234
+ const { TableDataProvider, DataTable, ColumnSelector } = createDataViewer({
235
+ metadata,
236
+ fetcher,
237
+ });
238
+
239
+ function TaskList() {
240
+ return (
241
+ <TableDataProvider
242
+ tableName="task"
243
+ columns={[
244
+ // Simple field
245
+ "title",
246
+
247
+ // Width specification
248
+ ["status", { width: 120 }],
249
+
250
+ // Custom renderer
251
+ ["priority", builtInRenderers.statusBadge({
252
+ mapping: {
253
+ high: { label: "High", color: "red" },
254
+ medium: { label: "Medium", color: "yellow" },
255
+ low: { label: "Low", color: "gray" },
256
+ },
257
+ })],
258
+
259
+ // Relation field + label
260
+ ["assignee.name", { label: "Assignee", width: 150 }],
261
+
262
+ // Complete configuration
263
+ {
264
+ field: "description",
265
+ width: 300,
266
+ minWidth: 200,
267
+ maxWidth: 500,
268
+ label: "Description",
269
+ },
270
+ ]}
271
+ >
272
+ <DataTable />
273
+ </TableDataProvider>
274
+ );
275
+ }
276
+ ```
@@ -53,8 +53,8 @@ function MyDataViewer() {
53
53
  return (
54
54
  <DataViewer.TableDataProvider
55
55
  tableName="User"
56
+ columns={["id", "name", "email"]}
56
57
  initialData={{
57
- selectedFields: ["id", "name", "email"],
58
58
  sort: { field: "name", direction: "Asc" },
59
59
  }}
60
60
  >
@@ -112,15 +112,14 @@ interface TableDataProviderProps {
112
112
  children: ReactNode;
113
113
  /** Table name to display (type-safe when metadata is `as const`) */
114
114
  tableName: string;
115
- /** Initial data for filters, selected fields, and relations */
115
+ /** Column definitions for the table (see columns.md for details) */
116
+ columns?: ColumnDef[];
117
+ /** Initial data for filters and sort */
116
118
  initialData?: DataViewerInitialData;
117
119
  }
118
120
 
119
121
  interface DataViewerInitialData {
120
122
  filters?: SearchFilters;
121
- selectedFields?: string[];
122
- selectedRelations?: string[];
123
- expandedRelationFields?: ExpandedRelationFields;
124
123
  sort?: { field: string; direction: "Asc" | "Desc" } | { field: string; direction: "Asc" | "Desc" }[];
125
124
  }
126
125
  ```
@@ -130,11 +129,16 @@ interface DataViewerInitialData {
130
129
  ```tsx
131
130
  <DataViewer.TableDataProvider
132
131
  tableName="Task"
132
+ columns={[
133
+ "id",
134
+ "title",
135
+ ["status", { width: 120 }],
136
+ "assignee.name",
137
+ "assignee.email",
138
+ "createdAt",
139
+ ]}
133
140
  initialData={{
134
141
  filters: [{ field: "status", fieldType: "enum", operator: "eq", value: "active" }],
135
- selectedFields: ["id", "title", "status", "createdAt"],
136
- selectedRelations: ["assignee"],
137
- expandedRelationFields: { assignee: ["name", "email"] },
138
142
  sort: { field: "createdAt", direction: "Desc" },
139
143
  }}
140
144
  >
@@ -51,6 +51,83 @@ The `byFieldName` object uses a `tableName:fieldName` key format:
51
51
  5. `byFieldType["string"]` — type-based fallback
52
52
  6. Default formatting (lowest)
53
53
 
54
+ ## Renderers vs Column API Renderer Option
55
+
56
+ There are two ways to define cell renderers: centralized management via `renderers` in `createDataViewer`, or ad-hoc specification using the `renderer` option in the [Column API](./columns.md).
57
+
58
+ ### Ad-hoc Renderer Specification with Column API
59
+
60
+ The Column API allows you to specify renderers on-the-fly using the `renderer` option in each column definition:
61
+
62
+ ```tsx
63
+ <TableDataProvider
64
+ tableName="task"
65
+ columns={[
66
+ ["status", StatusBadgeRenderer],
67
+ { field: "priority", renderer: PriorityRenderer },
68
+ ]}
69
+ >
70
+ ...
71
+ </TableDataProvider>
72
+ ```
73
+
74
+ ### Benefits of Centralized Management with renderers
75
+
76
+ Using `renderers` in `createDataViewer` for centralized renderer management provides the following benefits:
77
+
78
+ 1. **Eliminate Duplication**: When the same field is rendered across multiple views, you don't need to specify the renderer individually in each view.
79
+
80
+ 2. **Consistency for Relation Fields**: When expanding relation fields like `assignee.email` across multiple views, defining renderers once in `renderers` applies to all views automatically.
81
+
82
+ 3. **Improved Maintainability**: When a renderer change is needed, modifying it in one place reflects the change everywhere.
83
+
84
+ 4. **Efficiency with Wildcards**: Wildcard patterns like `*:*Email` or `*:id` allow you to set common field renderers across all tables at once.
85
+
86
+ 5. **Type-based Fallbacks**: The `byFieldType` option enables automatic rendering based on field types (e.g., all boolean fields rendered as icons).
87
+
88
+ ```tsx
89
+ // Example of centralized management with renderers
90
+ const DataViewer = createDataViewer({
91
+ metadata,
92
+ fetcher,
93
+ renderers: {
94
+ cell: {
95
+ byFieldName: {
96
+ // Define relation field renderers once
97
+ "*:*.email": EmailLink,
98
+ "*:id": TruncatedId,
99
+
100
+ // Table-specific renderers
101
+ "task:status": StatusBadge({
102
+ colorMap: { todo: "gray", "in-progress": "blue", done: "green" },
103
+ }),
104
+ },
105
+ byFieldType: {
106
+ boolean: BooleanIcon,
107
+ array: ArrayBadges,
108
+ },
109
+ },
110
+ },
111
+ });
112
+
113
+ // No need to specify renderer in columns for each view
114
+ <TableDataProvider tableName="task" columns={["title", "status", "assignee.email"]}>
115
+ ...
116
+ </TableDataProvider>
117
+ ```
118
+
119
+ ### Guidelines for Choosing an Approach
120
+
121
+ | Case | Recommended Approach |
122
+ |------|----------------------|
123
+ | Same field rendered across multiple views | Centralized management with `renderers` |
124
+ | Different renderer needed only in a specific view | Column API `renderer` option |
125
+ | Expanding relation fields | Centralized management with `renderers` |
126
+ | Common fields across all tables | `renderers` wildcard (`*:fieldName`, `*:*suffix`) |
127
+ | Type-based default rendering | `renderers.cell.byFieldType` |
128
+
129
+ > **Note**: Renderers specified via the Column API `renderer` option take precedence over centralized `renderers`. Use this when you need a different renderer only in a specific view.
130
+
54
131
  ## Usage with createDataViewer (Recommended)
55
132
 
56
133
  Set renderers at the `createDataViewer` level to apply them across all tables:
package/docs/labels.md CHANGED
@@ -30,10 +30,87 @@ For field labels (`getLabel("orders:status")`):
30
30
  2. `labels["*:status"]` — wildcard match
31
31
  3. `"status"` — fallback to field name
32
32
 
33
+ For nested relation fields (`getLabel("task:assignee.name")`):
34
+ 1. `labels["task:assignee.name"]` — exact match (highest)
35
+ 2. `labels["*:assignee.name"]` — wildcard match for full path
36
+ 3. `labels["assignee:name"]` — target table format fallback
37
+ 4. `labels["*:name"]` — target field wildcard
38
+ 5. `"name"` — fallback to target field name
39
+
33
40
  For UI labels (`getLabel("$:refresh")`):
34
41
  1. `labels["$:refresh"]` — custom override (highest)
35
42
  2. `DEFAULT_UI_LABELS["$:refresh"]` — built-in default
36
43
 
44
+ ## Labels vs Column API Label Option
45
+
46
+ There are two ways to define field labels: centralized management via `labels`, or ad-hoc specification using the `label` option in the [Column API](./columns.md).
47
+
48
+ ### Ad-hoc Label Specification with Column API
49
+
50
+ The Column API allows you to specify labels on-the-fly using the `label` option in each column definition:
51
+
52
+ ```tsx
53
+ <TableDataProvider
54
+ tableName="task"
55
+ columns={[
56
+ ["assignee.name", { label: "Assignee" }],
57
+ { field: "createdAt", label: "Created Date" },
58
+ ]}
59
+ >
60
+ ...
61
+ </TableDataProvider>
62
+ ```
63
+
64
+ ### Benefits of Centralized Management with labels
65
+
66
+ Using `labels` for centralized label management provides the following benefits:
67
+
68
+ 1. **Eliminate Duplication**: When the same field is used across multiple views, you don't need to specify the label individually in each view.
69
+
70
+ 2. **Consistency for Relation Fields**: When expanding relation fields like `assignee.name` across multiple views, defining them once in `labels` applies to all views automatically.
71
+
72
+ 3. **Improved Maintainability**: When a label change is needed, modifying it in one place reflects the change everywhere.
73
+
74
+ 4. **Efficiency with Wildcards**: Wildcard patterns like `*:createdAt` allow you to set common field labels across all tables at once.
75
+
76
+ ```tsx
77
+ // Example of centralized management with labels
78
+ const DataViewer = createDataViewer({
79
+ metadata,
80
+ fetcher,
81
+ labels: {
82
+ // Define relation field labels using target table format
83
+ // These labels apply to all tables referencing the same relation
84
+ "assignee:name": "Assignee",
85
+ "assignee:email": "Assignee Email",
86
+ "project:name": "Project Name",
87
+
88
+ // Or use table-specific format for precise control
89
+ "task:assignee.name": "Task Assignee", // Takes priority over "assignee:name"
90
+
91
+ // Common fields across multiple tables
92
+ "*:createdAt": "Created",
93
+ "*:updatedAt": "Updated",
94
+ },
95
+ });
96
+
97
+ // No need to specify label in columns for each view
98
+ <TableDataProvider tableName="task" columns={["title", "assignee.name", "createdAt"]}>
99
+ ...
100
+ </TableDataProvider>
101
+ ```
102
+
103
+ ### Guidelines for Choosing an Approach
104
+
105
+ | Case | Recommended Approach |
106
+ |------|----------------------|
107
+ | Same field displayed across multiple views | Centralized management with `labels` |
108
+ | Different label needed only in a specific view | Column API `label` option |
109
+ | Expanding relation fields | Centralized management with `labels` |
110
+ | Common fields across all tables | `labels` wildcard (`*:fieldName`) |
111
+
112
+ > **Note**: Labels specified via the Column API `label` option take precedence over `labels`. Use this when you need to display a different label only in a specific view.
113
+
37
114
  ## Usage with createDataViewer
38
115
 
39
116
  ```tsx
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@izumisy-tailor/tailor-data-viewer",
3
3
  "private": false,
4
- "version": "0.1.41",
4
+ "version": "0.1.42",
5
5
  "type": "module",
6
6
  "description": "Flexible data viewer component for Tailor Platform",
7
7
  "files": [