@tcn/ui 0.7.0 → 0.8.1

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 (137) hide show
  1. package/dist/{Color-6BZIO3FS-CWWwv-fq.js → Color-6BZIO3FS-C9xkPWgz.js} +2 -2
  2. package/dist/{Color-6BZIO3FS-CWWwv-fq.js.map → Color-6BZIO3FS-C9xkPWgz.js.map} +1 -1
  3. package/dist/{WithTooltip-65CFNBJE-DvcUZizH.js → WithTooltip-65CFNBJE-DEnh547F.js} +2 -2
  4. package/dist/{WithTooltip-65CFNBJE-DvcUZizH.js.map → WithTooltip-65CFNBJE-DEnh547F.js.map} +1 -1
  5. package/dist/actions/__docs__/components/showcase.js +1 -1
  6. package/dist/actions/index.d.ts +1 -0
  7. package/dist/actions/index.d.ts.map +1 -1
  8. package/dist/actions/index.js +8 -6
  9. package/dist/actions/index.js.map +1 -1
  10. package/dist/body.css +1 -0
  11. package/dist/form/field_presenters/field_presenter.d.ts +2 -2
  12. package/dist/form/field_presenters/field_presenter.d.ts.map +1 -1
  13. package/dist/form/field_presenters/field_presenter.js.map +1 -1
  14. package/dist/formatter-EIJCOSYU-DWmgEY3b.js +6 -0
  15. package/dist/{formatter-EIJCOSYU-D6nmx63h.js.map → formatter-EIJCOSYU-DWmgEY3b.js.map} +1 -1
  16. package/dist/inputs/color_input/color_input.js +10 -9
  17. package/dist/inputs/color_input/color_input.js.map +1 -1
  18. package/dist/inputs/date_picker/date_picker_header.js +7 -6
  19. package/dist/inputs/date_picker/date_picker_header.js.map +1 -1
  20. package/dist/inputs/date_picker/date_picker_input.js +3 -2
  21. package/dist/inputs/date_picker/date_picker_input.js.map +1 -1
  22. package/dist/inputs/date_picker/date_picker_time_selector.js +3 -2
  23. package/dist/inputs/date_picker/date_picker_time_selector.js.map +1 -1
  24. package/dist/inputs/date_picker/date_picker_year_input.js +6 -5
  25. package/dist/inputs/date_picker/date_picker_year_input.js.map +1 -1
  26. package/dist/inputs/date_picker/date_picker_year_selector.js +3 -2
  27. package/dist/inputs/date_picker/date_picker_year_selector.js.map +1 -1
  28. package/dist/inputs/multiselect/multiselect_inline_values.js +8 -7
  29. package/dist/inputs/multiselect/multiselect_inline_values.js.map +1 -1
  30. package/dist/inputs/multiselect/multiselect_values.js +3 -2
  31. package/dist/inputs/multiselect/multiselect_values.js.map +1 -1
  32. package/dist/inputs/phone_number_input/phone_number_input.js +44 -43
  33. package/dist/inputs/phone_number_input/phone_number_input.js.map +1 -1
  34. package/dist/inputs/select/select.js +3 -2
  35. package/dist/inputs/select/select.js.map +1 -1
  36. package/dist/inputs/suggestions/suggestion_list.js +3 -2
  37. package/dist/inputs/suggestions/suggestion_list.js.map +1 -1
  38. package/dist/layouts/body/body.d.ts +6 -0
  39. package/dist/layouts/body/body.d.ts.map +1 -0
  40. package/dist/layouts/body/body.js +21 -0
  41. package/dist/layouts/body/body.js.map +1 -0
  42. package/dist/layouts/index.d.ts +5 -0
  43. package/dist/layouts/index.d.ts.map +1 -1
  44. package/dist/layouts/index.js +38 -22
  45. package/dist/layouts/index.js.map +1 -1
  46. package/dist/layouts/rail/main/main.d.ts +6 -0
  47. package/dist/layouts/rail/main/main.d.ts.map +1 -0
  48. package/dist/layouts/rail/main/main.js +21 -0
  49. package/dist/layouts/rail/main/main.js.map +1 -0
  50. package/dist/layouts/rail/rail.d.ts +9 -0
  51. package/dist/layouts/rail/rail.d.ts.map +1 -0
  52. package/dist/layouts/rail/rail.js +55 -0
  53. package/dist/layouts/rail/rail.js.map +1 -0
  54. package/dist/layouts/rail/side/side.d.ts +6 -0
  55. package/dist/layouts/rail/side/side.d.ts.map +1 -0
  56. package/dist/layouts/rail/side/side.js +21 -0
  57. package/dist/layouts/rail/side/side.js.map +1 -0
  58. package/dist/layouts/rail/utility_strip/utility_strip.d.ts +9 -0
  59. package/dist/layouts/rail/utility_strip/utility_strip.d.ts.map +1 -0
  60. package/dist/layouts/rail/utility_strip/utility_strip.js +32 -0
  61. package/dist/layouts/rail/utility_strip/utility_strip.js.map +1 -0
  62. package/dist/layouts/scaffold/scaffold.js +31 -31
  63. package/dist/layouts/scaffold/scaffold.js.map +1 -1
  64. package/dist/layouts/table/table.d.ts +27 -0
  65. package/dist/layouts/table/table.d.ts.map +1 -0
  66. package/dist/layouts/table/table.js +70 -0
  67. package/dist/layouts/table/table.js.map +1 -0
  68. package/dist/main.css +1 -0
  69. package/dist/navigation/tabs/primitives/tabs_list.d.ts.map +1 -1
  70. package/dist/navigation/tabs/primitives/tabs_list.js +61 -21
  71. package/dist/navigation/tabs/primitives/tabs_list.js.map +1 -1
  72. package/dist/navigation/tabs/state/link/tab_link.d.ts.map +1 -1
  73. package/dist/navigation/tabs/state/link/tab_link.js +20 -17
  74. package/dist/navigation/tabs/state/link/tab_link.js.map +1 -1
  75. package/dist/navigation/tabs/state/tab.d.ts.map +1 -1
  76. package/dist/navigation/tabs/state/tab.js +8 -3
  77. package/dist/navigation/tabs/state/tab.js.map +1 -1
  78. package/dist/rail.css +1 -0
  79. package/dist/scaffold.css +1 -1
  80. package/dist/{showcase-DK557szS.js → showcase-y9D3_Y8T.js} +3413 -3396
  81. package/dist/showcase-y9D3_Y8T.js.map +1 -0
  82. package/dist/side.css +1 -0
  83. package/dist/stacks/box/box.d.ts +2 -2
  84. package/dist/stacks/box/box.d.ts.map +1 -1
  85. package/dist/stacks/box/box.js.map +1 -1
  86. package/dist/stacks/story_components/style_box.d.ts +1 -1
  87. package/dist/stacks/story_components/style_box.d.ts.map +1 -1
  88. package/dist/surfaces/alert/alert.js +3 -2
  89. package/dist/surfaces/alert/alert.js.map +1 -1
  90. package/dist/surfaces/pop_confirm/pop_confirm.js +13 -2
  91. package/dist/surfaces/pop_confirm/pop_confirm.js.map +1 -1
  92. package/dist/{syntaxhighlighter-ED5Y7EFY-DaMS-2cF.js → syntaxhighlighter-ED5Y7EFY-CqInEOwQ.js} +2 -2
  93. package/dist/{syntaxhighlighter-ED5Y7EFY-DaMS-2cF.js.map → syntaxhighlighter-ED5Y7EFY-CqInEOwQ.js.map} +1 -1
  94. package/dist/table.css +1 -0
  95. package/dist/table.module-BtSxOntS.js +5 -0
  96. package/dist/table.module-BtSxOntS.js.map +1 -0
  97. package/dist/themes/themes/ergo/ergo_theme.css +1 -1
  98. package/dist/themes/themes/ergo/ergo_theme.js +179 -44
  99. package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
  100. package/dist/utility_strip.css +1 -0
  101. package/package.json +2 -2
  102. package/src/actions/index.ts +1 -0
  103. package/src/form/field_presenters/field_presenter.ts +3 -3
  104. package/src/layouts/__stories__/composed.stories.tsx +113 -0
  105. package/src/layouts/__stories__/composed_stories.module.css +142 -0
  106. package/src/layouts/__stories__/utils.tsx +174 -0
  107. package/src/layouts/body/body.module.css +11 -0
  108. package/src/layouts/body/body.tsx +23 -0
  109. package/src/layouts/index.ts +10 -0
  110. package/src/layouts/rail/__stories__/rail.stories.tsx +64 -0
  111. package/src/layouts/rail/__stories__/rail_stories.module.css +25 -0
  112. package/src/layouts/rail/main/main.module.css +7 -0
  113. package/src/layouts/rail/main/main.tsx +26 -0
  114. package/src/layouts/rail/rail.module.css +10 -0
  115. package/src/layouts/rail/rail.tsx +62 -0
  116. package/src/layouts/rail/side/side.module.css +8 -0
  117. package/src/layouts/rail/side/side.tsx +25 -0
  118. package/src/layouts/rail/utility_strip/utility_strip.module.css +6 -0
  119. package/src/layouts/rail/utility_strip/utility_strip.tsx +40 -0
  120. package/src/layouts/scaffold/__stories__/scaffold.stories.tsx +53 -0
  121. package/src/layouts/scaffold/__stories__/scaffold_stories.module.css +31 -0
  122. package/src/layouts/scaffold/scaffold.module.css +4 -0
  123. package/src/layouts/table/__stories__/mock_data.ts +420 -0
  124. package/src/layouts/table/__stories__/table.stories.tsx +326 -0
  125. package/src/layouts/table/__stories__/table_stories.module.css +30 -0
  126. package/src/layouts/table/table.module.css +37 -0
  127. package/src/layouts/table/table.tsx +132 -0
  128. package/src/navigation/tabs/primitives/tabs_list.tsx +46 -2
  129. package/src/navigation/tabs/state/link/tab_link.tsx +4 -1
  130. package/src/navigation/tabs/state/tab.tsx +10 -0
  131. package/src/stacks/box/box.tsx +1 -1
  132. package/src/surfaces/modal/__stories__/modal.stories.tsx +5 -5
  133. package/src/surfaces/panel/__stories__/panel.stories.tsx +114 -1
  134. package/src/surfaces/pop_confirm/pop_confirm.stories.tsx +4 -2
  135. package/src/themes/themes/ergo/ergo_theme.css +178 -43
  136. package/dist/formatter-EIJCOSYU-D6nmx63h.js +0 -6
  137. package/dist/showcase-DK557szS.js.map +0 -1
