@arbor-education/design-system.components 0.0.7 → 0.1.0

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 (143) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/components/button/Button.d.ts +3 -3
  3. package/dist/components/button/Button.d.ts.map +1 -1
  4. package/dist/components/button/Button.js +7 -6
  5. package/dist/components/button/Button.js.map +1 -1
  6. package/dist/components/button/Button.test.js +1 -1
  7. package/dist/components/button/Button.test.js.map +1 -1
  8. package/dist/components/dropdown/Dropdown.d.ts +15 -0
  9. package/dist/components/dropdown/Dropdown.d.ts.map +1 -0
  10. package/dist/components/dropdown/Dropdown.js +15 -0
  11. package/dist/components/dropdown/Dropdown.js.map +1 -0
  12. package/dist/components/dropdown/Dropdown.stories.d.ts +20 -0
  13. package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -0
  14. package/dist/components/dropdown/Dropdown.stories.js +30 -0
  15. package/dist/components/dropdown/Dropdown.stories.js.map +1 -0
  16. package/dist/components/dropdown/Dropdown.test.d.ts.map +1 -0
  17. package/dist/components/dropdown/Dropdown.test.js +136 -0
  18. package/dist/components/dropdown/Dropdown.test.js.map +1 -0
  19. package/dist/components/dropdown/DropdownContent.d.ts +9 -0
  20. package/dist/components/dropdown/DropdownContent.d.ts.map +1 -0
  21. package/dist/components/dropdown/DropdownContent.js +10 -0
  22. package/dist/components/dropdown/DropdownContent.js.map +1 -0
  23. package/dist/components/dropdown/DropdownTrigger.d.ts +5 -0
  24. package/dist/components/dropdown/DropdownTrigger.d.ts.map +1 -0
  25. package/dist/components/dropdown/DropdownTrigger.js +7 -0
  26. package/dist/components/dropdown/DropdownTrigger.js.map +1 -0
  27. package/dist/components/dropdown/items/DropdownItem.d.ts +3 -0
  28. package/dist/components/dropdown/items/DropdownItem.d.ts.map +1 -0
  29. package/dist/components/dropdown/items/DropdownItem.js +8 -0
  30. package/dist/components/dropdown/items/DropdownItem.js.map +1 -0
  31. package/dist/components/dropdown/items/DropdownSelectItem.d.ts +8 -0
  32. package/dist/components/dropdown/items/DropdownSelectItem.d.ts.map +1 -0
  33. package/dist/components/dropdown/items/DropdownSelectItem.js +11 -0
  34. package/dist/components/dropdown/items/DropdownSelectItem.js.map +1 -0
  35. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts +11 -0
  36. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -0
  37. package/dist/components/formField/inputs/{dropdown/Dropdown.js → selectDropdown/SelectDropdown.js} +20 -9
  38. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -0
  39. package/dist/components/formField/inputs/{dropdown/Dropdown.stories.d.ts → selectDropdown/SelectDropdown.stories.d.ts} +3 -3
  40. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts.map +1 -0
  41. package/dist/components/formField/inputs/{dropdown/Dropdown.stories.js → selectDropdown/SelectDropdown.stories.js} +4 -4
  42. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js.map +1 -0
  43. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.d.ts +2 -0
  44. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.d.ts.map +1 -0
  45. package/dist/components/formField/inputs/{dropdown/Dropdown.test.js → selectDropdown/SelectDropdown.test.js} +14 -14
  46. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.js.map +1 -0
  47. package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.d.ts +16 -0
  48. package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.d.ts.map +1 -0
  49. package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.js +13 -0
  50. package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.js.map +1 -0
  51. package/dist/components/heading/Heading.stories.js +3 -3
  52. package/dist/components/heading/Heading.stories.js.map +1 -1
  53. package/dist/components/section/Section.d.ts +2 -2
  54. package/dist/components/section/Section.d.ts.map +1 -1
  55. package/dist/components/section/Section.js +2 -2
  56. package/dist/components/section/Section.js.map +1 -1
  57. package/dist/components/section/Section.test.js +1 -1
  58. package/dist/components/section/Section.test.js.map +1 -1
  59. package/dist/components/slideover/Slideover.js +1 -1
  60. package/dist/components/slideover/Slideover.js.map +1 -1
  61. package/dist/components/slideoverManager/SlideoverManager.stories.js +7 -7
  62. package/dist/components/slideoverManager/SlideoverManager.stories.js.map +1 -1
  63. package/dist/components/table/BulkActionsDropdown.d.ts.map +1 -1
  64. package/dist/components/table/BulkActionsDropdown.js +3 -4
  65. package/dist/components/table/BulkActionsDropdown.js.map +1 -1
  66. package/dist/components/table/Table.stories.js +3 -3
  67. package/dist/components/table/Table.stories.js.map +1 -1
  68. package/dist/components/table/Table.test.js +5 -5
  69. package/dist/components/table/Table.test.js.map +1 -1
  70. package/dist/index.css +70 -159
  71. package/dist/index.css.map +1 -1
  72. package/dist/index.d.ts +2 -1
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +2 -1
  75. package/dist/index.js.map +1 -1
  76. package/package.json +1 -1
  77. package/release/design-system.components.tgz +0 -0
  78. package/src/components/button/Button.test.tsx +1 -1
  79. package/src/components/button/Button.tsx +10 -7
  80. package/src/components/dropdown/Dropdown.stories.tsx +62 -0
  81. package/src/components/dropdown/Dropdown.test.tsx +368 -0
  82. package/src/components/dropdown/Dropdown.tsx +19 -0
  83. package/src/components/dropdown/DropdownContent.tsx +22 -0
  84. package/src/components/dropdown/DropdownTrigger.tsx +10 -0
  85. package/src/components/dropdown/dropdown.scss +48 -0
  86. package/src/components/dropdown/items/DropdownItem.tsx +11 -0
  87. package/src/components/dropdown/items/DropdownSelectItem.tsx +28 -0
  88. package/src/components/formField/inputs/{dropdown/Dropdown.stories.tsx → selectDropdown/SelectDropdown.stories.tsx} +4 -4
  89. package/src/components/formField/inputs/{dropdown/Dropdown.test.tsx → selectDropdown/SelectDropdown.test.tsx} +13 -13
  90. package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +102 -0
  91. package/src/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.tsx +46 -0
  92. package/src/components/formField/inputs/selectDropdown/selectDropdown.scss +22 -0
  93. package/src/components/heading/Heading.stories.tsx +3 -3
  94. package/src/components/icon/icon.scss +3 -0
  95. package/src/components/section/Section.test.tsx +1 -1
  96. package/src/components/section/Section.tsx +4 -4
  97. package/src/components/slideover/Slideover.tsx +1 -1
  98. package/src/components/slideoverManager/SlideoverManager.stories.tsx +14 -14
  99. package/src/components/table/BulkActionsDropdown.tsx +19 -23
  100. package/src/components/table/Table.stories.tsx +4 -4
  101. package/src/components/table/Table.test.tsx +5 -5
  102. package/src/index.scss +3 -5
  103. package/src/index.ts +2 -1
  104. package/src/tokens.scss +1 -0
  105. package/dist/components/formField/inputs/dropdown/Dropdown.d.ts +0 -11
  106. package/dist/components/formField/inputs/dropdown/Dropdown.d.ts.map +0 -1
  107. package/dist/components/formField/inputs/dropdown/Dropdown.js.map +0 -1
  108. package/dist/components/formField/inputs/dropdown/Dropdown.stories.d.ts.map +0 -1
  109. package/dist/components/formField/inputs/dropdown/Dropdown.stories.js.map +0 -1
  110. package/dist/components/formField/inputs/dropdown/Dropdown.test.d.ts.map +0 -1
  111. package/dist/components/formField/inputs/dropdown/Dropdown.test.js.map +0 -1
  112. package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.d.ts +0 -12
  113. package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.d.ts.map +0 -1
  114. package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.js +0 -15
  115. package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.js.map +0 -1
  116. package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.d.ts +0 -10
  117. package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.d.ts.map +0 -1
  118. package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.js +0 -12
  119. package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.js.map +0 -1
  120. package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.d.ts +0 -9
  121. package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.d.ts.map +0 -1
  122. package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.js +0 -21
  123. package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.js.map +0 -1
  124. package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.d.ts +0 -7
  125. package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.d.ts.map +0 -1
  126. package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.js +0 -16
  127. package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.js.map +0 -1
  128. package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.d.ts +0 -17
  129. package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.d.ts.map +0 -1
  130. package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.js +0 -151
  131. package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.js.map +0 -1
  132. package/src/components/formField/inputs/dropdown/Dropdown.tsx +0 -82
  133. package/src/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.tsx +0 -44
  134. package/src/components/formField/inputs/dropdown/buttons/dropdownButton/dropdownButton.scss +0 -12
  135. package/src/components/formField/inputs/dropdown/dropdown.scss +0 -24
  136. package/src/components/formField/inputs/dropdown/items/DropdownItemRenderer.tsx +0 -38
  137. package/src/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.tsx +0 -53
  138. package/src/components/formField/inputs/dropdown/items/dropdownItem/dropdownItem.scss +0 -62
  139. package/src/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.tsx +0 -48
  140. package/src/components/formField/inputs/dropdown/items/dropdownMultiLineItem/dropdownMultiLineItem.scss +0 -52
  141. package/src/components/formField/inputs/dropdown/wrapper/DropdownWrapper.tsx +0 -248
  142. package/src/components/formField/inputs/dropdown/wrapper/dropdownWrapper.scss +0 -37
  143. /package/dist/components/{formField/inputs/dropdown → dropdown}/Dropdown.test.d.ts +0 -0
