uikit-react-public 0.11.24 → 0.14.21

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 (184) hide show
  1. package/dist/components/Badge/Badge.d.ts +6 -0
  2. package/dist/components/Badge/Badge.stories.d.ts +15 -0
  3. package/dist/components/Badge/index.d.ts +2 -0
  4. package/dist/components/Button/Button.d.ts +2 -1
  5. package/dist/components/CookieNotice/CookieNotice.d.ts +16 -0
  6. package/dist/components/CookieNotice/index.d.ts +2 -0
  7. package/dist/components/Dialog/BaseDialog.d.ts +7 -2
  8. package/dist/components/FileInput/FileInput.d.ts +8 -0
  9. package/dist/components/FileInput/FileInput.stories.d.ts +16 -0
  10. package/dist/components/FileInput/__tests__/FileInput.test.d.ts +1 -0
  11. package/dist/components/FileInput/index.d.ts +2 -0
  12. package/dist/components/Header/Header.d.ts +4 -1
  13. package/dist/components/Heading/Heading.d.ts +1 -1
  14. package/dist/components/Link/BaseLink.d.ts +10 -0
  15. package/dist/components/Link/Link.d.ts +5 -10
  16. package/dist/components/Link/Link.stories.d.ts +1 -1
  17. package/dist/components/Link/index.d.ts +1 -1
  18. package/dist/components/Menu/MenuContent.d.ts +1 -1
  19. package/dist/components/Menu/MenuItem.d.ts +2 -0
  20. package/dist/components/Menu/MenuSection.d.ts +1 -1
  21. package/dist/components/Search/Search.d.ts +16 -0
  22. package/dist/components/Search/Search.stories.d.ts +34 -0
  23. package/dist/components/Search/__tests__/Search.test.d.ts +1 -0
  24. package/dist/components/Search/index.d.ts +2 -0
  25. package/dist/components/Select/Select.d.ts +1 -1
  26. package/dist/components/Select/Select.stories.d.ts +3 -7
  27. package/dist/components/Select/Select.types.d.ts +19 -14
  28. package/dist/components/Select/subcomponents/CustomOption.d.ts +1 -1
  29. package/dist/components/Select/subcomponents/CustomSelect.d.ts +1 -2
  30. package/dist/components/Select/subcomponents/Panel.d.ts +1 -1
  31. package/dist/components/Select/subcomponents/VisibleField.d.ts +4 -4
  32. package/dist/components/StandaloneLink/StandaloneLink.d.ts +12 -0
  33. package/dist/components/StandaloneLink/StandaloneLink.stories.d.ts +13 -0
  34. package/dist/components/StandaloneLink/__tests__/StandaloneLink.test.d.ts +1 -0
  35. package/dist/components/StandaloneLink/index.d.ts +2 -0
  36. package/dist/components/Table/Table.d.ts +10 -8
  37. package/dist/components/Table/Table.stories.d.ts +21 -0
  38. package/dist/components/Table/Table.types.d.ts +11 -0
  39. package/dist/components/Table/__tests__/Table.test.d.ts +1 -0
  40. package/dist/components/Table/index.d.ts +2 -1
  41. package/dist/components/Table/subcomponents/Body.d.ts +4 -0
  42. package/dist/components/Table/subcomponents/Cell/Cell.d.ts +12 -0
  43. package/dist/components/Table/subcomponents/Cell/Cell.stories.d.ts +313 -0
  44. package/dist/components/Table/subcomponents/Cell/CellContent.d.ts +10 -0
  45. package/dist/components/Table/subcomponents/Cell/__tests__/Cell.test.d.ts +1 -0
  46. package/dist/components/Table/subcomponents/Head.d.ts +4 -0
  47. package/dist/components/Table/subcomponents/HeadCell/HeadCell.d.ts +13 -0
  48. package/dist/components/Table/subcomponents/HeadCell/HeadCell.stories.d.ts +312 -0
  49. package/dist/components/Table/subcomponents/HeadCell/HeadCellContent.d.ts +10 -0
  50. package/dist/components/Table/subcomponents/HeadCell/__tests__/HeadCell.test.d.ts +1 -0
  51. package/dist/components/Table/subcomponents/Row.d.ts +5 -0
  52. package/dist/components/Table/subcomponents/SortIcon.d.ts +7 -0
  53. package/dist/components/Table/subcomponents/index.d.ts +10 -0
  54. package/dist/components/Tabs/Tab.d.ts +1 -1
  55. package/dist/components/Tabs/TabContext.d.ts +1 -0
  56. package/dist/components/Tabs/Tabs.d.ts +3 -1
  57. package/dist/components/Tabs/Tabs.stories.d.ts +3 -0
  58. package/dist/components/Timepicker/Timepicker.d.ts +10 -0
  59. package/dist/components/Timepicker/Timepicker.stories.d.ts +7 -0
  60. package/dist/components/Timepicker/__tests__/Timepicker.test.d.ts +1 -0
  61. package/dist/components/Timepicker/index.d.ts +2 -0
  62. package/dist/components/Timepicker/utils/convertDateToTimeString.d.ts +2 -0
  63. package/dist/components/Timepicker/utils/convertDateToTimeString.test.d.ts +1 -0
  64. package/dist/components/Timepicker/utils/index.d.ts +1 -0
  65. package/dist/components/WeekPicker/WeekPicker.d.ts +3 -0
  66. package/dist/components/WeekPicker/index.d.ts +1 -0
  67. package/dist/components/WeekPicker/subcomponents/CustomDatepicker.d.ts +17 -0
  68. package/dist/components/WeekPicker/subcomponents/DatepickerInput.d.ts +13 -0
  69. package/dist/components/WeekPicker/subcomponents/VisibleField.d.ts +15 -0
  70. package/dist/components/WeekPicker/subcomponents/index.d.ts +3 -0
  71. package/dist/components/index.d.ts +11 -0
  72. package/dist/hooks/index.d.ts +2 -0
  73. package/dist/hooks/useFocusTrap.d.ts +9 -0
  74. package/dist/index.d.ts +1 -0
  75. package/dist/index.js +5703 -4448
  76. package/dist/theme/defaultTheme.d.ts +7 -0
  77. package/dist/theme/useTheme.d.ts +14 -0
  78. package/dist/utils/__tests__/capitalise.test.d.ts +1 -0
  79. package/dist/utils/capitalise.d.ts +2 -0
  80. package/lib/components/Alert/Alert.tsx +7 -1
  81. package/lib/components/Alert/__tests__/__snapshots__/Alert.test.tsx.snap +4 -0
  82. package/lib/components/Badge/Badge.stories.tsx +19 -0
  83. package/lib/components/Badge/Badge.tsx +48 -0
  84. package/lib/components/Badge/index.ts +2 -0
  85. package/lib/components/Breadcrumbs/__tests__/__snapshots__/Breadcrumbs.test.tsx.snap +4 -4
  86. package/lib/components/Button/Button.tsx +5 -2
  87. package/lib/components/Calendar/subcomponents/Grid.tsx +0 -1
  88. package/lib/components/CookieNotice/CookieNotice.tsx +114 -0
  89. package/lib/components/CookieNotice/index.ts +2 -0
  90. package/lib/components/Dialog/BaseDialog.tsx +44 -4
  91. package/lib/components/Field/__tests__/Field.test.tsx +148 -148
  92. package/lib/components/FileInput/FileInput.stories.tsx +70 -0
  93. package/lib/components/FileInput/FileInput.tsx +68 -0
  94. package/lib/components/FileInput/__tests__/FileInput.test.tsx +99 -0
  95. package/lib/components/FileInput/__tests__/__snapshots__/FileInput.test.tsx.snap +91 -0
  96. package/lib/components/FileInput/index.ts +2 -0
  97. package/lib/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +25 -25
  98. package/lib/components/Header/Header.tsx +19 -2
  99. package/lib/components/Header/__tests__/__snapshots__/Header.test.tsx.snap +4 -4
  100. package/lib/components/Heading/Documentation.mdx +1 -1
  101. package/lib/components/Heading/Heading.tsx +1 -1
  102. package/lib/components/Heading/__tests__/Heading.test.tsx +7 -19
  103. package/lib/components/Heading/__tests__/__snapshots__/Heading.test.tsx.snap +7 -7
  104. package/lib/components/Label/Label.tsx +0 -2
  105. package/lib/components/Label/__tests__/__snapshots__/Label.test.tsx.snap +7 -7
  106. package/lib/components/Link/BaseLink.tsx +84 -0
  107. package/lib/components/Link/Link.tsx +72 -32
  108. package/lib/components/Link/__tests__/__snapshots__/link.test.tsx.snap +3 -3
  109. package/lib/components/Link/__tests__/link.test.tsx +6 -13
  110. package/lib/components/Link/index.ts +1 -1
  111. package/lib/components/Menu/Menu.context.tsx +3 -1
  112. package/lib/components/Menu/Menu.tsx +2 -2
  113. package/lib/components/Menu/MenuContent.tsx +5 -5
  114. package/lib/components/Menu/MenuItem.tsx +20 -3
  115. package/lib/components/Menu/MenuSection.tsx +4 -3
  116. package/lib/components/Pagination/PaginationControls.tsx +1 -3
  117. package/lib/components/Search/Search.stories.tsx +41 -0
  118. package/lib/components/Search/Search.tsx +167 -0
  119. package/lib/components/Search/__tests__/Search.test.tsx +94 -0
  120. package/lib/components/Search/__tests__/__snapshots__/Search.test.tsx.snap +179 -0
  121. package/lib/components/Search/index.ts +2 -0
  122. package/lib/components/Select/Select.stories.tsx +8 -35
  123. package/lib/components/Select/Select.tsx +2 -2
  124. package/lib/components/Select/Select.types.ts +20 -15
  125. package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +3 -3
  126. package/lib/components/Select/subcomponents/CustomOption.tsx +22 -9
  127. package/lib/components/Select/subcomponents/CustomSelect.tsx +31 -20
  128. package/lib/components/Select/subcomponents/Panel.tsx +4 -5
  129. package/lib/components/Select/subcomponents/VisibleField.tsx +26 -22
  130. package/lib/components/StandaloneLink/StandaloneLink.stories.tsx +32 -0
  131. package/lib/components/StandaloneLink/StandaloneLink.tsx +183 -0
  132. package/lib/components/StandaloneLink/__tests__/StandaloneLink.test.tsx +57 -0
  133. package/lib/components/StandaloneLink/__tests__/__snapshots__/StandaloneLink.test.tsx.snap +19 -0
  134. package/lib/components/StandaloneLink/index.ts +2 -0
  135. package/lib/components/Table/Table.stories.tsx +337 -0
  136. package/lib/components/Table/Table.tsx +42 -67
  137. package/lib/components/Table/Table.types.ts +14 -0
  138. package/lib/components/Table/__tests__/Table.test.tsx +121 -0
  139. package/lib/components/Table/__tests__/__snapshots__/Table.test.tsx.snap +210 -0
  140. package/lib/components/Table/index.ts +8 -1
  141. package/lib/components/Table/subcomponents/Body.tsx +18 -0
  142. package/lib/components/Table/subcomponents/Cell/Cell.stories.tsx +151 -0
  143. package/lib/components/Table/subcomponents/Cell/Cell.tsx +72 -0
  144. package/lib/components/Table/subcomponents/Cell/CellContent.tsx +91 -0
  145. package/lib/components/Table/subcomponents/Cell/__tests__/Cell.test.tsx +115 -0
  146. package/lib/components/Table/subcomponents/Cell/__tests__/__snapshots__/Cell.test.tsx.snap +107 -0
  147. package/lib/components/Table/subcomponents/Head.tsx +34 -0
  148. package/lib/components/Table/subcomponents/HeadCell/HeadCell.stories.tsx +85 -0
  149. package/lib/components/Table/subcomponents/HeadCell/HeadCell.tsx +99 -0
  150. package/lib/components/Table/subcomponents/HeadCell/HeadCellContent.tsx +61 -0
  151. package/lib/components/Table/subcomponents/HeadCell/__tests__/HeadCell.test.tsx +137 -0
  152. package/lib/components/Table/subcomponents/HeadCell/__tests__/__snapshots__/HeadCell.test.tsx.snap +110 -0
  153. package/lib/components/Table/subcomponents/Row.tsx +49 -0
  154. package/lib/components/Table/subcomponents/SortIcon.tsx +63 -0
  155. package/lib/components/Table/subcomponents/index.ts +14 -0
  156. package/lib/components/Tabs/Tab.tsx +3 -3
  157. package/lib/components/Tabs/TabContext.tsx +1 -0
  158. package/lib/components/Tabs/Tabs.stories.tsx +9 -3
  159. package/lib/components/Tabs/Tabs.tsx +10 -32
  160. package/lib/components/Tabs/__tests__/Tabs.test.tsx +10 -4
  161. package/lib/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +32 -32
  162. package/lib/components/Timepicker/Timepicker.stories.tsx +43 -0
  163. package/lib/components/Timepicker/Timepicker.tsx +96 -0
  164. package/lib/components/Timepicker/__tests__/Timepicker.test.tsx +55 -0
  165. package/lib/components/Timepicker/__tests__/__snapshots__/Timepicker.test.tsx.snap +19 -0
  166. package/lib/components/Timepicker/index.tsx +2 -0
  167. package/lib/components/Timepicker/utils/convertDateToTimeString.test.ts +54 -0
  168. package/lib/components/Timepicker/utils/convertDateToTimeString.ts +10 -0
  169. package/lib/components/Timepicker/utils/index.ts +1 -0
  170. package/lib/components/WeekPicker/WeekPicker.tsx +26 -0
  171. package/lib/components/WeekPicker/index.ts +1 -0
  172. package/lib/components/WeekPicker/subcomponents/CustomDatepicker.tsx +298 -0
  173. package/lib/components/WeekPicker/subcomponents/DatepickerInput.tsx +111 -0
  174. package/lib/components/WeekPicker/subcomponents/VisibleField.tsx +126 -0
  175. package/lib/components/WeekPicker/subcomponents/index.ts +3 -0
  176. package/lib/components/index.ts +17 -0
  177. package/lib/hooks/index.ts +2 -0
  178. package/lib/hooks/useFocusTrap.ts +123 -0
  179. package/lib/index.ts +1 -0
  180. package/lib/theme/defaultTheme.ts +7 -0
  181. package/lib/utils/__tests__/capitalise.test.ts +40 -0
  182. package/lib/utils/capitalise.ts +4 -0
  183. package/package.json +1 -1
  184. package/lib/components/Field/__tests__/__snapshots__/Field.test.tsx.snap +0 -300
