@sproutsocial/seeds-react-menu 0.2.3 → 0.3.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 +12 -0
- package/dist/esm/index.js +17 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +45 -39
- package/dist/index.d.ts +45 -39
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/MenuContent/MenuContent.tsx +2 -2
- package/src/MenuGroup/MenuGroup.feature +15 -15
- package/src/MenuGroup/MenuGroup.tsx +6 -2
- package/src/MenuGroup/MenuGroupTypes.ts +2 -0
- package/src/MenuGroup/__tests__/MenuGroup.test.tsx +144 -9
- package/src/MenuGroup/__tests__/MenuGroup.typetest.tsx +24 -12
- package/src/MenuItem/MenuItem.tsx +1 -1
- package/src/MenuRoot/__tests__/MenuRoot.test.tsx +1 -1
- package/src/MultiSelectMenu/MultiSelectMenu.tsx +8 -0
- package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +6 -6
- package/src/SingleSelectMenu/__tests__/SingleSelectMenu.test.tsx +3 -3
- package/src/hooks/useItemsFromChildren.tsx +12 -5
- package/src/types.ts +2 -0
package/package.json
CHANGED
|
@@ -11,10 +11,10 @@ import { useMenuContext } from "../MenuContext";
|
|
|
11
11
|
* <ActionMenu>
|
|
12
12
|
* <MenuHeader>Header</MenuHeader>
|
|
13
13
|
* <MenuContent>
|
|
14
|
-
* <MenuGroup>
|
|
14
|
+
* <MenuGroup id="1">
|
|
15
15
|
* <MenuItem>Item 1</MenuItem>
|
|
16
16
|
* </MenuGroup>
|
|
17
|
-
* <MenuGroup>
|
|
17
|
+
* <MenuGroup id="2">
|
|
18
18
|
* <MenuItem>Item 2</MenuItem>
|
|
19
19
|
* </MenuGroup>
|
|
20
20
|
* </MenuContent>
|
|
@@ -6,20 +6,19 @@ Feature: Menu Group
|
|
|
6
6
|
Background:
|
|
7
7
|
Given an end user is using the component
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
Scenario Outline: MenuGroup can be single select
|
|
10
|
+
When the MenuGroup has isSingleSelect set to <value>
|
|
11
|
+
And MenuContext is set to <menu type>
|
|
12
|
+
Then <selection behavior>
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Then the group behaves like a multi select
|
|
14
|
+
Examples:
|
|
15
|
+
| value | menu type | selection behavior |
|
|
16
|
+
| true | single | the group behaves like a single select |
|
|
17
|
+
| false | single | the group behaves like a single select |
|
|
18
|
+
| undefined | single | the group behaves like a single select |
|
|
19
|
+
| true | multiple | the group behaves like a single select |
|
|
20
|
+
| false | multiple | the group behaves like a multi select |
|
|
21
|
+
| undefined | multiple | the group behaves like a multi select |
|
|
23
22
|
|
|
24
23
|
Rule: API
|
|
25
24
|
Background:
|
|
@@ -59,14 +58,14 @@ Feature: Menu Group
|
|
|
59
58
|
Given a list of items is not empty
|
|
60
59
|
And the items are a valid object shape
|
|
61
60
|
When that list of items is provided to the items prop on a MenuGroup
|
|
62
|
-
And a valid component is provided to the customMenuItem prop
|
|
61
|
+
And a valid component is provided to the customMenuItem prop
|
|
63
62
|
Then the MenuGroup will render the items using the customMenuItem component
|
|
64
63
|
|
|
65
64
|
Scenario: MenuGroup ignores the menuItemType without items prop
|
|
66
65
|
When a MenuGroup has menuItemType set
|
|
67
66
|
And is not provided with a valid items prop
|
|
68
67
|
Then the menuItemType will be ignored
|
|
69
|
-
|
|
68
|
+
|
|
70
69
|
Scenario Outline: MenuGroup can set the type for all MenuItems
|
|
71
70
|
When menuItemType is set to <menu item type>
|
|
72
71
|
And MenuGroup is provided with a valid items prop
|
|
@@ -98,5 +97,6 @@ Feature: Menu Group
|
|
|
98
97
|
| children | ReactNode |
|
|
99
98
|
| title | string |
|
|
100
99
|
| items | TypeItem[] |
|
|
100
|
+
| isSingleSelect | boolean |
|
|
101
101
|
| menuItemType | TypeMenuItemProps['type'] |
|
|
102
102
|
| customMenuItem | tyepof MenuItem |
|
|
@@ -10,10 +10,10 @@ import { StyledMenuGroup, StyledTitle, StyledMenuGroupUl } from "./styles";
|
|
|
10
10
|
* @example
|
|
11
11
|
* <ActionMenu>
|
|
12
12
|
* <MenuContent>
|
|
13
|
-
* <MenuGroup title="Group 1">
|
|
13
|
+
* <MenuGroup title="Group 1" id="1">
|
|
14
14
|
* <MenuItem>Item 1</MenuItem>
|
|
15
15
|
* </MenuGroup>
|
|
16
|
-
* <MenuGroup title="Group
|
|
16
|
+
* <MenuGroup title="Group 2" id="2">
|
|
17
17
|
* <MenuItem>Item 2</MenuItem>
|
|
18
18
|
* </MenuGroup>
|
|
19
19
|
* </MenuContent>
|
|
@@ -27,6 +27,8 @@ export const MenuGroup = ({
|
|
|
27
27
|
children,
|
|
28
28
|
title,
|
|
29
29
|
color,
|
|
30
|
+
isSingleSelect,
|
|
31
|
+
id,
|
|
30
32
|
...rest
|
|
31
33
|
}: TypeMenuGroupProps) => {
|
|
32
34
|
// Generate a unique ID for the title
|
|
@@ -35,6 +37,8 @@ export const MenuGroup = ({
|
|
|
35
37
|
return (
|
|
36
38
|
<StyledMenuGroup
|
|
37
39
|
role="group"
|
|
40
|
+
id={id}
|
|
41
|
+
isSingleSelect={isSingleSelect}
|
|
38
42
|
aria-labelledby={title ? titleId : undefined}
|
|
39
43
|
// TODO: fix this type since `color` should be valid here. TS can't resolve the correct type.
|
|
40
44
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
@@ -23,6 +23,8 @@ export interface TypeMenuGroupStyledProps
|
|
|
23
23
|
export interface TypeMenuGroupProps
|
|
24
24
|
extends TypeMenuGroupStyledProps,
|
|
25
25
|
Omit<React.ComponentPropsWithoutRef<"ul">, "color"> {
|
|
26
|
+
id: string;
|
|
27
|
+
isSingleSelect?: boolean;
|
|
26
28
|
title?: string;
|
|
27
29
|
children: ReactNode;
|
|
28
30
|
titleAs?: string | React.ComponentType<any>;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
render,
|
|
3
|
+
screen,
|
|
4
|
+
waitFor,
|
|
5
|
+
fireEvent,
|
|
6
|
+
act,
|
|
7
|
+
} from "@sproutsocial/seeds-react-testing-library";
|
|
2
8
|
import {
|
|
3
9
|
SingleSelectMenu,
|
|
10
|
+
MultiSelectMenu,
|
|
4
11
|
MenuContent,
|
|
5
12
|
MenuGroup,
|
|
6
13
|
MenuToggleButton,
|
|
@@ -14,11 +21,139 @@ const menuItems = [
|
|
|
14
21
|
];
|
|
15
22
|
|
|
16
23
|
describe("MenuGroup", () => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
describe("User Behavior", () => {
|
|
25
|
+
it("allows multiple selections per group when isSingleSelect is undefined", () => {
|
|
26
|
+
render(
|
|
27
|
+
<MultiSelectMenu
|
|
28
|
+
defaultIsOpen
|
|
29
|
+
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
30
|
+
>
|
|
31
|
+
<MenuContent>
|
|
32
|
+
<MenuGroup id="group">
|
|
33
|
+
{menuItems.map(({ id, label }) => (
|
|
34
|
+
<MenuItem key={id} id={id}>
|
|
35
|
+
{label}
|
|
36
|
+
</MenuItem>
|
|
37
|
+
))}
|
|
38
|
+
</MenuGroup>
|
|
39
|
+
</MenuContent>
|
|
40
|
+
</MultiSelectMenu>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const options = screen.getAllByRole("option");
|
|
44
|
+
|
|
45
|
+
// Click on each option
|
|
46
|
+
for (const option of screen.getAllByRole("option")) {
|
|
47
|
+
fireEvent.click(option);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Expect all options to be selected.
|
|
51
|
+
for (const option of options) {
|
|
52
|
+
expect(option).toHaveAttribute("aria-selected", "true");
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
it("allows multiple selections per group when isSingleSelect is false", () => {
|
|
56
|
+
render(
|
|
57
|
+
<MultiSelectMenu
|
|
58
|
+
defaultIsOpen
|
|
59
|
+
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
60
|
+
>
|
|
61
|
+
<MenuContent>
|
|
62
|
+
<MenuGroup id="group" isSingleSelect={false}>
|
|
63
|
+
{menuItems.map(({ id, label }) => (
|
|
64
|
+
<MenuItem key={id} id={id}>
|
|
65
|
+
{label}
|
|
66
|
+
</MenuItem>
|
|
67
|
+
))}
|
|
68
|
+
</MenuGroup>
|
|
69
|
+
</MenuContent>
|
|
70
|
+
</MultiSelectMenu>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const options = screen.getAllByRole("option");
|
|
74
|
+
|
|
75
|
+
// Click on each option
|
|
76
|
+
for (const option of screen.getAllByRole("option")) {
|
|
77
|
+
fireEvent.click(option);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Expect all options to be selected.
|
|
81
|
+
for (const option of options) {
|
|
82
|
+
expect(option).toHaveAttribute("aria-selected", "true");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
it("only allows one selection per group when isSingleSelect is true", async () => {
|
|
86
|
+
render(
|
|
87
|
+
<MultiSelectMenu
|
|
88
|
+
defaultIsOpen
|
|
89
|
+
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
90
|
+
>
|
|
91
|
+
<MenuContent>
|
|
92
|
+
<MenuGroup id="group" isSingleSelect={true}>
|
|
93
|
+
{menuItems.map(({ id, label }) => (
|
|
94
|
+
<MenuItem key={id} id={id}>
|
|
95
|
+
{label}
|
|
96
|
+
</MenuItem>
|
|
97
|
+
))}
|
|
98
|
+
</MenuGroup>
|
|
99
|
+
</MenuContent>
|
|
100
|
+
</MultiSelectMenu>
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const options = screen.getAllByRole("option");
|
|
104
|
+
|
|
105
|
+
// Click on each option
|
|
106
|
+
for (const option of screen.getAllByRole("option")) {
|
|
107
|
+
fireEvent.click(option);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Expect only the last option to be selected.
|
|
111
|
+
await waitFor(() =>
|
|
112
|
+
expect(options[0]).toHaveAttribute("aria-selected", "false")
|
|
113
|
+
);
|
|
114
|
+
await waitFor(() =>
|
|
115
|
+
expect(options[1]).toHaveAttribute("aria-selected", "false")
|
|
116
|
+
);
|
|
117
|
+
await waitFor(() =>
|
|
118
|
+
expect(options[2]).toHaveAttribute("aria-selected", "true")
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
it("only allows one selection per group in SingleSelectMenu, even if isSingleSelect is false", async () => {
|
|
122
|
+
render(
|
|
123
|
+
<SingleSelectMenu
|
|
124
|
+
defaultIsOpen
|
|
125
|
+
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
126
|
+
>
|
|
127
|
+
<MenuContent>
|
|
128
|
+
<MenuGroup id="group" isSingleSelect={false}>
|
|
129
|
+
{menuItems.map(({ id, label }) => (
|
|
130
|
+
<MenuItem key={id} id={id}>
|
|
131
|
+
{label}
|
|
132
|
+
</MenuItem>
|
|
133
|
+
))}
|
|
134
|
+
</MenuGroup>
|
|
135
|
+
</MenuContent>
|
|
136
|
+
</SingleSelectMenu>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const options = screen.getAllByRole("option");
|
|
140
|
+
|
|
141
|
+
// Click on each option
|
|
142
|
+
for (const option of screen.getAllByRole("option")) {
|
|
143
|
+
fireEvent.click(option);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Expect only the last option to be selected.
|
|
147
|
+
await waitFor(() =>
|
|
148
|
+
expect(options[0]).toHaveAttribute("aria-selected", "false")
|
|
149
|
+
);
|
|
150
|
+
await waitFor(() =>
|
|
151
|
+
expect(options[1]).toHaveAttribute("aria-selected", "false")
|
|
152
|
+
);
|
|
153
|
+
await waitFor(() =>
|
|
154
|
+
expect(options[2]).toHaveAttribute("aria-selected", "true")
|
|
155
|
+
);
|
|
156
|
+
});
|
|
22
157
|
});
|
|
23
158
|
describe("API", () => {
|
|
24
159
|
it("renders a group with a title", () => {
|
|
@@ -29,7 +164,7 @@ describe("MenuGroup", () => {
|
|
|
29
164
|
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
30
165
|
>
|
|
31
166
|
<MenuContent>
|
|
32
|
-
<MenuGroup title={menuTitle}>
|
|
167
|
+
<MenuGroup title={menuTitle} id={menuTitle}>
|
|
33
168
|
{menuItems.map(({ id, label }) => (
|
|
34
169
|
<MenuItem key={id} id={id}>
|
|
35
170
|
{label}
|
|
@@ -55,7 +190,7 @@ describe("MenuGroup", () => {
|
|
|
55
190
|
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
56
191
|
>
|
|
57
192
|
<MenuContent>
|
|
58
|
-
<MenuGroup>
|
|
193
|
+
<MenuGroup id="group">
|
|
59
194
|
{menuItems.map(({ id, label }) => (
|
|
60
195
|
<MenuItem key={id} id={id}>
|
|
61
196
|
{label}
|
|
@@ -78,7 +213,7 @@ describe("MenuGroup", () => {
|
|
|
78
213
|
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
79
214
|
>
|
|
80
215
|
<MenuContent>
|
|
81
|
-
<MenuGroup>
|
|
216
|
+
<MenuGroup id="group">
|
|
82
217
|
{menuItems.map(({ id, label }) => (
|
|
83
218
|
<MenuItem key={id} id={id}>
|
|
84
219
|
{label}
|
|
@@ -4,41 +4,53 @@ import { Text } from "@sproutsocial/seeds-react-text";
|
|
|
4
4
|
export const MenuGroupTypeTest = () => (
|
|
5
5
|
<>
|
|
6
6
|
{/* MenuGroup allows children */}
|
|
7
|
-
<MenuGroup>Test</MenuGroup>
|
|
7
|
+
<MenuGroup id="group">Test</MenuGroup>
|
|
8
8
|
{/* MenuGroup allows children with MenuItems */}
|
|
9
|
-
<MenuGroup>
|
|
9
|
+
<MenuGroup id="group">
|
|
10
10
|
<MenuItem id="1">Test 1</MenuItem>
|
|
11
11
|
<MenuItem id="2">Test 2</MenuItem>
|
|
12
12
|
</MenuGroup>
|
|
13
|
+
{/* MenuGroup allows isSingleSelect prop */}
|
|
14
|
+
<MenuGroup id="group-1" isSingleSelect={true}>
|
|
15
|
+
<MenuItem id="1">Test 1</MenuItem>
|
|
16
|
+
</MenuGroup>
|
|
13
17
|
{/* MenuGroup allows title as a string */}
|
|
14
|
-
<MenuGroup title="title">
|
|
18
|
+
<MenuGroup id="group" title="title">
|
|
19
|
+
Test
|
|
20
|
+
</MenuGroup>
|
|
15
21
|
{/* MenuGroup allows titleAs as a string */}
|
|
16
|
-
<MenuGroup title="title" titleAs="h3">
|
|
22
|
+
<MenuGroup id="group" title="title" titleAs="h3">
|
|
17
23
|
Test
|
|
18
24
|
</MenuGroup>
|
|
19
25
|
{/* MenuGroup allows titleAs as a component */}
|
|
20
|
-
<MenuGroup title="title" titleAs={Text}>
|
|
26
|
+
<MenuGroup id="group" title="title" titleAs={Text}>
|
|
21
27
|
Test
|
|
22
28
|
</MenuGroup>
|
|
23
29
|
{/* MenuGroup accepts ul props */}
|
|
24
|
-
<MenuGroup aria-label="test label">
|
|
30
|
+
<MenuGroup id="group" aria-label="test label">
|
|
31
|
+
Test
|
|
32
|
+
</MenuGroup>
|
|
25
33
|
{/* MenuGroup accepts style props */}
|
|
26
|
-
<MenuGroup border="1px solid black" bg="white" width="150px">
|
|
34
|
+
<MenuGroup id="group" border="1px solid black" bg="white" width="150px">
|
|
27
35
|
Test
|
|
28
36
|
</MenuGroup>
|
|
29
37
|
|
|
30
38
|
{/* @ts-expect-error - MenuGroup requires children */}
|
|
31
|
-
<MenuGroup />
|
|
39
|
+
<MenuGroup id="group" />
|
|
32
40
|
{/* @ts-expect-error - MenuGroup title is invalid as number */}
|
|
33
|
-
<MenuGroup title={123}>
|
|
41
|
+
<MenuGroup id="group" title={123}>
|
|
42
|
+
Test
|
|
43
|
+
</MenuGroup>
|
|
34
44
|
{/* @ts-expect-error - MenuGroup title is invalid as boolean */}
|
|
35
|
-
<MenuGroup title={false}>
|
|
45
|
+
<MenuGroup id="group" title={false}>
|
|
46
|
+
Test
|
|
47
|
+
</MenuGroup>
|
|
36
48
|
{/* @ts-expect-error - MenuGroup titleAs is invalid as number */}
|
|
37
|
-
<MenuGroup title="title" titleAs={123}>
|
|
49
|
+
<MenuGroup id="group" title="title" titleAs={123}>
|
|
38
50
|
Test
|
|
39
51
|
</MenuGroup>
|
|
40
52
|
{/* @ts-expect-error - MenuGroup titleAs is invalid as boolean */}
|
|
41
|
-
<MenuGroup title="title" titleAs={false}>
|
|
53
|
+
<MenuGroup id="group" title="title" titleAs={false}>
|
|
42
54
|
Test
|
|
43
55
|
</MenuGroup>
|
|
44
56
|
</>
|
|
@@ -25,12 +25,12 @@ export const MenuItem = ({
|
|
|
25
25
|
}: TypeMenuItemProps) => {
|
|
26
26
|
const { getItemProps, itemsMap, highlightedIndex, isListbox } =
|
|
27
27
|
useContext(MenuContext);
|
|
28
|
+
const item = (itemsMap[id] || { index: 0, id }) as TypeDefaultItemProps;
|
|
28
29
|
const { ...optionProps } = useOptionProps({
|
|
29
30
|
id,
|
|
30
31
|
children,
|
|
31
32
|
...props,
|
|
32
33
|
});
|
|
33
|
-
const item = (itemsMap[id] || { index: 0, id }) as TypeDefaultItemProps;
|
|
34
34
|
const highlighted = highlightedIndex === item?.index;
|
|
35
35
|
|
|
36
36
|
return (
|
|
@@ -219,7 +219,7 @@ describe("MenuRoot", () => {
|
|
|
219
219
|
>
|
|
220
220
|
<MenuHeader title="Menu Header" />
|
|
221
221
|
<div>Custom Banner</div>
|
|
222
|
-
<MenuGroup title="Menu Group">
|
|
222
|
+
<MenuGroup title="Menu Group" id="group">
|
|
223
223
|
<MenuItem id="1">Item 1</MenuItem>
|
|
224
224
|
</MenuGroup>
|
|
225
225
|
<MenuFooter>Menu Footer</MenuFooter>
|
|
@@ -105,6 +105,14 @@ export const MultiSelectMenu = ({
|
|
|
105
105
|
} else {
|
|
106
106
|
addSelectedItem(newSelectedItem);
|
|
107
107
|
}
|
|
108
|
+
// If the newly selected item's group is single select, remove any other selected items in that group.
|
|
109
|
+
if (newSelectedItem.menuGroupProps?.isSingleSelect) {
|
|
110
|
+
const groupItems = selectedItems.filter(
|
|
111
|
+
(item) =>
|
|
112
|
+
item.menuGroupProps?.id === newSelectedItem.menuGroupProps?.id
|
|
113
|
+
);
|
|
114
|
+
groupItems.forEach((item) => removeSelectedItem(item));
|
|
115
|
+
}
|
|
108
116
|
break;
|
|
109
117
|
default:
|
|
110
118
|
break;
|
|
@@ -35,7 +35,7 @@ describe("MultiSelectMenu", () => {
|
|
|
35
35
|
}
|
|
36
36
|
>
|
|
37
37
|
<MenuContent>
|
|
38
|
-
<MenuGroup>
|
|
38
|
+
<MenuGroup id="group">
|
|
39
39
|
{menuItems.map((item) => (
|
|
40
40
|
<MenuItem key={item.id} id={item.id} label={item.label}>
|
|
41
41
|
{item.label}
|
|
@@ -78,7 +78,7 @@ describe("MultiSelectMenu", () => {
|
|
|
78
78
|
}
|
|
79
79
|
>
|
|
80
80
|
<MenuContent>
|
|
81
|
-
<MenuGroup>
|
|
81
|
+
<MenuGroup id="group">
|
|
82
82
|
{menuItems.map((item) => (
|
|
83
83
|
<MenuItem
|
|
84
84
|
key={item.id}
|
|
@@ -136,7 +136,7 @@ describe("MultiSelectMenu", () => {
|
|
|
136
136
|
}
|
|
137
137
|
>
|
|
138
138
|
<MenuContent>
|
|
139
|
-
<MenuGroup>
|
|
139
|
+
<MenuGroup id="group">
|
|
140
140
|
{menuItems.map((item) => (
|
|
141
141
|
<MenuItem
|
|
142
142
|
key={item.id}
|
|
@@ -199,7 +199,7 @@ describe("MultiSelectMenu", () => {
|
|
|
199
199
|
}
|
|
200
200
|
>
|
|
201
201
|
<MenuContent>
|
|
202
|
-
<MenuGroup>
|
|
202
|
+
<MenuGroup id="group">
|
|
203
203
|
{menuItems.map((item) => (
|
|
204
204
|
<MenuItem
|
|
205
205
|
key={item.id}
|
|
@@ -257,7 +257,7 @@ describe("MultiSelectMenu", () => {
|
|
|
257
257
|
selectedItems={[menuItems[1], menuItems[2]]}
|
|
258
258
|
>
|
|
259
259
|
<MenuContent>
|
|
260
|
-
<MenuGroup>
|
|
260
|
+
<MenuGroup id="group">
|
|
261
261
|
{menuItems.map((item) => (
|
|
262
262
|
<MenuItem
|
|
263
263
|
key={item.id}
|
|
@@ -310,7 +310,7 @@ describe("MultiSelectMenu", () => {
|
|
|
310
310
|
onSelectedItemsChange={mockSelectedItemsChange}
|
|
311
311
|
>
|
|
312
312
|
<MenuContent>
|
|
313
|
-
<MenuGroup>
|
|
313
|
+
<MenuGroup id="group">
|
|
314
314
|
{menuItems.map((item) => (
|
|
315
315
|
<MenuItem
|
|
316
316
|
key={item.id}
|
|
@@ -34,7 +34,7 @@ describe("User Behavior", () => {
|
|
|
34
34
|
}
|
|
35
35
|
>
|
|
36
36
|
<MenuContent>
|
|
37
|
-
<MenuGroup title="Menu Title">
|
|
37
|
+
<MenuGroup title="Menu Title" id="menu-title">
|
|
38
38
|
{menuItems.map(({ id, label }) => (
|
|
39
39
|
<MenuItem key={id} id={id}>
|
|
40
40
|
{label}
|
|
@@ -100,7 +100,7 @@ describe("User Behavior", () => {
|
|
|
100
100
|
}
|
|
101
101
|
>
|
|
102
102
|
<MenuContent>
|
|
103
|
-
<MenuGroup title="Menu Title">
|
|
103
|
+
<MenuGroup title="Menu Title" id="menu-title">
|
|
104
104
|
{menuItems.map(({ id, label }) => (
|
|
105
105
|
<MenuItem key={id} id={id}>
|
|
106
106
|
{label}
|
|
@@ -135,7 +135,7 @@ describe("User Behavior", () => {
|
|
|
135
135
|
menuToggleElement={<MenuToggleButton>Click me</MenuToggleButton>}
|
|
136
136
|
>
|
|
137
137
|
<MenuContent>
|
|
138
|
-
<MenuGroup title="Menu Title">
|
|
138
|
+
<MenuGroup title="Menu Title" id="menu-title">
|
|
139
139
|
{menuItems.map(({ id, label }) => (
|
|
140
140
|
<MenuItem key={id} id={id}>
|
|
141
141
|
{label}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import React, { type ReactNode, useMemo } from "react";
|
|
2
2
|
import type { TypeMenuItemProps } from "../MenuItem";
|
|
3
3
|
import type { TypeMenuContext } from "../MenuContext";
|
|
4
|
+
import type { TypeMenuGroupProps } from "../MenuGroup/index";
|
|
4
5
|
|
|
5
6
|
export interface TypeUseItemsFromChildrenProps {
|
|
6
7
|
children: ReactNode;
|
|
7
8
|
}
|
|
8
|
-
export interface
|
|
9
|
+
export interface TypeItemElement {
|
|
9
10
|
element: React.ReactElement<TypeMenuItemProps>;
|
|
10
11
|
parents: readonly React.ReactNode[];
|
|
11
12
|
}
|
|
13
|
+
export interface TypeMenuGroupElement {
|
|
14
|
+
element: React.ReactElement<TypeMenuGroupProps>;
|
|
15
|
+
}
|
|
12
16
|
|
|
13
17
|
// https://stackoverflow.com/questions/69119016/find-specific-children-components-regardless-of-depth-in-react
|
|
14
18
|
const walkAllChildren = (
|
|
@@ -43,12 +47,11 @@ const checkIfIsElement = (element: ReactNode, name: string): boolean => {
|
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
const getAllMenuItems = (children: ReactNode) => {
|
|
46
|
-
const allMenuItems:
|
|
47
|
-
// console.log("children", children);
|
|
50
|
+
const allMenuItems: TypeItemElement[] = [];
|
|
48
51
|
walkAllChildren(children, (element, parents) => {
|
|
49
52
|
if (checkIfIsElement(element, "MenuItem")) {
|
|
50
53
|
allMenuItems.push({
|
|
51
|
-
element: element as
|
|
54
|
+
element: element as TypeItemElement["element"],
|
|
52
55
|
parents,
|
|
53
56
|
});
|
|
54
57
|
}
|
|
@@ -63,12 +66,16 @@ export const useItemsFromChildren = ({
|
|
|
63
66
|
const allMenuItemElements = getAllMenuItems(children);
|
|
64
67
|
const menuItemsMap: TypeMenuContext["itemsMap"] = {};
|
|
65
68
|
const allMenuItems: TypeMenuContext["items"] = allMenuItemElements.map(
|
|
66
|
-
({ element }, index) => {
|
|
69
|
+
({ element, parents }, index) => {
|
|
70
|
+
const parentMenuGroup = parents.find((parent) =>
|
|
71
|
+
checkIfIsElement(parent, "MenuGroup")
|
|
72
|
+
) as TypeMenuGroupElement["element"];
|
|
67
73
|
const item = {
|
|
68
74
|
id: element.props.id,
|
|
69
75
|
disabled: element.props.disabled,
|
|
70
76
|
index,
|
|
71
77
|
menuItemProps: element.props,
|
|
78
|
+
menuGroupProps: parentMenuGroup ? parentMenuGroup.props : undefined,
|
|
72
79
|
};
|
|
73
80
|
menuItemsMap[`${item.id}`] = item;
|
|
74
81
|
return item;
|
package/src/types.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { TypeMenuItemProps } from "./MenuItem";
|
|
2
|
+
import type { TypeMenuGroupProps } from "./MenuGroup";
|
|
2
3
|
|
|
3
4
|
export interface TypeDefaultItemProps {
|
|
4
5
|
id: string;
|
|
5
6
|
index: number;
|
|
6
7
|
disabled?: boolean;
|
|
7
8
|
menuItemProps?: TypeMenuItemProps;
|
|
9
|
+
menuGroupProps?: TypeMenuGroupProps;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export type TypeNotEmptyChildren =
|