@@ -0,0 +1,326 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import React from 'react';
3
+ import { TTable, THead, TBody, TFoot, TR, TH, TD } from '../table.js';
4
+ import { Caption } from '../../../typography/index.js';
5
+ import { HStack, Spacer, VStack } from '../../../stacks/index.js';
6
+ import { Button } from '../../../actions/index.js';
7
+ import { SearchIcon } from '@tcn/icons/search_icon.js';
8
+ import { FilterOneIcon } from '@tcn/icons/filter_one_icon.js';
9
+ import { TwoClassSortIcon } from '@tcn/icons/two_class_sort_icon.js';
10
+ import { EditOneIcon } from '@tcn/icons/edit_one_icon.js';
11
+ import { TrashOneIcon } from '@tcn/icons/trash_one_icon.js';
12
+ import { CopyIcon } from '@tcn/icons/copy_icon.js';
13
+ import { Chip } from '../../../tokens/index.js';
14
+ import {
15
+ sb_tableMockData,
16
+ sb_tableGroupedMockData,
17
+ sb_tableStatusLabels,
18
+ type SBTableData,
19
+ sb_tableGroupedStats,
20
+ } from './mock_data.js';
21
+ import { CloudLightningIcon } from '@tcn/icons/cloud_lightning_icon.js';
22
+ import styles from './table_stories.module.css';
23
+
24
+ const meta: Meta<typeof TTable> = {
25
+ title: 'Layouts/Table',
26
+ component: TTable,
27
+ tags: ['autodocs'],
28
+ parameters: {
29
+ docs: {
30
+ description: {
31
+ component:
32
+ 'Semantic table layout components. Structure follows: caption, thead, tbody, tfoot, with proper scope and colSpan for accessibility.',
33
+ },
34
+ },
35
+ },
36
+ };
37
+
38
+ export default meta;
39
+
40
+ type Story = StoryObj<typeof TTable>;
41
+
42
+ const TableWrapper = ({ children }: { children: React.ReactNode }) => {
43
+ return (
44
+ <VStack padding="16px" height="100%" width="100%" maxHeight="500px">
45
+ {children}
46
+ </VStack>
47
+ );
48
+ };
49
+
50
+ const ExampleRow: React.FC<{ item: SBTableData }> = ({ item }) => {
51
+ return (
52
+ <TR isSelected={item.selected}>
53
+ <TH scope="row">{item.name}</TH>
54
+ <TD>{item.description}</TD>
55
+ <TD>{item.age}</TD>
56
+ <TD>{item.date.toLocaleDateString()}</TD>
57
+ <TD>
58
+ <Chip className={styles.chip} data-status={item.status}>
59
+ {sb_tableStatusLabels[item.status]}
60
+ </Chip>
61
+ </TD>
62
+ <TH scope="row">
63
+ <HStack gap="4px">
64
+ <Button utility hierarchy="tertiary" severity="dangerous">
65
+ <TrashOneIcon />
66
+ </Button>
67
+ <Button utility hierarchy="tertiary">
68
+ <CopyIcon />
69
+ </Button>
70
+ <Button utility hierarchy="tertiary">
71
+ <EditOneIcon />
72
+ </Button>
73
+ <Button utility hierarchy="primary">
74
+ <CloudLightningIcon />
75
+ </Button>
76
+ </HStack>
77
+ </TH>
78
+ </TR>
79
+ );
80
+ };
81
+
82
+ const ExampleHeader: React.FC<{
83
+ title: string;
84
+ canSort?: boolean;
85
+ canFilter?: boolean;
86
+ canSearch?: boolean;
87
+ }> = ({ title, canSort, canFilter, canSearch }) => {
88
+ return (
89
+ <TH scope="col">
90
+ <HStack gap="8px">
91
+ {title}
92
+ <Spacer />
93
+ <HStack gap="2px" minWidth="min-content" growWeight={0}>
94
+ {canSearch && (
95
+ <Button utility hierarchy="tertiary">
96
+ <SearchIcon />
97
+ </Button>
98
+ )}
99
+ {canFilter && (
100
+ <Button utility hierarchy="tertiary">
101
+ <FilterOneIcon />
102
+ </Button>
103
+ )}
104
+ {canSort && (
105
+ <Button utility hierarchy="tertiary">
106
+ <TwoClassSortIcon />
107
+ </Button>
108
+ )}
109
+ </HStack>
110
+ </HStack>
111
+ </TH>
112
+ );
113
+ };
114
+ /**
115
+ * Table structured like the [MDN table example](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table):
116
+ * caption, column headers with `scope="col"`, row headers with `scope="row"`, and a footer with a spanning cell.
117
+ */
118
+ export const Baseline: Story = {
119
+ render: () => (
120
+ <TableWrapper>
121
+ <TTable>
122
+ <caption>
123
+ <Caption>Example description of the a table (caption)</Caption>
124
+ </caption>
125
+ <THead>
126
+ <TR>
127
+ <ExampleHeader title="Name" canSort canFilter canSearch />
128
+ <ExampleHeader title="Description" canSort canFilter canSearch />
129
+ <ExampleHeader title="Age" canSort canFilter canSearch />
130
+ <ExampleHeader title="Date" canSort canFilter canSearch />
131
+ <ExampleHeader title="Status" canSort canFilter canSearch />
132
+ <TH scope="col">Actions</TH>
133
+ </TR>
134
+ </THead>
135
+ <TBody>
136
+ {sb_tableMockData.map(item => (
137
+ <ExampleRow key={item.name} item={item} />
138
+ ))}
139
+ </TBody>
140
+ <TFoot>
141
+ <TR>
142
+ <TH scope="row" colSpan={5}>
143
+ Total:
144
+ </TH>
145
+ <TD>33</TD>
146
+ </TR>
147
+ </TFoot>
148
+ </TTable>
149
+ </TableWrapper>
150
+ ),
151
+ };
152
+
153
+ export const MaxDimensions: Story = {
154
+ render: () => (
155
+ <TableWrapper>
156
+ <TTable style={{ height: '300px', width: '400px' }}>
157
+ <caption>
158
+ <Caption>Example description of the a table (caption)</Caption>
159
+ </caption>
160
+ <THead>
161
+ <TR>
162
+ <ExampleHeader title="Name" canSort canFilter canSearch />
163
+ <ExampleHeader title="Description" canSort canFilter canSearch />
164
+ <ExampleHeader title="Age" canSort canFilter canSearch />
165
+ <ExampleHeader title="Date" canSort canFilter canSearch />
166
+ <ExampleHeader title="Status" canSort canFilter canSearch />
167
+ <TH scope="col">Actions</TH>
168
+ </TR>
169
+ </THead>
170
+ <TBody>
171
+ {sb_tableMockData.map(item => (
172
+ <ExampleRow key={item.name} item={item} />
173
+ ))}
174
+ </TBody>
175
+ <TFoot>
176
+ <TR>
177
+ <TH scope="row" colSpan={5}>
178
+ Total:
179
+ </TH>
180
+ <TD>33</TD>
181
+ </TR>
182
+ </TFoot>
183
+ </TTable>
184
+ </TableWrapper>
185
+ ),
186
+ };
187
+
188
+ export const Sticky: Story = {
189
+ render: () => (
190
+ <TableWrapper>
191
+ <TTable sticky>
192
+ <caption>
193
+ <Caption>Example description of the a table (caption)</Caption>
194
+ </caption>
195
+ <THead>
196
+ <TR>
197
+ <ExampleHeader title="Name" canSort canFilter canSearch />
198
+ <ExampleHeader title="Description" canSort canFilter canSearch />
199
+ <ExampleHeader title="Age" canSort canFilter canSearch />
200
+ <ExampleHeader title="Date" canSort canFilter canSearch />
201
+ <ExampleHeader title="Status" canSort canFilter canSearch />
202
+ <TH scope="col">Actions</TH>
203
+ </TR>
204
+ </THead>
205
+ <TBody>
206
+ {sb_tableMockData.map(item => (
207
+ <ExampleRow key={item.name} item={item} />
208
+ ))}
209
+ </TBody>
210
+ <TFoot>
211
+ <TR>
212
+ <TH align="right">Total: 33</TH>
213
+ <TH scope="row" colSpan={4} />
214
+ <TH scope="row" />
215
+ </TR>
216
+ </TFoot>
217
+ </TTable>
218
+ </TableWrapper>
219
+ ),
220
+ };
221
+
222
+ const formatNumber = (number: number) => {
223
+ return number.toFixed(2);
224
+ };
225
+
226
+ export const SpanningCell: Story = {
227
+ render: () => (
228
+ <TableWrapper>
229
+ <TTable sticky>
230
+ <caption>
231
+ <Caption>Example description of the a table (caption)</Caption>
232
+ </caption>
233
+ <colgroup span={2}>Name</colgroup>
234
+ <THead>
235
+ <TR>
236
+ <TH scope="colgroup" colSpan={2}>
237
+ Personal Info
238
+ </TH>
239
+ <TH scope="colgroup" colSpan={3}>
240
+ Employment
241
+ </TH>
242
+ <TH scope="colgroup" colSpan={6}>
243
+ Performance
244
+ </TH>
245
+ <TH scope="row" />
246
+ </TR>
247
+ <TR>
248
+ <TH scope="col">First Name</TH>
249
+ <TH scope="col">Last Name</TH>
250
+ <TH scope="col">Branch</TH>
251
+ <TH scope="col">Department</TH>
252
+ <TH scope="col">Role</TH>
253
+ <TH scope="col">Q1</TH>
254
+ <TH scope="col">Q2</TH>
255
+ <TH scope="col">Q3</TH>
256
+ <TH scope="col">Q4</TH>
257
+ <TH scope="col">Score</TH>
258
+ <TH scope="col">Last Review</TH>
259
+ <TH scope="col">Actions</TH>
260
+ </TR>
261
+ </THead>
262
+ <TBody>
263
+ {sb_tableGroupedMockData.map((row, i) => (
264
+ <TR key={i}>
265
+ <TH>{row.firstName}</TH>
266
+ <TD>{row.lastName}</TD>
267
+ <TD>{row.branch}</TD>
268
+ <TD>{row.department}</TD>
269
+ <TD>{row.role}</TD>
270
+ <TD>{row.q1}</TD>
271
+ <TD>{row.q2}</TD>
272
+ <TD>{row.q3}</TD>
273
+ <TD>{row.q4}</TD>
274
+ <TD>{row.score}</TD>
275
+ <TD>{row.lastReview}</TD>
276
+ <TH scope="row">
277
+ <HStack gap="4px">
278
+ <Button utility hierarchy="tertiary" severity="dangerous">
279
+ <TrashOneIcon />
280
+ </Button>
281
+ <Button utility hierarchy="tertiary">
282
+ <CopyIcon />
283
+ </Button>
284
+ <Button utility hierarchy="tertiary">
285
+ <EditOneIcon />
286
+ </Button>
287
+ <Button utility hierarchy="primary">
288
+ <CloudLightningIcon />
289
+ </Button>
290
+ </HStack>
291
+ </TH>
292
+ </TR>
293
+ ))}
294
+ </TBody>
295
+ <TFoot>
296
+ <TR>
297
+ <TD scope="row" colSpan={4} />
298
+ <TH scope="row" align="right">
299
+ Totals:
300
+ </TH>
301
+ <TD>{formatNumber(sb_tableGroupedStats.totals.q1)}</TD>
302
+ <TD>{formatNumber(sb_tableGroupedStats.totals.q2)}</TD>
303
+ <TD>{formatNumber(sb_tableGroupedStats.totals.q3)}</TD>
304
+ <TD>{formatNumber(sb_tableGroupedStats.totals.q4)}</TD>
305
+ <TD>{formatNumber(sb_tableGroupedStats.year)}</TD>
306
+ <TD />
307
+ <TD />
308
+ </TR>
309
+ <TR>
310
+ <TD scope="row" colSpan={4} />
311
+ <TH scope="row" align="right">
312
+ Averages:
313
+ </TH>
314
+ <TD>{formatNumber(sb_tableGroupedStats.averages.q1)}</TD>
315
+ <TD>{formatNumber(sb_tableGroupedStats.averages.q2)}</TD>
316
+ <TD>{formatNumber(sb_tableGroupedStats.averages.q3)}</TD>
317
+ <TD>{formatNumber(sb_tableGroupedStats.averages.q4)}</TD>
318
+ <TD>{formatNumber(sb_tableGroupedStats.mean)}</TD>
319
+ <TD />
320
+ <TD />
321
+ </TR>
322
+ </TFoot>
323
+ </TTable>
324
+ </TableWrapper>
325
+ ),
326
+ };
@@ -0,0 +1,30 @@
1
+ .chip {
2
+ border: 1px solid var(--material);
3
+ background-color: var(--material);
4
+ color: var(--on-material);
5
+ font-size: 12px;
6
+ }
7
+
8
+ .chip[data-status="completed"] {
9
+ border: 1px solid var(--async-color-success);
10
+ background-color: var(--async-color-success);
11
+ color: var(--ergo-white);
12
+ }
13
+
14
+ .chip[data-status="in_progress"] {
15
+ border: 1px solid var(--async-color-pending);
16
+ background-color: var(--async-color-pending);
17
+ color: var(--ergo-white);
18
+ }
19
+
20
+ .chip[data-status="not_started"] {
21
+ border: 1px solid var(--async-color-initial);
22
+ background-color: var(--async-color-initial);
23
+ color: var(--ergo-white);
24
+ }
25
+
26
+ .chip[data-status="failed"] {
27
+ border: 1px solid var(--async-color-failed);
28
+ background-color: var(--async-color-failed);
29
+ color: var(--ergo-white);
30
+ }
@@ -0,0 +1,37 @@
1
+ @layer tcn-system {
2
+ :where(.table) {
3
+ overflow: auto;
4
+ width: 100%;
5
+ height: auto;
6
+ }
7
+ :where(.table[data-is-sticky="true"]) {
8
+ th:first-of-type {
9
+ position: sticky;
10
+ left: 0;
11
+ z-index: 2;
12
+ }
13
+
14
+ th:last-of-type {
15
+ position: sticky;
16
+ right: 0;
17
+ z-index: 2;
18
+ }
19
+
20
+ thead:first-of-type {
21
+ position: sticky;
22
+ top: 0;
23
+ z-index: 3;
24
+ }
25
+
26
+ tfoot:first-of-type {
27
+ position: sticky;
28
+ bottom: 0;
29
+ z-index: 3;
30
+ }
31
+ }
32
+
33
+ /* TODO: Double check */
34
+ :where(.tcn-th[data-should-fill="true"]) {
35
+ width: auto;
36
+ }
37
+ }
@@ -0,0 +1,132 @@
1
+ import React from 'react';
2
+ import clsx from 'clsx';
3
+ import styles from './table.module.css';
4
+ import { Box, type BoxProps } from '../../stacks/index.js';
5
+
6
+ export interface TTableProps extends React.TableHTMLAttributes<HTMLTableElement> {
7
+ // Makes the header, footer and first and last column (th only) sticky
8
+ sticky?: boolean;
9
+ }
10
+
11
+ export const TTable = ({
12
+ children,
13
+ className,
14
+ sticky = false,
15
+ ...props
16
+ }: React.PropsWithChildren<TTableProps>) => {
17
+ return (
18
+ <table
19
+ data-is-sticky={sticky}
20
+ className={clsx('tcn-table', styles.table, className)}
21
+ {...props}
22
+ >
23
+ {children}
24
+ </table>
25
+ );
26
+ };
27
+
28
+ export interface THeadProps extends React.HTMLAttributes<HTMLTableSectionElement> {}
29
+
30
+ // Header
31
+ export const THead = ({
32
+ children,
33
+ className,
34
+ ...props
35
+ }: React.PropsWithChildren<THeadProps>) => {
36
+ return (
37
+ <thead className={clsx('tcn-thead', className)} {...props}>
38
+ {children}
39
+ </thead>
40
+ );
41
+ };
42
+
43
+ // Body
44
+ export const TBody = ({
45
+ children,
46
+ className,
47
+ ...props
48
+ }: React.PropsWithChildren<React.HTMLAttributes<HTMLTableSectionElement>>) => {
49
+ return (
50
+ <tbody className={clsx('tcn-tbody', className)} {...props}>
51
+ {children}
52
+ </tbody>
53
+ );
54
+ };
55
+
56
+ export interface TFootProps extends React.HTMLAttributes<HTMLTableSectionElement> {}
57
+
58
+ // Footer
59
+ export const TFoot = ({
60
+ children,
61
+ className,
62
+ ...props
63
+ }: React.PropsWithChildren<TFootProps>) => {
64
+ return (
65
+ <tfoot className={clsx('tcn-tfoot', className)} {...props}>
66
+ {children}
67
+ </tfoot>
68
+ );
69
+ };
70
+
71
+ export interface TRProps extends React.HTMLAttributes<HTMLTableRowElement> {
72
+ isSelected?: boolean;
73
+ }
74
+
75
+ // Row
76
+ export const TR = ({
77
+ children,
78
+ className,
79
+ isSelected = false,
80
+ ...props
81
+ }: React.PropsWithChildren<TRProps>) => {
82
+ return (
83
+ <tr
84
+ className={clsx('tcn-tr', className)}
85
+ aria-selected={isSelected}
86
+ data-is-selected={isSelected}
87
+ {...props}
88
+ >
89
+ {children}
90
+ </tr>
91
+ );
92
+ };
93
+
94
+ export interface CellSharedProps extends Omit<BoxProps<HTMLTableCellElement>, 'as'> {
95
+ fill?: boolean;
96
+ }
97
+
98
+ export interface THProps
99
+ extends CellSharedProps,
100
+ React.ThHTMLAttributes<HTMLTableCellElement> {}
101
+
102
+ // Header Cell
103
+ export const TH = ({
104
+ children,
105
+ className,
106
+ fill = false,
107
+ ...props
108
+ }: React.PropsWithChildren<THProps>) => {
109
+ return (
110
+ <Box as="th" data-should-fill={fill} className={clsx('tcn-th', className)} {...props}>
111
+ {children}
112
+ </Box>
113
+ );
114
+ };
115
+
116
+ export interface TDProps
117
+ extends CellSharedProps,
118
+ React.TdHTMLAttributes<HTMLTableCellElement> {}
119
+
120
+ // Data Cell
121
+ export const TD = ({
122
+ children,
123
+ className,
124
+ fill = false,
125
+ ...props
126
+ }: React.PropsWithChildren<TDProps>) => {
127
+ return (
128
+ <td data-should-fill={fill} className={clsx('tcn-td', className)} {...props}>
129
+ {children}
130
+ </td>
131
+ );
132
+ };
@@ -1,4 +1,4 @@
1
- import { forwardRef, type FC, type PropsWithChildren } from 'react';
1
+ import { forwardRef, useCallback, type FC, type PropsWithChildren } from 'react';
2
2
  import { type BaseButtonProps } from '../../../actions/index.js';
