@marigold/components 0.0.3 → 0.3.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 (220) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/dist/ActionGroup/ActionGroup.d.ts +8 -0
  3. package/dist/ActionGroup/ActionGroup.stories.d.ts +5 -0
  4. package/dist/ActionGroup/index.d.ts +1 -0
  5. package/dist/Alert/Alert.d.ts +22 -1
  6. package/dist/Alert/Alert.stories.d.ts +5 -0
  7. package/dist/Badge/Badge.d.ts +5 -0
  8. package/dist/Badge/Badge.stories.d.ts +5 -0
  9. package/dist/Box.d.ts +2 -0
  10. package/dist/Button/Button.d.ts +9 -6
  11. package/dist/Button/Button.stories.d.ts +5 -0
  12. package/dist/Card/Card.d.ts +14 -0
  13. package/dist/Card/Card.stories.d.ts +5 -0
  14. package/dist/Card/index.d.ts +1 -0
  15. package/dist/Checkbox/Checkbox.d.ts +15 -3
  16. package/dist/Checkbox/Checkbox.stories.d.ts +5 -0
  17. package/dist/Checkbox/CheckboxIcons.d.ts +9 -0
  18. package/dist/Column/Column.d.ts +3 -1
  19. package/dist/Column/Column.stories.d.ts +5 -0
  20. package/dist/Columns/Columns.d.ts +2 -2
  21. package/dist/Columns/Columns.stories.d.ts +5 -0
  22. package/dist/Container/Container.stories.d.ts +5 -0
  23. package/dist/Dialog/Dialog.d.ts +12 -2
  24. package/dist/Dialog/Dialog.stories.d.ts +5 -0
  25. package/dist/Dialog/ModalDialog.d.ts +8 -0
  26. package/dist/Divider/Divider.d.ts +5 -0
  27. package/dist/Divider/Divider.stories.d.ts +5 -0
  28. package/dist/Field/Field.d.ts +5 -1
  29. package/dist/Field/Field.stories.d.ts +5 -0
  30. package/dist/Image/Image.d.ts +5 -0
  31. package/dist/Image/Image.stories.d.ts +5 -0
  32. package/dist/Inline/Inline.d.ts +7 -0
  33. package/dist/Inline/Inline.stories.d.ts +5 -0
  34. package/dist/Inline/index.d.ts +1 -0
  35. package/dist/Input/Input.d.ts +5 -0
  36. package/dist/Input/Input.stories.d.ts +5 -0
  37. package/dist/Label/Label.d.ts +14 -2
  38. package/dist/Label/Label.stories.d.ts +5 -0
  39. package/dist/Link/Link.d.ts +10 -6
  40. package/dist/Link/Link.stories.d.ts +5 -0
  41. package/dist/Menu/Menu.d.ts +3 -0
  42. package/dist/Menu/Menu.stories.d.ts +5 -0
  43. package/dist/MenuItem/MenuItem.d.ts +5 -0
  44. package/dist/MenuItem/MenuItem.stories.d.ts +5 -0
  45. package/dist/Message/Message.d.ts +5 -0
  46. package/dist/Message/Message.stories.d.ts +5 -0
  47. package/dist/Provider/MarigoldProvider.d.ts +11 -0
  48. package/dist/Provider/index.d.ts +3 -0
  49. package/dist/Radio/Radio.d.ts +11 -4
  50. package/dist/Radio/Radio.stories.d.ts +5 -0
  51. package/dist/Radio/RadioIcon.d.ts +9 -0
  52. package/dist/Select/ListBox.d.ts +9 -0
  53. package/dist/Select/ListBoxSection.d.ts +9 -0
  54. package/dist/Select/Option.d.ts +9 -0
  55. package/dist/Select/Popover.d.ts +9 -0
  56. package/dist/Select/Select.d.ts +25 -4
  57. package/dist/Select/Select.stories.d.ts +5 -0
  58. package/dist/Slider/Slider.d.ts +5 -0
  59. package/dist/Slider/Slider.stories.d.ts +5 -0
  60. package/dist/Stack/Stack.d.ts +1 -3
  61. package/dist/Stack/Stack.stories.d.ts +5 -0
  62. package/dist/Text/Text.d.ts +17 -10
  63. package/dist/Text/Text.stories.d.ts +5 -0
  64. package/dist/Textarea/Textarea.d.ts +7 -1
  65. package/dist/Textarea/Textarea.stories.d.ts +5 -0
  66. package/dist/ValidationMessage/ValidationMessage.d.ts +5 -0
  67. package/dist/ValidationMessage/ValidationMessage.stories.d.ts +5 -0
  68. package/dist/VisuallyHidden/VisuallyHidden.d.ts +1 -0
  69. package/dist/VisuallyHidden/VisuallyHidden.stories.d.ts +5 -0
  70. package/dist/VisuallyHidden/index.d.ts +1 -0
  71. package/dist/components.cjs.development.js +1075 -562
  72. package/dist/components.cjs.development.js.map +1 -1
  73. package/dist/components.cjs.production.min.js +1 -1
  74. package/dist/components.cjs.production.min.js.map +1 -1
  75. package/dist/components.esm.js +1012 -542
  76. package/dist/components.esm.js.map +1 -1
  77. package/dist/index.d.ts +6 -2
  78. package/dist/theme.d.ts +23 -48
  79. package/package.json +21 -2
  80. package/src/ActionGroup/ActionGroup.stories.tsx +47 -0
  81. package/src/ActionGroup/ActionGroup.test.tsx +83 -0
  82. package/src/ActionGroup/ActionGroup.tsx +32 -0
  83. package/src/ActionGroup/index.ts +1 -0
  84. package/src/Alert/Alert.stories.tsx +32 -0
  85. package/src/Alert/Alert.test.tsx +5 -2
  86. package/src/Alert/Alert.tsx +27 -34
  87. package/src/Badge/Badge.stories.tsx +38 -0
  88. package/src/Badge/Badge.test.tsx +12 -16
  89. package/src/Badge/Badge.tsx +14 -3
  90. package/src/Box.ts +2 -0
  91. package/src/Button/Button.stories.tsx +57 -0
  92. package/src/Button/Button.test.tsx +76 -13
  93. package/src/Button/Button.tsx +61 -18
  94. package/src/Card/Card.stories.tsx +41 -0
  95. package/src/Card/Card.test.tsx +71 -0
  96. package/src/Card/Card.tsx +48 -0
  97. package/src/Card/index.ts +1 -0
  98. package/src/Checkbox/Checkbox.stories.tsx +78 -0
  99. package/src/Checkbox/Checkbox.test.tsx +138 -23
  100. package/src/Checkbox/Checkbox.tsx +81 -52
  101. package/src/Checkbox/CheckboxIcons.tsx +59 -0
  102. package/src/Column/Column.stories.tsx +33 -0
  103. package/src/Column/Column.test.tsx +8 -0
  104. package/src/Column/Column.tsx +12 -2
  105. package/src/Columns/Columns.stories.tsx +75 -0
  106. package/src/Columns/Columns.test.tsx +34 -23
  107. package/src/Columns/Columns.tsx +30 -30
  108. package/src/Container/Container.stories.tsx +14 -0
  109. package/src/Dialog/Dialog.stories.tsx +88 -0
  110. package/src/Dialog/Dialog.test.tsx +129 -18
  111. package/src/Dialog/Dialog.tsx +113 -15
  112. package/src/Dialog/ModalDialog.tsx +76 -0
  113. package/src/Divider/Divider.stories.tsx +30 -0
  114. package/src/Divider/Divider.test.tsx +13 -5
  115. package/src/Divider/Divider.tsx +12 -0
  116. package/src/Field/Field.stories.tsx +110 -0
  117. package/src/Field/Field.test.tsx +74 -33
  118. package/src/Field/Field.tsx +27 -20
  119. package/src/Image/Image.stories.tsx +34 -0
  120. package/src/Image/Image.test.tsx +4 -1
  121. package/src/Image/Image.tsx +13 -1
  122. package/src/Inline/Inline.stories.tsx +39 -0
  123. package/src/Inline/Inline.test.tsx +99 -0
  124. package/src/Inline/Inline.tsx +38 -0
  125. package/src/Inline/index.ts +1 -0
  126. package/src/Input/Input.stories.tsx +54 -0
  127. package/src/Input/Input.test.tsx +7 -3
  128. package/src/Input/Input.tsx +13 -1
  129. package/src/Label/Label.stories.tsx +41 -0
  130. package/src/Label/Label.test.tsx +40 -5
  131. package/src/Label/Label.tsx +54 -8
  132. package/src/Link/Link.stories.tsx +35 -0
  133. package/src/Link/Link.test.tsx +51 -21
  134. package/src/Link/Link.tsx +39 -13
  135. package/src/Menu/Menu.stories.tsx +62 -0
  136. package/src/Menu/Menu.test.tsx +11 -6
  137. package/src/Menu/Menu.tsx +22 -14
  138. package/src/MenuItem/MenuItem.stories.tsx +30 -0
  139. package/src/MenuItem/MenuItem.test.tsx +22 -13
  140. package/src/MenuItem/MenuItem.tsx +19 -10
  141. package/src/Message/Message.stories.tsx +30 -0
  142. package/src/Message/Message.test.tsx +4 -1
  143. package/src/Message/Message.tsx +18 -14
  144. package/src/Provider/MarigoldProvider.test.tsx +136 -0
  145. package/src/Provider/MarigoldProvider.tsx +47 -0
  146. package/src/Provider/index.ts +4 -0
  147. package/src/Radio/Radio.stories.tsx +78 -0
  148. package/src/Radio/Radio.test.tsx +129 -18
  149. package/src/Radio/Radio.tsx +62 -71
  150. package/src/Radio/RadioIcon.tsx +49 -0
  151. package/src/Select/ListBox.tsx +40 -0
  152. package/src/Select/ListBoxSection.tsx +40 -0
  153. package/src/Select/Option.tsx +48 -0
  154. package/src/Select/Popover.tsx +50 -0
  155. package/src/Select/Select.stories.tsx +81 -0
  156. package/src/Select/Select.test.tsx +317 -35
  157. package/src/Select/Select.tsx +162 -18
  158. package/src/Slider/Slider.stories.tsx +24 -0
  159. package/src/Slider/Slider.test.tsx +10 -6
  160. package/src/Slider/Slider.tsx +25 -13
  161. package/src/Stack/Stack.stories.tsx +57 -0
  162. package/src/Stack/Stack.test.tsx +93 -65
  163. package/src/Stack/Stack.tsx +27 -32
  164. package/src/Text/Text.stories.tsx +61 -0
  165. package/src/Text/Text.test.tsx +41 -36
  166. package/src/Text/Text.tsx +56 -31
  167. package/src/Textarea/Textarea.stories.tsx +64 -0
  168. package/src/Textarea/Textarea.test.tsx +11 -8
  169. package/src/Textarea/Textarea.tsx +41 -38
  170. package/src/ValidationMessage/ValidationMessage.stories.tsx +27 -0
  171. package/src/ValidationMessage/ValidationMessage.test.tsx +9 -4
  172. package/src/ValidationMessage/ValidationMessage.tsx +23 -12
  173. package/src/VisuallyHidden/VisuallyHidden.stories.tsx +19 -0
  174. package/src/VisuallyHidden/VisuallyHidden.test.tsx +10 -0
  175. package/src/VisuallyHidden/VisuallyHidden.tsx +1 -0
  176. package/src/VisuallyHidden/index.ts +1 -0
  177. package/src/index.ts +7 -2
  178. package/src/theme.ts +49 -48
  179. package/dist/Box/Box.d.ts +0 -45
  180. package/dist/Box/index.d.ts +0 -1
  181. package/dist/Heading/Heading.d.ts +0 -7
  182. package/dist/Heading/index.d.ts +0 -1
  183. package/dist/Hidden/Hidden.d.ts +0 -5
  184. package/dist/Hidden/index.d.ts +0 -1
  185. package/src/Alert/Alert.stories.mdx +0 -45
  186. package/src/Badge/Badge.stories.mdx +0 -43
  187. package/src/Box/Box.stories.mdx +0 -38
  188. package/src/Box/Box.test.tsx +0 -133
  189. package/src/Box/Box.tsx +0 -152
  190. package/src/Box/index.ts +0 -1
  191. package/src/Button/Button.stories.mdx +0 -176
  192. package/src/Checkbox/Checkbox.stories.mdx +0 -119
  193. package/src/Column/Column.stories.mdx +0 -74
  194. package/src/Columns/Columns.stories.mdx +0 -247
  195. package/src/Container/Container.stories.mdx +0 -36
  196. package/src/Dialog/Dialog.stories.mdx +0 -64
  197. package/src/Divider/Divider.stories.mdx +0 -43
  198. package/src/Field/Field.stories.mdx +0 -57
  199. package/src/Heading/Heading.stories.mdx +0 -91
  200. package/src/Heading/Heading.test.tsx +0 -77
  201. package/src/Heading/Heading.tsx +0 -19
  202. package/src/Heading/index.ts +0 -1
  203. package/src/Hidden/Hidden.stories.mdx +0 -64
  204. package/src/Hidden/Hidden.test.tsx +0 -24
  205. package/src/Hidden/Hidden.tsx +0 -16
  206. package/src/Hidden/index.ts +0 -1
  207. package/src/Image/Image.stories.mdx +0 -40
  208. package/src/Input/Input.stories.mdx +0 -45
  209. package/src/Label/Label.stories.mdx +0 -34
  210. package/src/Link/Link.stories.mdx +0 -38
  211. package/src/Menu/Menu.stories.mdx +0 -49
  212. package/src/MenuItem/MenuItem.stories.mdx +0 -32
  213. package/src/Message/Message.stories.mdx +0 -44
  214. package/src/Radio/Radio.stories.mdx +0 -100
  215. package/src/Select/Select.stories.mdx +0 -44
  216. package/src/Slider/Slider.stories.mdx +0 -58
  217. package/src/Stack/Stack.stories.mdx +0 -105
  218. package/src/Text/Text.stories.mdx +0 -60
  219. package/src/Textarea/Textarea.stories.mdx +0 -65
  220. package/src/ValidationMessage/ValidationMessage.stories.mdx +0 -36
