@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.
- package/CHANGELOG.md +12 -0
- package/dist/components/button/Button.d.ts +3 -3
- package/dist/components/button/Button.d.ts.map +1 -1
- package/dist/components/button/Button.js +7 -6
- package/dist/components/button/Button.js.map +1 -1
- package/dist/components/button/Button.test.js +1 -1
- package/dist/components/button/Button.test.js.map +1 -1
- package/dist/components/dropdown/Dropdown.d.ts +15 -0
- package/dist/components/dropdown/Dropdown.d.ts.map +1 -0
- package/dist/components/dropdown/Dropdown.js +15 -0
- package/dist/components/dropdown/Dropdown.js.map +1 -0
- package/dist/components/dropdown/Dropdown.stories.d.ts +20 -0
- package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -0
- package/dist/components/dropdown/Dropdown.stories.js +30 -0
- package/dist/components/dropdown/Dropdown.stories.js.map +1 -0
- package/dist/components/dropdown/Dropdown.test.d.ts.map +1 -0
- package/dist/components/dropdown/Dropdown.test.js +136 -0
- package/dist/components/dropdown/Dropdown.test.js.map +1 -0
- package/dist/components/dropdown/DropdownContent.d.ts +9 -0
- package/dist/components/dropdown/DropdownContent.d.ts.map +1 -0
- package/dist/components/dropdown/DropdownContent.js +10 -0
- package/dist/components/dropdown/DropdownContent.js.map +1 -0
- package/dist/components/dropdown/DropdownTrigger.d.ts +5 -0
- package/dist/components/dropdown/DropdownTrigger.d.ts.map +1 -0
- package/dist/components/dropdown/DropdownTrigger.js +7 -0
- package/dist/components/dropdown/DropdownTrigger.js.map +1 -0
- package/dist/components/dropdown/items/DropdownItem.d.ts +3 -0
- package/dist/components/dropdown/items/DropdownItem.d.ts.map +1 -0
- package/dist/components/dropdown/items/DropdownItem.js +8 -0
- package/dist/components/dropdown/items/DropdownItem.js.map +1 -0
- package/dist/components/dropdown/items/DropdownSelectItem.d.ts +8 -0
- package/dist/components/dropdown/items/DropdownSelectItem.d.ts.map +1 -0
- package/dist/components/dropdown/items/DropdownSelectItem.js +11 -0
- package/dist/components/dropdown/items/DropdownSelectItem.js.map +1 -0
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts +11 -0
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -0
- package/dist/components/formField/inputs/{dropdown/Dropdown.js → selectDropdown/SelectDropdown.js} +20 -9
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -0
- package/dist/components/formField/inputs/{dropdown/Dropdown.stories.d.ts → selectDropdown/SelectDropdown.stories.d.ts} +3 -3
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts.map +1 -0
- package/dist/components/formField/inputs/{dropdown/Dropdown.stories.js → selectDropdown/SelectDropdown.stories.js} +4 -4
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js.map +1 -0
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.d.ts +2 -0
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.d.ts.map +1 -0
- package/dist/components/formField/inputs/{dropdown/Dropdown.test.js → selectDropdown/SelectDropdown.test.js} +14 -14
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.js.map +1 -0
- package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.d.ts +16 -0
- package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.d.ts.map +1 -0
- package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.js +13 -0
- package/dist/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.js.map +1 -0
- package/dist/components/heading/Heading.stories.js +3 -3
- package/dist/components/heading/Heading.stories.js.map +1 -1
- package/dist/components/section/Section.d.ts +2 -2
- package/dist/components/section/Section.d.ts.map +1 -1
- package/dist/components/section/Section.js +2 -2
- package/dist/components/section/Section.js.map +1 -1
- package/dist/components/section/Section.test.js +1 -1
- package/dist/components/section/Section.test.js.map +1 -1
- package/dist/components/slideover/Slideover.js +1 -1
- package/dist/components/slideover/Slideover.js.map +1 -1
- package/dist/components/slideoverManager/SlideoverManager.stories.js +7 -7
- package/dist/components/slideoverManager/SlideoverManager.stories.js.map +1 -1
- package/dist/components/table/BulkActionsDropdown.d.ts.map +1 -1
- package/dist/components/table/BulkActionsDropdown.js +3 -4
- package/dist/components/table/BulkActionsDropdown.js.map +1 -1
- package/dist/components/table/Table.stories.js +3 -3
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/Table.test.js +5 -5
- package/dist/components/table/Table.test.js.map +1 -1
- package/dist/index.css +70 -159
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/release/design-system.components.tgz +0 -0
- package/src/components/button/Button.test.tsx +1 -1
- package/src/components/button/Button.tsx +10 -7
- package/src/components/dropdown/Dropdown.stories.tsx +62 -0
- package/src/components/dropdown/Dropdown.test.tsx +368 -0
- package/src/components/dropdown/Dropdown.tsx +19 -0
- package/src/components/dropdown/DropdownContent.tsx +22 -0
- package/src/components/dropdown/DropdownTrigger.tsx +10 -0
- package/src/components/dropdown/dropdown.scss +48 -0
- package/src/components/dropdown/items/DropdownItem.tsx +11 -0
- package/src/components/dropdown/items/DropdownSelectItem.tsx +28 -0
- package/src/components/formField/inputs/{dropdown/Dropdown.stories.tsx → selectDropdown/SelectDropdown.stories.tsx} +4 -4
- package/src/components/formField/inputs/{dropdown/Dropdown.test.tsx → selectDropdown/SelectDropdown.test.tsx} +13 -13
- package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +102 -0
- package/src/components/formField/inputs/selectDropdown/items/item/SelectDropdownItem.tsx +46 -0
- package/src/components/formField/inputs/selectDropdown/selectDropdown.scss +22 -0
- package/src/components/heading/Heading.stories.tsx +3 -3
- package/src/components/icon/icon.scss +3 -0
- package/src/components/section/Section.test.tsx +1 -1
- package/src/components/section/Section.tsx +4 -4
- package/src/components/slideover/Slideover.tsx +1 -1
- package/src/components/slideoverManager/SlideoverManager.stories.tsx +14 -14
- package/src/components/table/BulkActionsDropdown.tsx +19 -23
- package/src/components/table/Table.stories.tsx +4 -4
- package/src/components/table/Table.test.tsx +5 -5
- package/src/index.scss +3 -5
- package/src/index.ts +2 -1
- package/src/tokens.scss +1 -0
- package/dist/components/formField/inputs/dropdown/Dropdown.d.ts +0 -11
- package/dist/components/formField/inputs/dropdown/Dropdown.d.ts.map +0 -1
- package/dist/components/formField/inputs/dropdown/Dropdown.js.map +0 -1
- package/dist/components/formField/inputs/dropdown/Dropdown.stories.d.ts.map +0 -1
- package/dist/components/formField/inputs/dropdown/Dropdown.stories.js.map +0 -1
- package/dist/components/formField/inputs/dropdown/Dropdown.test.d.ts.map +0 -1
- package/dist/components/formField/inputs/dropdown/Dropdown.test.js.map +0 -1
- package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.d.ts +0 -12
- package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.d.ts.map +0 -1
- package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.js +0 -15
- package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.js.map +0 -1
- package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.d.ts +0 -10
- package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.d.ts.map +0 -1
- package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.js +0 -12
- package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.js.map +0 -1
- package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.d.ts +0 -9
- package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.d.ts.map +0 -1
- package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.js +0 -21
- package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.js.map +0 -1
- package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.d.ts +0 -7
- package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.d.ts.map +0 -1
- package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.js +0 -16
- package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.js.map +0 -1
- package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.d.ts +0 -17
- package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.d.ts.map +0 -1
- package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.js +0 -151
- package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.js.map +0 -1
- package/src/components/formField/inputs/dropdown/Dropdown.tsx +0 -82
- package/src/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.tsx +0 -44
- package/src/components/formField/inputs/dropdown/buttons/dropdownButton/dropdownButton.scss +0 -12
- package/src/components/formField/inputs/dropdown/dropdown.scss +0 -24
- package/src/components/formField/inputs/dropdown/items/DropdownItemRenderer.tsx +0 -38
- package/src/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.tsx +0 -53
- package/src/components/formField/inputs/dropdown/items/dropdownItem/dropdownItem.scss +0 -62
- package/src/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.tsx +0 -48
- package/src/components/formField/inputs/dropdown/items/dropdownMultiLineItem/dropdownMultiLineItem.scss +0 -52
- package/src/components/formField/inputs/dropdown/wrapper/DropdownWrapper.tsx +0 -248
- package/src/components/formField/inputs/dropdown/wrapper/dropdownWrapper.scss +0 -37
- /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 {
|
|
2
|
+
import { SelectDropdown } from './SelectDropdown';
|
|
3
3
|
|
|
4
|
-
const meta: Meta<typeof
|
|
5
|
-
title: 'Components/
|
|
6
|
-
component:
|
|
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 {
|
|
5
|
+
import { SelectDropdown } from './SelectDropdown';
|
|
6
6
|
|
|
7
|
-
describe('
|
|
7
|
+
describe('SelectDropdown component', () => {
|
|
8
8
|
test('renders with default placeholder', () => {
|
|
9
9
|
render(
|
|
10
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
171
|
+
<SelectDropdown
|
|
172
172
|
options={[
|
|
173
173
|
{ label: 'Option 1', value: 'option1', header: 'header1' },
|
|
174
174
|
{ label: 'Option 2', value: 'option2', header: 'header2' },
|