@fragments-sdk/ui 0.9.4 → 0.9.6

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.
Files changed (126) hide show
  1. package/dist/assets/ui.css +443 -247
  2. package/dist/blocks/components/index.d.ts +0 -2
  3. package/dist/blocks/components/index.d.ts.map +1 -1
  4. package/dist/codeblock.cjs +187 -184
  5. package/dist/codeblock.cjs.map +1 -1
  6. package/dist/codeblock.js +183 -180
  7. package/dist/codeblock.js.map +1 -1
  8. package/dist/components/Box/Box.module.scss.cjs +73 -0
  9. package/dist/components/Box/Box.module.scss.cjs.map +1 -1
  10. package/dist/components/Box/Box.module.scss.js +73 -0
  11. package/dist/components/Box/Box.module.scss.js.map +1 -1
  12. package/dist/components/ButtonGroup/ButtonGroup.module.scss.cjs +6 -0
  13. package/dist/components/ButtonGroup/ButtonGroup.module.scss.cjs.map +1 -1
  14. package/dist/components/ButtonGroup/ButtonGroup.module.scss.js +6 -0
  15. package/dist/components/ButtonGroup/ButtonGroup.module.scss.js.map +1 -1
  16. package/dist/components/CodeBlock/CodeBlock.module.scss.cjs +20 -23
  17. package/dist/components/CodeBlock/CodeBlock.module.scss.cjs.map +1 -1
  18. package/dist/components/CodeBlock/CodeBlock.module.scss.js +20 -23
  19. package/dist/components/CodeBlock/CodeBlock.module.scss.js.map +1 -1
  20. package/dist/components/CodeBlock/index.d.ts +11 -7
  21. package/dist/components/CodeBlock/index.d.ts.map +1 -1
  22. package/dist/components/Combobox/Combobox.module.scss.cjs +15 -15
  23. package/dist/components/Combobox/Combobox.module.scss.js +15 -15
  24. package/dist/components/DataTable/DataTable.module.scss.cjs +84 -0
  25. package/dist/components/DataTable/DataTable.module.scss.cjs.map +1 -0
  26. package/dist/components/DataTable/DataTable.module.scss.js +84 -0
  27. package/dist/components/DataTable/DataTable.module.scss.js.map +1 -0
  28. package/dist/components/DataTable/index.cjs +383 -0
  29. package/dist/components/DataTable/index.cjs.map +1 -0
  30. package/dist/components/DataTable/index.d.ts +78 -0
  31. package/dist/components/DataTable/index.d.ts.map +1 -0
  32. package/dist/components/DataTable/index.js +366 -0
  33. package/dist/components/DataTable/index.js.map +1 -0
  34. package/dist/components/Drawer/Drawer.module.scss.cjs +9 -0
  35. package/dist/components/Drawer/Drawer.module.scss.cjs.map +1 -1
  36. package/dist/components/Drawer/Drawer.module.scss.js +9 -0
  37. package/dist/components/Drawer/Drawer.module.scss.js.map +1 -1
  38. package/dist/components/Image/Image.module.scss.cjs +12 -0
  39. package/dist/components/Image/Image.module.scss.cjs.map +1 -1
  40. package/dist/components/Image/Image.module.scss.js +12 -0
  41. package/dist/components/Image/Image.module.scss.js.map +1 -1
  42. package/dist/components/Link/Link.module.scss.cjs +3 -0
  43. package/dist/components/Link/Link.module.scss.cjs.map +1 -1
  44. package/dist/components/Link/Link.module.scss.js +3 -0
  45. package/dist/components/Link/Link.module.scss.js.map +1 -1
  46. package/dist/components/List/List.module.scss.cjs +5 -0
  47. package/dist/components/List/List.module.scss.cjs.map +1 -1
  48. package/dist/components/List/List.module.scss.js +5 -0
  49. package/dist/components/List/List.module.scss.js.map +1 -1
  50. package/dist/components/Loading/Loading.module.scss.cjs +5 -0
  51. package/dist/components/Loading/Loading.module.scss.cjs.map +1 -1
  52. package/dist/components/Loading/Loading.module.scss.js +5 -0
  53. package/dist/components/Loading/Loading.module.scss.js.map +1 -1
  54. package/dist/components/Markdown/Markdown.module.scss.cjs +1 -1
  55. package/dist/components/Markdown/Markdown.module.scss.js +1 -1
  56. package/dist/components/Message/Message.module.scss.cjs +22 -16
  57. package/dist/components/Message/Message.module.scss.cjs.map +1 -1
  58. package/dist/components/Message/Message.module.scss.js +22 -16
  59. package/dist/components/Message/Message.module.scss.js.map +1 -1
  60. package/dist/components/Message/index.cjs +5 -3
  61. package/dist/components/Message/index.cjs.map +1 -1
  62. package/dist/components/Message/index.d.ts +5 -1
  63. package/dist/components/Message/index.d.ts.map +1 -1
  64. package/dist/components/Message/index.js +5 -3
  65. package/dist/components/Message/index.js.map +1 -1
  66. package/dist/components/Skeleton/Skeleton.module.scss.cjs +14 -0
  67. package/dist/components/Skeleton/Skeleton.module.scss.cjs.map +1 -1
  68. package/dist/components/Skeleton/Skeleton.module.scss.js +14 -0
  69. package/dist/components/Skeleton/Skeleton.module.scss.js.map +1 -1
  70. package/dist/components/Stack/Stack.module.scss.cjs +14 -0
  71. package/dist/components/Stack/Stack.module.scss.cjs.map +1 -1
  72. package/dist/components/Stack/Stack.module.scss.js +14 -0
  73. package/dist/components/Stack/Stack.module.scss.js.map +1 -1
  74. package/dist/components/Table/Table.module.scss.cjs +21 -36
  75. package/dist/components/Table/Table.module.scss.cjs.map +1 -1
  76. package/dist/components/Table/Table.module.scss.js +21 -36
  77. package/dist/components/Table/Table.module.scss.js.map +1 -1
  78. package/dist/components/Table/index.d.ts +35 -55
  79. package/dist/components/Table/index.d.ts.map +1 -1
  80. package/dist/components/Text/Text.module.scss.cjs +14 -0
  81. package/dist/components/Text/Text.module.scss.cjs.map +1 -1
  82. package/dist/components/Text/Text.module.scss.js +14 -0
  83. package/dist/components/Text/Text.module.scss.js.map +1 -1
  84. package/dist/components/Textarea/Textarea.module.scss.cjs +4 -0
  85. package/dist/components/Textarea/Textarea.module.scss.cjs.map +1 -1
  86. package/dist/components/Textarea/Textarea.module.scss.js +4 -0
  87. package/dist/components/Textarea/Textarea.module.scss.js.map +1 -1
  88. package/dist/components/ToggleGroup/ToggleGroup.module.scss.cjs +5 -0
  89. package/dist/components/ToggleGroup/ToggleGroup.module.scss.cjs.map +1 -1
  90. package/dist/components/ToggleGroup/ToggleGroup.module.scss.js +5 -0
  91. package/dist/components/ToggleGroup/ToggleGroup.module.scss.js.map +1 -1
  92. package/dist/index.cjs +119 -117
  93. package/dist/index.cjs.map +1 -1
  94. package/dist/index.d.ts +2 -1
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +3 -1
  97. package/dist/index.js.map +1 -1
  98. package/dist/table.cjs +44 -262
  99. package/dist/table.cjs.map +1 -1
  100. package/dist/table.js +47 -248
  101. package/dist/table.js.map +1 -1
  102. package/fragments.json +1 -1
  103. package/package.json +110 -118
  104. package/src/blocks/components/index.ts +0 -3
  105. package/src/components/CodeBlock/CodeBlock.module.scss +16 -34
  106. package/src/components/CodeBlock/index.tsx +351 -345
  107. package/src/components/Combobox/Combobox.module.scss +13 -9
  108. package/src/components/ConversationList/ConversationList.fragment.tsx +96 -129
  109. package/src/components/DataTable/DataTable.fragment.tsx +754 -0
  110. package/src/components/DataTable/DataTable.module.scss +300 -0
  111. package/src/components/DataTable/DataTable.test.tsx +224 -0
  112. package/src/components/DataTable/index.tsx +533 -0
  113. package/src/components/Message/Message.fragment.tsx +34 -0
  114. package/src/components/Message/Message.module.scss +11 -0
  115. package/src/components/Message/index.tsx +12 -3
  116. package/src/components/Table/Table.fragment.tsx +190 -175
  117. package/src/components/Table/Table.module.scss +15 -88
  118. package/src/components/Table/Table.test.tsx +184 -94
  119. package/src/components/Table/index.tsx +105 -374
  120. package/src/index.ts +15 -4
  121. package/src/tokens/_computed.scss +7 -6
  122. package/src/tokens/_density.scss +87 -47
  123. package/src/tokens/_variables.scss +46 -31
  124. package/dist/blocks/components/DataTable.d.ts +0 -19
  125. package/dist/blocks/components/DataTable.d.ts.map +0 -1
  126. package/src/blocks/components/DataTable.tsx +0 -124