@@ -1,67 +1,349 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
3
- import { ThemeProvider } from '@marigold/system';
2
+ import { fireEvent, render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+
5
+ import { Item, MarigoldProvider, Section } from '..';
4
6
  import { Select } from './Select';
5
7
 
6
8
  const theme = {
7
- select: {
8
- default: {
9
- fontFamily: 'Inter',
9
+ colors: {
10
+ red: 'red',
11
+ },
12
+ fonts: {
13
+ body: 'Inter',
14
+ },
15
+ button: {
16
+ select: {
17
+ fontFamily: 'body',
18
+ errorOpened: {
19
+ color: 'red',
20
+ },
10
21
  },
11
- other: {
12
- fontFamily: 'Oswald',
22
+ },
23
+ label: {
24
+ above: {
25
+ fontSize: '8px',
26
+ },
27
+ inline: {
28
+ fontSize: '14px',
13
29
  },
14
30
  },
15
31
  };
16
32
 
17
- test('supports default variant and themeSection', () => {
33
+ test('supports button select variant', () => {
18
34
  render(
19
- <ThemeProvider theme={theme}>
20
- <Select title="select">
21
- <option>1</option>
35
+ <MarigoldProvider theme={theme}>
36
+ <Select label="MyLabel" data-testid="selectId">
37
+ <Item>1</Item>
22
38
  </Select>
23
- </ThemeProvider>
39
+ </MarigoldProvider>
24
40
  );
25
- const select = screen.getByTitle(/select/);
26
-
41
+ const select = screen.getByTestId('selectId');
42
+ expect(select).toBeDefined();
27
43
  expect(select).toHaveStyle(`font-family: Inter`);
28
44
  });
29
45
 
30
- test('accepts other variant than default', () => {
46
+ test('supports default labelVariant', () => {
31
47
  render(
32
- <ThemeProvider theme={theme}>
33
- <Select title="select" variant="other">
34
- <option>1</option>
48
+ <MarigoldProvider theme={theme}>
49
+ <Select label="MyLabel" data-testid="selectId">
50
+ <Item>1</Item>
35
51
  </Select>
36
- </ThemeProvider>
52
+ </MarigoldProvider>
37
53
  );
38
- const select = screen.getByTitle(/select/);
54
+ const label = screen.getAllByText('MyLabel');
55
+ expect(label[0]).toHaveStyle(`font-size: 8px`);
56
+ });
39
57
 
40
- expect(select).toHaveStyle(`font-family: Oswald`);
58
+ test('supports other labelVariant than default', () => {
59
+ render(
60
+ <MarigoldProvider theme={theme}>
61
+ <Select label="MyLabel" data-testid="selectId" labelVariant="inline">
62
+ <Item>1</Item>
63
+ </Select>
64
+ </MarigoldProvider>
65
+ );
66
+ const label = screen.getAllByText('MyLabel');
67
+ expect(label[0]).toHaveStyle(`font-size: 14px`);
68
+ });
69
+
70
+ test('accepts custom styles prop className', () => {
71
+ render(
72
+ <MarigoldProvider theme={theme}>
73
+ <Select
74
+ label="MyLabel"
75
+ className="custom-class-name"
76
+ data-testid="selectId"
77
+ >
78
+ <Item>1</Item>
79
+ </Select>
80
+ </MarigoldProvider>
81
+ );
82
+ const select = screen.getByTestId('selectId');
83
+ expect(select.className).toMatch('custom-class-name');
41
84
  });
42
85
 
43
- test('renders correct HTML element', () => {
86
+ test('supports label with htmlFor prop', () => {
44
87
  render(
45
- <ThemeProvider theme={theme}>
46
- <Select title="select">
47
- <option>1</option>
88
+ <MarigoldProvider theme={theme}>
89
+ <Select label="MyLabel">
90
+ <Item>1</Item>
48
91
  </Select>
49
- </ThemeProvider>
92
+ </MarigoldProvider>
50
93
  );
51
- const select = screen.getByTitle(/select/);
94
+ const selectLabel = screen.getAllByText(/MyLabel/);
95
+ expect(selectLabel[0]).toHaveAttribute('for');
96
+ });
52
97
 
53
- expect(select instanceof HTMLSelectElement).toBeTruthy();
98
+ test('supports disabled prop', () => {
99
+ render(
100
+ <MarigoldProvider theme={theme}>
101
+ <Select label="MyLabel" data-testid="selectId" disabled>
102
+ <Item>1</Item>
103
+ </Select>
104
+ </MarigoldProvider>
105
+ );
106
+ const select = screen.getByTestId('selectId');
107
+ expect(select).toHaveAttribute('disabled');
108
+ fireEvent.click(select);
109
+ expect(select).toHaveAttribute('aria-expanded', 'false');
54
110
  });
55
111
 
56
- test('accepts custom styles prop className', () => {
112
+ test('supports placeholder prop', () => {
57
113
  render(
58
- <ThemeProvider theme={theme}>
59
- <Select className="custom-class-name" title="select">
60
- <option>1</option>
114
+ <MarigoldProvider theme={theme}>
115
+ <Select label="MyLabel" placeholder="placeholder" data-testid="selectId">
116
+ <Item>1</Item>
61
117
  </Select>
62
- </ThemeProvider>
118
+ </MarigoldProvider>
63
119
  );
64
- const select = screen.getByTitle(/select/);
120
+ const button = screen.getByTestId('selectId');
121
+ expect(button).toHaveTextContent(/placeholder/);
122
+ });
65
123
 
66
- expect(select.className).toMatch('custom-class-name');
124
+ test('supports required prop', () => {
125
+ render(
126
+ <MarigoldProvider theme={theme}>
127
+ <Select label="MyLabel" required data-testid="selectId">
128
+ <Item>1</Item>
129
+ </Select>
130
+ </MarigoldProvider>
131
+ );
132
+ const selectLabel = screen.getAllByText(/MyLabel/);
133
+ expect(selectLabel[0]).toContainHTML('path d="M10.8 3.84003');
134
+ });
135
+
136
+ test('supports error and errorMessage prop', () => {
137
+ render(
138
+ <MarigoldProvider theme={theme}>
139
+ <Select label="MyLabel" error errorMessage="error" data-testid="selectId">
140
+ <Item>1</Item>
141
+ </Select>
142
+ </MarigoldProvider>
143
+ );
144
+ const validationMessage = screen.getAllByText(/error/);
145
+ expect(validationMessage).toBeDefined();
146
+ });
147
+
148
+ test('supports width prop', () => {
149
+ render(
150
+ <MarigoldProvider theme={theme}>
151
+ <Select label="MyLabel" width="120px" data-testid="selectId">
152
+ <Item>1</Item>
153
+ </Select>
154
+ </MarigoldProvider>
155
+ );
156
+ const select = screen.getByTestId('selectId');
157
+
158
+ expect(select.parentElement).toHaveStyle(`width: 120px`);
159
+ });
160
+
161
+ test('option list opens when element is clicked', () => {
162
+ render(
163
+ <MarigoldProvider theme={theme}>
164
+ <Select label="MyLabel" data-testid="selectId">
165
+ <Item>Red</Item>
166
+ </Select>
167
+ </MarigoldProvider>
168
+ );
169
+ const button = screen.getByTestId('selectId');
170
+ expect(button).toHaveAttribute('aria-expanded', 'false');
171
+
172
+ fireEvent.click(button);
173
+
174
+ // more than one item found because of the HiddenSelect component
175
+ const items = screen.getByRole('listbox');
176
+ expect(items).toBeVisible();
177
+ expect(button).toHaveAttribute('aria-expanded', 'true');
178
+ });
179
+
180
+ test('option list opens when element is clicked and theres an error', () => {
181
+ render(
182
+ <MarigoldProvider theme={theme}>
183
+ <Select label="MyLabel" error errorMessage="error" data-testid="selectId">
184
+ <Item>Red</Item>
185
+ </Select>
186
+ </MarigoldProvider>
187
+ );
188
+ const button = screen.getByTestId('selectId');
189
+ expect(button).toHaveAttribute('aria-expanded', 'false');
190
+
191
+ fireEvent.click(button);
192
+
193
+ const items = screen.getByRole('listbox');
194
+ expect(items).toBeVisible();
195
+ expect(button).toHaveAttribute('aria-expanded', 'true');
196
+ expect(button).toHaveStyle(`color: red`);
197
+ });
198
+
199
+ test('supports click and select an option', () => {
200
+ render(
201
+ <MarigoldProvider theme={theme}>
202
+ <Select label="MyLabel" data-testid="selectId">
203
+ <Item>Red</Item>
204
+ </Select>
205
+ </MarigoldProvider>
206
+ );
207
+ const button = screen.getByTestId('selectId');
208
+ expect(button).toHaveAttribute('aria-expanded', 'false');
209
+
210
+ fireEvent.click(button);
211
+ expect(button).toHaveAttribute('aria-expanded', 'true');
212
+ const items = screen.getAllByText(/Red/);
213
+ expect(items[1]).toBeVisible();
214
+ expect(items[1]).toHaveAttribute('aria-selected', 'false');
215
+
216
+ fireEvent.click(items[1]);
217
+ expect(button).toHaveTextContent('Red');
218
+
219
+ fireEvent.click(button);
220
+
221
+ // after selecting one item there are three elements with item text
222
+ const newItems = screen.getAllByText(/Red/);
223
+ expect(newItems[2]).toHaveAttribute('aria-selected', 'true');
224
+ });
225
+
226
+ test('popup closes after an option is selected', () => {
227
+ render(
228
+ <MarigoldProvider theme={theme}>
229
+ <Select label="MyLabel" data-testid="selectId">
230
+ <Item>Red</Item>
231
+ </Select>
232
+ </MarigoldProvider>
233
+ );
234
+ const button = screen.getByTestId('selectId');
235
+ expect(button).toHaveAttribute('aria-expanded', 'false');
236
+
237
+ fireEvent.click(button);
238
+ expect(button).toHaveAttribute('aria-expanded', 'true');
239
+ const items = screen.getAllByText(/Red/);
240
+ expect(items[1]).toBeVisible();
241
+
242
+ fireEvent.click(items[1]);
243
+ expect(button).toHaveTextContent('Red');
244
+ expect(items[1]).not.toBeVisible();
245
+ });
246
+
247
+ test('dismiss popup by clicking escape', () => {
248
+ render(
249
+ <MarigoldProvider theme={theme}>
250
+ <Select label="MyLabel" data-testid="selectId">
251
+ <Item>Red</Item>
252
+ </Select>
253
+ </MarigoldProvider>
254
+ );
255
+ const selectButton = screen.getByTestId('selectId');
256
+ fireEvent.click(selectButton);
257
+ expect(selectButton).toHaveAttribute('aria-expanded', 'true');
258
+ userEvent.type(selectButton, '{esc}');
259
+ expect(selectButton).toHaveAttribute('aria-expanded', 'false');
260
+ });
261
+
262
+ test('allow users to dismiss the popup with hidden dismiss button', () => {
263
+ render(
264
+ <MarigoldProvider theme={theme}>
265
+ <Select label="MyLabel" data-testid="selectId">
266
+ <Item>Red</Item>
267
+ </Select>
268
+ </MarigoldProvider>
269
+ );
270
+ const selectButton = screen.getByTestId('selectId');
271
+ fireEvent.click(selectButton);
272
+
273
+ const dismissButton = screen.getByLabelText(/Dismiss/);
274
+ expect(dismissButton).toBeDefined();
275
+
276
+ fireEvent.click(dismissButton);
277
+ expect(selectButton).toHaveAttribute('aria-expanded', 'false');
278
+ });
279
+
280
+ test('supports default selectedKey prop', () => {
281
+ render(
282
+ <MarigoldProvider theme={theme}>
283
+ <Select label="MyLabel" data-testid="selectId" defaultSelectedKey="Red">
284
+ <Item key="Red">Red</Item>
285
+ <Item key="Orange">Orange</Item>
286
+ </Select>
287
+ </MarigoldProvider>
288
+ );
289
+ const button = screen.getByTestId('selectId');
290
+ expect(button).toHaveTextContent('Red');
291
+ });
292
+
293
+ test('supports change default selectedKey', () => {
294
+ render(
295
+ <MarigoldProvider theme={theme}>
296
+ <Select label="MyLabel" data-testid="selectId" defaultSelectedKey="Red">
297
+ <Item key="Red">Red</Item>
298
+ <Item key="Orange">Orange</Item>
299
+ </Select>
300
+ </MarigoldProvider>
301
+ );
302
+ const button = screen.getByTestId('selectId');
303
+ expect(button).toHaveTextContent('Red');
304
+
305
+ fireEvent.click(button);
306
+ const items = screen.getAllByText(/Red/);
307
+ fireEvent.click(items[1]);
308
+
309
+ expect(button).toHaveTextContent('Red');
310
+ });
311
+
312
+ test('supports disabled item prop', () => {
313
+ render(
314
+ <MarigoldProvider theme={theme}>
315
+ <Select label="MyLabel" data-testid="selectId" disabledKeys={['Red']}>
316
+ <Item key="Red">Red</Item>
317
+ <Item key="Orange">Orange</Item>
318
+ </Select>
319
+ </MarigoldProvider>
320
+ );
321
+ const button = screen.getByTestId('selectId');
322
+ fireEvent.click(button);
323
+ const redItem = screen.getAllByText(/Red/);
324
+ fireEvent.click(redItem[1]);
325
+ expect(button).toHaveTextContent('Select an option');
326
+
327
+ const orangeItem = screen.getAllByText(/Orange/);
328
+ fireEvent.click(orangeItem[1]);
329
+ expect(button).toHaveTextContent('Orange');
330
+ });
331
+
332
+ test('supports section with items', () => {
333
+ render(
334
+ <MarigoldProvider theme={theme}>
335
+ <Select label="Section" data-testid="selectId">
336
+ <Section title="Color">
337
+ <Item>Red</Item>
338
+ </Section>
339
+ </Select>
340
+ </MarigoldProvider>
341
+ );
342
+ const button = screen.getByTestId('selectId');
343
+ fireEvent.click(button);
344
+
345
+ const items = screen.getAllByText(/Red/);
346
+ expect(items[1]).toBeVisible();
347
+ const sections = screen.getAllByText(/Color/);
348
+ expect(sections[0]).toBeVisible();
67
349
  });
@@ -1,30 +1,174 @@
1
- import React from 'react';
2
- import { ArrowDown } from '@marigold/icons';
3
- import { useStyles } from '@marigold/system';
1
+ import React, { Ref, RefObject, useRef } from 'react';
2
+ import { useSelectState } from '@react-stately/select';
3
+ import { useButton } from '@react-aria/button';
4
+ import { mergeProps } from '@react-aria/utils';
5
+ import { useFocusRing } from '@react-aria/focus';
6
+ import { HiddenSelect, useSelect } from '@react-aria/select';
7
+ import type { AriaSelectProps } from '@react-types/select';
8
+ import { useOverlayTriggerState } from '@react-stately/overlays';
9
+ import { useOverlayTrigger, useOverlayPosition } from '@react-aria/overlays';
10
+ import { SingleSelection } from '@react-types/shared';
11
+
4
12
  import { ComponentProps } from '@marigold/types';
13
+ import { ArrowDown, ArrowUp, Exclamation, Required } from '@marigold/icons';
14
+ import { ResponsiveStyleValue } from '@marigold/system';
15
+
5
16
  import { Box } from '../Box';
17
+ import { Label } from '../Label';
18
+ import { ValidationMessage } from '../ValidationMessage';
19
+ import { ListBox } from './ListBox';
20
+ import { Popover } from './Popover';
6
21
 
7
- export type SelectProps = { variant?: string } & ComponentProps<'select'>;
22
+ // Theme Extension
23
+ // ---------------
24
+ export interface SelectThemeExtension<Value> {
25
+ select?: {
26
+ __default: Value;
27
+ disabled?: Value;
28
+ listbox?: {
29
+ __default: Value;
30
+ error?: Value;
31
+ };
32
+ section?: Value;
33
+ option?: Value;
34
+ };
35
+ }
8
36
 
9
- export const Select: React.FC<SelectProps> = ({
10
- variant = 'default',
11
- children,
37
+ // Props
38
+ // ---------------
39
+ export type SelectProps = {
40
+ labelVariant?: string;
41
+ placeholder?: string;
42
+ disabled?: boolean;
43
+ required?: boolean;
44
+ width?: ResponsiveStyleValue<number | string>;
45
+ error?: boolean;
46
+ errorMessage?: string;
47
+ } & ComponentProps<'select'> &
48
+ AriaSelectProps<object> &
49
+ SingleSelection;
50
+
51
+ // Component
52
+ // ---------------
53
+ export const Select = ({
54
+ labelVariant = 'above',
55
+ placeholder = 'Select an option',
56
+ disabled,
57
+ required,
58
+ error,
59
+ errorMessage,
60
+ width,
61
+ className,
12
62
  ...props
13
- }) => {
14
- const iconStyles = useStyles({
15
- css: {
16
- alignSelf: 'center',
17
- pointerEvents: 'none',
18
- ml: '-28px',
19
- },
63
+ }: SelectProps) => {
64
+ const state = useSelectState(props);
65
+ const overlayTriggerState = useOverlayTriggerState({});
66
+ const triggerRef = useRef<HTMLElement>() as RefObject<HTMLElement>;
67
+ const overlayRef = useRef<HTMLDivElement>();
68
+
69
+ // Get props for the overlay
70
+ const { overlayProps } = useOverlayTrigger(
71
+ { type: 'listbox' },
72
+ overlayTriggerState,
73
+ triggerRef
74
+ );
75
+ // Get popover positioning props relative to the trigger
76
+ const { overlayProps: positionProps } = useOverlayPosition({
77
+ targetRef: triggerRef,
78
+ overlayRef: overlayRef as RefObject<HTMLElement>,
79
+ placement: 'bottom',
80
+ shouldFlip: false,
81
+ isOpen: state.isOpen,
82
+ onClose: state.close,
20
83
  });
84
+ // Get props for child elements from useSelect
85
+ const { labelProps, triggerProps, valueProps, menuProps } = useSelect(
86
+ props,
87
+ state,
88
+ triggerRef
89
+ );
90
+ // Get props for the button based on the trigger props from useSelect
91
+ const { buttonProps } = useButton(triggerProps, triggerRef);
92
+
93
+ const { focusProps } = useFocusRing();
21
94
 
22
95
  return (
23
- <Box display="flex">
24
- <Box as="select" variant={`select.${variant}`} {...props}>
25
- {children}
96
+ <Box position="relative" display="inline-block" width={width && width}>
97
+ {props.label && (
98
+ <Box>
99
+ <Label {...labelProps} htmlFor={labelProps.id} variant={labelVariant}>
100
+ {required ? (
101
+ <Box as="span" display="inline-flex" alignItems="center">
102
+ {props.label}
103
+ <Box as={Required} size={16} css={{ color: 'error' }} />
104
+ </Box>
105
+ ) : (
106
+ props.label
107
+ )}
108
+ </Label>
109
+ </Box>
110
+ )}
111
+ <HiddenSelect
112
+ state={state}
113
+ triggerRef={triggerRef}
114
+ label={props.label}
115
+ name={props.name}
116
+ isDisabled={disabled}
117
+ />
118
+ <Box
119
+ as="button"
120
+ {...mergeProps(buttonProps, focusProps)}
121
+ ref={triggerRef as RefObject<HTMLButtonElement>}
122
+ variant={
123
+ error && state.isOpen && !disabled
124
+ ? 'button.select.errorOpened'
125
+ : error
126
+ ? 'button.select.error'
127
+ : state.isOpen && !disabled
128
+ ? 'button.select.open'
129
+ : 'button.select'
130
+ }
131
+ disabled={disabled}
132
+ className={className}
133
+ >
134
+ <Box
135
+ as="span"
136
+ {...valueProps}
137
+ variant={disabled ? 'select.disabled' : 'select'}
138
+ >
139
+ {state.selectedItem ? state.selectedItem.rendered : placeholder}
140
+ </Box>
141
+ {state.isOpen && !disabled ? (
142
+ <Box as={ArrowUp} size={16} css={{ fill: 'text' }} />
143
+ ) : (
144
+ <Box
145
+ as={ArrowDown}
146
+ size={16}
147
+ css={{ fill: disabled ? 'disabled' : 'text' }}
148
+ />
149
+ )}
26
150
  </Box>
27
- <ArrowDown className={iconStyles} />
151
+ {state.isOpen && !disabled && (
152
+ <Box
153
+ as={Popover}
154
+ {...overlayProps}
155
+ {...positionProps}
156
+ css={{
157
+ width: triggerRef.current && triggerRef.current.offsetWidth + 'px',
158
+ }}
159
+ ref={overlayRef as Ref<HTMLDivElement>}
160
+ isOpen={state.isOpen}
161
+ onClose={state.close}
162
+ >
163
+ <ListBox error={error} {...menuProps} state={state} />
164
+ </Box>
165
+ )}
166
+ {error && errorMessage && (
167
+ <Box as="span" display="inline-flex" alignItems="center">
168
+ <Box as={Exclamation} size={16} css={{ color: 'error' }} />
169
+ <ValidationMessage>{errorMessage}</ValidationMessage>
170
+ </Box>
171
+ )}
28
172
  </Box>
29
173
  );
30
174
  };
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import type { Meta, ComponentStory } from '@storybook/react';
3
+ import { Slider } from './Slider';
4
+
5
+ export default {
6
+ title: 'Components/Slider',
7
+ argTypes: {
8
+ variant: {
9
+ control: {
10
+ type: 'text',
11
+ },
12
+ description: 'Style',
13
+ table: {
14
+ defaultValue: {
15
+ summary: '__default',
16
+ },
17
+ },
18
+ },
19
+ },
20
+ } as Meta;
21
+
22
+ export const Basic: ComponentStory<typeof Slider> = args => (
23
+ <Slider name="vol" min="0" max="50" {...args} />
24
+ );
@@ -4,12 +4,16 @@ import { ThemeProvider } from '@marigold/system';
4
4
  import { Slider } from './Slider';
5
5
 
6
6
  const theme = {
7
- form: {
8
- slider: {
9
- fontFamily: 'Oswald Regular',
7
+ fonts: {
8
+ regular: 'Oswald Regular',
9
+ body: 'Inter',
10
+ },
11
+ slider: {
12
+ __default: {
13
+ fontFamily: 'regular',
10
14
  },
11
- range: {
12
- fontFamily: 'Inter',
15
+ special: {
16
+ fontFamily: 'body',
13
17
  },
14
18
  },
15
19
  };
@@ -28,7 +32,7 @@ test('supports default variant and themeSection', () => {
28
32
  test('accepts other variant than default', () => {
29
33
  render(
30
34
  <ThemeProvider theme={theme}>
31
- <Slider variant="range" title="slider" />
35
+ <Slider variant="special" title="slider" />
32
36
  </ThemeProvider>
33
37
  );
34
38
  const slider = screen.getByTitle(/slider/);
@@ -1,23 +1,35 @@
1
1
  import React from 'react';
2
- import { useStyles } from '@marigold/system';
3
2
  import { ComponentProps } from '@marigold/types';
4
3
 
4
+ import { Box } from '../Box';
5
+
6
+ // Theme Extension
7
+ // ---------------
8
+ export interface SliderThemeExtension<Value> {
9
+ slider?: {
10
+ [key: string]: Value;
11
+ };
12
+ }
13
+
14
+ // Props
15
+ // ---------------
5
16
  export type SliderProps = {
6
17
  variant?: string;
7
18
  } & ComponentProps<'input'>;
8
19
 
20
+ // Component
21
+ // ---------------
9
22
  export const Slider: React.FC<SliderProps> = ({
10
- variant = 'slider',
23
+ variant = '',
11
24
  className,
12
25
  ...props
13
- }) => {
14
- const classNames = useStyles({
15
- variant: `form.${variant}`,
16
- css: {
17
- verticalAlign: 'middle',
18
- },
19
- className,
20
- });
21
-
22
- return <input type="range" className={classNames} {...props} />;
23
- };
26
+ }) => (
27
+ <Box
28
+ as="input"
29
+ type="range"
30
+ css={{ verticalAlign: 'middle' }}
31
+ variant={`slider.${variant}`}
32
+ className={className}
33
+ {...props}
34
+ />
35
+ );