banhaten 0.5.0 → 0.6.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/CHANGELOG.md +177 -139
- package/CONTRIBUTING.md +46 -46
- package/README.md +192 -192
- package/SECURITY.md +15 -15
- package/banhaten.config.example.json +11 -11
- package/docs/design-spacing.md +13 -13
- package/docs/design-system/README.md +22 -22
- package/docs/design-system/adoption-quality-remediation.md +87 -87
- package/docs/design-system/agent-usage.md +13 -13
- package/docs/design-system/appearance-presets.md +189 -189
- package/docs/design-system/appearances/default.md +94 -94
- package/docs/design-system/appearances/rounded.md +95 -95
- package/docs/design-system/appearances/sharp.md +95 -95
- package/docs/design-system/component-docs-pattern.md +81 -81
- package/docs/design-system/component-implementation-ownership-audit.md +245 -245
- package/docs/design-system/component-quality-audit-2026-08-20.md +225 -225
- package/docs/design-system/component-showcase-consistency-report.md +219 -219
- package/docs/design-system/component-token-consistency-audit.md +173 -173
- package/docs/design-system/components/README.md +91 -90
- package/docs/design-system/components/accordion.md +52 -52
- package/docs/design-system/components/activity-feed.md +93 -93
- package/docs/design-system/components/activity-heatmap.md +56 -56
- package/docs/design-system/components/alert-dialog.md +71 -71
- package/docs/design-system/components/alert.md +103 -97
- package/docs/design-system/components/app-header.md +97 -0
- package/docs/design-system/components/aspect-ratio.md +45 -45
- package/docs/design-system/components/attribute.md +101 -89
- package/docs/design-system/components/autocomplete.md +75 -75
- package/docs/design-system/components/avatar.md +53 -53
- package/docs/design-system/components/badge.md +55 -55
- package/docs/design-system/components/banner.md +86 -86
- package/docs/design-system/components/breadcrumbs.md +181 -177
- package/docs/design-system/components/button-group.md +97 -97
- package/docs/design-system/components/button.md +80 -80
- package/docs/design-system/components/card.md +102 -88
- package/docs/design-system/components/carousel.md +46 -46
- package/docs/design-system/components/catalog-components.md +46 -46
- package/docs/design-system/components/checkbox.md +53 -53
- package/docs/design-system/components/collapsible.md +63 -63
- package/docs/design-system/components/command.md +61 -61
- package/docs/design-system/components/context-menu.md +45 -45
- package/docs/design-system/components/date-picker.md +80 -80
- package/docs/design-system/components/divider.md +102 -102
- package/docs/design-system/components/empty-state.md +56 -56
- package/docs/design-system/components/field.md +70 -70
- package/docs/design-system/components/file-upload.md +186 -186
- package/docs/design-system/components/hover-card.md +47 -47
- package/docs/design-system/components/icons.md +48 -48
- package/docs/design-system/components/input-group.md +57 -57
- package/docs/design-system/components/input-otp.md +56 -56
- package/docs/design-system/components/input.md +111 -109
- package/docs/design-system/components/item.md +58 -58
- package/docs/design-system/components/kbd.md +49 -49
- package/docs/design-system/components/label.md +49 -49
- package/docs/design-system/components/menu.md +94 -73
- package/docs/design-system/components/menubar.md +46 -46
- package/docs/design-system/components/modal.md +104 -104
- package/docs/design-system/components/native-select.md +53 -53
- package/docs/design-system/components/navigation-menu.md +49 -49
- package/docs/design-system/components/onboarding-step-list-item.md +81 -81
- package/docs/design-system/components/page-header.md +127 -93
- package/docs/design-system/components/pagination.md +56 -56
- package/docs/design-system/components/popover.md +59 -59
- package/docs/design-system/components/progress-slider.md +49 -49
- package/docs/design-system/components/progress.md +76 -76
- package/docs/design-system/components/radio-card.md +50 -50
- package/docs/design-system/components/radio-group.md +56 -56
- package/docs/design-system/components/resizable.md +43 -43
- package/docs/design-system/components/scroll-area.md +50 -50
- package/docs/design-system/components/segmented-control.md +54 -54
- package/docs/design-system/components/select.md +68 -64
- package/docs/design-system/components/sheet.md +66 -66
- package/docs/design-system/components/sidebar.md +167 -111
- package/docs/design-system/components/skeleton.md +74 -74
- package/docs/design-system/components/slideout.md +64 -64
- package/docs/design-system/components/slider.md +62 -62
- package/docs/design-system/components/social-button.md +49 -49
- package/docs/design-system/components/spinner.md +62 -62
- package/docs/design-system/components/steps.md +65 -65
- package/docs/design-system/components/table.md +499 -422
- package/docs/design-system/components/tabs.md +74 -59
- package/docs/design-system/components/tag.md +79 -79
- package/docs/design-system/components/textarea.md +49 -49
- package/docs/design-system/components/timeline.md +82 -82
- package/docs/design-system/components/toast.md +57 -57
- package/docs/design-system/components/toggle.md +80 -80
- package/docs/design-system/components/toolbar.md +89 -89
- package/docs/design-system/components/tooltip.md +92 -92
- package/docs/design-system/components/typography.md +18 -18
- package/docs/design-system/density-and-elevation.md +65 -65
- package/docs/design-system/design-system-test-missing-items.md +368 -368
- package/docs/design-system/direction-and-localization.md +23 -23
- package/docs/design-system/form-recipes.md +73 -73
- package/docs/design-system/gap-promotion-policy.md +21 -21
- package/docs/design-system/icons.md +74 -74
- package/docs/design-system/installation.md +70 -70
- package/docs/design-system/launch-comparison-shadcn.md +98 -98
- package/docs/design-system/registry-and-cli.md +64 -64
- package/docs/design-system/release-policy.md +30 -30
- package/docs/design-system/roadmap.md +14 -14
- package/docs/design-system/support-matrix.md +23 -23
- package/docs/design-system/tabs.md +54 -54
- package/docs/design-system/template-gap-audit.md +32 -32
- package/docs/design-system/templates.md +59 -59
- package/docs/design-system/token-governance.md +72 -72
- package/package.json +98 -98
- package/registry/component-contract.json +40 -40
- package/registry/components/accordion.tsx +155 -155
- package/registry/components/alert-dialog.tsx +297 -297
- package/registry/components/alert.tsx +309 -293
- package/registry/components/app-header.tsx +151 -0
- package/registry/components/aspect-ratio.tsx +30 -30
- package/registry/components/attribute.tsx +489 -454
- package/registry/components/autocomplete.tsx +598 -598
- package/registry/components/avatar.tsx +385 -385
- package/registry/components/badge.tsx +630 -629
- package/registry/components/button-group.tsx +565 -565
- package/registry/components/button.tsx +128 -127
- package/registry/components/card.tsx +651 -638
- package/registry/components/carousel.tsx +296 -296
- package/registry/components/chart.tsx +203 -203
- package/registry/components/checkbox.tsx +247 -247
- package/registry/components/collapsible.tsx +109 -109
- package/registry/components/command.tsx +208 -208
- package/registry/components/context-menu.tsx +236 -236
- package/registry/components/date-picker.tsx +1289 -1289
- package/registry/components/expanded/ActivityFeed.tsx +227 -227
- package/registry/components/expanded/ActivityHeatmap.tsx +64 -64
- package/registry/components/expanded/Banner.tsx +179 -179
- package/registry/components/expanded/Breadcrumbs.tsx +135 -131
- package/registry/components/expanded/CatalogComponentsShowcase.tsx +205 -205
- package/registry/components/expanded/CatalogDivider.tsx +49 -49
- package/registry/components/expanded/EmptyState.tsx +155 -155
- package/registry/components/expanded/FileUpload.tsx +543 -543
- package/registry/components/expanded/OnboardingStepListItem.tsx +60 -60
- package/registry/components/expanded/PageHeader.tsx +209 -179
- package/registry/components/expanded/Slideout.tsx +108 -108
- package/registry/components/expanded/Steps.tsx +402 -402
- package/registry/components/expanded/Table.tsx +1109 -1092
- package/registry/components/expanded/Timeline.tsx +203 -203
- package/registry/components/expanded/activityFeed.css +248 -248
- package/registry/components/expanded/activityHeatmap.css +66 -66
- package/registry/components/expanded/banner.css +260 -260
- package/registry/components/expanded/breadcrumbs.css +157 -140
- package/registry/components/expanded/catalogComponentsShowcase.css +87 -87
- package/registry/components/expanded/divider.css +75 -75
- package/registry/components/expanded/emptyState.css +111 -111
- package/registry/components/expanded/fileUpload.css +457 -457
- package/registry/components/expanded/onboardingStepListItem.css +135 -135
- package/registry/components/expanded/pageHeader.css +284 -248
- package/registry/components/expanded/slideout.css +276 -276
- package/registry/components/expanded/steps.css +356 -356
- package/registry/components/expanded/table.css +655 -606
- package/registry/components/expanded/timeline.css +286 -286
- package/registry/components/field.tsx +283 -283
- package/registry/components/hover-card.tsx +48 -48
- package/registry/components/input-content.ts +107 -107
- package/registry/components/input-group.tsx +130 -130
- package/registry/components/input-otp.tsx +584 -584
- package/registry/components/input.tsx +992 -990
- package/registry/components/item.tsx +189 -189
- package/registry/components/kbd.tsx +246 -246
- package/registry/components/label.tsx +78 -78
- package/registry/components/menu.tsx +1091 -978
- package/registry/components/menubar.tsx +226 -226
- package/registry/components/modal.tsx +872 -872
- package/registry/components/native-select.tsx +205 -205
- package/registry/components/navigation-menu.tsx +171 -171
- package/registry/components/pagination.css +36 -36
- package/registry/components/pagination.tsx +560 -560
- package/registry/components/popover.tsx +338 -338
- package/registry/components/progress-slider.tsx +216 -216
- package/registry/components/radio-card.tsx +676 -676
- package/registry/components/radio-group.tsx +570 -570
- package/registry/components/resizable.tsx +108 -108
- package/registry/components/scroll-area.tsx +91 -91
- package/registry/components/segmented-control.tsx +87 -87
- package/registry/components/select-content.tsx +218 -218
- package/registry/components/select.tsx +937 -937
- package/registry/components/sheet.tsx +305 -305
- package/registry/components/sidebar.tsx +1535 -1433
- package/registry/components/skeleton.css +54 -54
- package/registry/components/skeleton.tsx +482 -482
- package/registry/components/slider.tsx +380 -380
- package/registry/components/social-button.tsx +387 -387
- package/registry/components/spinner.tsx +78 -78
- package/registry/components/table-elements.tsx +287 -128
- package/registry/components/tabs.css +246 -222
- package/registry/components/tabs.tsx +245 -241
- package/registry/components/tag.tsx +423 -423
- package/registry/components/textarea.tsx +610 -610
- package/registry/components/toast.tsx +136 -136
- package/registry/components/toggle.tsx +256 -256
- package/registry/components/toolbar.tsx +539 -539
- package/registry/components/tooltip.tsx +433 -433
- package/registry/examples/alert-demo.tsx +34 -34
- package/registry/examples/alert-dialog-demo.tsx +42 -42
- package/registry/examples/app-header-demo.tsx +40 -0
- package/registry/examples/aspect-ratio-demo.tsx +11 -11
- package/registry/examples/attribute-demo.tsx +65 -65
- package/registry/examples/autocomplete-demo.tsx +109 -109
- package/registry/examples/avatar-demo.tsx +132 -132
- package/registry/examples/badge-demo.tsx +69 -69
- package/registry/examples/button-demo.tsx +86 -86
- package/registry/examples/button-group-demo.tsx +63 -63
- package/registry/examples/card-demo.tsx +86 -86
- package/registry/examples/carousel-demo.tsx +43 -43
- package/registry/examples/chart-demo.tsx +18 -18
- package/registry/examples/checkbox-demo.tsx +62 -62
- package/registry/examples/collapsible-demo.tsx +16 -16
- package/registry/examples/collection-filter-bar-demo.tsx +135 -135
- package/registry/examples/command-demo.tsx +42 -42
- package/registry/examples/command-palette-demo.tsx +235 -235
- package/registry/examples/context-menu-demo.tsx +29 -29
- package/registry/examples/control-density-contract-demo.tsx +65 -65
- package/registry/examples/date-picker-demo.tsx +127 -127
- package/registry/examples/divider-demo.tsx +17 -17
- package/registry/examples/expanded/activity-heatmap-demo.tsx +29 -29
- package/registry/examples/expanded/banner-demo.tsx +48 -48
- package/registry/examples/expanded/breadcrumbs-demo.tsx +59 -59
- package/registry/examples/expanded/empty-state-demo.tsx +39 -39
- package/registry/examples/expanded/file-upload-demo.tsx +60 -60
- package/registry/examples/expanded/onboarding-step-demo.tsx +11 -11
- package/registry/examples/expanded/slideout-demo.tsx +15 -15
- package/registry/examples/expanded/steps-demo.tsx +29 -29
- package/registry/examples/expanded/tabs-demo.tsx +13 -13
- package/registry/examples/expanded/timeline-demo.tsx +18 -18
- package/registry/examples/field-demo.tsx +51 -51
- package/registry/examples/hover-card-demo.tsx +23 -23
- package/registry/examples/input-actions-demo.tsx +36 -36
- package/registry/examples/input-demo.tsx +87 -87
- package/registry/examples/input-group-demo.tsx +16 -16
- package/registry/examples/input-otp-demo.tsx +72 -72
- package/registry/examples/item-demo.tsx +46 -46
- package/registry/examples/kbd-demo.tsx +12 -12
- package/registry/examples/label-demo.tsx +20 -20
- package/registry/examples/menu-demo.tsx +122 -122
- package/registry/examples/menubar-demo.tsx +34 -34
- package/registry/examples/modal-demo.tsx +74 -74
- package/registry/examples/native-select-demo.tsx +16 -16
- package/registry/examples/navigation-menu-demo.tsx +29 -29
- package/registry/examples/pagination-demo.tsx +20 -20
- package/registry/examples/popover-demo.tsx +547 -547
- package/registry/examples/progress-demo.tsx +37 -37
- package/registry/examples/progress-slider-demo.tsx +29 -29
- package/registry/examples/radio-card-demo.tsx +51 -51
- package/registry/examples/radio-group-demo.tsx +62 -62
- package/registry/examples/resizable-demo.tsx +22 -22
- package/registry/examples/results-toolbar-demo.tsx +44 -44
- package/registry/examples/scroll-area-demo.tsx +15 -15
- package/registry/examples/segmented-control-demo.tsx +39 -39
- package/registry/examples/select-demo.tsx +87 -87
- package/registry/examples/sheet-demo.tsx +47 -47
- package/registry/examples/sidebar-demo.tsx +101 -101
- package/registry/examples/slider-demo.tsx +31 -31
- package/registry/examples/social-button-demo.tsx +61 -61
- package/registry/examples/spinner-demo.tsx +59 -59
- package/registry/examples/tabs-demo.tsx +13 -13
- package/registry/examples/tag-demo.tsx +29 -29
- package/registry/examples/textarea-demo.tsx +79 -79
- package/registry/examples/toast-demo.tsx +35 -35
- package/registry/examples/toggle-demo.tsx +59 -59
- package/registry/examples/tooltip-demo.tsx +115 -115
- package/registry/guidance/USAGE.md +27 -27
- package/registry/hooks/use-direction.ts +207 -207
- package/registry/index.json +387 -17
- package/registry/styles/globals.css +46 -0
- package/registry.json +24 -2
- package/schema/config.schema.json +51 -51
- package/schema/registry.schema.json +194 -194
- package/schema/tokens.schema.json +167 -167
- package/src/cli/command-line.js +194 -194
- package/src/cli/config-editors.js +9 -9
- package/src/cli/doctor.js +399 -399
- package/src/cli/file-transaction.js +302 -302
- package/src/cli/global-css.js +153 -153
- package/src/cli/index.js +1002 -1002
- package/src/cli/install-plan.js +592 -592
- package/src/cli/install-receipt.js +171 -171
- package/src/cli/jsonc.js +203 -203
- package/src/cli/package-install-state.js +254 -254
- package/src/cli/package-manager.js +238 -238
- package/src/cli/package-plan.js +185 -185
- package/src/cli/package-version.js +97 -97
- package/src/cli/registry-query.js +207 -207
- package/src/cli/reporting.js +55 -55
- package/src/cli/schema-validation.js +19 -19
- package/src/cli/source-scanner.js +686 -686
- package/src/cli/vite-config.js +478 -478
- package/tokens/banhaten.tokens.json +184 -0
|
@@ -1,420 +1,491 @@
|
|
|
1
|
-
# Table
|
|
2
|
-
|
|
3
|
-
The table component family includes `Table` and `DataTable`.
|
|
4
|
-
|
|
5
|
-
`Table` is the composable data surface. It owns layout, headers, row states, selection, sortable header affordances, and RTL mirroring. Cells are filled by table item variants, so new content can be added without creating one-off row layouts.
|
|
6
|
-
|
|
7
|
-
`DataTable` is the dataset workflow wrapper. It composes the existing Banhaten `Table`, `Toolbar`, `Pagination`, and `Spinner` components to provide search, filters, sorting, selection summary, bulk actions, pagination, and empty/loading/error states.
|
|
8
|
-
|
|
9
|
-
Figma references:
|
|
10
|
-
|
|
11
|
-
- `Table Example`, node `449:2354`
|
|
12
|
-
- `Table item 1.2`, node `438:41157`
|
|
13
|
-
|
|
14
|
-
## Structure
|
|
15
|
-
|
|
16
|
-
```tsx
|
|
17
|
-
<Table
|
|
18
|
-
dir="ltr"
|
|
19
|
-
columns={columns}
|
|
20
|
-
rows={rows}
|
|
21
|
-
selectedRowIds={selectedRowIds}
|
|
22
|
-
onSelectedRowIdsChange={setSelectedRowIds}
|
|
23
|
-
/>
|
|
24
|
-
|
|
25
|
-
<DataTable
|
|
26
|
-
title="Component Operations"
|
|
27
|
-
description="Track component readiness across design-system work."
|
|
28
|
-
columns={columns}
|
|
29
|
-
rows={rows}
|
|
30
|
-
selectedRowIds={selectedRowIds}
|
|
31
|
-
onSelectedRowIdsChange={setSelectedRowIds}
|
|
32
|
-
search={{ label: "Search projects", placeholder: "Search projects..." }}
|
|
33
|
-
filters={filters}
|
|
34
|
-
pagination={{ pageSize: 10 }}
|
|
35
|
-
/>
|
|
36
|
-
|
|
37
|
-
<TableRoot aria-label="Compact audit data">
|
|
38
|
-
<TableHeader>
|
|
39
|
-
<TableRowElement>
|
|
40
|
-
<TableHead>Activity</TableHead>
|
|
41
|
-
</TableRowElement>
|
|
42
|
-
</TableHeader>
|
|
43
|
-
<TableBody>
|
|
44
|
-
<TableRowElement>
|
|
45
|
-
<TableCell>Member invited</TableCell>
|
|
46
|
-
</TableRowElement>
|
|
47
|
-
</TableBody>
|
|
48
|
-
</TableRoot>
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The table is composed from these parts:
|
|
52
|
-
|
|
53
|
-
- `Table`: scroll container and semantic table/grid root.
|
|
54
|
-
- `TableHeader`: one header row with sortable header cells.
|
|
55
|
-
- `TableRow`: one data row with `default`, `selected`, and `disabled` states plus native hover styling.
|
|
56
|
-
- `TableCell`: the layout shell for any table item.
|
|
57
|
-
- `TableItem`: the content variant rendered inside a cell.
|
|
58
|
-
- `DataTable`: composed workflow shell for search, filters, actions, pagination, and table states.
|
|
59
|
-
- `DataTableSurface`: ARIA table surface for bespoke CSS-grid rows while keeping the table root owned by Banhaten.
|
|
60
|
-
- `TableRoot`, `TableHeader`, `TableBody`, `TableRowElement`, `TableHead`, and `TableCell`: owned semantic elements for product-specific read-only tables that do not fit the data-column API.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{ id: "
|
|
74
|
-
{ id: "
|
|
75
|
-
{ id: "
|
|
76
|
-
{ id: "
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
| `
|
|
168
|
-
|
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
type
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
{
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
-
|
|
378
|
-
-
|
|
379
|
-
-
|
|
380
|
-
|
|
381
|
-
##
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
1
|
+
# Table
|
|
2
|
+
|
|
3
|
+
The table component family includes `Table` and `DataTable`.
|
|
4
|
+
|
|
5
|
+
`Table` is the composable data surface. It owns layout, headers, row states, selection, sortable header affordances, and RTL mirroring. Cells are filled by table item variants, so new content can be added without creating one-off row layouts.
|
|
6
|
+
|
|
7
|
+
`DataTable` is the dataset workflow wrapper. It composes the existing Banhaten `Table`, `Toolbar`, `Pagination`, and `Spinner` components to provide search, filters, sorting, selection summary, bulk actions, pagination, and empty/loading/error states.
|
|
8
|
+
|
|
9
|
+
Figma references:
|
|
10
|
+
|
|
11
|
+
- `Table Example`, node `449:2354`
|
|
12
|
+
- `Table item 1.2`, node `438:41157`
|
|
13
|
+
|
|
14
|
+
## Structure
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
<Table
|
|
18
|
+
dir="ltr"
|
|
19
|
+
columns={columns}
|
|
20
|
+
rows={rows}
|
|
21
|
+
selectedRowIds={selectedRowIds}
|
|
22
|
+
onSelectedRowIdsChange={setSelectedRowIds}
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
<DataTable
|
|
26
|
+
title="Component Operations"
|
|
27
|
+
description="Track component readiness across design-system work."
|
|
28
|
+
columns={columns}
|
|
29
|
+
rows={rows}
|
|
30
|
+
selectedRowIds={selectedRowIds}
|
|
31
|
+
onSelectedRowIdsChange={setSelectedRowIds}
|
|
32
|
+
search={{ label: "Search projects", placeholder: "Search projects..." }}
|
|
33
|
+
filters={filters}
|
|
34
|
+
pagination={{ pageSize: 10 }}
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
<TableRoot aria-label="Compact audit data">
|
|
38
|
+
<TableHeader>
|
|
39
|
+
<TableRowElement>
|
|
40
|
+
<TableHead>Activity</TableHead>
|
|
41
|
+
</TableRowElement>
|
|
42
|
+
</TableHeader>
|
|
43
|
+
<TableBody>
|
|
44
|
+
<TableRowElement>
|
|
45
|
+
<TableCell>Member invited</TableCell>
|
|
46
|
+
</TableRowElement>
|
|
47
|
+
</TableBody>
|
|
48
|
+
</TableRoot>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The table is composed from these parts:
|
|
52
|
+
|
|
53
|
+
- `Table`: scroll container and semantic table/grid root.
|
|
54
|
+
- `TableHeader`: one header row with sortable header cells.
|
|
55
|
+
- `TableRow`: one data row with `default`, `selected`, and `disabled` states plus native hover styling.
|
|
56
|
+
- `TableCell`: the layout shell for any table item.
|
|
57
|
+
- `TableItem`: the content variant rendered inside a cell.
|
|
58
|
+
- `DataTable`: composed workflow shell for search, filters, actions, pagination, and table states.
|
|
59
|
+
- `DataTableSurface`: ARIA table surface for bespoke CSS-grid rows while keeping the table root owned by Banhaten.
|
|
60
|
+
- `TableRoot`, `TableHeader`, `TableBody`, `TableRowElement`, `TableHead`, and `TableCell`: owned semantic elements for product-specific read-only tables that do not fit the data-column API. They are unstyled unless `TableRoot` has `size="comfortable"`.
|
|
61
|
+
- `TableContainer`: the rounded, bordered frame for the owned elements.
|
|
62
|
+
- `TableCellText`: line + subline content for an owned `TableCell`, with an optional caption and leading icon or avatar.
|
|
63
|
+
- `TableCellLink`: a flush text action for a cell label that opens a record.
|
|
64
|
+
|
|
65
|
+
Use semantic `<table>` markup when the table is mostly read-only data. Use an ARIA grid only when the product needs cell-level keyboard navigation. Interactive content inside cells, such as checkboxes, toggles, and actions, must remain real controls.
|
|
66
|
+
|
|
67
|
+
## Layout
|
|
68
|
+
|
|
69
|
+
The Figma sample is `1024px` wide, but the implementation should be responsive. Fixed columns keep their design widths and the primary text column fills the remaining space.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const exampleColumns = [
|
|
73
|
+
{ id: "select", width: 42, kind: "selection" },
|
|
74
|
+
{ id: "project", header: "Project Name", width: "fill", sortable: true },
|
|
75
|
+
{ id: "leader", header: "Project Leader", width: 203, sortable: true },
|
|
76
|
+
{ id: "status", header: "Status", width: 147, sortable: true },
|
|
77
|
+
{ id: "progress", header: "Progress", width: 160, sortable: true },
|
|
78
|
+
{ id: "deadline", header: "Deadline", width: 155, sortable: true },
|
|
79
|
+
{ id: "actions", width: 44, kind: "actions" },
|
|
80
|
+
];
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
RTL tables reverse the visual column order and align cell content to the end. The selection cell moves to the far right, and the action cell moves to the far left. The row data order should not change. An `avatarText` name and caption each resolve their own content direction, so an opposing script truncates at its actual end without changing the avatar layout.
|
|
84
|
+
|
|
85
|
+
## Sizing
|
|
86
|
+
|
|
87
|
+
| Area | Size | LTR height | RTL height |
|
|
88
|
+
| --- | --- | ---: | ---: |
|
|
89
|
+
| Header row | `sm` | `44px` | `44px` |
|
|
90
|
+
| Body row | `sm` | `48px` | `44px` |
|
|
91
|
+
| Body row | `lg` | `56px` | `56px` |
|
|
92
|
+
| Header row | `comfortable` | `44px` | `44px` |
|
|
93
|
+
| Body row | `comfortable` | `64px` | `64px` |
|
|
94
|
+
|
|
95
|
+
`sm` rows use body-sm (14/20) text and `lg` rows use body-md (15/24), matching the `sm` and `lg` cells of `Table item 1.2`.
|
|
96
|
+
|
|
97
|
+
Cell padding is `12px` on the inline axis. The fixed `44px` action column keeps `8px` inline padding around its `28px` action button. Inline content gap is `6px`; header label and sort icon gap is `4px`. The base checkbox and action columns use fixed dimensions so hover, selected, and icon states do not shift the table.
|
|
98
|
+
|
|
99
|
+
### Comfortable size
|
|
100
|
+
|
|
101
|
+
`size="comfortable"` follows the `Table item 1.2` spec used in product frames:
|
|
102
|
+
|
|
103
|
+
- Header cells are `44px` tall with `8px` inline padding, body-sm medium (14/20) labels in `content/subtle`, on the `interactive/table/header` fill.
|
|
104
|
+
- Body cells are `64px` tall with `8px` inline padding and body-md (15/24) text in `content/default`.
|
|
105
|
+
- Rows have no dividers.
|
|
106
|
+
- The frame has a `12px` radius.
|
|
107
|
+
|
|
108
|
+
Items inside a comfortable table use the `lg` item sizes. Use `Badge size="lg"` for status badges in comfortable rows.
|
|
109
|
+
|
|
110
|
+
The owned table elements take the same spec. Set `size="comfortable"` on `TableRoot` and wrap it in `TableContainer`, which draws the `12px` frame with a `1px` subtle border inside its edge and scrolls the table horizontally. Without a size, `TableRoot` and its cells stay unstyled.
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
<TableContainer>
|
|
114
|
+
<TableRoot aria-label="Requisitions" size="comfortable">
|
|
115
|
+
<TableHeader>
|
|
116
|
+
<TableRowElement>
|
|
117
|
+
<TableHead>Request</TableHead>
|
|
118
|
+
<TableHead>Approval</TableHead>
|
|
119
|
+
</TableRowElement>
|
|
120
|
+
</TableHeader>
|
|
121
|
+
<TableBody>
|
|
122
|
+
<TableRowElement>
|
|
123
|
+
<TableCell>Senior Product Designer</TableCell>
|
|
124
|
+
<TableCell><Badge color="amber" size="lg">Your approval</Badge></TableCell>
|
|
125
|
+
</TableRowElement>
|
|
126
|
+
</TableBody>
|
|
127
|
+
</TableRoot>
|
|
128
|
+
</TableContainer>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Line + subline cells
|
|
132
|
+
|
|
133
|
+
`Table item 1.2` gives its `lg` text cells an optional caption (`hasCaption`) under the label. This covers base leading, base regular, subtle, avatar + text, brand logo + text, payment and image cells.
|
|
134
|
+
|
|
135
|
+
- The label keeps the row's text size: body-md (15/24) in `lg` and `comfortable` rows.
|
|
136
|
+
- The caption is body-2xs (12/16) in `content/subtle`.
|
|
137
|
+
- There is no gap between the two lines, so the pair fills `40px`. That leaves `8px` above and below in a `56px` `lg` row, and `12px` in a `64px` comfortable row.
|
|
138
|
+
- A leading icon or avatar sits `6px` before the text, and the pair centres on it.
|
|
139
|
+
- `sm` cells in Figma are single-line.
|
|
140
|
+
|
|
141
|
+
In `Table`, set `caption` on a `text`, `avatarText`, `brandLogoText`, `payment` or `image` item. DataTable search also matches captions, while sorting uses the label.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
{ id: "request", header: "Request", width: "fill", item: (row) => ({ type: "text", weight: "medium", value: row.title, caption: row.id }) }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
With the owned elements, put `TableCellText` inside `TableCell`. Its children are the label. Set `weight="medium"` for a leading label and `tone="subtle"` for secondary text. When the label opens the record, make it a `TableCellLink`, not a link `Button`. A Button brings its control padding, which moves the label off the caption's start edge and adds space above the pair. `TableCellLink` keeps the cell's text style and colour and sits flush with the cell padding. It underlines on hover and focus and draws the focus ring 2px outside the text. It renders a button by default; with `asChild` it styles an anchor or router link.
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
<TableCell>
|
|
151
|
+
<TableCellText caption="REQ-1042 · Product">
|
|
152
|
+
<TableCellLink onClick={() => openRequest("1042")}>Senior Product Designer</TableCellLink>
|
|
153
|
+
</TableCellText>
|
|
154
|
+
</TableCell>
|
|
155
|
+
|
|
156
|
+
<TableCellText caption="REQ-1038 · Platform">
|
|
157
|
+
<TableCellLink asChild>
|
|
158
|
+
<a href="#/requisitions/1038">Frontend Engineer</a>
|
|
159
|
+
</TableCellLink>
|
|
160
|
+
</TableCellText>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## States
|
|
164
|
+
|
|
165
|
+
| State | Treatment |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| `default` | Default background with a subtle bottom divider. |
|
|
168
|
+
| Native hover | Ghost hover background across the full row. |
|
|
169
|
+
| `selected` | Selected row background and checked selection control. |
|
|
170
|
+
| `disabled` | Disabled content color and no row actions. |
|
|
171
|
+
|
|
172
|
+
The selected checkbox uses the brand or checkbox fill and a mode-aware contrasting check icon. Header checkbox supports checked, unchecked, and indeterminate states.
|
|
173
|
+
|
|
174
|
+
## Token Mapping
|
|
175
|
+
|
|
176
|
+
The Figma nodes use names such as `interactive/table/header`, `interactive/ghost/hover`, and `bg/neutral/soft`. In this documentation set, map those to the semantic token contract:
|
|
177
|
+
|
|
178
|
+
| Figma intent | Design-system token |
|
|
179
|
+
| --- | --- |
|
|
180
|
+
| Table header background | `background.muted` |
|
|
181
|
+
| Row hover background | `background.hover` |
|
|
182
|
+
| Row selected background | `background.selected` |
|
|
183
|
+
| Row divider | `border.subtle` |
|
|
184
|
+
| Body text | `content.default` |
|
|
185
|
+
| Header text and secondary labels | `content.subtle` |
|
|
186
|
+
| Checkbox active fill | `background.brand` |
|
|
187
|
+
| Progress track | `background.muted` |
|
|
188
|
+
| Progress fill | `background.success` or component success fill |
|
|
189
|
+
| Row height | `density.tableRowHeight` |
|
|
190
|
+
| Control radius | `shape.control` |
|
|
191
|
+
| Avatar, badge, and progress radius | `shape.badge` |
|
|
192
|
+
|
|
193
|
+
Do not hard-code preset-specific values in the table. Appearance presets may change density, radius, borders, and hover treatment while preserving the same component API.
|
|
194
|
+
|
|
195
|
+
## Column API
|
|
196
|
+
|
|
197
|
+
Use semantic names in code even if the Figma variant labels are title-cased.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
type TableDirection = "ltr" | "rtl";
|
|
201
|
+
type TableSize = "sm" | "lg";
|
|
202
|
+
type TableRowState = "default" | "selected" | "disabled";
|
|
203
|
+
|
|
204
|
+
type TableColumn<Row> = {
|
|
205
|
+
id: string;
|
|
206
|
+
header?: string;
|
|
207
|
+
width?: number | "fill";
|
|
208
|
+
minWidth?: number;
|
|
209
|
+
align?: "start" | "center" | "end";
|
|
210
|
+
sortable?: boolean;
|
|
211
|
+
sortValue?: (row: Row) => TableSortValue;
|
|
212
|
+
searchValue?: (row: Row) => string;
|
|
213
|
+
kind?: "data" | "selection" | "actions";
|
|
214
|
+
item?: TableItemFactory<Row>;
|
|
215
|
+
renderCell?: (row: Row) => React.ReactNode;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
type TableItemFactory<Row> = (row: Row) => TableItem;
|
|
219
|
+
type TableSortValue = string | number | boolean | Date | null | undefined;
|
|
220
|
+
type TableSortState = { columnId: string; direction: "asc" | "desc" } | null;
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Use `item` for standard content variants and `renderCell` only for custom product-specific cells. Custom cells still render inside `TableCell`, so spacing, alignment, row height, truncation, RTL behavior, and disabled state stay consistent.
|
|
224
|
+
|
|
225
|
+
Cell content resolves in this order: `renderCell`, `item`, then an own row field whose key matches the column `id`. The implicit row-field fallback applies only to ordinary data columns and renders string or number values—including `0`—through the standard text item. Use `item` or `renderCell` for formatted values, booleans, dates, objects, and custom nodes. Selection and action columns never read an implicit row value; return `null` from `renderCell` when a deliberately blank data cell is required.
|
|
226
|
+
|
|
227
|
+
Use `sortValue` when the visible cell content should sort by an underlying value, such as dates, percentages, status rank, or formatted numbers. Use `searchValue` when the cell should contribute searchable text that is different from the visible content.
|
|
228
|
+
|
|
229
|
+
## Table Items
|
|
230
|
+
|
|
231
|
+
The item catalog in Figma supports these cell content variants. Code may normalize visual variants into semantic item types; for example, `baseLeading`, `baseRegular`, and `subtle` all map to the `text` item with different `weight` and `tone` values.
|
|
232
|
+
|
|
233
|
+
| Figma item type | Use for | Notes |
|
|
234
|
+
| --- | --- | --- |
|
|
235
|
+
| `checkbox` | Row selection | Fixed selection column. |
|
|
236
|
+
| `baseLeading` | Primary label | Medium body label; `lg` may include caption. |
|
|
237
|
+
| `baseRegular` | Standard body text | Regular body label; `lg` may include caption. |
|
|
238
|
+
| `subtle` | Secondary text | Uses subtle content color; `lg` may include caption. |
|
|
239
|
+
| `avatarText` | Person, owner, assignee | Avatar plus medium label; `lg` may include caption. Name and caption resolve direction independently. |
|
|
240
|
+
| `rating` | Star rating | Five 20px stars with full, half, and empty states. |
|
|
241
|
+
| `progress` | Percent completion | 8px track, percent label, mirrored fill in RTL. |
|
|
242
|
+
| `toggle` | Binary row setting | 32px switch in `sm`; preserve native button semantics. |
|
|
243
|
+
| `badges` | Status and categorical chips | Supports multiple badges and overflow count. |
|
|
244
|
+
| `onlyAvatar` | Compact person cell | Single avatar centered or aligned by column. |
|
|
245
|
+
| `avatarStack` | Team or group membership | Overlapped 20px avatars, optional count and add button. |
|
|
246
|
+
| `tags` | Neutral labels | Bordered tags with compact 24px height. |
|
|
247
|
+
| `file` | Attachment indicator | Icon-only file cell. |
|
|
248
|
+
| `brandLogoText` | Brand or account identity | Logo plus medium label; `lg` may include caption. |
|
|
249
|
+
| `payment` | Payment method | Payment icon plus optional medium label; `lg` may include caption. |
|
|
250
|
+
| `image` | Thumbnail item | Image thumbnail plus medium label; `lg` may include caption. |
|
|
251
|
+
| `action` | Single row menu | Icon button, usually overflow menu. |
|
|
252
|
+
| `actionGroup` | Multiple row actions | View, edit, delete group; reverse order in RTL. |
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
type TableItem =
|
|
256
|
+
| { type: "checkbox"; checked: boolean; indeterminate?: boolean }
|
|
257
|
+
| { type: "text"; tone?: "default" | "subtle"; weight?: "regular" | "medium"; value: string; caption?: string }
|
|
258
|
+
| { type: "avatarText"; name: string; caption?: string; src?: string; status?: "top" | "bottom" }
|
|
259
|
+
| { type: "rating"; value: number; max?: 5 }
|
|
260
|
+
| { type: "progress"; value: number; label?: string; tone?: "success" | "brand" }
|
|
261
|
+
| { type: "toggle"; checked: boolean; disabled?: boolean }
|
|
262
|
+
| { type: "badges"; items: BadgeItem[]; maxVisible?: number }
|
|
263
|
+
| { type: "onlyAvatar"; src?: string; alt: string }
|
|
264
|
+
| { type: "avatarStack"; avatars: AvatarItem[]; count?: number; canAdd?: boolean }
|
|
265
|
+
| { type: "tags"; items: string[]; maxVisible?: number }
|
|
266
|
+
| { type: "file"; label?: string }
|
|
267
|
+
| { type: "brandLogoText"; label: string; caption?: string; logoSrc?: string }
|
|
268
|
+
| { type: "payment"; brand: "maestro" | "mastercard" | "visa" | string; label?: string; caption?: string }
|
|
269
|
+
| { type: "image"; label: string; caption?: string; src: string }
|
|
270
|
+
| { type: "action"; label: string; icon: "more"; onAction: () => void }
|
|
271
|
+
| { type: "actionGroup"; actions: TableAction[] };
|
|
272
|
+
|
|
273
|
+
type BadgeItem = {
|
|
274
|
+
label: string;
|
|
275
|
+
tone?: "blue" | "fuchsia" | "amber" | "neutral" | "success" | "warning" | "danger";
|
|
276
|
+
dot?: boolean;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
type AvatarItem = {
|
|
280
|
+
src?: string;
|
|
281
|
+
alt: string;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
type TableAction = {
|
|
285
|
+
label: string;
|
|
286
|
+
icon: "view" | "edit" | "delete" | "more";
|
|
287
|
+
onAction: () => void;
|
|
288
|
+
};
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The item union intentionally describes content, not layout. `TableCell` decides padding, alignment, truncation, and RTL mirroring.
|
|
292
|
+
|
|
293
|
+
## Adding Items
|
|
294
|
+
|
|
295
|
+
Add a new table item by defining it on a column. The row supplies content values; the column controls presentation.
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
const columns: TableColumn<Project>[] = [
|
|
299
|
+
{
|
|
300
|
+
id: "project",
|
|
301
|
+
header: "Project Name",
|
|
302
|
+
width: "fill",
|
|
303
|
+
sortable: true,
|
|
304
|
+
item: (project) => ({
|
|
305
|
+
type: "text",
|
|
306
|
+
value: project.name,
|
|
307
|
+
weight: "regular",
|
|
308
|
+
}),
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: "leader",
|
|
312
|
+
header: "Project Leader",
|
|
313
|
+
width: 203,
|
|
314
|
+
sortable: true,
|
|
315
|
+
item: (project) => ({
|
|
316
|
+
type: "avatarText",
|
|
317
|
+
name: project.leader.name,
|
|
318
|
+
src: project.leader.avatarUrl,
|
|
319
|
+
}),
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "status",
|
|
323
|
+
header: "Status",
|
|
324
|
+
width: 147,
|
|
325
|
+
sortable: true,
|
|
326
|
+
item: (project) => ({
|
|
327
|
+
type: "badges",
|
|
328
|
+
items: [{ label: project.status, tone: "blue", dot: true }],
|
|
329
|
+
}),
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: "progress",
|
|
333
|
+
header: "Progress",
|
|
334
|
+
width: 160,
|
|
335
|
+
sortable: true,
|
|
336
|
+
item: (project) => ({
|
|
337
|
+
type: "progress",
|
|
338
|
+
value: project.progress,
|
|
339
|
+
label: `${project.progress}%`,
|
|
340
|
+
tone: "success",
|
|
341
|
+
}),
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
id: "deadline",
|
|
345
|
+
header: "Deadline",
|
|
346
|
+
width: 155,
|
|
347
|
+
sortable: true,
|
|
348
|
+
item: (project) => ({
|
|
349
|
+
type: "text",
|
|
350
|
+
value: project.deadlineLabel,
|
|
351
|
+
}),
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
id: "actions",
|
|
355
|
+
width: 44,
|
|
356
|
+
kind: "actions",
|
|
357
|
+
item: (project) => ({
|
|
358
|
+
type: "action",
|
|
359
|
+
label: `Open actions for ${project.name}`,
|
|
360
|
+
icon: "more",
|
|
361
|
+
onAction: () => openProjectActions(project.id),
|
|
362
|
+
}),
|
|
363
|
+
},
|
|
364
|
+
];
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
For a custom item, first check whether the content can be represented by the existing union. Add a new item type only when the content appears in more than one table or needs shared behavior such as keyboard handling, truncation, or overflow.
|
|
368
|
+
|
|
369
|
+
## Header Behavior
|
|
370
|
+
|
|
371
|
+
Sortable headers render the label plus the expand-up-down icon. When sorted, the header cell sets `aria-sort` to `ascending` or `descending`; otherwise it uses `none`. Header controls must have a clear label such as `Sort by Project Name`.
|
|
372
|
+
|
|
373
|
+
`Table` accepts `sort` and `onSortChange` when the host owns sorting. `DataTable` can own sorting internally with `defaultSort`, or operate as a controlled component with `sort` and `onSortChange`.
|
|
374
|
+
|
|
375
|
+
The select-all checkbox announces:
|
|
376
|
+
|
|
377
|
+
- unchecked when no visible rows are selected;
|
|
378
|
+
- checked when all visible rows are selected;
|
|
379
|
+
- indeterminate when some visible rows are selected.
|
|
380
|
+
|
|
381
|
+
## Data Table
|
|
382
|
+
|
|
383
|
+
Use `DataTable` when the product needs the complete dataset browsing workflow instead of only rows and columns.
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
const filters = [
|
|
387
|
+
{
|
|
388
|
+
id: "review",
|
|
389
|
+
label: "Status",
|
|
390
|
+
value: "Review",
|
|
391
|
+
active: statusFilter === "Review",
|
|
392
|
+
onAction: () => setStatusFilter("Review"),
|
|
393
|
+
predicate: (row) => row.status === "Review",
|
|
394
|
+
},
|
|
395
|
+
];
|
|
396
|
+
|
|
397
|
+
<DataTable
|
|
398
|
+
actions={[{ label: "Export", variant: "soft" }]}
|
|
399
|
+
bulkActions={[{ label: "Archive", variant: "outline" }]}
|
|
400
|
+
columns={columns}
|
|
401
|
+
defaultSort={{ columnId: "project", direction: "asc" }}
|
|
402
|
+
emptyState={{
|
|
403
|
+
title: "No matching projects",
|
|
404
|
+
description: "Try another search or return to all statuses.",
|
|
405
|
+
}}
|
|
406
|
+
filters={filters}
|
|
407
|
+
pagination={{ pageSize: 10 }}
|
|
408
|
+
rows={rows}
|
|
409
|
+
search={{ label: "Search projects", placeholder: "Search projects..." }}
|
|
410
|
+
/>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
`DataTable` applies active filter predicates, searches row text from `getRowSearchText`, column `searchValue`, or table item text, sorts by column `sortValue` or table item value, and paginates the resulting rows. Set `pagination={false}` for unpaginated datasets. When `pagination.showCaption` is enabled, the Pagination caption receives the same measured visible range and total as the DataTable footer summary; the standalone Pagination demo default is never used for table data.
|
|
414
|
+
|
|
415
|
+
Direction does not choose language. Pass `pagination.messages` to localize Pagination controls and accessible names, and pass `labels.resultsSummary` to format the measured DataTable range and total. An explicit `pagination.ariaLabel` overrides the message catalog's navigation label.
|
|
416
|
+
|
|
417
|
+
## Accessibility
|
|
418
|
+
|
|
419
|
+
- Preserve native controls for checkbox, toggle, buttons, and menu triggers.
|
|
420
|
+
- Use `aria-selected` on selected rows.
|
|
421
|
+
- Provide labels for icon-only actions.
|
|
422
|
+
- Keep focus outlines visible through `border.focus`.
|
|
423
|
+
- Truncate long text visually, but keep the full value available through `title` or an accessible label when useful.
|
|
424
|
+
- Do not put multiple unrelated buttons into one unlabeled cell; group row actions with a clear accessible name.
|
|
425
|
+
|
|
426
|
+
## Usage
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
<Table
|
|
430
|
+
dir="ltr"
|
|
431
|
+
columns={columns}
|
|
432
|
+
rows={projects}
|
|
433
|
+
selectedRowIds={selectedRowIds}
|
|
434
|
+
onSelectedRowIdsChange={setSelectedRowIds}
|
|
435
|
+
/>
|
|
436
|
+
|
|
437
|
+
<Table
|
|
438
|
+
dir="rtl"
|
|
439
|
+
columns={columns}
|
|
440
|
+
rows={projects}
|
|
441
|
+
selectedRowIds={selectedRowIds}
|
|
442
|
+
onSelectedRowIdsChange={setSelectedRowIds}
|
|
443
|
+
/>
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Preset Impact
|
|
447
|
+
|
|
448
|
+
- `default`: normal row density, quiet header background, modest control radius.
|
|
449
|
+
- `rounded`: slightly taller rows, softer hover treatment, larger badges and control radius.
|
|
450
|
+
- `sharp`: denser rows, stronger dividers, square or near-square controls.
|
|
451
|
+
|
|
452
|
+
## Implementation Notes
|
|
453
|
+
|
|
454
|
+
- Use Remix icons for Figma icon matches: `expand-up-down-line`, `more-fill`, `check-fill`, `eye-line`, `pencil-line`, and `delete-bin-line`.
|
|
455
|
+
- Progress bars are `8px` high and pill-shaped. In RTL, the filled segment grows from the right.
|
|
456
|
+
- Badges are `24px` tall, pill-shaped, and use `11px / 14px` medium text.
|
|
457
|
+
- Tags are `24px` tall with a 1px border and `13px / 20px` medium text.
|
|
458
|
+
- Avatar-with-text cells use the shared 24px avatar in `sm` rows. Avatar-only and avatar-stack cells use 20px avatars. All three use 32px avatars in `lg` rows.
|
|
459
|
+
- Action buttons are fixed `28px` in `sm` rows and `32px` in `lg` rows.
|
|
460
|
+
|
|
461
|
+
## Install
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
npx banhaten add table
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## API
|
|
468
|
+
|
|
469
|
+
Supported runtime exports: `DataTable`, `Table`, `DataTableSurface`, `TableBody`, `TableCell`, `TableCellLink`, `TableCellText`, `TableContainer`, `TableHead`, `TableHeader`, `TableRoot`, `TableRowElement`.
|
|
470
|
+
|
|
471
|
+
Use the live registry docs for the generated API table and source-backed examples.
|
|
472
|
+
|
|
473
|
+
## Examples
|
|
474
|
+
|
|
475
|
+
Primary registry example: `examples/expanded/table-demo.tsx`.
|
|
476
|
+
|
|
477
|
+
Open the live component page or run `npx banhaten docs table` to inspect source files, install command, and examples.
|
|
478
|
+
|
|
479
|
+
## Token Contract
|
|
480
|
+
|
|
481
|
+
Component styles must resolve through Banhaten semantic tokens, component aliases, or documented exception-ledger values.
|
|
482
|
+
Do not add raw colors, pixel values, opacity utilities, z-index values, durations, or shadow literals directly to component source.
|
|
483
|
+
Covered source files: `components/expanded/table.css`, `components/expanded/Table.tsx`.
|
|
484
|
+
|
|
485
|
+
## RTL Rules
|
|
486
|
+
|
|
487
|
+
The table layout inherits document direction and uses logical spacing. Within `avatarText`, the independently truncated name and caption use `dir="auto"`; this preserves the identifying start of Latin content in an RTL table (and Arabic content in an LTR table) while the avatar and columns continue to mirror with the table.
|
|
488
|
+
|
|
418
489
|
<!-- registry-contract:start -->
|
|
419
490
|
## Registry Contract
|
|
420
491
|
|
|
@@ -423,12 +494,18 @@ This table is generated from `registry/index.json`; edit the component-owned sou
|
|
|
423
494
|
|
|
424
495
|
| Field | Values |
|
|
425
496
|
| --- | --- |
|
|
426
|
-
| `supportedExports` | `components/expanded/Table.tsx: DataTable, Table`, `components/table-elements.tsx: DataTableSurface, TableBody, TableCell, TableHead, TableHeader, TableRoot, TableRowElement` |
|
|
427
|
-
| `parts` | `Table`, `DataTable`, `DataTableSurface`, `TableRoot`, `TableHeader`, `TableBody`, `TableRowElement`, `TableHead`, `TableCell` |
|
|
497
|
+
| `supportedExports` | `components/expanded/Table.tsx: DataTable, Table`, `components/table-elements.tsx: DataTableSurface, TableBody, TableCell, TableCellLink, TableCellText, TableContainer, TableHead, TableHeader, TableRoot, TableRowElement` |
|
|
498
|
+
| `parts` | `Table`, `DataTable`, `DataTableSurface`, `TableContainer`, `TableRoot`, `TableHeader`, `TableBody`, `TableRowElement`, `TableHead`, `TableCell`, `TableCellText`, `TableCellLink` |
|
|
428
499
|
| `registryDependencies` | `avatar`, `badge`, `button`, `checkbox`, `pagination`, `progress`, `spinner`, `tag`, `toggle`, `toolbar` |
|
|
429
|
-
| `
|
|
500
|
+
| `props.Table.size` | `"sm" \| "lg" \| "comfortable"; default "sm"; sm and lg are 48px and 56px rows; sm uses body-sm text and lg uses body-md, as in the Table item 1.2 sm and lg cells. comfortable follows Table item 1.2: 44px headers in body-sm medium on the table-header fill, 64px rows with 8px cells and body-md text, no row rules, and a 12px radius.` |
|
|
501
|
+
| `props.TableItem.caption` | `string; default null; Second line under the label on text, avatarText, brandLogoText, payment and image items (Table item 1.2 hasCaption): body-2xs in content/subtle with no gap. Use it in lg or comfortable rows; the two lines fill 40px of a 56px row. Search matches captions; sorting uses the label.` |
|
|
502
|
+
| `props.TableRoot.size` | `"comfortable"; default null; Applies the comfortable spec to TableHead and TableCell. Without a size the owned elements stay unstyled.` |
|
|
503
|
+
| `props.TableContainer` | `div; default null; Frame for the owned elements: 12px radius, default background, and a 1px subtle border drawn inside the edge. The table scrolls horizontally inside it.` |
|
|
504
|
+
| `props.TableCellText` | `span; default null; Text content for an owned TableCell: children are the label, caption adds the second line, leading adds an icon or avatar 6px before the text, and tone (default \| subtle) and weight (regular \| medium) set the label. The label keeps the cell text size; use TableCellLink when it opens a record.` |
|
|
505
|
+
| `props.TableCellLink` | `button; default null; A cell label that opens a record: it keeps the cell text style and colour, sits flush with the cell padding, underlines on hover and focus, and shows the focus ring 2px outside the text. Renders a button, or its child (such as an anchor) with asChild. Use it as the TableCellText label instead of a padded Button.` |
|
|
506
|
+
| `features` | `measured DataTable pagination captions`, `explicit DataTable pagination message catalogs`, `safe row-key cell fallback`, `comfortable size with 44px headers, 64px rows, 8px cells, body-md text, and no row rules`, `TableContainer frame with a 12px radius and an inside border`, `comfortable size on the owned table elements through TableRoot size`, `line + subline cells: an optional caption under text, avatarText, brandLogoText, payment and image items, and TableCellText for the owned elements`, `TableCellLink for flush record links in owned cells` |
|
|
430
507
|
| `modes` | `table`, `data-table` |
|
|
431
|
-
| `sizes` | `sm`, `lg` |
|
|
508
|
+
| `sizes` | `sm`, `lg`, `comfortable` |
|
|
432
509
|
| `states` | `default`, `selected`, `disabled`, `loading`, `empty`, `error` |
|
|
433
|
-
| `tokens` | `--bh-avatar-fallback-font-size-md`, `--bh-avatar-fallback-font-size-xs`, `--bh-avatar-size-md`, `--bh-avatar-size-xs`, `--bh-bg-accent-amber-subtle`, `--bh-bg-accent-fuchsia-subtle`, `--bh-bg-accent-golden-default`, `--bh-bg-brand-default`, `--bh-bg-brand-soft`, `--bh-bg-danger-subtle`, `--bh-bg-default`, `--bh-bg-muted`, `--bh-bg-neutral-medium`, `--bh-bg-neutral-soft`, `--bh-bg-neutral-subtle`, `--bh-bg-raised`, `--bh-bg-success-default`, `--bh-bg-success-subtle`, `--bh-bg-warning-default`, `--bh-bg-warning-subtle`, `--bh-border-brand-strong`, `--bh-border-default`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-content-accent-amber-strong`, `--bh-content-accent-fuchsia-strong`, `--bh-content-brand-default`, `--bh-content-danger-strong`, `--bh-content-default`, `--bh-content-muted`, `--bh-content-on-brand`, `--bh-content-subtle`, `--bh-content-success-default`, `--bh-content-warning-strong`, `--bh-control-default`, `--bh-duration-fast`, `--bh-easing-standard`, `--bh-focus-ring-width`, `--bh-font-family`, `--bh-font-weight-semibold`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-190`, `--bh-icon-stroke-225`, `--bh-input-width`, `--bh-interactive-ghost-hover`, `--bh-opacity-0`, `--bh-opacity-100`, `--bh-opacity-muted`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-radius-md-6`, `--bh-radius-sm-4`, `--bh-space-12xl-128`, `--bh-space-16xl-256`, `--bh-space-19xl-384`, `--bh-space-2xl-14`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-7xl-40`, `--bh-space-8xl-48`, `--bh-space-md-8`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-table-min-width`, `--bh-table-selection-column-min-width`, `--bh-text-base-letter-spacing`, `--bh-text-body-2xs-regular-font-size`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-3xs-regular-font-size`, `--bh-text-body-3xs-regular-line-height`, `--bh-text-body-lg-semibold-font-size`, `--bh-text-body-lg-semibold-font-weight`, `--bh-text-body-lg-semibold-line-height`, `--bh-text-body-md-semibold-font-size`, `--bh-text-body-md-semibold-font-weight`, `--bh-text-body-md-semibold-line-height`, `--bh-text-body-sm-medium-font-weight`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-line-height`, `--bh-text-body-xs-regular-font-size`, `--elevation-component-focus-ring` |
|
|
510
|
+
| `tokens` | `--bh-avatar-fallback-font-size-md`, `--bh-avatar-fallback-font-size-xs`, `--bh-avatar-size-md`, `--bh-avatar-size-xs`, `--bh-bg-accent-amber-subtle`, `--bh-bg-accent-fuchsia-subtle`, `--bh-bg-accent-golden-default`, `--bh-bg-brand-default`, `--bh-bg-brand-soft`, `--bh-bg-danger-subtle`, `--bh-bg-default`, `--bh-bg-muted`, `--bh-bg-neutral-medium`, `--bh-bg-neutral-soft`, `--bh-bg-neutral-subtle`, `--bh-bg-raised`, `--bh-bg-success-default`, `--bh-bg-success-subtle`, `--bh-bg-warning-default`, `--bh-bg-warning-subtle`, `--bh-border-brand-strong`, `--bh-border-default`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-content-accent-amber-strong`, `--bh-content-accent-fuchsia-strong`, `--bh-content-brand-default`, `--bh-content-danger-strong`, `--bh-content-default`, `--bh-content-muted`, `--bh-content-on-brand`, `--bh-content-subtle`, `--bh-content-success-default`, `--bh-content-warning-strong`, `--bh-control-default`, `--bh-duration-fast`, `--bh-easing-standard`, `--bh-focus-ring-width`, `--bh-font-family`, `--bh-font-weight-medium`, `--bh-font-weight-regular`, `--bh-font-weight-semibold`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-190`, `--bh-icon-stroke-225`, `--bh-input-width`, `--bh-interactive-ghost-hover`, `--bh-opacity-0`, `--bh-opacity-100`, `--bh-opacity-muted`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-radius-md-6`, `--bh-radius-sm-4`, `--bh-space-12xl-128`, `--bh-space-16xl-256`, `--bh-space-19xl-384`, `--bh-space-2xl-14`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-7xl-40`, `--bh-space-8xl-48`, `--bh-space-md-8`, `--bh-space-none`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-table-caption-content`, `--bh-table-cell-text-gap`, `--bh-table-comfortable-cell-content`, `--bh-table-comfortable-cell-padding-x`, `--bh-table-comfortable-header-bg`, `--bh-table-comfortable-header-content`, `--bh-table-comfortable-header-height`, `--bh-table-comfortable-min-width`, `--bh-table-comfortable-radius`, `--bh-table-comfortable-row-height`, `--bh-table-container-bg`, `--bh-table-container-border`, `--bh-table-container-radius`, `--bh-table-min-width`, `--bh-table-selection-column-min-width`, `--bh-text-base-letter-spacing`, `--bh-text-body-2xs-regular-font-size`, `--bh-text-body-2xs-regular-font-weight`, `--bh-text-body-2xs-regular-letter-spacing`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-3xs-regular-font-size`, `--bh-text-body-3xs-regular-line-height`, `--bh-text-body-lg-semibold-font-size`, `--bh-text-body-lg-semibold-font-weight`, `--bh-text-body-lg-semibold-line-height`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-font-weight`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-body-md-semibold-font-size`, `--bh-text-body-md-semibold-font-weight`, `--bh-text-body-md-semibold-line-height`, `--bh-text-body-sm-medium-font-size`, `--bh-text-body-sm-medium-font-weight`, `--bh-text-body-sm-medium-letter-spacing`, `--bh-text-body-sm-medium-line-height`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-line-height`, `--bh-text-body-xs-regular-font-size`, `--elevation-component-focus-ring` |
|
|
434
511
|
<!-- registry-contract:end -->
|