@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,41 +1,60 @@
|
|
|
1
1
|
import { MenuItem } from "../MenuItem";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
4
|
+
function MenuItemTypeTest() {
|
|
5
|
+
return (
|
|
6
|
+
<>
|
|
7
|
+
{/* Valid MenuItem with minimal required props */}
|
|
8
|
+
<MenuItem id="test">Test</MenuItem>
|
|
9
|
+
{/* Valid MenuItem allows href */}
|
|
10
|
+
<MenuItem id="test" href="test.com">
|
|
11
|
+
Test
|
|
12
|
+
</MenuItem>
|
|
13
|
+
{/* Valid MenuItem allows active prop */}
|
|
14
|
+
<MenuItem id="test" active>
|
|
15
|
+
Test
|
|
16
|
+
</MenuItem>
|
|
17
|
+
{/* Valid MenuItem allows active prop false */}
|
|
18
|
+
<MenuItem id="test" active={false}>
|
|
19
|
+
Test
|
|
20
|
+
</MenuItem>
|
|
21
|
+
{/* Valid MenuItem allows inputType prop to be checkbox */}
|
|
22
|
+
<MenuItem id="test" inputType="checkbox">
|
|
23
|
+
Test
|
|
24
|
+
</MenuItem>
|
|
25
|
+
{/* Valid MenuItem allows inputType prop to be radio */}
|
|
26
|
+
<MenuItem id="test" inputType="radio">
|
|
27
|
+
Test
|
|
28
|
+
</MenuItem>
|
|
29
|
+
{/* Valid MenuItem allows inputType prop to be switch */}
|
|
30
|
+
<MenuItem id="test" inputType="switch">
|
|
31
|
+
Test
|
|
32
|
+
</MenuItem>
|
|
33
|
+
{/* Valid MenuItem allows inputType prop to be icon */}
|
|
34
|
+
<MenuItem id="test" inputType="icon">
|
|
35
|
+
Test
|
|
36
|
+
</MenuItem>
|
|
37
|
+
{/* Valid MenuItem allows inputLabelId prop as a string */}
|
|
38
|
+
<MenuItem id="test" inputLabelId="Test Label">
|
|
39
|
+
Test
|
|
40
|
+
</MenuItem>
|
|
15
41
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/* @ts-expect-error - disabled may not be a string */
|
|
36
|
-
disabled="true"
|
|
37
|
-
>
|
|
38
|
-
Test
|
|
39
|
-
</MenuItem>
|
|
40
|
-
</>
|
|
41
|
-
);
|
|
42
|
+
{/* @ts-expect-error - no children is invalid */}
|
|
43
|
+
<MenuItem id="foo" />
|
|
44
|
+
{/* @ts-expect-error - missing id is invalid */}
|
|
45
|
+
<MenuItem>Test</MenuItem>
|
|
46
|
+
<MenuItem
|
|
47
|
+
/* @ts-expect-error - id may not be a number */
|
|
48
|
+
id={0}
|
|
49
|
+
/* @ts-expect-error - active may not be a string */
|
|
50
|
+
active="true"
|
|
51
|
+
/* @ts-expect-error - inputType must be valid */
|
|
52
|
+
inputType="invalid"
|
|
53
|
+
/* @ts-expect-error - inputLabelId may not be a number */
|
|
54
|
+
inputLabelId={0}
|
|
55
|
+
>
|
|
56
|
+
Test
|
|
57
|
+
</MenuItem>
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
}
|
package/src/MenuItem/styles.tsx
CHANGED
|
@@ -16,18 +16,37 @@ const highlightedStyles = css`
|
|
|
16
16
|
background-color: ${({ theme }) => theme.colors.listItem.background.hover};
|
|
17
17
|
`;
|
|
18
18
|
|
|
19
|
-
export const MenuItemContainer = styled.li
|
|
19
|
+
export const MenuItemContainer = styled.li`
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
list-style-type: none;
|
|
22
|
+
margin-left: ${({ theme }) => theme.space[300]};
|
|
23
|
+
margin-right: ${({ theme }) => theme.space[300]};
|
|
24
|
+
transition: all ${({ theme }) => theme.duration["fast"]};
|
|
25
|
+
|
|
26
|
+
&:first-child {
|
|
27
|
+
margin-top: ${({ theme }) => theme.space[300]};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&:last-child {
|
|
31
|
+
margin-bottom: ${({ theme }) => theme.space[300]};
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
export const StyledMenuItem = styled.div<{ $highlighted: boolean }>`
|
|
36
|
+
display: block;
|
|
37
|
+
box-sizing: border-box;
|
|
38
|
+
width: 100%;
|
|
20
39
|
border-radius: ${({ theme }) => theme.radii[500]};
|
|
21
40
|
background-color: ${({ theme }) => theme.colors.listItem.background.base};
|
|
22
41
|
border: 1px solid ${({ theme }) => theme.colors.listItem.background.base};
|
|
23
42
|
color: ${({ theme }) => theme.colors.text.body};
|
|
24
43
|
font-family: ${({ theme }) => theme.fontFamily};
|
|
25
44
|
font-weight: ${({ theme }) => theme.fontWeights.normal};
|
|
45
|
+
text-align: left;
|
|
46
|
+
text-decoration: none;
|
|
26
47
|
padding: ${({ theme }) => theme.space[300]};
|
|
27
|
-
list-style-type: none;
|
|
28
48
|
outline: 0;
|
|
29
49
|
${({ theme }) => theme.typography[200]};
|
|
30
|
-
transition: all ${({ theme }) => theme.duration["fast"]};
|
|
31
50
|
|
|
32
51
|
/* Highlighted */
|
|
33
52
|
${({ $highlighted }) => $highlighted && highlightedStyles}
|
|
@@ -69,3 +88,23 @@ export const MenuItemContainer = styled.li<{ $highlighted: boolean }>`
|
|
|
69
88
|
${layout}
|
|
70
89
|
${space}
|
|
71
90
|
`;
|
|
91
|
+
|
|
92
|
+
export const MenuItemRow = styled.div`
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
pointer-events: none;
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
export const MenuItemAction = styled.div`
|
|
99
|
+
margin-right: ${({ theme }) => theme.space[300]};
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: center;
|
|
103
|
+
flex-shrink: 0;
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
export const MenuItemText = styled.div`
|
|
107
|
+
word-break: break-word;
|
|
108
|
+
flex-grow: 1;
|
|
109
|
+
min-width: 0;
|
|
110
|
+
`;
|
|
@@ -6,18 +6,9 @@ import { Radio } from "@sproutsocial/seeds-react-radio";
|
|
|
6
6
|
import { useTheme } from "styled-components";
|
|
7
7
|
import { Box } from "@sproutsocial/seeds-react-box";
|
|
8
8
|
import { MenuContext } from "../MenuContext";
|
|
9
|
-
import { MenuItem } from "../MenuItem";
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
MenuItemSelectableAction,
|
|
14
|
-
MenuItemSelectableRow,
|
|
15
|
-
MenuItemSelectableText,
|
|
16
|
-
} from "./styles";
|
|
17
|
-
import type {
|
|
18
|
-
TypeMenuItemSelectableProps,
|
|
19
|
-
InputType,
|
|
20
|
-
} from "./MenuItemSelectableTypes";
|
|
10
|
+
import type { TypeMenuItemProps, InputType } from "./MenuItemTypes";
|
|
11
|
+
import { MenuItemAction, MenuItemRow, MenuItemText } from "./styles";
|
|
21
12
|
|
|
22
13
|
/**
|
|
23
14
|
* SelectionIndicator - handles logic for appropriate rendering of inputs
|
|
@@ -47,7 +38,7 @@ export const SelectionIndicator = ({
|
|
|
47
38
|
onChange={() => {}}
|
|
48
39
|
checked={selected}
|
|
49
40
|
aria-labelledby={labeledBy}
|
|
50
|
-
id={`
|
|
41
|
+
id={`MenuItem-${inputType}-${id}`}
|
|
51
42
|
/>
|
|
52
43
|
);
|
|
53
44
|
case "switch":
|
|
@@ -56,15 +47,15 @@ export const SelectionIndicator = ({
|
|
|
56
47
|
onClick={inputStub}
|
|
57
48
|
checked={!!selected}
|
|
58
49
|
aria-labelledby={labeledBy}
|
|
59
|
-
id={`
|
|
50
|
+
id={`MenuItem-${inputType}-${id}`}
|
|
60
51
|
/>
|
|
61
52
|
);
|
|
62
53
|
case "radio":
|
|
63
54
|
return (
|
|
64
55
|
<Radio
|
|
65
56
|
checked={selected}
|
|
66
|
-
id={`
|
|
67
|
-
name={`
|
|
57
|
+
id={`MenuItem-${inputType}-${id}`}
|
|
58
|
+
name={`MenuItem-${inputType}`}
|
|
68
59
|
aria-labelledby={labeledBy}
|
|
69
60
|
/>
|
|
70
61
|
);
|
|
@@ -80,15 +71,15 @@ export const SelectionIndicator = ({
|
|
|
80
71
|
};
|
|
81
72
|
|
|
82
73
|
/**
|
|
83
|
-
*
|
|
74
|
+
* useOptionProps - props needed for the option menu items
|
|
84
75
|
*/
|
|
85
|
-
export const
|
|
76
|
+
export const useOptionProps = ({
|
|
86
77
|
id,
|
|
87
78
|
children,
|
|
88
79
|
inputType = "icon",
|
|
89
|
-
|
|
80
|
+
inputLabelId,
|
|
90
81
|
...props
|
|
91
|
-
}:
|
|
82
|
+
}: TypeMenuItemProps) => {
|
|
92
83
|
const { selectedItem, selectedItems, isListbox } = useContext(MenuContext);
|
|
93
84
|
const selected = useMemo(
|
|
94
85
|
() =>
|
|
@@ -97,40 +88,26 @@ export const MenuItemSelectable = ({
|
|
|
97
88
|
[selectedItem, selectedItems]
|
|
98
89
|
);
|
|
99
90
|
|
|
100
|
-
|
|
101
|
-
console.warn(
|
|
102
|
-
"MenuItemSelectable is invalid when isListbox is false (Menu)"
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// const roles: Record<InputType, TypeMenuItemRoles> = {
|
|
107
|
-
// checkbox: "menuitemcheckbox",
|
|
108
|
-
// switch: "menuitemcheckbox",
|
|
109
|
-
// radio: "menuitemradio",
|
|
110
|
-
// icon: "option",
|
|
111
|
-
// };
|
|
112
|
-
|
|
113
|
-
const label = inputLabel || `menu-item-child-${id}`;
|
|
91
|
+
const label = inputLabelId || `menu-item-child-${id}`;
|
|
114
92
|
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
);
|
|
93
|
+
return isListbox
|
|
94
|
+
? {
|
|
95
|
+
active: selected,
|
|
96
|
+
["aria-selected"]: selected,
|
|
97
|
+
children: (
|
|
98
|
+
<MenuItemRow>
|
|
99
|
+
<MenuItemAction>
|
|
100
|
+
<SelectionIndicator
|
|
101
|
+
id={id}
|
|
102
|
+
aria-labelledby={label}
|
|
103
|
+
inputType={inputType}
|
|
104
|
+
selected={!!selected}
|
|
105
|
+
/>
|
|
106
|
+
</MenuItemAction>
|
|
107
|
+
<MenuItemText id={label}>{children}</MenuItemText>
|
|
108
|
+
</MenuItemRow>
|
|
109
|
+
),
|
|
110
|
+
...props,
|
|
111
|
+
}
|
|
112
|
+
: {};
|
|
136
113
|
};
|
|
@@ -5,7 +5,7 @@ import { Popout, type TypePopoutProps } from "@sproutsocial/seeds-react-popout";
|
|
|
5
5
|
import { MenuProvider, type TypeMenuProviderProps } from "../MenuContext";
|
|
6
6
|
|
|
7
7
|
export interface TypeMenuRootOwnProps {
|
|
8
|
-
|
|
8
|
+
menuToggleElement: React.ReactElement<any>;
|
|
9
9
|
popoutProps?: Partial<
|
|
10
10
|
Omit<TypePopoutProps, "isOpen" | "setIsOpen" | "content" | "children">
|
|
11
11
|
>;
|
|
@@ -20,14 +20,21 @@ const CustomPopoutContent = styled(Popout.Content)`
|
|
|
20
20
|
margin-left: 0;
|
|
21
21
|
margin-right: 0;
|
|
22
22
|
`;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @link https://seeds.sproutsocial.com/components/menu-root/
|
|
26
|
+
*
|
|
27
|
+
* @description MenuRoot is a utility component used to handle {@link https://github.com/sproutsocial/seeds/blob/dev/seeds-react/seeds-react-menu/src/MenuContext.tsx | MenuContext}. This component should rarely, if ever, be used directly unless building a custom menu type.
|
|
28
|
+
*/
|
|
29
|
+
|
|
23
30
|
export const MenuRoot = ({
|
|
24
31
|
children,
|
|
25
|
-
|
|
32
|
+
menuToggleElement,
|
|
26
33
|
popoutProps,
|
|
27
34
|
width = "300px",
|
|
28
35
|
...contextProps
|
|
29
36
|
}: TypeMenuRootProps) => {
|
|
30
|
-
const {
|
|
37
|
+
const { isOpen, openMenu, closeMenu } = contextProps;
|
|
31
38
|
const setIsOpen = useCallback(
|
|
32
39
|
(newIsOpen: boolean) => {
|
|
33
40
|
newIsOpen ? openMenu?.() : closeMenu?.();
|
|
@@ -41,19 +48,7 @@ export const MenuRoot = ({
|
|
|
41
48
|
isOpen={!!isOpen}
|
|
42
49
|
setIsOpen={setIsOpen}
|
|
43
50
|
content={
|
|
44
|
-
<CustomPopoutContent width={width}>
|
|
45
|
-
<div
|
|
46
|
-
{...getMenuProps?.(
|
|
47
|
-
{
|
|
48
|
-
...(isListbox ? {} : { role: "menu" }),
|
|
49
|
-
},
|
|
50
|
-
// Suppress ref error for now
|
|
51
|
-
{ suppressRefError: true }
|
|
52
|
-
)}
|
|
53
|
-
>
|
|
54
|
-
{children}
|
|
55
|
-
</div>
|
|
56
|
-
</CustomPopoutContent>
|
|
51
|
+
<CustomPopoutContent width={width}>{children}</CustomPopoutContent>
|
|
57
52
|
}
|
|
58
53
|
focusLockProps={{
|
|
59
54
|
// correct jerking motion when scrolling while the Popout is open
|
|
@@ -63,7 +58,7 @@ export const MenuRoot = ({
|
|
|
63
58
|
disableWrapperAria
|
|
64
59
|
{...popoutProps}
|
|
65
60
|
>
|
|
66
|
-
{
|
|
61
|
+
{menuToggleElement}
|
|
67
62
|
</Popout>
|
|
68
63
|
</MenuProvider>
|
|
69
64
|
);
|
|
@@ -78,7 +78,7 @@ describe("MenuRoot", () => {
|
|
|
78
78
|
it("renders custom children", () => {
|
|
79
79
|
render(
|
|
80
80
|
<MenuRoot
|
|
81
|
-
|
|
81
|
+
menuToggleElement={<MenuToggleButton>Open Menu</MenuToggleButton>}
|
|
82
82
|
isOpen
|
|
83
83
|
>
|
|
84
84
|
<MenuHeader title="Menu Header" />
|
|
@@ -120,7 +120,7 @@ describe("MenuRoot", () => {
|
|
|
120
120
|
|
|
121
121
|
return (
|
|
122
122
|
<MenuRoot
|
|
123
|
-
|
|
123
|
+
menuToggleElement={
|
|
124
124
|
<MenuToggleButton appearance="secondary">
|
|
125
125
|
Open Menu (<TestComponent />)
|
|
126
126
|
</MenuToggleButton>
|
|
@@ -181,7 +181,7 @@ describe("MenuRoot", () => {
|
|
|
181
181
|
({ TestComponent, contextProps, testBtnText, testFn }) => {
|
|
182
182
|
render(
|
|
183
183
|
<MenuRoot
|
|
184
|
-
|
|
184
|
+
menuToggleElement={<MenuToggleButton>Open Menu</MenuToggleButton>}
|
|
185
185
|
isOpen
|
|
186
186
|
{...contextProps}
|
|
187
187
|
>
|
|
@@ -4,20 +4,20 @@ import { MenuRoot, MenuContent, MenuHeader, MenuFooter } from "../../index";
|
|
|
4
4
|
export const MenuRootTypeTest = () => (
|
|
5
5
|
<>
|
|
6
6
|
{/* Valid MenuRoot with minimal required props */}
|
|
7
|
-
<MenuRoot
|
|
7
|
+
<MenuRoot menuToggleElement={<>test</>}>Test</MenuRoot>
|
|
8
8
|
{/* Valid MenuRoot allows MenuContent by itself as a child */}
|
|
9
|
-
<MenuRoot
|
|
9
|
+
<MenuRoot menuToggleElement={<>test</>}>
|
|
10
10
|
<MenuContent>Test</MenuContent>
|
|
11
11
|
</MenuRoot>
|
|
12
12
|
{/* Valid MenuRoot allows multiple children without fragment */}
|
|
13
|
-
<MenuRoot
|
|
13
|
+
<MenuRoot menuToggleElement={<>test</>}>
|
|
14
14
|
<MenuHeader>Test</MenuHeader>
|
|
15
15
|
<MenuContent>Test</MenuContent>
|
|
16
16
|
<MenuFooter>Test</MenuFooter>
|
|
17
17
|
</MenuRoot>
|
|
18
18
|
{/* Valid MenuRoot accepts Downshift props and functions for useSelect */}
|
|
19
19
|
<MenuRoot
|
|
20
|
-
|
|
20
|
+
menuToggleElement={<>test</>}
|
|
21
21
|
onSelectedItemChange={() => {}}
|
|
22
22
|
selectItem={() => {}}
|
|
23
23
|
selectedItem={null}
|
|
@@ -30,7 +30,7 @@ export const MenuRootTypeTest = () => (
|
|
|
30
30
|
</MenuRoot>
|
|
31
31
|
{/* Valid MenuRoot accepts Downshift props and functions for useMultipleSelection */}
|
|
32
32
|
<MenuRoot
|
|
33
|
-
|
|
33
|
+
menuToggleElement={<>test</>}
|
|
34
34
|
onSelectedItemsChange={() => {}}
|
|
35
35
|
selectedItems={[]}
|
|
36
36
|
multiSelectStateReducer={(c) => c}
|
|
@@ -40,19 +40,19 @@ export const MenuRootTypeTest = () => (
|
|
|
40
40
|
Test
|
|
41
41
|
</MenuRoot>
|
|
42
42
|
|
|
43
|
-
{/* @ts-expect-error -
|
|
43
|
+
{/* @ts-expect-error - menuToggleElement is a required prop */}
|
|
44
44
|
<MenuRoot>Test</MenuRoot>
|
|
45
45
|
<MenuRoot
|
|
46
|
-
/* @ts-expect-error -
|
|
47
|
-
|
|
46
|
+
/* @ts-expect-error - menuToggleElement must be a valid react element */
|
|
47
|
+
menuToggleElement="test"
|
|
48
48
|
>
|
|
49
49
|
Test
|
|
50
50
|
</MenuRoot>
|
|
51
51
|
{/* @ts-expect-error - when no children provided */}
|
|
52
|
-
<MenuRoot
|
|
52
|
+
<MenuRoot menuToggleElement="test" />
|
|
53
53
|
|
|
54
54
|
<MenuRoot
|
|
55
|
-
|
|
55
|
+
menuToggleElement={<>test</>}
|
|
56
56
|
/* @ts-expect-error - isOpen can't be a string */
|
|
57
57
|
isOpen="false"
|
|
58
58
|
/* @ts-expect-error - onSelectedItemChange can't be a string */
|
|
@@ -34,6 +34,14 @@ const selectReducerForMultiSelect: TypeMultiSelectMenuProps["stateReducer"] = (
|
|
|
34
34
|
return changes;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @link https://seeds.sproutsocial.com/components/multiselect-menu/
|
|
39
|
+
*
|
|
40
|
+
* @description A MultiSelectMenu allows multiple {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/MenuItem | MenuItems} to be selected from a list or groups of lists.
|
|
41
|
+
*
|
|
42
|
+
* @see {@link https://seeds.sproutsocial.com/components/menu-group/ | MenuGroup}
|
|
43
|
+
*/
|
|
44
|
+
|
|
37
45
|
export const MultiSelectMenu = ({
|
|
38
46
|
children,
|
|
39
47
|
stateReducer: externalStateReducer,
|
|
@@ -91,7 +99,6 @@ export const MultiSelectMenu = ({
|
|
|
91
99
|
case useSelect.stateChangeTypes.ToggleButtonKeyDownEnter:
|
|
92
100
|
case useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton:
|
|
93
101
|
case useSelect.stateChangeTypes.ItemClick:
|
|
94
|
-
case useSelect.stateChangeTypes.ToggleButtonBlur:
|
|
95
102
|
if (!newSelectedItem) break;
|
|
96
103
|
if (selectedItems?.find((item) => item?.id === newSelectedItem.id)) {
|
|
97
104
|
removeSelectedItem(newSelectedItem);
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
MultiSelectMenu,
|
|
4
4
|
MenuContent,
|
|
5
5
|
MenuToggleButton,
|
|
6
|
-
|
|
6
|
+
MenuItem,
|
|
7
7
|
MenuGroup,
|
|
8
8
|
} from "../../index";
|
|
9
9
|
import {
|
|
@@ -30,7 +30,7 @@ describe("MultiSelectMenu", () => {
|
|
|
30
30
|
it("should render the menuItemSelectables when provided as children", async () => {
|
|
31
31
|
const { user } = render(
|
|
32
32
|
<MultiSelectMenu
|
|
33
|
-
|
|
33
|
+
menuToggleElement={
|
|
34
34
|
<MenuToggleButton aria-label="testable menu" role="button">
|
|
35
35
|
Testable Menu
|
|
36
36
|
</MenuToggleButton>
|
|
@@ -39,9 +39,9 @@ describe("MultiSelectMenu", () => {
|
|
|
39
39
|
<MenuContent>
|
|
40
40
|
<MenuGroup>
|
|
41
41
|
{menuItems.map((item) => (
|
|
42
|
-
<
|
|
42
|
+
<MenuItem key={item.id} id={item.id} label={item.label}>
|
|
43
43
|
{item.label}
|
|
44
|
-
</
|
|
44
|
+
</MenuItem>
|
|
45
45
|
))}
|
|
46
46
|
</MenuGroup>
|
|
47
47
|
</MenuContent>
|
|
@@ -73,7 +73,7 @@ describe("MultiSelectMenu", () => {
|
|
|
73
73
|
xit("should allow for multiple items to be selected", async () => {
|
|
74
74
|
const { user } = render(
|
|
75
75
|
<MultiSelectMenu
|
|
76
|
-
|
|
76
|
+
menuToggleElement={
|
|
77
77
|
<MenuToggleButton aria-label="testable menu" role="button">
|
|
78
78
|
Testable Menu
|
|
79
79
|
</MenuToggleButton>
|
|
@@ -82,14 +82,14 @@ describe("MultiSelectMenu", () => {
|
|
|
82
82
|
<MenuContent>
|
|
83
83
|
<MenuGroup>
|
|
84
84
|
{menuItems.map((item) => (
|
|
85
|
-
<
|
|
85
|
+
<MenuItem
|
|
86
86
|
key={item.id}
|
|
87
87
|
id={item.id}
|
|
88
88
|
data-testid={item.id}
|
|
89
89
|
label={item.label}
|
|
90
90
|
>
|
|
91
91
|
{item.label}
|
|
92
|
-
</
|
|
92
|
+
</MenuItem>
|
|
93
93
|
))}
|
|
94
94
|
</MenuGroup>
|
|
95
95
|
</MenuContent>
|
|
@@ -129,7 +129,7 @@ describe("MultiSelectMenu", () => {
|
|
|
129
129
|
xit("Should maintain selected state when closed and reopened", async () => {
|
|
130
130
|
const { user } = render(
|
|
131
131
|
<MultiSelectMenu
|
|
132
|
-
|
|
132
|
+
menuToggleElement={
|
|
133
133
|
<MenuToggleButton aria-label="testable menu" role="button">
|
|
134
134
|
Testable Menu
|
|
135
135
|
</MenuToggleButton>
|
|
@@ -138,14 +138,14 @@ describe("MultiSelectMenu", () => {
|
|
|
138
138
|
<MenuContent>
|
|
139
139
|
<MenuGroup>
|
|
140
140
|
{menuItems.map((item) => (
|
|
141
|
-
<
|
|
141
|
+
<MenuItem
|
|
142
142
|
key={item.id}
|
|
143
143
|
id={item.id}
|
|
144
144
|
data-testid={item.id}
|
|
145
145
|
label={item.label}
|
|
146
146
|
>
|
|
147
147
|
{item.label}
|
|
148
|
-
</
|
|
148
|
+
</MenuItem>
|
|
149
149
|
))}
|
|
150
150
|
</MenuGroup>
|
|
151
151
|
</MenuContent>
|
|
@@ -190,7 +190,7 @@ describe("MultiSelectMenu", () => {
|
|
|
190
190
|
xit("should be able to navigate using the keyboard", async () => {
|
|
191
191
|
const { user } = render(
|
|
192
192
|
<MultiSelectMenu
|
|
193
|
-
|
|
193
|
+
menuToggleElement={
|
|
194
194
|
<MenuToggleButton aria-label="testable menu" role="button">
|
|
195
195
|
Testable Menu
|
|
196
196
|
</MenuToggleButton>
|
|
@@ -199,14 +199,14 @@ describe("MultiSelectMenu", () => {
|
|
|
199
199
|
<MenuContent>
|
|
200
200
|
<MenuGroup>
|
|
201
201
|
{menuItems.map((item) => (
|
|
202
|
-
<
|
|
202
|
+
<MenuItem
|
|
203
203
|
key={item.id}
|
|
204
204
|
id={item.id}
|
|
205
205
|
data-testid={item.id}
|
|
206
206
|
label={item.label}
|
|
207
207
|
>
|
|
208
208
|
{item.label}
|
|
209
|
-
</
|
|
209
|
+
</MenuItem>
|
|
210
210
|
))}
|
|
211
211
|
</MenuGroup>
|
|
212
212
|
</MenuContent>
|
|
@@ -246,7 +246,7 @@ describe("MultiSelectMenu", () => {
|
|
|
246
246
|
xit("should stay open after menu item selection", async () => {
|
|
247
247
|
const { user } = render(
|
|
248
248
|
<MultiSelectMenu
|
|
249
|
-
|
|
249
|
+
menuToggleElement={
|
|
250
250
|
<MenuToggleButton aria-label="testable menu" role="button">
|
|
251
251
|
Testable Menu
|
|
252
252
|
</MenuToggleButton>
|
|
@@ -255,14 +255,14 @@ describe("MultiSelectMenu", () => {
|
|
|
255
255
|
<MenuContent>
|
|
256
256
|
<MenuGroup>
|
|
257
257
|
{menuItems.map((item) => (
|
|
258
|
-
<
|
|
258
|
+
<MenuItem
|
|
259
259
|
key={item.id}
|
|
260
260
|
id={item.id}
|
|
261
261
|
data-testid={item.id}
|
|
262
262
|
label={item.label}
|
|
263
263
|
>
|
|
264
264
|
{item.label}
|
|
265
|
-
</
|
|
265
|
+
</MenuItem>
|
|
266
266
|
))}
|
|
267
267
|
</MenuGroup>
|
|
268
268
|
</MenuContent>
|
|
@@ -297,7 +297,7 @@ describe("MultiSelectMenu", () => {
|
|
|
297
297
|
xit("Should work with a initially selected items", async () => {
|
|
298
298
|
const { user } = render(
|
|
299
299
|
<MultiSelectMenu
|
|
300
|
-
|
|
300
|
+
menuToggleElement={
|
|
301
301
|
<MenuToggleButton aria-label="testable menu" role="button">
|
|
302
302
|
Testable Menu
|
|
303
303
|
</MenuToggleButton>
|
|
@@ -307,14 +307,14 @@ describe("MultiSelectMenu", () => {
|
|
|
307
307
|
<MenuContent>
|
|
308
308
|
<MenuGroup>
|
|
309
309
|
{menuItems.map((item) => (
|
|
310
|
-
<
|
|
310
|
+
<MenuItem
|
|
311
311
|
key={item.id}
|
|
312
312
|
id={item.id}
|
|
313
313
|
data-testid={item.id}
|
|
314
314
|
label={item.label}
|
|
315
315
|
>
|
|
316
316
|
{item.label}
|
|
317
|
-
</
|
|
317
|
+
</MenuItem>
|
|
318
318
|
))}
|
|
319
319
|
</MenuGroup>
|
|
320
320
|
</MenuContent>
|