@@ -1,148 +1,53 @@
1
1
  import React from 'react';
2
2
  import { defineFragment } from '@fragments-sdk/cli/core';
3
- import { Table, createColumns } from '.';
4
- import { Badge } from '../Badge';
5
-
6
- // Sample data types
7
- interface User {
8
- id: string;
9
- name: string;
10
- email: string;
11
- status: 'active' | 'inactive' | 'pending';
12
- role: string;
13
- }
14
-
15
- const sampleUsers: User[] = [
16
- { id: '1', name: 'Alice Johnson', email: 'alice@example.com', status: 'active', role: 'Admin' },
17
- { id: '2', name: 'Bob Smith', email: 'bob@example.com', status: 'active', role: 'Editor' },
18
- { id: '3', name: 'Carol Williams', email: 'carol@example.com', status: 'pending', role: 'Viewer' },
19
- { id: '4', name: 'David Brown', email: 'david@example.com', status: 'inactive', role: 'Editor' },
20
- ];
21
-
22
- const columns = createColumns<User>([
23
- { key: 'name', header: 'Name' },
24
- { key: 'email', header: 'Email' },
25
- {
26
- key: 'status',
27
- header: 'Status',
28
- cell: (row) => (
29
- <Badge
30
- variant={row.status === 'active' ? 'success' : row.status === 'pending' ? 'warning' : 'default'}
31
- dot
32
- >
33
- {row.status}
34
- </Badge>
35
- ),
36
- },
37
- { key: 'role', header: 'Role' },
38
- ]);
3
+ import { Table } from '.';
39
4
 
