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,94 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { vi } from 'vitest';
5
+ import { ThemeContextProvider } from '../../../theme/useTheme';
6
+ import Search from '../Search';
7
+
8
+ describe('Search', () => {
9
+ // Snapshot tests
10
+
11
+ test('snapshot: no props', () => {
12
+ const renderResult = render(
13
+ <ThemeContextProvider>
14
+ <Search />
15
+ </ThemeContextProvider>
16
+ );
17
+ expect(renderResult.container.firstChild).toMatchSnapshot();
18
+ });
19
+
20
+ test('snapshot: testId prop', () => {
21
+ const renderResult = render(
22
+ <ThemeContextProvider>
23
+ <Search testId='test123' />
24
+ </ThemeContextProvider>
25
+ );
26
+ expect(renderResult.container.firstChild).toMatchSnapshot();
27
+ });
28
+
29
+ // Interaction tests
30
+
31
+ test('test ID: default', () => {
32
+ render(
33
+ <ThemeContextProvider>
34
+ <Search />
35
+ </ThemeContextProvider>
36
+ );
37
+ const search = screen.getByTestId('ucl-uikit-search');
38
+ expect(search).toBeInTheDocument();
39
+ });
40
+
41
+ test('test ID: custom', () => {
42
+ render(
43
+ <ThemeContextProvider>
44
+ <Search testId='custom-test-id' />
45
+ </ThemeContextProvider>
46
+ );
47
+ const search = screen.getByTestId('custom-test-id');
48
+ expect(search).toBeInTheDocument();
49
+ });
50
+
51
+ test('allows user to type in search input', async () => {
52
+ const user = userEvent.setup();
53
+
54
+ render(
55
+ <ThemeContextProvider>
56
+ <Search />
57
+ </ThemeContextProvider>
58
+ );
59
+
60
+ const input = screen.getByRole('textbox');
61
+
62
+ await user.type(input, 'hello world');
63
+ expect(input).toHaveValue('hello world');
64
+ });
65
+
66
+ test('allows user to click on search button', async () => {
67
+ const user = userEvent.setup();
68
+ render(
69
+ <ThemeContextProvider>
70
+ <Search />
71
+ </ThemeContextProvider>
72
+ );
73
+
74
+ const input = screen.getByTestId('ucl-uikit-search-search-btn');
75
+ await user.click(input);
76
+ });
77
+
78
+ test('calls onSearch when seatch button is clicked', async () => {
79
+ const user = userEvent.setup();
80
+ const mockOnSearch = vi.fn();
81
+
82
+ render(
83
+ <ThemeContextProvider>
84
+ <Search onSearch={mockOnSearch} />
85
+ </ThemeContextProvider>
86
+ );
87
+ const input = screen.getByRole('textbox');
88
+ await user.type(input, 'test search');
89
+ const searchButton = screen.getByTestId('ucl-uikit-search-search-btn');
90
+ await user.click(searchButton);
91
+
92
+ expect(mockOnSearch).toHaveBeenCalledWith('test search');
93
+ });
94
+ });
@@ -0,0 +1,179 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`Search > snapshot: no props 1`] = `
4
+ <div
5
+ class="ucl-uikit-search css-176lya2"
6
+ >
7
+ <span
8
+ class="ucl-uikit-input css-1bpb1dw"
9
+ >
10
+ <input
11
+ class="ucl-uikit-input__input css-181a0dk"
12
+ data-testid="ucl-uikit-search"
13
+ id=""
14
+ placeholder=""
15
+ type="text"
16
+ value=""
17
+ />
18
+ </span>
19
+ <div
20
+ class="css-1mxrgm7"
21
+ >
22
+ <button
23
+ aria-label="Clear search"
24
+ class="ucl-uikit-icon-button css-1317vpi"
25
+ data-testid="ucl-uikit-search-clear-search-btn"
26
+ >
27
+ <svg
28
+ class="ucl-uikit-icon css-148hpxb"
29
+ data-testid="ucl-uikit-icon"
30
+ fill="none"
31
+ height="24"
32
+ stroke="currentColor"
33
+ stroke-linecap="round"
34
+ stroke-linejoin="round"
35
+ stroke-width="2"
36
+ viewBox="0 0 24 24"
37
+ width="24"
38
+ xmlns="http://www.w3.org/2000/svg"
39
+ >
40
+ <line
41
+ x1="18"
42
+ x2="6"
43
+ y1="6"
44
+ y2="18"
45
+ />
46
+ <line
47
+ x1="6"
48
+ x2="18"
49
+ y1="6"
50
+ y2="18"
51
+ />
52
+ </svg>
53
+ </button>
54
+ <div
55
+ class="css-5jwcn5"
56
+ />
57
+ <button
58
+ aria-label="Search"
59
+ class="ucl-uikit-icon-button css-1soyhl2"
60
+ data-testid="ucl-uikit-search-search-btn"
61
+ >
62
+ <svg
63
+ class="ucl-uikit-icon css-148hpxb"
64
+ data-testid="ucl-uikit-icon"
65
+ fill="none"
66
+ height="24"
67
+ stroke="currentColor"
68
+ stroke-linecap="round"
69
+ stroke-linejoin="round"
70
+ stroke-width="2"
71
+ viewBox="0 0 24 24"
72
+ width="24"
73
+ xmlns="http://www.w3.org/2000/svg"
74
+ >
75
+ <circle
76
+ cx="11"
77
+ cy="11"
78
+ r="8"
79
+ />
80
+ <line
81
+ x1="21"
82
+ x2="16.65"
83
+ y1="21"
84
+ y2="16.65"
85
+ />
86
+ </svg>
87
+ </button>
88
+ </div>
89
+ </div>
90
+ `;
91
+
92
+ exports[`Search > snapshot: testId prop 1`] = `
93
+ <div
94
+ class="ucl-uikit-search css-176lya2"
95
+ >
96
+ <span
97
+ class="ucl-uikit-input css-1bpb1dw"
98
+ >
99
+ <input
100
+ class="ucl-uikit-input__input css-181a0dk"
101
+ data-testid="test123"
102
+ id=""
103
+ placeholder=""
104
+ type="text"
105
+ value=""
106
+ />
107
+ </span>
108
+ <div
109
+ class="css-1mxrgm7"
110
+ >
111
+ <button
112
+ aria-label="Clear search"
113
+ class="ucl-uikit-icon-button css-1317vpi"
114
+ data-testid="test123-clear-search-btn"
115
+ >
116
+ <svg
117
+ class="ucl-uikit-icon css-148hpxb"
118
+ data-testid="ucl-uikit-icon"
119
+ fill="none"
120
+ height="24"
121
+ stroke="currentColor"
122
+ stroke-linecap="round"
123
+ stroke-linejoin="round"
124
+ stroke-width="2"
125
+ viewBox="0 0 24 24"
126
+ width="24"
127
+ xmlns="http://www.w3.org/2000/svg"
128
+ >
129
+ <line
130
+ x1="18"
131
+ x2="6"
132
+ y1="6"
133
+ y2="18"
134
+ />
135
+ <line
136
+ x1="6"
137
+ x2="18"
138
+ y1="6"
139
+ y2="18"
140
+ />
141
+ </svg>
142
+ </button>
143
+ <div
144
+ class="css-5jwcn5"
145
+ />
146
+ <button
147
+ aria-label="Search"
148
+ class="ucl-uikit-icon-button css-1soyhl2"
149
+ data-testid="test123-search-btn"
150
+ >
151
+ <svg
152
+ class="ucl-uikit-icon css-148hpxb"
153
+ data-testid="ucl-uikit-icon"
154
+ fill="none"
155
+ height="24"
156
+ stroke="currentColor"
157
+ stroke-linecap="round"
158
+ stroke-linejoin="round"
159
+ stroke-width="2"
160
+ viewBox="0 0 24 24"
161
+ width="24"
162
+ xmlns="http://www.w3.org/2000/svg"
163
+ >
164
+ <circle
165
+ cx="11"
166
+ cy="11"
167
+ r="8"
168
+ />
169
+ <line
170
+ x1="21"
171
+ x2="16.65"
172
+ y1="21"
173
+ y2="16.65"
174
+ />
175
+ </svg>
176
+ </button>
177
+ </div>
178
+ </div>
179
+ `;
@@ -0,0 +1,2 @@
1
+ export { default } from './Search';
2
+ export type { SearchProps } from './Search';
@@ -1,7 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { useArgs } from '@storybook/preview-api';
3
3
  import Select from './Select';
