@sproutsocial/seeds-react-menu 0.1.0 → 0.2.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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +17 -1
- package/dist/esm/index.js +209 -198
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +103 -37
- package/dist/index.d.ts +103 -37
- package/dist/index.js +239 -229
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
- package/src/ActionMenu/ActionMenu.feature +9 -3
- package/src/ActionMenu/ActionMenu.tsx +75 -42
- package/src/ActionMenu/__tests__/ActionMenu.test.tsx +36 -42
- package/src/ActionMenu/__tests__/ActionMenu.typetest.tsx +13 -13
- package/src/MenuContent/MenuContent.tsx +31 -1
- package/src/MenuContent/MenuContentTypes.ts +2 -2
- package/src/MenuContent/styles.tsx +10 -6
- package/src/MenuFooter/MenuFooter.feature +1 -1
- package/src/MenuFooter/MenuFooter.tsx +8 -0
- package/src/MenuFooter/__tests__/MenuFooter.test.tsx +53 -26
- package/src/MenuFooter/styles.tsx +1 -1
- package/src/MenuGroup/MenuGroup.feature +14 -26
- package/src/MenuGroup/MenuGroup.tsx +21 -1
- package/src/MenuGroup/__tests__/MenuGroup.test.tsx +40 -6
- package/src/MenuGroup/styles.tsx +4 -3
- package/src/MenuHeader/MenuHeader.tsx +16 -5
- package/src/MenuHeader/__tests__/MenuHeader.test.tsx +50 -6
- package/src/MenuHeader/styles.tsx +21 -22
- package/src/MenuItem/MenuItem.feature +66 -26
- package/src/MenuItem/MenuItem.tsx +35 -20
- package/src/MenuItem/MenuItemTypes.ts +13 -1
- package/src/MenuItem/__tests__/MenuItem.test.tsx +431 -154
- package/src/MenuItem/__tests__/MenuItem.typetest.tsx +57 -38
- package/src/MenuItem/styles.tsx +42 -3
- package/src/{MenuItemSelectable/MenuItemSelectable.tsx → MenuItem/useOptionProps.tsx} +31 -54
- package/src/MenuRoot/MenuRoot.tsx +12 -17
- package/src/MenuRoot/__tests__/MenuRoot.test.tsx +3 -3
- package/src/MenuRoot/__tests__/MenuRoot.typetest.tsx +10 -10
- package/src/MultiSelectMenu/MultiSelectMenu.tsx +8 -1
- package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +19 -19
- package/src/MultiSelectMenu/__tests__/MultiSelectMenu.typetest.tsx +10 -10
- package/src/SingleSelectMenu/SingleSelectMenu.feature +11 -0
- package/src/SingleSelectMenu/SingleSelectMenu.tsx +9 -0
- package/src/SingleSelectMenu/__tests__/SingleSelectMenu.test.tsx +11 -14
- package/src/SingleSelectMenu/__tests__/SingleSelectMenu.typetest.tsx +14 -14
- package/src/__tests__/Menu.test.tsx +20 -16
- package/src/hooks/useItemsFromChildren.tsx +1 -4
- package/src/index.ts +0 -1
- package/src/menuTestingUtils.tsx +7 -4
- package/src/MenuItemSelectable/MenuItemSelectable.feature +0 -99
- package/src/MenuItemSelectable/MenuItemSelectableTypes.ts +0 -15
- package/src/MenuItemSelectable/__tests__/MenuItemSelectable.test.tsx +0 -303
- package/src/MenuItemSelectable/__tests__/MenuItemSelectable.typetest.tsx +0 -56
- package/src/MenuItemSelectable/index.ts +0 -5
- package/src/MenuItemSelectable/styles.tsx +0 -21
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
/* eslint-disable testing-library/no-unnecessary-act */
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { render, screen, act } from "@sproutsocial/seeds-react-testing-library";
|
|
4
|
-
import { PointerEventsCheckLevel } from "@testing-library/user-event";
|
|
5
|
-
import { type UserEvent } from "@testing-library/user-event";
|
|
6
|
-
import {
|
|
7
|
-
MenuContent,
|
|
8
|
-
MenuToggleButton,
|
|
9
|
-
MenuItemSelectable,
|
|
10
|
-
MultiSelectMenu,
|
|
11
|
-
INPUT_TYPES,
|
|
12
|
-
} from "../../index";
|
|
13
|
-
import { Button } from "@sproutsocial/seeds-react-button";
|
|
14
|
-
import { TestWithMenuRoot, TestWithMockedMenu } from "../../menuTestingUtils";
|
|
15
|
-
|
|
16
|
-
const books = [
|
|
17
|
-
{
|
|
18
|
-
index: 0,
|
|
19
|
-
id: "book-1",
|
|
20
|
-
author: "Harper Lee",
|
|
21
|
-
title: "To Kill a Mockingbird",
|
|
22
|
-
},
|
|
23
|
-
{ index: 1, id: "book-2", author: "Lev Tolstoy", title: "War and Peace" },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
const testByType = {
|
|
27
|
-
[INPUT_TYPES.CHECKBOX]: {
|
|
28
|
-
isSelected: () => {
|
|
29
|
-
expect(
|
|
30
|
-
screen.getByRole("checkbox", { checked: true })
|
|
31
|
-
).toBeInTheDocument();
|
|
32
|
-
},
|
|
33
|
-
isNotSelected: () => {
|
|
34
|
-
expect(
|
|
35
|
-
screen.queryByRole("checkbox", { checked: true })
|
|
36
|
-
).not.toBeInTheDocument();
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
[INPUT_TYPES.RADIO]: {
|
|
40
|
-
isSelected: () => {
|
|
41
|
-
expect(screen.getByRole("radio", { checked: true })).toBeInTheDocument();
|
|
42
|
-
},
|
|
43
|
-
isNotSelected: () => {
|
|
44
|
-
expect(
|
|
45
|
-
screen.queryByRole("radio", { checked: true })
|
|
46
|
-
).not.toBeInTheDocument();
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
[INPUT_TYPES.SWITCH]: {
|
|
50
|
-
isSelected: () => {
|
|
51
|
-
expect(screen.getByRole("switch", { checked: true })).toBeInTheDocument();
|
|
52
|
-
},
|
|
53
|
-
isNotSelected: () => {
|
|
54
|
-
expect(
|
|
55
|
-
screen.queryByRole("switch", { checked: true })
|
|
56
|
-
).not.toBeInTheDocument();
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
[INPUT_TYPES.ICON]: {
|
|
60
|
-
isSelected: () => {
|
|
61
|
-
expect(
|
|
62
|
-
screen.getByDataQaLabel({ icon: "check-solid" })
|
|
63
|
-
).toBeInTheDocument();
|
|
64
|
-
},
|
|
65
|
-
isNotSelected: () => {
|
|
66
|
-
expect(
|
|
67
|
-
screen.queryByDataQaLabel({ icon: "check-solid" })
|
|
68
|
-
).not.toBeInTheDocument();
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
describe("MenuItemSelectable ", () => {
|
|
74
|
-
describe("User Behavior", () => {
|
|
75
|
-
xdescribe.each([
|
|
76
|
-
INPUT_TYPES.CHECKBOX,
|
|
77
|
-
INPUT_TYPES.RADIO,
|
|
78
|
-
INPUT_TYPES.SWITCH,
|
|
79
|
-
INPUT_TYPES.ICON,
|
|
80
|
-
])("when the input type is %s", (inputType) => {
|
|
81
|
-
test.each([
|
|
82
|
-
{
|
|
83
|
-
action: "clicking with mouse",
|
|
84
|
-
activate: async (user: UserEvent) => {
|
|
85
|
-
const firstItem = screen.getByRole("option", {
|
|
86
|
-
name: books[0].title,
|
|
87
|
-
});
|
|
88
|
-
await user
|
|
89
|
-
.setup({
|
|
90
|
-
pointerEventsCheck: PointerEventsCheckLevel.Never,
|
|
91
|
-
})
|
|
92
|
-
.click(firstItem);
|
|
93
|
-
},
|
|
94
|
-
deactivate: async (user: UserEvent) => {
|
|
95
|
-
const firstItem = screen.getByRole("option", {
|
|
96
|
-
name: books[0].title,
|
|
97
|
-
});
|
|
98
|
-
await user
|
|
99
|
-
.setup({
|
|
100
|
-
pointerEventsCheck: PointerEventsCheckLevel.Never,
|
|
101
|
-
})
|
|
102
|
-
.click(firstItem);
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
action: "pressing the Enter key",
|
|
107
|
-
activate: async (user: UserEvent) => {
|
|
108
|
-
await user.keyboard("{arrowdown}{enter}");
|
|
109
|
-
},
|
|
110
|
-
deactivate: async (user: UserEvent) => {
|
|
111
|
-
await user.keyboard("{enter}");
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
action: "pressing the Space key",
|
|
116
|
-
activate: async (user: UserEvent) => {
|
|
117
|
-
await user.keyboard("{arrowdown}{space}");
|
|
118
|
-
},
|
|
119
|
-
deactivate: async (user: UserEvent) => {
|
|
120
|
-
await user.keyboard("{space}");
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
])(
|
|
124
|
-
"can be checked and unchecked when $action",
|
|
125
|
-
async ({ activate, deactivate }) => {
|
|
126
|
-
const { user } = render(
|
|
127
|
-
<MultiSelectMenu
|
|
128
|
-
triggerElement={<MenuToggleButton>Open</MenuToggleButton>}
|
|
129
|
-
defaultIsOpen
|
|
130
|
-
>
|
|
131
|
-
<MenuContent>
|
|
132
|
-
{books.map((book) => {
|
|
133
|
-
return (
|
|
134
|
-
<MenuItemSelectable
|
|
135
|
-
key={book.id}
|
|
136
|
-
id={book.id}
|
|
137
|
-
inputType={inputType}
|
|
138
|
-
>
|
|
139
|
-
{book.title}
|
|
140
|
-
</MenuItemSelectable>
|
|
141
|
-
);
|
|
142
|
-
})}
|
|
143
|
-
</MenuContent>
|
|
144
|
-
</MultiSelectMenu>
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
// check that menu is visible
|
|
148
|
-
expect(
|
|
149
|
-
screen.getByRole("option", { name: books[0].title })
|
|
150
|
-
).toBeInTheDocument();
|
|
151
|
-
|
|
152
|
-
// Check that checkboxes are rendered
|
|
153
|
-
if (inputType !== INPUT_TYPES.ICON)
|
|
154
|
-
expect(screen.getAllByRole(inputType)).toHaveLength(books.length);
|
|
155
|
-
|
|
156
|
-
// Perform the action to select the item
|
|
157
|
-
await activate(user);
|
|
158
|
-
|
|
159
|
-
expect(
|
|
160
|
-
await screen.findByRole("option", { selected: true })
|
|
161
|
-
).toBeInTheDocument();
|
|
162
|
-
|
|
163
|
-
testByType[inputType].isSelected();
|
|
164
|
-
|
|
165
|
-
// Perform the action to deselect the item
|
|
166
|
-
deactivate(user);
|
|
167
|
-
|
|
168
|
-
expect(
|
|
169
|
-
await screen.findByRole("option", {
|
|
170
|
-
name: books[0].title,
|
|
171
|
-
selected: false,
|
|
172
|
-
})
|
|
173
|
-
).toBeInTheDocument();
|
|
174
|
-
|
|
175
|
-
testByType[inputType].isNotSelected();
|
|
176
|
-
}
|
|
177
|
-
);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
describe("API", () => {
|
|
182
|
-
it("accepts children", () => {
|
|
183
|
-
render(
|
|
184
|
-
<MenuItemSelectable id="test-menu-item-selectable">
|
|
185
|
-
Hello world!
|
|
186
|
-
<Button>Hello menu item!</Button>
|
|
187
|
-
</MenuItemSelectable>
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
const menuItemSelectable = screen.getByText("Hello world!");
|
|
191
|
-
const button = screen.getByText("Hello menu item!");
|
|
192
|
-
expect(menuItemSelectable).toContainElement(button);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test.each([INPUT_TYPES.CHECKBOX, INPUT_TYPES.RADIO, INPUT_TYPES.SWITCH])(
|
|
196
|
-
"supports %s input types",
|
|
197
|
-
(inputType) => {
|
|
198
|
-
render(
|
|
199
|
-
<MenuItemSelectable
|
|
200
|
-
id="test-menu-item-selectable"
|
|
201
|
-
inputType={inputType}
|
|
202
|
-
>
|
|
203
|
-
Hello world!
|
|
204
|
-
</MenuItemSelectable>
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
expect(screen.getByRole(inputType)).toBeInTheDocument();
|
|
208
|
-
}
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
it("uses the check icon by default when no inputType provided", () => {
|
|
212
|
-
const testItem = books[0];
|
|
213
|
-
render(
|
|
214
|
-
<TestWithMockedMenu context={{ items: books, selectedItem: testItem }}>
|
|
215
|
-
<MenuItemSelectable id={testItem.id}>
|
|
216
|
-
{testItem.title}
|
|
217
|
-
</MenuItemSelectable>
|
|
218
|
-
</TestWithMockedMenu>
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
expect(
|
|
222
|
-
screen.getByDataQaLabel({ icon: "check-solid" })
|
|
223
|
-
).toBeInTheDocument();
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe.each([
|
|
227
|
-
INPUT_TYPES.CHECKBOX,
|
|
228
|
-
INPUT_TYPES.RADIO,
|
|
229
|
-
INPUT_TYPES.SWITCH,
|
|
230
|
-
INPUT_TYPES.ICON,
|
|
231
|
-
])("when the input type is %s", (inputType) => {
|
|
232
|
-
it("enables it's checked state when selected in the context", () => {
|
|
233
|
-
const testItem = books[0];
|
|
234
|
-
render(
|
|
235
|
-
<TestWithMockedMenu
|
|
236
|
-
context={{ items: books, selectedItem: testItem }}
|
|
237
|
-
>
|
|
238
|
-
<MenuItemSelectable id={testItem.id} inputType={inputType}>
|
|
239
|
-
{testItem.title}
|
|
240
|
-
</MenuItemSelectable>
|
|
241
|
-
</TestWithMockedMenu>
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
screen.getByRole("option", { selected: true });
|
|
245
|
-
testByType[inputType].isSelected();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it("enables it's checked state when selected by default", () => {
|
|
249
|
-
const testItem = books[0];
|
|
250
|
-
render(
|
|
251
|
-
<TestWithMenuRoot
|
|
252
|
-
triggerElement={<MenuToggleButton>Open</MenuToggleButton>}
|
|
253
|
-
{...{
|
|
254
|
-
isOpen: true,
|
|
255
|
-
items: books,
|
|
256
|
-
selectedItem: testItem,
|
|
257
|
-
}}
|
|
258
|
-
>
|
|
259
|
-
<MenuItemSelectable id={testItem.id} inputType={inputType}>
|
|
260
|
-
{testItem.title}
|
|
261
|
-
</MenuItemSelectable>
|
|
262
|
-
</TestWithMenuRoot>
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
screen.getByRole("option", { selected: true });
|
|
266
|
-
testByType[inputType].isSelected();
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it("allows its checked state to be deselected when using the context", async () => {
|
|
270
|
-
const testItem = books[0];
|
|
271
|
-
const TestItemWithState = () => {
|
|
272
|
-
const [selectedItem, setSelectedItem] = React.useState<
|
|
273
|
-
typeof testItem | undefined
|
|
274
|
-
>(testItem);
|
|
275
|
-
return (
|
|
276
|
-
<TestWithMockedMenu context={{ items: books, selectedItem }}>
|
|
277
|
-
<MenuItemSelectable
|
|
278
|
-
id={testItem.id}
|
|
279
|
-
inputType={inputType}
|
|
280
|
-
onClick={() => setSelectedItem(undefined)}
|
|
281
|
-
>
|
|
282
|
-
{testItem.title}
|
|
283
|
-
</MenuItemSelectable>
|
|
284
|
-
</TestWithMockedMenu>
|
|
285
|
-
);
|
|
286
|
-
};
|
|
287
|
-
const { user } = render(<TestItemWithState />);
|
|
288
|
-
|
|
289
|
-
const menuItem = screen.getByRole("option", { selected: true });
|
|
290
|
-
testByType[inputType].isSelected();
|
|
291
|
-
|
|
292
|
-
await user.click(menuItem);
|
|
293
|
-
|
|
294
|
-
expect(
|
|
295
|
-
await screen.findByRole("option", {
|
|
296
|
-
selected: false,
|
|
297
|
-
})
|
|
298
|
-
).toBeInTheDocument();
|
|
299
|
-
testByType[inputType].isNotSelected();
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { MenuItemSelectable } from "../MenuItemSelectable";
|
|
2
|
-
|
|
3
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
4
|
-
function MenuItemSelectableTypeTest() {
|
|
5
|
-
return (
|
|
6
|
-
<>
|
|
7
|
-
{/* Valid MenuItemSelectable with minimal required props */}
|
|
8
|
-
<MenuItemSelectable id="test">Test</MenuItemSelectable>
|
|
9
|
-
{/* Valid MenuItemSelectable allows active prop */}
|
|
10
|
-
<MenuItemSelectable id="test" active>
|
|
11
|
-
Test
|
|
12
|
-
</MenuItemSelectable>
|
|
13
|
-
{/* Valid MenuItemSelectable allows active prop false */}
|
|
14
|
-
<MenuItemSelectable id="test" active={false}>
|
|
15
|
-
Test
|
|
16
|
-
</MenuItemSelectable>
|
|
17
|
-
{/* Valid MenuItemSelectable allows inputType prop to be checkbox */}
|
|
18
|
-
<MenuItemSelectable id="test" inputType="checkbox">
|
|
19
|
-
Test
|
|
20
|
-
</MenuItemSelectable>
|
|
21
|
-
{/* Valid MenuItemSelectable allows inputType prop to be radio */}
|
|
22
|
-
<MenuItemSelectable id="test" inputType="radio">
|
|
23
|
-
Test
|
|
24
|
-
</MenuItemSelectable>
|
|
25
|
-
{/* Valid MenuItemSelectable allows inputType prop to be switch */}
|
|
26
|
-
<MenuItemSelectable id="test" inputType="switch">
|
|
27
|
-
Test
|
|
28
|
-
</MenuItemSelectable>
|
|
29
|
-
{/* Valid MenuItemSelectable allows inputType prop to be icon */}
|
|
30
|
-
<MenuItemSelectable id="test" inputType="icon">
|
|
31
|
-
Test
|
|
32
|
-
</MenuItemSelectable>
|
|
33
|
-
{/* Valid MenuItemSelectable allows inputLabel prop as a string */}
|
|
34
|
-
<MenuItemSelectable id="test" inputLabel="Test Label">
|
|
35
|
-
Test
|
|
36
|
-
</MenuItemSelectable>
|
|
37
|
-
|
|
38
|
-
{/* @ts-expect-error - no children is invalid */}
|
|
39
|
-
<MenuItemSelectable id="foo" />
|
|
40
|
-
{/* @ts-expect-error - missing id is invalid */}
|
|
41
|
-
<MenuItemSelectable>Test</MenuItemSelectable>
|
|
42
|
-
<MenuItemSelectable
|
|
43
|
-
/* @ts-expect-error - id may not be a number */
|
|
44
|
-
id={0}
|
|
45
|
-
/* @ts-expect-error - active may not be a string */
|
|
46
|
-
active="true"
|
|
47
|
-
/* @ts-expect-error - inputType must be valid */
|
|
48
|
-
inputType="invalid"
|
|
49
|
-
/* @ts-expect-error - inputLabel may not be a number */
|
|
50
|
-
inputLabel={0}
|
|
51
|
-
>
|
|
52
|
-
Test
|
|
53
|
-
</MenuItemSelectable>
|
|
54
|
-
</>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import styled from "styled-components";
|
|
2
|
-
|
|
3
|
-
export const MenuItemSelectableRow = styled.div`
|
|
4
|
-
display: flex;
|
|
5
|
-
align-items: center;
|
|
6
|
-
pointer-events: none;
|
|
7
|
-
`;
|
|
8
|
-
|
|
9
|
-
export const MenuItemSelectableAction = styled.div`
|
|
10
|
-
margin-right: ${({ theme }) => theme.space[300]};
|
|
11
|
-
display: flex;
|
|
12
|
-
align-items: center;
|
|
13
|
-
justify-content: center;
|
|
14
|
-
flex-shrink: 0;
|
|
15
|
-
`;
|
|
16
|
-
|
|
17
|
-
export const MenuItemSelectableText = styled.div`
|
|
18
|
-
word-break: break-word;
|
|
19
|
-
flex-grow: 1;
|
|
20
|
-
min-width: 0;
|
|
21
|
-
`;
|