3
3
  import { HStack, type HStackProps } from '../../../stacks/h_stack.js';
4
4
  import clsx from 'clsx';
@@ -6,15 +6,59 @@ import { Toggle } from '../../../actions/toggle/toggle.js';
6
6
 
7
7
  export type TabsListProps = HStackProps;
8
8
 
9
+ const navigateTabs = (event: React.KeyboardEvent<HTMLDivElement>) => {
10
+ const tabs = Array.from(event.currentTarget.querySelectorAll(':scope > [role="tab"]'));
11
+ const currentTab = event.currentTarget.querySelector(':focus');
12
+ const currentIndex = currentTab ? tabs.indexOf(currentTab) : -1;
13
+ if (currentIndex === -1) return; // Exit if the focused element is not a tab
14
+ let newIndex = 0;
15
+
16
+ switch (event.key) {
17
+ case 'ArrowRight':
18
+ newIndex = (currentIndex + 1) % tabs.length;
19
+ break;
20
+ case 'ArrowLeft':
21
+ newIndex = (currentIndex - 1 + tabs.length) % tabs.length;
22
+ break;
23
+ case 'Home':
24
+ newIndex = 0;
25
+ break;
26
+ case 'End':
27
+ newIndex = tabs.length - 1;
28
+ break;
29
+ default:
30
+ return; // Exit if the key is not recognized
31
+ }
32
+
33
+ event.preventDefault();
34
+ event.stopPropagation();
35
+ tabs[newIndex]['focus']();
36
+ };
37
+
9
38
  export const TabsList: FC<PropsWithChildren<TabsListProps>> = ({
10
39
  children,
11
40
  className,
12
41
  role = 'tablist',
13
42
  as = 'menu',
43
+ onKeyDown,
14
44
  ...props
15
45
  }) => {
46
+ const handleKeyDown = useCallback(
47
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
48
+ navigateTabs(event);
49
+ onKeyDown && onKeyDown(event);
50
+ },
51
+ [onKeyDown]
52
+ );
53
+
16
54
  return (
17
- <HStack as={as} role={role} className={clsx('tcn-tabs-list', className)} {...props}>
55
+ <HStack
56
+ onKeyDown={handleKeyDown}
57
+ as={as}
58
+ role={role}
59
+ className={clsx('tcn-tabs-list', className)}
60
+ {...props}
61
+ >
18
62
  {children}
19
63
  </HStack>
20
64
  );
