@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.
Files changed (54) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +17 -1
  3. package/dist/esm/index.js +209 -198
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/index.d.mts +103 -37
  6. package/dist/index.d.ts +103 -37
  7. package/dist/index.js +239 -229
  8. package/dist/index.js.map +1 -1
  9. package/package.json +6 -5
  10. package/src/ActionMenu/ActionMenu.feature +9 -3
  11. package/src/ActionMenu/ActionMenu.tsx +75 -42
  12. package/src/ActionMenu/__tests__/ActionMenu.test.tsx +36 -42
  13. package/src/ActionMenu/__tests__/ActionMenu.typetest.tsx +13 -13
  14. package/src/MenuContent/MenuContent.tsx +31 -1
  15. package/src/MenuContent/MenuContentTypes.ts +2 -2
  16. package/src/MenuContent/styles.tsx +10 -6
  17. package/src/MenuFooter/MenuFooter.feature +1 -1
  18. package/src/MenuFooter/MenuFooter.tsx +8 -0
  19. package/src/MenuFooter/__tests__/MenuFooter.test.tsx +53 -26
  20. package/src/MenuFooter/styles.tsx +1 -1
  21. package/src/MenuGroup/MenuGroup.feature +14 -26
  22. package/src/MenuGroup/MenuGroup.tsx +21 -1
  23. package/src/MenuGroup/__tests__/MenuGroup.test.tsx +40 -6
  24. package/src/MenuGroup/styles.tsx +4 -3
  25. package/src/MenuHeader/MenuHeader.tsx +16 -5
  26. package/src/MenuHeader/__tests__/MenuHeader.test.tsx +50 -6
  27. package/src/MenuHeader/styles.tsx +21 -22
  28. package/src/MenuItem/MenuItem.feature +66 -26
  29. package/src/MenuItem/MenuItem.tsx +35 -20
  30. package/src/MenuItem/MenuItemTypes.ts +13 -1
  31. package/src/MenuItem/__tests__/MenuItem.test.tsx +431 -154
  32. package/src/MenuItem/__tests__/MenuItem.typetest.tsx +57 -38
  33. package/src/MenuItem/styles.tsx +42 -3
  34. package/src/{MenuItemSelectable/MenuItemSelectable.tsx → MenuItem/useOptionProps.tsx} +31 -54
  35. package/src/MenuRoot/MenuRoot.tsx +12 -17
  36. package/src/MenuRoot/__tests__/MenuRoot.test.tsx +3 -3
  37. package/src/MenuRoot/__tests__/MenuRoot.typetest.tsx +10 -10
  38. package/src/MultiSelectMenu/MultiSelectMenu.tsx +8 -1
  39. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +19 -19
  40. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.typetest.tsx +10 -10
  41. package/src/SingleSelectMenu/SingleSelectMenu.feature +11 -0
  42. package/src/SingleSelectMenu/SingleSelectMenu.tsx +9 -0
  43. package/src/SingleSelectMenu/__tests__/SingleSelectMenu.test.tsx +11 -14
  44. package/src/SingleSelectMenu/__tests__/SingleSelectMenu.typetest.tsx +14 -14
  45. package/src/__tests__/Menu.test.tsx +20 -16
  46. package/src/hooks/useItemsFromChildren.tsx +1 -4
  47. package/src/index.ts +0 -1
  48. package/src/menuTestingUtils.tsx +7 -4
  49. package/src/MenuItemSelectable/MenuItemSelectable.feature +0 -99
  50. package/src/MenuItemSelectable/MenuItemSelectableTypes.ts +0 -15
  51. package/src/MenuItemSelectable/__tests__/MenuItemSelectable.test.tsx +0 -303
  52. package/src/MenuItemSelectable/__tests__/MenuItemSelectable.typetest.tsx +0 -56
  53. package/src/MenuItemSelectable/index.ts +0 -5
  54. 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,5 +0,0 @@
1
- export * from "./MenuItemSelectable";
2
-
3
- export * from "./MenuItemSelectableTypes";
4
-
5
- export * from "./styles";
@@ -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
- `;