@@ -0,0 +1,368 @@
1
+ import { describe, expect, test, vi } from 'vitest';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import '@testing-library/jest-dom/vitest';
5
+ import { Dropdown } from './Dropdown';
6
+ import { Button } from 'Components/button/Button';
7
+
8
+ describe('Dropdown component', () => {
9
+ test('renders dropdown with trigger and content', () => {
10
+ render(
11
+ <Dropdown>
12
+ <Dropdown.Trigger>
13
+ <Button>Open Menu</Button>
14
+ </Dropdown.Trigger>
15
+ <Dropdown.Content>
16
+ <Dropdown.Item>Item 1</Dropdown.Item>
17
+ </Dropdown.Content>
18
+ </Dropdown>,
19
+ );
20
+
21
+ expect(screen.getByText('Open Menu')).toBeInTheDocument();
22
+ });
23
+
24
+ test('opens dropdown when trigger is clicked', async () => {
25
+ render(
26
+ <Dropdown>
27
+ <Dropdown.Trigger>
28
+ <Button>Open Menu</Button>
29
+ </Dropdown.Trigger>
30
+ <Dropdown.Content>
31
+ <Dropdown.Item>Item 1</Dropdown.Item>
32
+ </Dropdown.Content>
33
+ </Dropdown>,
34
+ );
35
+
36
+ const trigger = screen.getByText('Open Menu');
37
+ await userEvent.click(trigger);
38
+
39
+ expect(await screen.findByText('Item 1')).toBeInTheDocument();
40
+ });
41
+
42
+ test('renders multiple dropdown items', async () => {
43
+ render(
44
+ <Dropdown>
45
+ <Dropdown.Trigger>
46
+ <Button>Open Menu</Button>
47
+ </Dropdown.Trigger>
48
+ <Dropdown.Content>
49
+ <Dropdown.Item>Item 1</Dropdown.Item>
50
+ <Dropdown.Item>Item 2</Dropdown.Item>
51
+ <Dropdown.Item>Item 3</Dropdown.Item>
52
+ </Dropdown.Content>
53
+ </Dropdown>,
54
+ );
55
+
56
+ const trigger = screen.getByText('Open Menu');
57
+ await userEvent.click(trigger);
58
+
59
+ expect(await screen.findByText('Item 1')).toBeInTheDocument();
60
+ expect(screen.getByText('Item 2')).toBeInTheDocument();
61
+ expect(screen.getByText('Item 3')).toBeInTheDocument();
62
+ });
63
+
64
+ test('calls onSelect when item is clicked', async () => {
65
+ const onClick = vi.fn();
66
+
67
+ render(
68
+ <Dropdown>
69
+ <Dropdown.Trigger>
70
+ <Button>Open Menu</Button>
71
+ </Dropdown.Trigger>
72
+ <Dropdown.Content>
73
+ <Dropdown.Item onClick={onClick}>Item 1</Dropdown.Item>
74
+ </Dropdown.Content>
75
+ </Dropdown>,
76
+ );
77
+
78
+ const trigger = screen.getByText('Open Menu');
79
+ await userEvent.click(trigger);
80
+
81
+ const item = await screen.findByText('Item 1');
82
+ await userEvent.click(item);
83
+
84
+ expect(onClick).toHaveBeenCalled();
85
+ });
86
+
87
+ test('closes dropdown after item selection', async () => {
88
+ render(
89
+ <Dropdown>
90
+ <Dropdown.Trigger>
91
+ <Button>Open Menu</Button>
92
+ </Dropdown.Trigger>
93
+ <Dropdown.Content>
94
+ <Dropdown.Item>Item 1</Dropdown.Item>
95
+ </Dropdown.Content>
96
+ </Dropdown>,
97
+ );
98
+
99
+ const trigger = screen.getByText('Open Menu');
100
+ await userEvent.click(trigger);
101
+
102
+ const item = await screen.findByText('Item 1');
103
+ await userEvent.click(item);
104
+
105
+ // Wait for dropdown to close
106
+ await waitFor(() => expect(screen.queryByText('Item 1')).not.toBeInTheDocument());
107
+ });
108
+
109
+ test('passes through props to Menu.Root', () => {
110
+ const onOpenChange = vi.fn();
111
+ render(
112
+ <Dropdown onOpenChange={onOpenChange}>
113
+ <Dropdown.Trigger>
114
+ <Button>Open Menu</Button>
115
+ </Dropdown.Trigger>
116
+ <Dropdown.Content>
117
+ <Dropdown.Item>Item 1</Dropdown.Item>
118
+ </Dropdown.Content>
119
+ </Dropdown>,
120
+ );
121
+
122
+ const trigger = screen.getByText('Open Menu');
123
+ expect(trigger).toBeInTheDocument();
124
+ });
125
+
126
+ test('renders dropdown with disabled item', async () => {
127
+ render(
128
+ <Dropdown>
129
+ <Dropdown.Trigger>
130
+ <Button>Open Menu</Button>
131
+ </Dropdown.Trigger>
132
+ <Dropdown.Content>
133
+ <Dropdown.Item disabled>Disabled Item</Dropdown.Item>
134
+ <Dropdown.Item>Enabled Item</Dropdown.Item>
135
+ </Dropdown.Content>
136
+ </Dropdown>,
137
+ );
138
+
139
+ const trigger = screen.getByText('Open Menu');
140
+ await userEvent.click(trigger);
141
+
142
+ const disabledItem = await screen.findByText('Disabled Item');
143
+ const enabledItem = screen.getByText('Enabled Item');
144
+
145
+ expect(disabledItem).toBeInTheDocument();
146
+ expect(enabledItem).toBeInTheDocument();
147
+ });
148
+
149
+ test('renders dropdown content with custom className', async () => {
150
+ render(
151
+ <Dropdown>
152
+ <Dropdown.Trigger>
153
+ <Button>Open Menu</Button>
154
+ </Dropdown.Trigger>
155
+ <Dropdown.Content contentProps={{ className: 'custom-dropdown-content' }}>
156
+ <Dropdown.Item>Item 1</Dropdown.Item>
157
+ </Dropdown.Content>
158
+ </Dropdown>,
159
+ );
160
+
161
+ const trigger = screen.getByText('Open Menu');
162
+ await userEvent.click(trigger);
163
+
164
+ const content = await screen.findByText('Item 1');
165
+ expect(content.closest('.ds-dropdown__content')).toBeInTheDocument();
166
+ });
167
+
168
+ test('renders dropdown item with custom className', async () => {
169
+ render(
170
+ <Dropdown>
171
+ <Dropdown.Trigger>
172
+ <Button>Open Menu</Button>
173
+ </Dropdown.Trigger>
174
+ <Dropdown.Content>
175
+ <Dropdown.Item className="custom-item">Item 1</Dropdown.Item>
176
+ </Dropdown.Content>
177
+ </Dropdown>,
178
+ );
179
+
180
+ const trigger = screen.getByText('Open Menu');
181
+ await userEvent.click(trigger);
182
+
183
+ const item = await screen.findByText('Item 1');
184
+ expect(item).toHaveClass('ds-dropdown__item', 'custom-item');
185
+ });
186
+
187
+ test('renders complex content inside dropdown', async () => {
188
+ render(
189
+ <Dropdown>
190
+ <Dropdown.Trigger>
191
+ <Button>Open Menu</Button>
192
+ </Dropdown.Trigger>
193
+ <Dropdown.Content>
194
+ <div>
195
+ <Dropdown.Item>Item 1</Dropdown.Item>
196
+ <div>Separator</div>
197
+ <Dropdown.Item>Item 2</Dropdown.Item>
198
+ </div>
199
+ </Dropdown.Content>
200
+ </Dropdown>,
201
+ );
202
+
203
+ const trigger = screen.getByText('Open Menu');
204
+ await userEvent.click(trigger);
205
+
206
+ expect(await screen.findByText('Item 1')).toBeInTheDocument();
207
+ expect(screen.getByText('Separator')).toBeInTheDocument();
208
+ expect(screen.getByText('Item 2')).toBeInTheDocument();
209
+ });
210
+
211
+ test('handles controlled open state', async () => {
212
+ const { rerender } = render(
213
+ <Dropdown open={false}>
214
+ <Dropdown.Trigger>
215
+ <Button>Open Menu</Button>
216
+ </Dropdown.Trigger>
217
+ <Dropdown.Content>
218
+ <Dropdown.Item>Item 1</Dropdown.Item>
219
+ </Dropdown.Content>
220
+ </Dropdown>,
221
+ );
222
+
223
+ expect(screen.queryByText('Item 1')).not.toBeInTheDocument();
224
+
225
+ rerender(
226
+ <Dropdown open={true}>
227
+ <Dropdown.Trigger>
228
+ <Button>Open Menu</Button>
229
+ </Dropdown.Trigger>
230
+ <Dropdown.Content>
231
+ <Dropdown.Item>Item 1</Dropdown.Item>
232
+ </Dropdown.Content>
233
+ </Dropdown>,
234
+ );
235
+
236
+ expect(await screen.findByText('Item 1')).toBeInTheDocument();
237
+ });
238
+
239
+ describe('Dropdown.SelectItem', () => {
240
+ test('renders select item with selected state', async () => {
241
+ render(
242
+ <Dropdown>
243
+ <Dropdown.Trigger>
244
+ <Button>Open Menu</Button>
245
+ </Dropdown.Trigger>
246
+ <Dropdown.Content>
247
+ <Dropdown.SelectItem selected={true} onSelectChange={vi.fn()}>
248
+ Item 1
249
+ </Dropdown.SelectItem>
250
+ </Dropdown.Content>
251
+ </Dropdown>,
252
+ );
253
+
254
+ const trigger = screen.getByText('Open Menu');
255
+ await userEvent.click(trigger);
256
+
257
+ const item = await screen.findByText('Item 1');
258
+ expect(item).toBeInTheDocument();
259
+ expect(item).toHaveClass('ds-dropdown__item', 'ds-dropdown__item--select');
260
+ const checkIcon = item.querySelector('.ds-dropdown__item--check-icon');
261
+ expect(checkIcon).toBeInTheDocument();
262
+ });
263
+
264
+ test('renders select item with unselected state', async () => {
265
+ render(
266
+ <Dropdown>
267
+ <Dropdown.Trigger>
268
+ <Button>Open Menu</Button>
269
+ </Dropdown.Trigger>
270
+ <Dropdown.Content>
271
+ <Dropdown.SelectItem selected={false} onSelectChange={vi.fn()}>
272
+ Item 1
273
+ </Dropdown.SelectItem>
274
+ </Dropdown.Content>
275
+ </Dropdown>,
276
+ );
277
+
278
+ const trigger = screen.getByText('Open Menu');
279
+ await userEvent.click(trigger);
280
+
281
+ const item = await screen.findByText('Item 1');
282
+ expect(item).toBeInTheDocument();
283
+ expect(item).toHaveClass('ds-dropdown__item', 'ds-dropdown__item--select');
284
+ const checkIcon = item.querySelector('.ds-dropdown__item--check-icon');
285
+ expect(checkIcon).not.toBeInTheDocument();
286
+ });
287
+
288
+ test('calls onSelectChange when clicked', async () => {
289
+ const onSelectChange = vi.fn();
290
+
291
+ render(
292
+ <Dropdown>
293
+ <Dropdown.Trigger>
294
+ <Button>Open Menu</Button>
295
+ </Dropdown.Trigger>
296
+ <Dropdown.Content>
297
+ <Dropdown.SelectItem selected={false} onSelectChange={onSelectChange}>
298
+ Item 1
299
+ </Dropdown.SelectItem>
300
+ </Dropdown.Content>
301
+ </Dropdown>,
302
+ );
303
+
304
+ const trigger = screen.getByText('Open Menu');
305
+ await userEvent.click(trigger);
306
+
307
+ const item = await screen.findByText('Item 1');
308
+ await userEvent.click(item);
309
+
310
+ expect(onSelectChange).toHaveBeenCalled();
311
+ });
312
+
313
+ test('renders multiple select items', async () => {
314
+ render(
315
+ <Dropdown>
316
+ <Dropdown.Trigger>
317
+ <Button>Open Menu</Button>
318
+ </Dropdown.Trigger>
319
+ <Dropdown.Content>
320
+ <Dropdown.SelectItem selected={true} onSelectChange={vi.fn()}>
321
+ Item 1
322
+ </Dropdown.SelectItem>
323
+ <Dropdown.SelectItem selected={false} onSelectChange={vi.fn()}>
324
+ Item 2
325
+ </Dropdown.SelectItem>
326
+ <Dropdown.SelectItem selected={true} onSelectChange={vi.fn()}>
327
+ Item 3
328
+ </Dropdown.SelectItem>
329
+ </Dropdown.Content>
330
+ </Dropdown>,
331
+ );
332
+
333
+ const trigger = screen.getByText('Open Menu');
334
+ await userEvent.click(trigger);
335
+
336
+ expect(await screen.findByText('Item 1')).toBeInTheDocument();
337
+ expect(screen.getByText('Item 2')).toBeInTheDocument();
338
+ expect(screen.getByText('Item 3')).toBeInTheDocument();
339
+ });
340
+
341
+ test('handles disabled select item', async () => {
342
+ const onSelectChange = vi.fn();
343
+
344
+ render(
345
+ <Dropdown>
346
+ <Dropdown.Trigger>
347
+ <Button>Open Menu</Button>
348
+ </Dropdown.Trigger>
349
+ <Dropdown.Content>
350
+ <Dropdown.SelectItem
351
+ selected={false}
352
+ onSelectChange={onSelectChange}
353
+ disabled
354
+ >
355
+ Disabled Item
356
+ </Dropdown.SelectItem>
357
+ </Dropdown.Content>
358
+ </Dropdown>,
359
+ );
360
+
361
+ const trigger = screen.getByText('Open Menu');
362
+ await userEvent.click(trigger);
363
+
364
+ const item = await screen.findByText('Disabled Item');
365
+ expect(item).toBeInTheDocument();
366
+ });
367
+ });
368
+ });
@@ -0,0 +1,19 @@
1
+ import { DropdownMenu } from 'radix-ui';
2
+ import { DropdownTrigger } from './DropdownTrigger';
3
+ import { DropdownContent } from './DropdownContent';
4
+ import { DropdownItem } from './items/DropdownItem';
5
+ import { DropdownSelectItem } from './items/DropdownSelectItem';
6
+
7
+ export const Dropdown = (props: DropdownMenu.DropdownMenuProps) => {
8
+ const { children, ...rest } = props;
9
+ return (
10
+ <DropdownMenu.Root {...rest}>
11
+ {children}
12
+ </DropdownMenu.Root>
13
+ );
14
+ };
15
+
16
+ Dropdown.Trigger = DropdownTrigger;
17
+ Dropdown.Content = DropdownContent;
18
+ Dropdown.Item = DropdownItem;
19
+ Dropdown.SelectItem = DropdownSelectItem;
@@ -0,0 +1,22 @@
1
+ import { DropdownMenu } from 'radix-ui';
2
+ import { useContext } from 'react';
3
+ import { PopupParentContext } from 'Utils/PopupParentContext';
4
+
5
+ type DropdownContentProps = {
6
+ children: React.ReactNode;
7
+ portalProps?: DropdownMenu.DropdownMenuPortalProps;
8
+ contentProps?: DropdownMenu.DropdownMenuContentProps;
9
+ };
10
+
11
+ export const DropdownContent = (props: DropdownContentProps) => {
12
+ const { children, portalProps, contentProps } = props;
13
+ const popupParentRef = useContext(PopupParentContext);
14
+
15
+ return (
16
+ <DropdownMenu.Portal {...portalProps} container={popupParentRef.current}>
17
+ <DropdownMenu.Content align="start" {...contentProps} className="ds-dropdown__content">
18
+ {children}
19
+ </DropdownMenu.Content>
20
+ </DropdownMenu.Portal>
21
+ );
22
+ };
@@ -0,0 +1,10 @@
1
+ import { DropdownMenu } from 'radix-ui';
2
+
3
+ export const DropdownTrigger = (props: DropdownMenu.DropdownMenuTriggerProps & { children: React.ReactElement }) => {
4
+ const { children, ...rest } = props;
5
+ return (
6
+ <DropdownMenu.Trigger asChild {...rest}>
7
+ {children}
8
+ </DropdownMenu.Trigger>
9
+ );
10
+ };
@@ -0,0 +1,48 @@
1
+ .ds-dropdown__content {
2
+ padding: var(--form-dropdown-spacing-vertical) var(--form-dropdown-spacing-horizontal);
3
+ box-shadow: 0 4px 12px 0 rgb(32 32 32 / 8%);
4
+ border-radius: var(--banner-radius);
5
+ background-color: var(--form-dropdown-color-background);
6
+ min-width: 200px;
7
+ border: 1px solid var(--color-grey-200);
8
+ font-family: var(--type-body-p-family);
9
+ font-size: var(--type-body-p-size);
10
+ font-style: normal;
11
+ font-weight: var(--type-body-p-weight);
12
+ line-height: 150%;
13
+ }
14
+
15
+ .ds-dropdown__item {
16
+ padding: var(--form-dropdown-form-drop-item-spacing-vertical) var(--form-dropdown-form-drop-item-spacing-horizontal);
17
+ border-radius: var(--form-dropdown-form-drop-item-radius, 8px);
18
+ display: flex;
19
+ align-items: center;
20
+ background: var(--form-dropdown-form-drop-item-default-color-background);
21
+ color: var(--form-dropdown-form-drop-item-default-color-text);
22
+ gap: var(--form-dropdown-form-drop-item-spacing-gap-horizontal);
23
+ cursor: pointer;
24
+
25
+ &:hover {
26
+ background: var(--form-dropdown-form-drop-item-hover-color-background);
27
+ outline: none;
28
+ }
29
+
30
+ &:focus-visible:not(:hover) {
31
+ background: var(--form-dropdown-form-drop-item-focus-default-color-background);
32
+ outline: var(--focus-border) solid var(--form-dropdown-form-drop-item-focus-default-color-focus);
33
+ }
34
+
35
+ &[aria-checked="true"] {
36
+ background: var(--form-dropdown-form-drop-item-active-color-background);
37
+ color: var(--form-dropdown-form-drop-item-active-color-text);
38
+ }
39
+
40
+ &[aria-disabled="true"] {
41
+ pointer-events: none;
42
+ opacity: 0.5;
43
+ }
44
+
45
+ .ds-dropdown__item--check-icon {
46
+ margin-left: auto;
47
+ }
48
+ }
@@ -0,0 +1,11 @@
1
+ import { DropdownMenu } from 'radix-ui';
2
+ import classNames from 'classnames';
3
+
4
+ export const DropdownItem = (props: DropdownMenu.DropdownMenuItemProps) => {
5
+ const { children, className = '', ...rest } = props;
6
+ return (
7
+ <DropdownMenu.Item className={classNames('ds-dropdown__item', className)} {...rest}>
8
+ {children}
9
+ </DropdownMenu.Item>
10
+ );
11
+ };
@@ -0,0 +1,28 @@
1
+ import { DropdownMenu } from 'radix-ui';
2
+ import classNames from 'classnames';
3
+ import { Icon } from 'Components/icon/Icon';
4
+
5
+ export type DropdowSelectItemProps = {
6
+ selected: boolean;
7
+ onSelectChange: (selected: boolean) => void;
8
+ closeAfterSelection?: boolean;
9
+ } & DropdownMenu.DropdownMenuCheckboxItemProps;
10
+
11
+ export const DropdownSelectItem = (props: DropdowSelectItemProps) => {
12
+ const { children, selected, onSelectChange, closeAfterSelection = true, className = '', ...rest } = props;
13
+ return (
14
+ <DropdownMenu.CheckboxItem
15
+ checked={selected}
16
+ onCheckedChange={onSelectChange}
17
+ className={classNames('ds-dropdown__item ds-dropdown__item--select', className)}
18
+ // prevent default stops the dropdown from closing
19
+ onSelect={!closeAfterSelection ? e => e.preventDefault() : undefined}
20
+ {...rest}
21
+ >
22
+ {children}
23
+ <DropdownMenu.ItemIndicator className="ds-dropdown__item--check-icon">
24
+ <Icon name="check" size={16} />
25
+ </DropdownMenu.ItemIndicator>
26
+ </DropdownMenu.CheckboxItem>
27
+ );
28
+ };
@@ -1,9 +1,9 @@
1
1
  import type { Meta } from '@storybook/react-vite';