@@ -13,7 +13,7 @@ export interface TabLinkProps
13
13
  TabLinkOwnProps {}
14
14
 
15
15
  export const TabLink = forwardRef<HTMLButtonElement, PropsWithChildren<TabLinkProps>>(
16
- ({ children, value, onClick, minWidth, maxWidth, ...props }, forwardedRef) => {
16
+ ({ children, value, onClick, minWidth, maxWidth, id, ...props }, forwardedRef) => {
17
17
  const { ref: internalRef, isMatch } = useTabLink(value);
18
18
  const state = useTabs();
19
19
  const ref = useForkRef(internalRef, forwardedRef);
@@ -36,6 +36,9 @@ export const TabLink = forwardRef<HTMLButtonElement, PropsWithChildren<TabLinkPr
36
36
  onClick={handleClick}
37
37
  minWidth={pickMinWidth}
38
38
  maxWidth={pickMaxWidth}
39
+ id={`tab-${value}`}
40
+ aria-controls={`tabpanel-${value}`}
41
+ tabIndex={isMatch ? 0 : -1}
39
42
  {...props}
40
43
  >
41
44
  {children}
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  import type { FC, PropsWithChildren } from 'react';
2
3
  import { useTabs } from './context.js';
3
4
 
@@ -8,5 +9,14 @@ export interface TabProps {
8
9
  export const Tab: FC<PropsWithChildren<TabProps>> = ({ value, children }) => {
9
10
  const state = useTabs();
10
11
  if (state.value !== value) return null;
12
+
13
+ if (children && React.isValidElement(children)) {
14
+ return React.cloneElement(children, {
15
+ ...children.props,
16
+ id: children.props.id ?? `tabpanel-${value}`,
17
+ role: children.props.role ?? 'tabpanel',
18
+ });
19
+ }
20
+
11
21
  return children;
12
22
  };
@@ -12,7 +12,7 @@ import { removeUndefinedProperties } from '../utils/remove_undefined_properties.
12
12
  import styles from '../stack.module.css';
13
13
  import { HandleProps } from './handle_props.js';
14
14
 
15
- export interface BoxProps extends HTMLAttributes<HTMLElement> {
15
+ export interface BoxProps<T extends HTMLElement = HTMLElement> extends HTMLAttributes<T> {
16
16
  as?: string;
17
17
  style?: React.CSSProperties;
18
18
  className?: string;
@@ -31,13 +31,13 @@ export const ModalStory = () => {
31
31
  <Header>
32
32
  <Title>Modal Title</Title>
33
33
  <Spacer />
34
- <Button utility hierarchy="tertiary" size="md" onClick={toggle}>
34
+ <Button utility hierarchy="tertiary" onClick={toggle}>
35
35
  <CrossIcon />
36
36
  </Button>
37
- <Button hierarchy="secondary" size="md" onClick={toggle}>
37
+ <Button utility hierarchy="secondary" onClick={toggle}>
38
38
  <CrossIcon />
39
39
  </Button>
40
- <Button utility hierarchy="primary" size="md" onClick={toggle}>
40
+ <Button utility hierarchy="primary" onClick={toggle}>
41
41
  <CrossIcon />
42
42
  </Button>
43
43
  </Header>
@@ -47,10 +47,10 @@ export const ModalStory = () => {
47
47
  <Button utility hierarchy="tertiary">
48
48
  <BugIcon />
49
49
  </Button>
50
- <Button hierarchy="secondary">
50
+ <Button utility hierarchy="secondary">
51
51
  <BugIcon />
52
52
  </Button>
53
- <Button utility size="md" hierarchy="primary">
53
+ <Button utility hierarchy="primary">
54
54
  <BugIcon />
55
55
  </Button>
56
56
  </UtilityBar>