40
5
  export default defineFragment({
41
6
  component: Table,
42
7
 
43
8
  meta: {
44
9
  name: 'Table',
45
- description: 'Data table with sorting and row selection. Use for displaying structured data that needs to be scanned, compared, or acted upon.',
10
+ description: 'Semantic HTML table with compound API. For data-rich tables with sorting and selection, use DataTable.',
46
11
  category: 'display',
47
12
  status: 'stable',
48
- tags: ['table', 'data', 'grid', 'list', 'sorting'],
13
+ tags: ['table', 'semantic', 'html', 'layout'],
49
14
  since: '0.1.0',
50
- dependencies: [
51
- { name: '@tanstack/react-table', version: '>=8.0.0', reason: 'Table state management and rendering' },
52
- ],
53
15
  },
54
16
 
55
17
  usage: {
56
18
  when: [
57
- 'Displaying structured, tabular data',
58
- 'Data that users need to scan and compare',
59
- 'Lists with multiple attributes per item',
60
- 'Data that needs sorting or selection',
19
+ 'Displaying simple tabular data with semantic HTML',
20
+ 'Static data that does not need sorting or selection',
21
+ 'Showing structured information with headers and rows',
22
+ 'Tables with footers or captions',
61
23
  ],
62
24
  whenNot: [
25
+ 'Data that needs sorting (use DataTable)',
26
+ 'Data that needs row selection (use DataTable)',
27
+ 'Data that needs clickable rows (use DataTable)',
63
28
  'Simple lists (use List component)',
64
- 'Card-based layouts (use Grid with Cards)',
65
- 'Heavily interactive data (consider DataGrid)',
66
- 'Small screens (consider card or list view)',
67
29
  ],
68
30
  guidelines: [
69
- 'Keep columns to a reasonable number (5-7 max)',
70
- 'Use consistent alignment (numbers right, text left)',
71
- 'Provide meaningful empty states',
72
- 'Consider mobile responsiveness',
31
+ 'Use Table.HeaderCell for column headers (adds scope="col" by default)',
32
+ 'Use Table.Caption for table descriptions (can be visually hidden)',
33
+ 'Keep tables simple — use DataTable for interactive features',
34
+ 'Consider mobile responsiveness; table scrolls horizontally on small screens',
73
35
  ],
74
36
  accessibility: [
75
- 'Proper table semantics with headers',
76
- 'Sortable columns are keyboard accessible',
77
- 'Row selection is properly announced',
37
+ 'Semantic HTML table elements (thead, tbody, tfoot, th, td)',
38
+ 'Column headers have scope="col" by default',
39
+ 'Caption provides table description for screen readers',
40
+ 'Supports visually-hidden caption for screen-reader-only labels',
78
41
  ],
79
42
  },
80
43
 
81
44
  props: {
82
- columns: {
83
- type: 'array',
84
- description: 'Column definitions',
85
- required: true,
86
- },
87
- data: {
88
- type: 'array',
89
- description: 'Data rows to display',
90
- required: true,
91
- },
92
- getRowId: {
93
- type: 'function',
94
- description: 'Unique key extractor for each row',
95
- },
96
- sortable: {
97
- type: 'boolean',
98
- description: 'Enable column sorting',
99
- default: 'false',
100
- },
101
- sorting: {
102
- type: 'object',
103
- description: 'Controlled sorting state',
104
- },
105
- onSortingChange: {
106
- type: 'function',
107
- description: 'Sorting change handler',
108
- },
109
- selectable: {
110
- type: 'boolean',
111
- description: 'Enable row selection',
112
- default: 'false',
113
- },
114
- rowSelection: {
115
- type: 'object',
116
- description: 'Controlled row selection state',
117
- },
118
- onRowSelectionChange: {
119
- type: 'function',
120
- description: 'Row selection change handler',
121
- },
122
- onRowClick: {
123
- type: 'function',
124
- description: 'Handler for row clicks',
125
- },
126
- emptyMessage: {
127
- type: 'string',
128
- description: 'Message when no data',
129
- default: 'No data available',
130
- },
131
45
  size: {
132
46
  type: 'enum',
133
47
  description: 'Table density',
134
48
  values: ['sm', 'md'],
135
49
  default: 'md',
136
50
  },
137
- caption: {
138
- type: 'string',
139
- description: 'Visible caption for the table',
140
- },
141
- captionHidden: {
142
- type: 'boolean',
143
- default: 'false',
144
- description: 'Hide caption visually but keep it for screen readers',
145
- },
146
51
  striped: {
147
52
  type: 'boolean',
148
53
  description: 'Show alternating row backgrounds',
@@ -156,110 +61,220 @@ export default defineFragment({
156
61
  },
157
62
 
158
63
  relations: [
159
- { component: 'EmptyState', relationship: 'sibling', note: 'Use EmptyState for empty table states' },
160
- { component: 'Badge', relationship: 'sibling', note: 'Use Badge for status columns' },
64
+ { component: 'DataTable', relationship: 'alternative', note: 'Use DataTable for sorting, selection, and TanStack features' },
161
65
  ],
162
66
 
163
67
  contract: {
164
68
  propsSummary: [
165
- 'columns: ColumnDef[] - column definitions',
166
- 'data: T[] - row data array',
167
- 'sortable: boolean - enable sorting',
168
- 'selectable: boolean - enable row selection',
169
69
  'size: sm|md - table density',
170
70
  'striped: boolean - alternating row backgrounds',
171
71
  'bordered: boolean - bordered container',
72
+ 'Table.Head, Table.Body, Table.Footer - section wrappers',
73
+ 'Table.Row - table row with optional selected prop',
74
+ 'Table.HeaderCell - th with scope="col"',
75
+ 'Table.Cell - td element',
76
+ 'Table.Caption - caption with optional hidden prop',
172
77
  ],
173
78
  scenarioTags: [
174
- 'data.table',
175
- 'display.list',
176
- 'data.grid',
79
+ 'display.table',
80
+ 'layout.table',
81
+ 'semantic.html',
177
82
  ],
178
- a11yRules: ['A11Y_TABLE_HEADERS', 'A11Y_TABLE_SORT'],
83
+ a11yRules: ['A11Y_TABLE_HEADERS', 'A11Y_TABLE_CAPTION'],
179
84
  },
180
85
 
181
86
  ai: {
182
- compositionPattern: 'simple',
87
+ compositionPattern: 'compound',
183
88
  commonPatterns: [
184
- '<Table columns={[{header:"Name",accessorKey:"name"},{header:"Status",accessorKey:"status"}]} data={[{name:"Item 1",status:"Active"}]} />',
89
+ '<Table><Table.Head><Table.Row><Table.HeaderCell>Name</Table.HeaderCell></Table.Row></Table.Head><Table.Body><Table.Row><Table.Cell>Alice</Table.Cell></Table.Row></Table.Body></Table>',
185
90
  ],
186
91
  },
187
92
 
188
93
  variants: [
189
94
  {
190
95
  name: 'Default',
191
- description: 'Basic data table',
96
+ description: 'Basic semantic table',
192
97
  render: () => (
193
- <Table
194
- columns={columns}
195
- data={sampleUsers}
196
- />
98
+ <Table aria-label="Team members">
99
+ <Table.Head>
100
+ <Table.Row>
101
+ <Table.HeaderCell>Name</Table.HeaderCell>
102
+ <Table.HeaderCell>Role</Table.HeaderCell>
103
+ <Table.HeaderCell>Status</Table.HeaderCell>
104
+ </Table.Row>
105
+ </Table.Head>
106
+ <Table.Body>
107
+ <Table.Row>
108
+ <Table.Cell>Alice Johnson</Table.Cell>
109
+ <Table.Cell>Engineer</Table.Cell>
110
+ <Table.Cell>Active</Table.Cell>
111
+ </Table.Row>
112
+ <Table.Row>
113
+ <Table.Cell>Bob Smith</Table.Cell>
114
+ <Table.Cell>Designer</Table.Cell>
115
+ <Table.Cell>Active</Table.Cell>
116
+ </Table.Row>
117
+ <Table.Row>
118
+ <Table.Cell>Carol Williams</Table.Cell>
119
+ <Table.Cell>PM</Table.Cell>
120
+ <Table.Cell>Away</Table.Cell>
121
+ </Table.Row>
122
+ </Table.Body>
123
+ </Table>
197
124
  ),
198
125
  },
199
126
  {
200
- name: 'Sortable',
201
- description: 'Table with sortable columns',
127
+ name: 'Striped',
128
+ description: 'Alternating row backgrounds for readability',
202
129
  render: () => (
203
- <Table
204
- columns={columns}
205
- data={sampleUsers}
206
- sortable
207
- />
130
+ <Table striped aria-label="Inventory">
131
+ <Table.Head>
132
+ <Table.Row>
133
+ <Table.HeaderCell>Item</Table.HeaderCell>
134
+ <Table.HeaderCell>Category</Table.HeaderCell>
135
+ <Table.HeaderCell>Qty</Table.HeaderCell>
136
+ </Table.Row>
137
+ </Table.Head>
138
+ <Table.Body>
139
+ <Table.Row>
140
+ <Table.Cell>Widget A</Table.Cell>
141
+ <Table.Cell>Hardware</Table.Cell>
142
+ <Table.Cell>120</Table.Cell>
143
+ </Table.Row>
144
+ <Table.Row>
145
+ <Table.Cell>Widget B</Table.Cell>
146
+ <Table.Cell>Software</Table.Cell>
147
+ <Table.Cell>85</Table.Cell>
148
+ </Table.Row>
149
+ <Table.Row>
150
+ <Table.Cell>Widget C</Table.Cell>
151
+ <Table.Cell>Hardware</Table.Cell>
152
+ <Table.Cell>200</Table.Cell>
153
+ </Table.Row>
154
+ <Table.Row>
155
+ <Table.Cell>Widget D</Table.Cell>
156
+ <Table.Cell>Software</Table.Cell>
157
+ <Table.Cell>50</Table.Cell>
158
+ </Table.Row>
159
+ </Table.Body>
160
+ </Table>
208
161
  ),
209
162
  },
210
163
  {
211
- name: 'Clickable Rows',
212
- description: 'Table with clickable rows',
164
+ name: 'Bordered',
165
+ description: 'Table with bordered container',
213
166
  render: () => (
214
- <Table
215
- columns={columns}
216
- data={sampleUsers}
217
- onRowClick={(row) => alert(`Clicked: ${row.name}`)}
218
- />
167
+ <Table bordered aria-label="Pricing">
168
+ <Table.Head>
169
+ <Table.Row>
170
+ <Table.HeaderCell>Plan</Table.HeaderCell>
171
+ <Table.HeaderCell>Price</Table.HeaderCell>
172
+ <Table.HeaderCell>Features</Table.HeaderCell>
173
+ </Table.Row>
174
+ </Table.Head>
175
+ <Table.Body>
176
+ <Table.Row>
177
+ <Table.Cell>Basic</Table.Cell>
178
+ <Table.Cell>$9/mo</Table.Cell>
179
+ <Table.Cell>5 projects</Table.Cell>
180
+ </Table.Row>
181
+ <Table.Row>
182
+ <Table.Cell>Pro</Table.Cell>
183
+ <Table.Cell>$29/mo</Table.Cell>
184
+ <Table.Cell>Unlimited</Table.Cell>
185
+ </Table.Row>
186
+ </Table.Body>
187
+ </Table>
219
188
  ),
220
189
  },
221
190
  {
222
191
  name: 'Compact',
223
- description: 'Smaller, denser table',
224
- render: () => (
225
- <Table
226
- columns={columns}
227
- data={sampleUsers}
228
- size="sm"
229
- />
230
- ),
231
- },
232
- {
233
- name: 'Striped',
234
- description: 'Table with alternating row backgrounds',
192
+ description: 'Small, dense table',
235
193
  render: () => (
236
- <Table
237
- columns={columns}
238
- data={sampleUsers}
239
- striped
240
- />
194
+ <Table size="sm" aria-label="Shortcuts">
195
+ <Table.Head>
196
+ <Table.Row>
197
+ <Table.HeaderCell>Key</Table.HeaderCell>
198
+ <Table.HeaderCell>Action</Table.HeaderCell>
199
+ </Table.Row>
200
+ </Table.Head>
201
+ <Table.Body>
202
+ <Table.Row>
203
+ <Table.Cell>Ctrl+S</Table.Cell>
204
+ <Table.Cell>Save</Table.Cell>
205
+ </Table.Row>
206
+ <Table.Row>
207
+ <Table.Cell>Ctrl+Z</Table.Cell>
208
+ <Table.Cell>Undo</Table.Cell>
209
+ </Table.Row>
210
+ <Table.Row>
211
+ <Table.Cell>Ctrl+C</Table.Cell>
212
+ <Table.Cell>Copy</Table.Cell>
213
+ </Table.Row>
214
+ </Table.Body>
215
+ </Table>
241
216
  ),
242
217
  },
243
218
  {
244
- name: 'Bordered',
245
- description: 'Table with bordered container',
219
+ name: 'With Caption',
220
+ description: 'Table with visible caption',
246
221
  render: () => (
247
- <Table
248
- columns={columns}
249
- data={sampleUsers}
250
- bordered
251
- />
222
+ <Table aria-label="Q1 results">
223
+ <Table.Caption>Quarterly Results (Q1 2026)</Table.Caption>
224
+ <Table.Head>
225
+ <Table.Row>
226
+ <Table.HeaderCell>Metric</Table.HeaderCell>
227
+ <Table.HeaderCell>Value</Table.HeaderCell>
228
+ <Table.HeaderCell>Change</Table.HeaderCell>
229
+ </Table.Row>
230
+ </Table.Head>
231
+ <Table.Body>
232
+ <Table.Row>
233
+ <Table.Cell>Revenue</Table.Cell>
234
+ <Table.Cell>$1.2M</Table.Cell>
235
+ <Table.Cell>+15%</Table.Cell>
236
+ </Table.Row>
237
+ <Table.Row>
238
+ <Table.Cell>Users</Table.Cell>
239
+ <Table.Cell>24,500</Table.Cell>
240
+ <Table.Cell>+8%</Table.Cell>
241
+ </Table.Row>
242
+ </Table.Body>
243
+ </Table>
252
244
  ),
253
245
  },
254
246
  {
255
- name: 'Empty State',
256
- description: 'Table with no data',
247
+ name: 'With Footer',
248
+ description: 'Table with footer row for totals',
257
249
  render: () => (
258
- <Table
259
- columns={columns}
260
- data={[]}
261
- emptyMessage="No users found"
262
- />
250
+ <Table bordered aria-label="Expenses">
251
+ <Table.Head>
252
+ <Table.Row>
253
+ <Table.HeaderCell>Category</Table.HeaderCell>
254
+ <Table.HeaderCell>Amount</Table.HeaderCell>
255
+ </Table.Row>
256
+ </Table.Head>
257
+ <Table.Body>
258
+ <Table.Row>
259
+ <Table.Cell>Marketing</Table.Cell>
260
+ <Table.Cell>$12,000</Table.Cell>
261
+ </Table.Row>
262
+ <Table.Row>
263
+ <Table.Cell>Engineering</Table.Cell>
264
+ <Table.Cell>$45,000</Table.Cell>
265
+ </Table.Row>
266
+ <Table.Row>
267
+ <Table.Cell>Operations</Table.Cell>
268
+ <Table.Cell>$8,000</Table.Cell>
269
+ </Table.Row>
270
+ </Table.Body>
271
+ <Table.Footer>
272
+ <Table.Row>
273
+ <Table.Cell>Total</Table.Cell>
274
+ <Table.Cell>$65,000</Table.Cell>
275
+ </Table.Row>
276
+ </Table.Footer>
277
+ </Table>
263
278
  ),
264
279
  },
265
280
  ],
@@ -51,10 +51,10 @@
51
51
  position: sticky;
52
52
  top: 0;
53
53
  z-index: 1;
54
- }
55
54
 
56
- .headerRow {
57
- border-bottom: 1px solid var(--fui-border, $fui-border);
55
+ > tr {
56
+ border-bottom: 1px solid var(--fui-border, $fui-border);
57
+ }
58
58
  }
59
59
 
60
60
  .th {
@@ -63,7 +63,6 @@
63
63
  color: var(--fui-text-secondary, $fui-text-secondary);
64
64
  white-space: nowrap;
65
65
  vertical-align: middle;
66
- user-select: none;
67
66
 
68
67
  &:first-child {
69
68
  border-top-left-radius: var(--fui-radius-md, $fui-radius-md);
@@ -80,41 +79,6 @@
80
79
  gap: var(--fui-space-1, $fui-space-1);
81
80
  }
82
81
 
83
- // Sortable header cell (for focus styles)
84
- .thSortable {
85
- padding: 0;
86
- }
87
-
88
- .sortButton {
89
- @include button-reset;
90
- @include interactive-base;
91
-
92
- display: flex;
93
- align-items: center;
94
- justify-content: space-between;
95
- gap: var(--fui-space-1, $fui-space-1);
96
- width: 100%;
97
- padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-md, $fui-padding-item-md);
98
- color: inherit;
99
- text-align: left;
100
- transition: color var(--fui-transition-fast, $fui-transition-fast);
101
-
102
- &:hover {
103
- color: var(--fui-text-primary, $fui-text-primary);
104
- }
105
- }
106
-
107
- .sortIndicator {
108
- display: inline-flex;
109
- align-items: center;
110
- color: var(--fui-text-tertiary, $fui-text-tertiary);
111
- flex-shrink: 0;
112
-
113
- .sortButton:hover & {
114
- color: var(--fui-text-secondary, $fui-text-secondary);
115
- }
116
- }
117
-
118
82
  // Body
119
83
  .tbody {}
120
84
 
@@ -131,22 +95,6 @@
131
95
  }
132
96
  }
133
97
 
134
- .clickable {
135
- cursor: pointer;
136
-
137
- &:hover {
138
- background-color: var(--fui-bg-hover, $fui-bg-hover);
139
- }
140
-
141
- &:focus-visible {
142
- @include focus-ring;
143
- }
144
-
145
- &:active {
146
- background-color: var(--fui-bg-active, $fui-bg-active);
147
- }
148
- }
149
-
150
98
  .selected {
151
99
  background-color: rgba($fui-color-accent, 0.08);
152
100
 
@@ -161,19 +109,20 @@
161
109
  line-height: var(--fui-line-height-normal, $fui-line-height-normal);
162
110
  }
163
111
 
164
- // Striped rows
165
- .striped {
166
- .row:nth-child(even) {
167
- background-color: var(--fui-bg-subtle, $fui-bg-subtle);
168
- }
112
+ // Footer
113
+ .tfoot {
114
+ border-top: 2px solid var(--fui-border, $fui-border);
169
115
 
170
- // Hover and selected override stripe
171
- .clickable:hover {
172
- background-color: var(--fui-bg-hover, $fui-bg-hover);
116
+ .td,
117
+ .th {
118
+ font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
173
119
  }
120
+ }
174
121
 
175
- .clickable:active {
176
- background-color: var(--fui-bg-active, $fui-bg-active);
122
+ // Striped rows
123
+ .striped {
124
+ .tbody .row:nth-child(even) {
125
+ background-color: var(--fui-bg-subtle, $fui-bg-subtle);
177
126
  }
178
127
 
179
128
  .selected {
@@ -192,20 +141,6 @@
192
141
  overflow: hidden;
193
142
  }
194
143
 
195
- // Empty state
196
- .emptyState {
197
- display: flex;
198
- align-items: center;
199
- justify-content: center;
200
- padding: var(--fui-space-12, $fui-space-12) var(--fui-space-6, $fui-space-6);
201
- }
202
-
203
- .emptyMessage {
204
- font-family: var(--fui-font-sans, $fui-font-sans);
205
- font-size: var(--fui-font-size-sm, $fui-font-size-sm);
206
- color: var(--fui-text-tertiary, $fui-text-tertiary);
207
- }
208
-
209
144
  // Responsive: allow horizontal scroll on small screens
210
145
  @include below-sm {
211
146
  .wrapper {
@@ -221,17 +156,13 @@
221
156
  // ============================================
222
157
 
223
158
  @media (prefers-contrast: more) {
224
- .headerRow {
159
+ .thead > tr {
225
160
  border-bottom-width: 2px;
226
161
  }
227
162
 
228
163
  .row {
229
164
  border-bottom-width: 2px;
230
165
  }
231
-
232
- .sortButton:focus-visible {
233
- outline-width: 3px;
234
- }
235
166
  }
236
167
 
237
168
  // ============================================
@@ -242,8 +173,4 @@
242
173
  .row {
243
174
  transition: none;
244
175
  }
245
-
246
- .sortButton {
247
- transition: none;
248
- }
249
176
  }