2
- import { Dropdown } from './Dropdown';
2
+ import { SelectDropdown } from './SelectDropdown';
3
3
 
4
- const meta: Meta<typeof Dropdown> = {
5
- title: 'Components/Dropdown',
6
- component: Dropdown,
4
+ const meta: Meta<typeof SelectDropdown> = {
5
+ title: 'Components/FormField/Inputs/SelectDropdown',
6
+ component: SelectDropdown,
7
7
  };
8
8
 
9
9
  export const Default = {
@@ -2,12 +2,12 @@ import { describe, expect, test, vi } from 'vitest';
2
2
  import { render, screen } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
  import '@testing-library/jest-dom/vitest';
5
- import { Dropdown } from './Dropdown';
5
+ import { SelectDropdown } from './SelectDropdown';
6
6
 
7
- describe('Dropdown component', () => {
7
+ describe('SelectDropdown component', () => {
8
8
  test('renders with default placeholder', () => {
9
9
  render(
10
- <Dropdown
10
+ <SelectDropdown
11
11
  onSelectionChange={vi.fn()}
12
12
  disabled={false}
13
13
  options={[{ label: 'Label 1', value: 'label1' }]}
@@ -18,7 +18,7 @@ describe('Dropdown component', () => {
18
18
 
19
19
  test('renders custom placeholder', () => {
20
20
  render(
21
- <Dropdown
21
+ <SelectDropdown
22
22
  onSelectionChange={vi.fn()}
23
23
  placeholder="Hello I'm a Dropdown!"
24
24
  disabled={false}
@@ -30,7 +30,7 @@ describe('Dropdown component', () => {
30
30
 
31
31
  test('click opens options', async () => {
32
32
  render(
33
- <Dropdown
33
+ <SelectDropdown
34
34
  onSelectionChange={vi.fn()}
35
35
  placeholder="Hello I'm a Dropdown!"
36
36
  disabled={false}
@@ -44,7 +44,7 @@ describe('Dropdown component', () => {
44
44
 
45
45
  test('does not open options when disabled', async () => {
46
46
  render(
47
- <Dropdown
47
+ <SelectDropdown
48
48
  onSelectionChange={vi.fn()}
49
49
  placeholder="Disabled Dropdown"
50
50
  disabled={true}
@@ -59,7 +59,7 @@ describe('Dropdown component', () => {
59
59
  test('clicking option triggers onSelectionChange', async () => {
60
60
  const onSelectionChange = vi.fn();
61
61
  render(
62
- <Dropdown
62
+ <SelectDropdown
63
63
  onSelectionChange={onSelectionChange}
64
64
  placeholder="Hello I'm a Dropdown!"
65
65
  disabled={false}
@@ -76,7 +76,7 @@ describe('Dropdown component', () => {
76
76
 
77
77
  test('renders multiple options (Default story)', async () => {
78
78
  render(
79
- <Dropdown
79
+ <SelectDropdown
80
80
  options={[
81
81
  { label: 'Option 1', value: 'option1' },
82
82
  { label: 'Option 2', value: 'option2' },
@@ -94,7 +94,7 @@ describe('Dropdown component', () => {
94
94
 
95
95
  test('renders icons (WithIcon story)', async () => {
96
96
  render(
97
- <Dropdown
97
+ <SelectDropdown
98
98
  options={[
99
99
  { label: 'Option 1', value: 'option1', icon: '3-dot' },
100
100
  { label: 'Option 2', value: 'option2', icon: 'user' },
@@ -112,7 +112,7 @@ describe('Dropdown component', () => {
112
112
  test('supports multi-select (DefaultMultiSelect story)', async () => {
113
113
  const onSelectionChange = vi.fn();
114
114
  render(
115
- <Dropdown
115
+ <SelectDropdown
116
116
  multiple
117
117
  options={[
118
118
  { label: 'Option 1', value: 'option1' },
@@ -134,7 +134,7 @@ describe('Dropdown component', () => {
134
134
 
135
135
  test('applies error class when errorText is provided', () => {
136
136
  render(
137
- <Dropdown
137
+ <SelectDropdown
138
138
  onSelectionChange={vi.fn()}
139
139
  placeholder="Select"
140
140
  disabled={false}
@@ -149,7 +149,7 @@ describe('Dropdown component', () => {
149
149
 
150
150
  test('renders grouped options (WithGroups story)', async () => {
151
151
  render(
152
- <Dropdown
152
+ <SelectDropdown
153
153
  options={[
154
154
  { label: 'Option 1', value: 'option1', group: 'Group 1' },
155
155
  { label: 'Option 2', value: 'option2', group: 'Group 1' },
@@ -168,7 +168,7 @@ describe('Dropdown component', () => {
168
168
 
169
169
  test('renders multiline items (MultilineItems story)', async () => {
170
170
  render(
171
- <Dropdown
171
+ <SelectDropdown
172
172
  options={[
173
173
  { label: 'Option 1', value: 'option1', header: 'header1' },
174
174
  { label: 'Option 2', value: 'option2', header: 'header2' },