@@ -0,0 +1,337 @@
1
+ import { useEffect, useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import Table from './Table';
4
+ import Badge from '../Badge/Badge';
5
+ import Icon from '../Icon/Icon';
6
+ import type { SortPattern } from './Table.types';
7
+
8
+ const meta = {
9
+ title: 'Components/Work in progress/Table',
10
+ component: Table,
11
+ parameters: {
12
+ layout: 'padded',
13
+ },
14
+ tags: ['autodocs'],
15
+ } satisfies Meta<typeof Table>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Simple: Story = {
21
+ name: 'Simple table',
22
+ render: (args) => {
23
+ // These would probably be records from a database API response..
24
+ const exampleData = [
25
+ {
26
+ name: 'Alexander Graham Bell',
27
+ born: 1847,
28
+ occupation: 'Inventor',
29
+ },
30
+ {
31
+ name: 'Francis Crick',
32
+ born: 1916,
33
+ occupation: 'Molecular Biologist',
34
+ },
35
+ {
36
+ name: 'Jeremy Bentham',
37
+ born: 1748,
38
+ occupation: 'Philosopher',
39
+ },
40
+ {
41
+ name: 'John Langbourne',
42
+ born: 1810,
43
+ occupation: 'Cat',
44
+ },
45
+ ];
46
+
47
+ const [data, setData] = useState(exampleData);
48
+
49
+ const [sortingPattern, setSortingPattern] = useState<SortPattern>({
50
+ accessor: null,
51
+ order: null,
52
+ });
53
+
54
+ useEffect(() => {
55
+ // We would expect to sort data on the backend in practice,
56
+ // but sorting on the frontend is also an option.
57
+ const sortedData = sortData(
58
+ exampleData,
59
+ sortingPattern?.accessor as keyof (typeof exampleData)[0],
60
+ sortingPattern?.order
61
+ );
62
+ setData(sortedData);
63
+ }, [sortingPattern]);
64
+
65
+ const handleSortChange = (
66
+ accessor: string,
67
+ order: 'asc' | 'desc' | null,
68
+ _event: React.UIEvent
69
+ ) => {
70
+ setSortingPattern({ accessor, order });
71
+ console.log(`Sorting changed: ${accessor} - ${order}`);
72
+ };
73
+
74
+ // Function that can handle alphanumberical sorting for any given accessor
75
+ // ('accessor' is the column's data name)
76
+ const sortData = (
77
+ data: typeof exampleData,
78
+ accessor: keyof (typeof exampleData)[0] | null,
79
+ order: 'asc' | 'desc' | null
80
+ ) => {
81
+ if (!accessor || order === null) return data;
82
+ return [...data].sort((a, b) => {
83
+ if (a[accessor] < b[accessor]) return order === 'asc' ? -1 : 1;
84
+ if (a[accessor] > b[accessor]) return order === 'asc' ? 1 : -1;
85
+ return 0;
86
+ });
87
+ };
88
+
89
+ return (
90
+ <Table {...args}>
91
+ <Table.Head>
92
+ <Table.HeadCell
93
+ accessor='name'
94
+ sort={
95
+ sortingPattern?.accessor === 'name' ? sortingPattern.order : null
96
+ }
97
+ onSortChange={handleSortChange}
98
+ >
99
+ Name
100
+ </Table.HeadCell>
101
+ <Table.HeadCell
102
+ accessor='born'
103
+ sort={
104
+ sortingPattern?.accessor === 'born' ? sortingPattern.order : null
105
+ }
106
+ onSortChange={handleSortChange}
107
+ >
108
+ Birth year
109
+ </Table.HeadCell>
110
+ <Table.HeadCell
111
+ accessor='occupation'
112
+ sort={
113
+ sortingPattern?.accessor === 'occupation'
114
+ ? sortingPattern.order
115
+ : null
116
+ }
117
+ onSortChange={handleSortChange}
118
+ >
119
+ Occupation
120
+ </Table.HeadCell>
121
+ </Table.Head>
122
+ <Table.Body>
123
+ {data.map((record, index) => (
124
+ <Table.Row key={index}>
125
+ <Table.Cell>{record.name}</Table.Cell>
126
+ <Table.Cell variant='numeric'>
127
+ <time dateTime={record.born.toString()}>{record.born}</time>
128
+ </Table.Cell>
129
+ <Table.Cell>{record.occupation}</Table.Cell>
130
+ </Table.Row>
131
+ ))}
132
+ </Table.Body>
133
+ </Table>
134
+ );
135
+ },
136
+ };
137
+
138
+ export const CellVariants: Story = {
139
+ name: 'Cell variants',
140
+ render: (args) => {
141
+ const [selectedRows, setSelectedRows] = useState([false, false, false]);
142
+ const selectRow = (index: number) => (checked: boolean) => {
143
+ const newSelectedRows = [...selectedRows];
144
+ newSelectedRows[index] = checked;
145
+ setSelectedRows(newSelectedRows);
146
+ };
147
+ const handleButtonClick = (event: React.UIEvent, row: number) => {
148
+ console.log(`Button clicked for row: ${row}`, event);
149
+ alert(`Button clicked for row: ${row}`);
150
+ };
151
+
152
+ return (
153
+ <Table {...args}>
154
+ <Table.Head>
155
+ <Table.HeadCell showSortIcon={false}>Default (text)</Table.HeadCell>
156
+ <Table.HeadCell showSortIcon={false}>Text emphasised</Table.HeadCell>
157
+ <Table.HeadCell showSortIcon={false}>Numeric</Table.HeadCell>
158
+ <Table.HeadCell showSortIcon={false}>
159
+ Numeric emphasised
160
+ </Table.HeadCell>
161
+ <Table.HeadCell showSortIcon={false}>Checkbox</Table.HeadCell>
162
+ <Table.HeadCell showSortIcon={false}>Badge</Table.HeadCell>
163
+ <Table.HeadCell showSortIcon={false}>Icon</Table.HeadCell>
164
+ <Table.HeadCell showSortIcon={false}>Icon & text</Table.HeadCell>
165
+ <Table.HeadCell showSortIcon={false}>Button</Table.HeadCell>
166
+ <Table.HeadCell showSortIcon={false}>Link</Table.HeadCell>
167
+ </Table.Head>
168
+ <Table.Body>
169
+ <Table.Row selected={selectedRows[0]}>
170
+ <Table.Cell>Row 1</Table.Cell>
171
+ <Table.Cell>
172
+ <strong>Row 1</strong>
173
+ </Table.Cell>
174
+ <Table.Cell variant='numeric'>12345</Table.Cell>
175
+ <Table.Cell variant='numeric'>
176
+ <strong>12345</strong>
177
+ </Table.Cell>
178
+ <Table.Cell
179
+ variant='checkbox'
180
+ checked={selectedRows[0]}
181
+ onCheck={selectRow(0)}
182
+ />
183
+ <Table.Cell>
184
+ <Badge>Row 1</Badge>
185
+ </Table.Cell>
186
+ <Table.Cell icon={<Icon.File />}></Table.Cell>
187
+ <Table.Cell icon={<Icon.Compass size={20} />}>Row 1</Table.Cell>
188
+ <Table.Cell
189
+ variant='button'
190
+ onButtonClick={(event) => handleButtonClick(event, 1)}
191
+ >
192
+ Row 1
193
+ </Table.Cell>
194
+ <Table.Cell>
195
+ <a
196
+ href='https://www.ucl.ac.uk'
197
+ target='_blank'
198
+ rel='noopener noreferrer'
199
+ >
200
+ Visit us
201
+ </a>
202
+ </Table.Cell>
203
+ </Table.Row>
204
+ <Table.Row selected={selectedRows[1]}>
205
+ <Table.Cell>Row 2</Table.Cell>
206
+ <Table.Cell>
207
+ <strong>Row 2</strong>
208
+ </Table.Cell>
209
+ <Table.Cell variant='numeric'>67890</Table.Cell>
210
+ <Table.Cell variant='numeric'>
211
+ <strong>67890</strong>
212
+ </Table.Cell>
213
+ <Table.Cell
214
+ variant='checkbox'
215
+ checked={selectedRows[1]}
216
+ onCheck={selectRow(1)}
217
+ />
218
+ <Table.Cell>
219
+ <Badge>Row 2</Badge>
220
+ </Table.Cell>
221
+ <Table.Cell icon={<Icon.Anchor />}></Table.Cell>
222
+ <Table.Cell icon={<Icon.Folder size={20} />}>Row 2</Table.Cell>
223
+ <Table.Cell
224
+ variant='button'
225
+ onButtonClick={(event) => handleButtonClick(event, 2)}
226
+ >
227
+ Row 2
228
+ </Table.Cell>
229
+ <Table.Cell>
230
+ <a
231
+ href='https://www.ucl.ac.uk'
232
+ target='_blank'
233
+ rel='noopener noreferrer'
234
+ >
235
+ Visit us
236
+ </a>
237
+ </Table.Cell>
238
+ </Table.Row>
239
+ <Table.Row selected={selectedRows[2]}>
240
+ <Table.Cell>Row 3</Table.Cell>
241
+ <Table.Cell>
242
+ <strong>Row 3</strong>
243
+ </Table.Cell>
244
+ <Table.Cell variant='numeric'>11223</Table.Cell>
245
+ <Table.Cell variant='numeric'>
246
+ <strong>11223</strong>
247
+ </Table.Cell>
248
+ <Table.Cell
249
+ variant='checkbox'
250
+ checked={selectedRows[2]}
251
+ onCheck={selectRow(2)}
252
+ />
253
+ <Table.Cell>
254
+ <Badge>Row 3</Badge>
255
+ </Table.Cell>
256
+ <Table.Cell icon={<Icon.Aperture />}></Table.Cell>
257
+ <Table.Cell icon={<Icon.PenTool size={20} />}>Row 3</Table.Cell>
258
+ <Table.Cell
259
+ variant='button'
260
+ onButtonClick={(event) => handleButtonClick(event, 3)}
261
+ >
262
+ Row 3
263
+ </Table.Cell>
264
+ <Table.Cell>
265
+ <a
266
+ href='https://www.ucl.ac.uk'
267
+ target='_blank'
268
+ rel='noopener noreferrer'
269
+ >
270
+ Visit us
271
+ </a>
272
+ </Table.Cell>
273
+ </Table.Row>
274
+ </Table.Body>
275
+ </Table>
276
+ );
277
+ },
278
+ };
279
+
280
+ export const WithHeaderCellCheckbox: Story = {
281
+ name: 'With header cell checkbox',
282
+ render: (args) => {
283
+ const data = [
284
+ { course: 'Computer Science BSc', code: 'G400' },
285
+ { course: 'Art and Technology BA', code: 'W102' },
286
+ { course: 'Robotics and Artificial Intelligence MEng', code: 'H700' },
287
+ { course: 'Urban Studies BSc', code: 'K440' },
288
+ ];
289
+
290
+ const [selectedRows, setSelectedRows] = useState([
291
+ false,
292
+ false,
293
+ false,
294
+ false,
295
+ ]);
296
+
297
+ const handleAllRowsSelect = (checked: boolean) => {
298
+ const newSelectedRows = selectedRows.map(() => checked);
299
+ setSelectedRows(newSelectedRows);
300
+ console.log('All rows selected:', checked);
301
+ };
302
+
303
+ return (
304
+ <Table {...args}>
305
+ <Table.Head>
306
+ <Table.HeadCell
307
+ variant='checkbox'
308
+ checked={selectedRows.every(Boolean)}
309
+ onCheck={handleAllRowsSelect}
310
+ />
311
+ <Table.HeadCell showSortIcon={false}>Course</Table.HeadCell>
312
+ <Table.HeadCell showSortIcon={false}>Code</Table.HeadCell>
313
+ </Table.Head>
314
+ <Table.Body>
315
+ {data.map((record, index) => (
316
+ <Table.Row
317
+ key={index}
318
+ selected={selectedRows[index]}
319
+ >
320
+ <Table.Cell
321
+ variant='checkbox'
322
+ checked={selectedRows[index]}
323
+ onCheck={(checked) => {
324
+ const newSelectedRows = [...selectedRows];
325
+ newSelectedRows[index] = checked;
326
+ setSelectedRows(newSelectedRows);
327
+ }}
328
+ />
329
+ <Table.Cell>{record.course}</Table.Cell>
330
+ <Table.Cell>{record.code}</Table.Cell>
331
+ </Table.Row>
332
+ ))}
333
+ </Table.Body>
334
+ </Table>
335
+ );
336
+ },
337
+ };
@@ -1,68 +1,43 @@
1
- import {
2
- memo,
3
- TableHTMLAttributes,
4
- forwardRef,
5
- } from 'react';
6
1
  import { css, cx } from '@emotion/css';
7
- import { useTheme } from '../..';
8
-
9
- export const NAME = 'ucl-uikit-table';
10
-
11
- export interface TableProps
12
- extends TableHTMLAttributes<HTMLTableElement> {
13
- testId?: string;
14
- }
15
-
16
- export type Ref = HTMLTableElement;
17
-
18
- const Table = forwardRef<Ref, TableProps>(
19
- (
20
- { testId = NAME, className, children, ...props },
21
- ref
22
- ) => {
23
- const [theme] = useTheme();
24
-
25
- const baseStyle = css`
26
- border-collapse: collapse;
27
- font-family: ${theme.font.family.primary};
28
- color: ${theme.color.text.primary};
29
- text-align: left;
30
-
31
- th,
32
- td {
33
- padding: ${theme.padding.p8};
34
- border-bottom: 1px solid
35
- ${theme.color.neutral.grey20};
36
- }
37
-
38
- thead {
39
- th {
40
- padding-bottom: ${theme.padding.p2};
41
- font-weight: 400;
42
- color: ${theme.color.text.secondary};
43
- }
44
- }
45
-
46
- tbody {
47
- th {
48
- font-weight: 700;
49
- }
50
- }
51
- `;
52
-
53
- const style = cx(NAME, baseStyle, className);
54
-
55
- return (
56
- <table
57
- ref={ref}
58
- className={style}
59
- data-testid={testId}
60
- {...props}
61
- >
62
- {children}
63
- </table>
64
- );
65
- }
66
- );
67
-
68
- export default memo(Table);
2
+ import { Head, HeadCell, Body, Row, Cell } from './subcomponents';
3
+ import { useTheme } from '../../theme';
4
+ import type { TableProps } from './Table.types';
5
+
6
+ const NAME = 'ucl-uikit-table';
7
+
8
+ const Table = ({
9
+ testId = NAME,
10
+ className,
11
+ children,
12
+ ref,
13
+ ...props
14
+ }: TableProps) => {
15
+ const [theme] = useTheme();
16
+
17
+ const baseStyle = css`
18
+ font-family: ${theme.font.family.primary};
19
+ font-size: ${theme.font.size.f14};
20
+ border-collapse: collapse;
21
+ `;
22
+
23
+ const style = cx(baseStyle, className, NAME);
24
+
25
+ return (
26
+ <table
27
+ className={style}
28
+ data-testid={testId}
29
+ ref={ref}
30
+ {...props}
31
+ >
32
+ {children}
33
+ </table>
34
+ );
35
+ };
36
+
37
+ Table.Head = Head;
38
+ Table.HeadCell = HeadCell;
39
+ Table.Body = Body;
40
+ Table.Row = Row;
41
+ Table.Cell = Cell;
42
+
43
+ export default Table;
@@ -0,0 +1,14 @@
1
+ export type ColumnVariant = 'default' | 'numeric' | 'checkbox' | 'button';
2
+
3
+ export type SortOrder = 'asc' | 'desc' | null;
4
+
5
+ export type SortPattern = {
6
+ accessor: string | null;
7
+ order: SortOrder;
8
+ };
9
+
10
+ export interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
11
+ testId?: string;
12
+ className?: string;
13
+ ref?: React.Ref<HTMLTableElement>;
14
+ }
@@ -0,0 +1,121 @@
1
+ import { createRef } from 'react';
2
+ import { describe, expect, test } from 'vitest';
3
+ import { render, screen, within } from '@testing-library/react';
4
+ import Table from '../Table';
5
+ import { ThemeContextProvider } from '../../../theme/useTheme';
6
+
7
+ describe('Table', () => {
8
+ // Snapshot tests
9
+
10
+ test('Snapshot: basic table', () => {
11
+ const renderResult = render(
12
+ <ThemeContextProvider>
13
+ <Table>
14
+ <Table.Head>
15
+ <Table.HeadCell>Header Cell 1</Table.HeadCell>
16
+ <Table.HeadCell>Header Cell 2</Table.HeadCell>
17
+ <Table.HeadCell>Header Cell 3</Table.HeadCell>
18
+ </Table.Head>
19
+ <Table.Body>
20
+ <Table.Row>
21
+ <Table.Cell>Test Cell 1</Table.Cell>
22
+ <Table.Cell>Test Cell 2</Table.Cell>
23
+ <Table.Cell>Test Cell 3</Table.Cell>
24
+ </Table.Row>
25
+ </Table.Body>
26
+ </Table>
27
+ </ThemeContextProvider>
28
+ );
29
+ expect(renderResult.container.firstChild).toMatchSnapshot();
30
+ });
31
+
32
+ // Unit tests
33
+
34
+ test('Can be found via default test ID', () => {
35
+ const defaultTestId = 'ucl-uikit-table';
36
+ render(
37
+ <ThemeContextProvider>
38
+ <Table>
39
+ <Table.Body>
40
+ <Table.Row>
41
+ <Table.Cell>Test Cell</Table.Cell>
42
+ </Table.Row>
43
+ </Table.Body>
44
+ </Table>
45
+ </ThemeContextProvider>
46
+ );
47
+ expect(screen.getByTestId(defaultTestId)).toBeInTheDocument();
48
+ });
49
+
50
+ test('Can be found via custom test ID', () => {
51
+ const customTestId = 'custom-table-test-id';
52
+ render(
53
+ <ThemeContextProvider>
54
+ <Table testId={customTestId}>
55
+ <Table.Body>
56
+ <Table.Row>
57
+ <Table.Cell>Test Cell</Table.Cell>
58
+ </Table.Row>
59
+ </Table.Body>
60
+ </Table>
61
+ </ThemeContextProvider>
62
+ );
63
+ expect(screen.getByTestId(customTestId)).toBeInTheDocument();
64
+ });
65
+
66
+ test('Can find all subcomponents from Table via testId', () => {
67
+ const testId = 'table-with-subcomponents';
68
+ render(
69
+ <ThemeContextProvider>
70
+ <Table testId={testId}>
71
+ <Table.Head>
72
+ <Table.HeadCell>Header Cell</Table.HeadCell>
73
+ </Table.Head>
74
+ <Table.Body>
75
+ <Table.Row>
76
+ <Table.Cell>Body Cell</Table.Cell>
77
+ </Table.Row>
78
+ </Table.Body>
79
+ </Table>
80
+ </ThemeContextProvider>
81
+ );
82
+ const table = screen.getByTestId(testId);
83
+ expect(table).toBeInTheDocument();
84
+ // <Table.Head>
85
+ expect(
86
+ within(table).getByTestId('ucl-uikit-table__head')
87
+ ).toBeInTheDocument();
88
+ // <Table.HeadCell>
89
+ expect(
90
+ within(table).getByTestId('ucl-uikit-table__head-cell')
91
+ ).toBeInTheDocument();
92
+ // <Table.Body>
93
+ expect(
94
+ within(table).getByTestId('ucl-uikit-table__body')
95
+ ).toBeInTheDocument();
96
+ // <Table.Row>
97
+ expect(
98
+ within(table).getByTestId('ucl-uikit-table__row')
99
+ ).toBeInTheDocument();
100
+ // <Table.Cell>
101
+ expect(
102
+ within(table).getByTestId('ucl-uikit-table__cell')
103
+ ).toBeInTheDocument();
104
+ });
105
+
106
+ test('Can get Table from ref prop', () => {
107
+ const ref = createRef<HTMLTableElement>();
108
+ render(
109
+ <ThemeContextProvider>
110
+ <Table ref={ref}>
111
+ <Table.Body>
112
+ <Table.Row>
113
+ <Table.Cell>Test Cell</Table.Cell>
114
+ </Table.Row>
115
+ </Table.Body>
116
+ </Table>
117
+ </ThemeContextProvider>
118
+ );
119
+ expect(ref.current).toBeInstanceOf(HTMLTableElement);
120
+ });
121
+ });