4
- import { Icon } from '../';
5
4
 
6
5
  const meta = {
7
6
  title: 'Components/Ready to use/Select',
@@ -20,8 +19,8 @@ const meta = {
20
19
  testId: {
21
20
  control: { type: 'text' },
22
21
  },
23
- width: {
24
- control: { type: 'range', min: 80, max: 624, step: 1 },
22
+ lineBreak: {
23
+ control: { type: 'boolean' },
25
24
  },
26
25
  },
27
26
  args: {
@@ -41,7 +40,7 @@ type Story = StoryObj<typeof meta>;
41
40
  export const Default: Story = {
42
41
  render: () => {
43
42
  const [args, updateArgs] = useArgs();
44
- const onValueChange = (value: string) => {
43
+ const onValueChange = (value: string | number) => {
45
44
  updateArgs({ value });
46
45
  };
47
46
  return (
@@ -61,7 +60,7 @@ export const Native: Story = {
61
60
  },
62
61
  render: () => {
63
62
  const [args, updateArgs] = useArgs();
64
- const onValueChange = (value: string) => {
63
+ const onValueChange = (value: string | number) => {
65
64
  updateArgs({ value });
66
65
  };
67
66
  return (
@@ -75,39 +74,13 @@ export const Native: Story = {
75
74
  },
76
75
  };
77
76
 
78
- export const OptionsWithIcons: Story = {
79
- name: 'Options with icons',
80
- args: {
81
- options: [
82
- { label: 'Option 1', value: '1', icon: <Icon.Printer /> },
83
- { label: 'Option 2', value: '2', icon: <Icon.User /> },
84
- { label: 'Option 3', value: '3', icon: <Icon.Info /> },
85
- ],
86
- },
87
- render: () => {
88
- const [args, updateArgs] = useArgs();
89
- const onValueChange = (value: string) => {
90
- updateArgs({ value });
91
- };
92
-
93
- return (
94
- <Select
95
- {...args}
96
- options={args.options}
97
- value={args.value}
98
- onValueChange={onValueChange}
99
- />
100
- );
101
- },
102
- };
103
-
104
77
  export const Disabled: Story = {
105
78
  args: {
106
79
  disabled: true,
107
80
  },
108
81
  render: () => {
109
82
  const [args, updateArgs] = useArgs();
110
- const onValueChange = (value: string) => {
83
+ const onValueChange = (value: string | number) => {
111
84
  updateArgs({ value });
112
85
  };
113
86
 
@@ -126,7 +99,7 @@ export const WithPlaceholder: Story = {
126
99
  args: { placeholder: 'Please select an option' },
127
100
  render: () => {
128
101
  const [args, updateArgs] = useArgs();
129
- const onValueChange = (value: string) => {
102
+ const onValueChange = (value: string | number) => {
130
103
  updateArgs({ value });
131
104
  };
132
105
 
@@ -152,7 +125,7 @@ export const SingleLongOption: Story = {
152
125
  },
153
126
  render: () => {
154
127
  const [args, updateArgs] = useArgs();
155
- const onValueChange = (value: string) => {
128
+ const onValueChange = (value: string | number) => {
156
129
  updateArgs({ value });
157
130
  };
158
131
 
@@ -190,7 +163,7 @@ export const ManyOptions: Story = {
190
163
  },
191
164
  render: () => {
192
165
  const [args, updateArgs] = useArgs();
193
- const onValueChange = (value: string) => {
166
+ const onValueChange = (value: string | number) => {
194
167
  updateArgs({ value });
195
168
  };
196
169
  return (
@@ -1,7 +1,7 @@
1
1
  import { NativeSelect, CustomSelect } from './subcomponents';
2
2
  import { SelectProps } from './Select.types';
3
3
 
4
- const Select = (props: SelectProps) => {
4
+ const Select = <T extends string | number = string>(props: SelectProps<T>) => {
5
5
  if (props.native) {
6
6
  const { onChange, value, ...rest } = props;
7
7
  return (
@@ -14,7 +14,7 @@ const Select = (props: SelectProps) => {
14
14
  } else {
15
15
  const { onValueChange, ...rest } = props;
16
16
  return (
17
- <CustomSelect
17
+ <CustomSelect<T>
18
18
  onValueChange={onValueChange}
19
19
  {...rest}
20
20
  />
@@ -3,7 +3,7 @@
3
3
  * Maps to a native <option> element in <NativeSelect>
4
4
  * or a <CustomOption> in <CustomSelect>
5
5
  */
6
- export type OptionData = {
6
+ export type OptionData<T> = {
7
7
  /**
8
8
  * Display text shown to the user
9
9
  */
@@ -12,23 +12,25 @@ export type OptionData = {
12
12
  * Data-friendly value that is returned when the option is selected
13
13
  * We assume this will be submitted to a server or used in some other way
14
14
  */
15
- value: string;
15
+ value: T;
16
16
  /**
17
- * Optional icon to be displayed to the left of the option text.
18
- * Only used by <CustomOption>
19
- * Takes in an actual <Icon> component
17
+ * Apply additional props to a select option component.
20
18
  */
19
+ optionProps?: Record<string, unknown>;
20
+ /**
21
+ Deprecated
22
+ */
21
23
  icon?: React.ReactNode;
22
24
  };
23
25
 
24
26
  /**
25
27
  * Top level props that <Select> accepts when implemented
26
28
  */
27
- interface BaseSelectProps {
29
+ interface BaseSelectProps<T = string> {
28
30
  /**
29
31
  * An array of option data, to be rendered either natively or custom
30
32
  */
31
- options: OptionData[];
33
+ options: OptionData<T>[];
32
34
  /**
33
35
  * Placeholder text shown when no option is selected
34
36
  * Displayed in visible field of custom implementation
@@ -70,15 +72,17 @@ interface BaseSelectProps {
70
72
  * Internal props for the custom implementation, with <div> as root element
71
73
  * onChange already exists on <div>. We override it.
72
74
  */
73
- export type CustomSelectProps = Omit<
75
+ export type CustomSelectProps<T> = Omit<
74
76
  React.HTMLAttributes<HTMLDivElement>,
75
77
  'onChange'
76
78
  > &
77
- BaseSelectProps & {
79
+ BaseSelectProps<T> & {
78
80
  native?: false;
79
- value?: string | number;
81
+ value?: T;
80
82
  disabled?: boolean;
81
- onValueChange?: (value: string, ev: React.UIEvent) => void;
83
+ lineBreak?: boolean;
84
+ panelClassName?: string;
85
+ onValueChange?: (value: T, ev: React.UIEvent) => void;
82
86
  ref?: React.RefObject<HTMLDivElement | null>;
83
87
  };
84
88
 
@@ -92,16 +96,17 @@ export type NativeSelectProps = React.SelectHTMLAttributes<HTMLSelectElement> &
92
96
  ref?: React.RefObject<HTMLSelectElement | null>;
93
97
  };
94
98
 
95
- export type SelectProps = NativeSelectProps | CustomSelectProps;
99
+ export type SelectProps<T> = NativeSelectProps | CustomSelectProps<T>;
96
100
 
97
101
  /**
98
102
  * Each option as displayed in the Panel of <CustomSelect>
99
103
  * Roughly equivalent to a custom version of <option>
100
104
  */
101
- export interface CustomOptionProps
105
+ export interface CustomOptionProps<T>
102
106
  extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {
103
- value: string;
107
+ value: T;
104
108
  testId?: string;
105
109
  isSelected?: boolean;
106
- onSelect: (event: React.MouseEvent, value: string) => void;
110
+ onSelect: (event: React.MouseEvent, value: T) => void;
111
+ lineBreak?: boolean;
107
112
  }
@@ -4,7 +4,7 @@ exports[`Select > Snapshot: default 1`] = `
4
4
  <div
5
5
  aria-expanded="false"
6
6
  aria-haspopup="listbox"
7
- class="ucl-uikit-select css-1g3qqhm"
7
+ class="ucl-uikit-select css-1g9yg1n"
8
8
  data-testid="ucl-uikit-select"
9
9
  role="combobox"
10
10
  tabindex="0"
@@ -14,10 +14,10 @@ exports[`Select > Snapshot: default 1`] = `
14
14
  data-testid="ucl-uikit-select__visible-field"
15
15
  >
16
16
  <span
17
- class="css-1oagzrk"
17
+ class="css-16t5nns"
18
18
  />
19
19
  <svg
20
- class="ucl-uikit-icon css-1ieo112"
20
+ class="ucl-uikit-icon css-hseitk"
21
21
  data-testid="ucl-uikit-icon"
22
22
  fill="none"
23
23
  height="24"
@@ -5,15 +5,16 @@ import { CustomOptionProps } from '../Select.types';
5
5
 
6
6
  const NAME = 'ucl-uikit-select__option';
7
7
 
8
- const CustomOption = ({
8
+ const CustomOption = <T extends string | number>({
9
9
  value,
10
10
  isSelected = false,
11
11
  onSelect,
12
+ lineBreak = false,
12
13
  testId = NAME,
13
14
  className,
14
15
  children,
15
16
  ...props
16
- }: CustomOptionProps) => {
17
+ }: CustomOptionProps<T>) => {
17
18
  const [theme] = useTheme();
18
19
  const internalRef = useRef<HTMLDivElement>(null);
19
20
 
@@ -33,30 +34,42 @@ const CustomOption = ({
33
34
  };
34
35
 
35
36
  const baseStyle = css`
36
- display: flex;
37
- align-items: center;
38
- justify-content: left;
39
37
  gap: 16px;
40
- width: 100%;
41
38
  min-height: 40px;
42
39
  box-sizing: border-box;
43
- padding: 8px 16px;
40
+ padding: ${theme.padding.p8} ${theme.padding.p16};
44
41
  font-family: ${theme.font.family.primary};
45
42
  font-size: ${theme.font.size.f16};
43
+ line-height: ${theme.font.lineHeight.h140};
46
44
  background-color: ${theme.color.neutral.white};
47
45
  overflow: hidden;
48
- white-space: nowrap;
49
46
 
50
47
  &:hover {
51
48
  background-color: ${theme.color.neutral.grey5};
52
49
  }
53
50
  `;
54
51
 
52
+ const noLineWrapStyle = css`
53
+ white-space: nowrap;
54
+ text-overflow: ellipsis;
55
+ `;
56
+
57
+ const lineBreakStyle = css`
58
+ word-break: break-all;
59
+ `;
60
+
55
61
  const selectedStyle = css`
56
62
  background-color: ${theme.color.neutral.grey5};
57
63
  `;
58
64
 
59
- const style = cx(NAME, baseStyle, isSelected && selectedStyle, className);
65
+ const style = cx(
66
+ NAME,
67
+ baseStyle,
68
+ !lineBreak && noLineWrapStyle,
69
+ lineBreak && lineBreakStyle,
70
+ isSelected && selectedStyle,
71
+ className
72
+ );
60
73
 
61
74
  return (
62
75
  <div
@@ -5,20 +5,36 @@ import { useTheme } from '../../../theme';
5
5
  import type { CustomSelectProps } from '../Select.types';
6
6
 
7
7
  const NAME = 'ucl-uikit-select';
8
- export const DEFAULT_WIDTH_PX = 200;
9
8
 
10
- const CustomSelect = ({
9
+ const CustomSelect = <T extends string | number>({
11
10
  value,
12
11
  options = [],
13
12
  onValueChange,
14
13
  disabled,
15
14
  placeholder,
16
- width = DEFAULT_WIDTH_PX,
15
+ lineBreak = false,
16
+ width,
17
17
  testId = NAME,
18
18
  className,
19
+ panelClassName,
19
20
  ref,
20
21
  ...props
21
- }: CustomSelectProps) => {
22
+ }: CustomSelectProps<T>) => {
23
+ // todo: remove width prop (deprecated)
24
+ if (width !== undefined) {
25
+ console.warn(
26
+ 'Select width prop is deprecated; it has no effect. Use className instead.'
27
+ );
28
+ }
29
+
30
+ const atLeastOneOptionHasIcon = !!options.find(
31
+ (opt) => opt.icon !== undefined
32
+ );
33
+ // todo: remove option icon prop (deprecated)
34
+ if (atLeastOneOptionHasIcon) {
35
+ console.warn('Select option icon prop is deprecated; it has no effect.');
36
+ }
37
+
22
38
  const internalRef = useRef<HTMLDivElement>(null);
23
39
  const effectiveRef = ref || internalRef;
24
40
 
@@ -58,7 +74,7 @@ const CustomSelect = ({
58
74
  };
59
75
 
60
76
  // Used by <CustomOption> and passed as prop
61
- const handleSelect = (event: React.MouseEvent, optionValue: string) => {
77
+ const handleSelect = (event: React.MouseEvent, optionValue: T) => {
62
78
  if (onValueChange) onValueChange(optionValue, event);
63
79
  closePanel();
64
80
  };
@@ -134,7 +150,8 @@ const CustomSelect = ({
134
150
  justify-content: space-between;
135
151
  position: relative;
136
152
  min-width: 80px;
137
- width: fit-content;
153
+ width: 100%;
154
+ max-width: calc(100vw - 64px);
138
155
  height: 48px;
139
156
  box-sizing: border-box;
140
157
  padding: 0 16px;
@@ -156,10 +173,6 @@ const CustomSelect = ({
156
173
  }
157
174
  `;
158
175
 
159
- const widthStyle = css`
160
- width: ${width}px;
161
- `;
162
-
163
176
  const disabledStyle = css`
164
177
  color: ${theme.color.text.disabled};
165
178
  border: ${theme.border.b1} solid ${theme.color.neutral.grey20};
@@ -170,13 +183,7 @@ const CustomSelect = ({
170
183
  }
171
184
  `;
172
185
 
173
- const style = cx(
174
- NAME,
175
- baseStyle,
176
- !!width && widthStyle,
177
- disabled && disabledStyle,
178
- className
179
- );
186
+ const style = cx(NAME, baseStyle, disabled && disabledStyle, className);
180
187
 
181
188
  return (
182
189
  <div
@@ -198,17 +205,21 @@ const CustomSelect = ({
198
205
  disabled={disabled}
199
206
  />
200
207
  {isOpen && (
201
- <Panel role='listbox'>
208
+ <Panel
209
+ className={panelClassName}
210
+ role='listbox'
211
+ >
202
212
  {options.map((option) => (
203
- <CustomOption
213
+ <CustomOption<T>
204
214
  key={option.value}
205
215
  value={option.value}
206
216
  isSelected={value === option.value}
207
217
  onSelect={handleSelect}
218
+ lineBreak={lineBreak}
208
219
  role='option'
209
220
  aria-selected={value === option.value}
221
+ {...option.optionProps}
210
222
  >
211
- {option.icon}
212
223
  {option.label}
213
224
  </CustomOption>
214
225
  ))}