@sproutsocial/seeds-react-menu 0.1.0 → 0.2.1
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/.eslintignore +6 -0
- package/.eslintrc.js +6 -2
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +23 -1
- package/dist/esm/index.js +243 -217
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +104 -38
- package/dist/index.d.ts +104 -38
- package/dist/index.js +272 -243
- package/dist/index.js.map +1 -1
- package/package.json +10 -7
- package/src/ActionMenu/ActionMenu.feature +9 -3
- package/src/ActionMenu/ActionMenu.tsx +76 -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 +3 -4
- package/src/MenuContent/styles.tsx +10 -6
- package/src/MenuContext.tsx +1 -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/MenuGroupTypes.ts +1 -0
- package/src/MenuGroup/__tests__/MenuGroup.test.tsx +41 -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/__tests__/MenuHeader.typetest.tsx +1 -0
- package/src/MenuHeader/styles.tsx +21 -22
- package/src/MenuItem/MenuItem.feature +66 -26
- package/src/MenuItem/MenuItem.tsx +39 -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 +44 -3
- package/src/{MenuItemSelectable/MenuItemSelectable.tsx → MenuItem/useOptionProps.tsx} +32 -54
- package/src/MenuRoot/MenuRoot.feature +20 -10
- package/src/MenuRoot/MenuRoot.tsx +14 -18
- package/src/MenuRoot/__tests__/MenuRoot.test.tsx +168 -34
- package/src/MenuRoot/__tests__/MenuRoot.typetest.tsx +10 -10
- package/src/MultiSelectMenu/MultiSelectMenu.feature +2 -13
- package/src/MultiSelectMenu/MultiSelectMenu.tsx +9 -1
- package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +154 -153
- package/src/MultiSelectMenu/__tests__/MultiSelectMenu.typetest.tsx +10 -10
- package/src/SingleSelectMenu/SingleSelectMenu.feature +11 -0
- package/src/SingleSelectMenu/SingleSelectMenu.tsx +10 -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 +2 -4
- package/src/index.ts +0 -1
- package/src/menuTestingUtils.tsx +7 -4
- package/tsconfig.json +1 -1
- 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
|
@@ -6,30 +6,20 @@ Feature: Menu Group
|
|
|
6
6
|
Background:
|
|
7
7
|
Given an end user is using the component
|
|
8
8
|
|
|
9
|
-
Scenario: MenuGroup
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Then <selection behavior>
|
|
24
|
-
|
|
25
|
-
Examples:
|
|
26
|
-
| group type | menu type | selection behavior |
|
|
27
|
-
| single | single | the menu uses the default behavior |
|
|
28
|
-
| multiple | multiple | the menu uses the default behavior |
|
|
29
|
-
| single | multiple | the group behaves like a single select |
|
|
30
|
-
| multiple | single | the group throws an error and uses default behavior |
|
|
31
|
-
| default | single | the menu uses the default behavior |
|
|
32
|
-
| default | multiple | the menu uses the default behavior |
|
|
9
|
+
Scenario: MenuGroup has isSingleSelect true
|
|
10
|
+
When the MenuGroup isSingleSelect is true
|
|
11
|
+
And MenuContext is set to multiple selection
|
|
12
|
+
Then the group behaves like a single select
|
|
13
|
+
|
|
14
|
+
Scenario: MenuGroup has isSingleSelect undefined
|
|
15
|
+
When the MenuGroup isSingleSelect is undefined
|
|
16
|
+
And MenuContext is set to multiple selection
|
|
17
|
+
Then the group behaves like a multi select
|
|
18
|
+
|
|
19
|
+
Scenario: MenuGroup has isSingleSelect false
|
|
20
|
+
When the MenuGroup isSingleSelect is false
|
|
21
|
+
And MenuContext is set to multiple selection
|
|
22
|
+
Then the group behaves like a multi select
|
|
33
23
|
|
|
34
24
|
Rule: API
|
|
35
25
|
Background:
|
|
@@ -98,8 +88,6 @@ Feature: Menu Group
|
|
|
98
88
|
| a red Banner | top | a red Banner at the top of the group |
|
|
99
89
|
| hint text | bottom | hint text at the bottom of the group |
|
|
100
90
|
| text | top and bottom | text at the top and bottom of the group |
|
|
101
|
-
| no content | top | no content at the top of the group |
|
|
102
|
-
| no content | bottom | no content at the bottom of the group |
|
|
103
91
|
|
|
104
92
|
Scenario Outline: MenuRoot checks its types
|
|
105
93
|
When <propName> is passed a value
|
|
@@ -2,6 +2,26 @@ import React from "react";
|
|
|
2
2
|
import type { TypeMenuGroupProps } from "./MenuGroupTypes";
|
|
3
3
|
import { StyledMenuGroup, StyledTitle, StyledMenuGroupUl } from "./styles";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @link https://seeds.sproutsocial.com/components/menu-group/
|
|
7
|
+
*
|
|
8
|
+
* @description MenuGroup is used to group lists of {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/MenuItem | MenuItems}.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* <ActionMenu>
|
|
12
|
+
* <MenuContent>
|
|
13
|
+
* <MenuGroup title="Group 1">
|
|
14
|
+
* <MenuItem>Item 1</MenuItem>
|
|
15
|
+
* </MenuGroup>
|
|
16
|
+
* <MenuGroup title="Group 1">
|
|
17
|
+
* <MenuItem>Item 2</MenuItem>
|
|
18
|
+
* </MenuGroup>
|
|
19
|
+
* </MenuContent>
|
|
20
|
+
* </ActionMenu>
|
|
21
|
+
*
|
|
22
|
+
* @see {@link https://seeds.sproutsocial.com/components/menu-item/ | MenuItem}
|
|
23
|
+
*/
|
|
24
|
+
|
|
5
25
|
export const MenuGroup = ({
|
|
6
26
|
titleAs = "h3",
|
|
7
27
|
children,
|
|
@@ -23,7 +43,7 @@ export const MenuGroup = ({
|
|
|
23
43
|
{...rest}
|
|
24
44
|
>
|
|
25
45
|
{title && (
|
|
26
|
-
<StyledTitle as={titleAs} id={titleId}>
|
|
46
|
+
<StyledTitle role="heading" as={titleAs} id={titleId}>
|
|
27
47
|
{title}
|
|
28
48
|
</StyledTitle>
|
|
29
49
|
)}
|
|
@@ -15,8 +15,10 @@ const menuItems = [
|
|
|
15
15
|
|
|
16
16
|
describe("MenuGroup", () => {
|
|
17
17
|
xdescribe("User Behavior", () => {
|
|
18
|
-
|
|
19
|
-
xit("
|
|
18
|
+
// To be enabled when isSingleSelect prop added
|
|
19
|
+
xit("behaves as it normally would when isSingleSelect is undefined", () => {});
|
|
20
|
+
xit("behaves as it normally would when isSingleSelect is false", () => {});
|
|
21
|
+
xit("behaves like a single select when isSingleSelect is true", () => {});
|
|
20
22
|
});
|
|
21
23
|
describe("API", () => {
|
|
22
24
|
it("renders a group with a title", () => {
|
|
@@ -24,7 +26,7 @@ describe("MenuGroup", () => {
|
|
|
24
26
|
render(
|
|
25
27
|
<SingleSelectMenu
|
|
26
28
|
defaultIsOpen
|
|
27
|
-
|
|
29
|
+
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
28
30
|
>
|
|
29
31
|
<MenuContent>
|
|
30
32
|
<MenuGroup title={menuTitle}>
|
|
@@ -41,13 +43,17 @@ describe("MenuGroup", () => {
|
|
|
41
43
|
// Find associated label for the group. (Menu Group uses aria-labelledby to point to heading element)
|
|
42
44
|
const menuGroup = screen.getByRole("group", { name: menuTitle });
|
|
43
45
|
expect(menuGroup).toBeInTheDocument();
|
|
46
|
+
expect(
|
|
47
|
+
// eslint-disable-next-line testing-library/prefer-presence-queries
|
|
48
|
+
screen.queryByRole("heading", { name: menuTitle })
|
|
49
|
+
).toBeInTheDocument();
|
|
44
50
|
});
|
|
45
51
|
|
|
46
52
|
it("when no title is provided, it should not render a title", () => {
|
|
47
53
|
render(
|
|
48
54
|
<SingleSelectMenu
|
|
49
55
|
defaultIsOpen
|
|
50
|
-
|
|
56
|
+
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
51
57
|
>
|
|
52
58
|
<MenuContent>
|
|
53
59
|
<MenuGroup>
|
|
@@ -66,11 +72,11 @@ describe("MenuGroup", () => {
|
|
|
66
72
|
expect(menuTitle).not.toBeInTheDocument();
|
|
67
73
|
});
|
|
68
74
|
|
|
69
|
-
it("
|
|
75
|
+
it("renders MenuItem children correctly", () => {
|
|
70
76
|
render(
|
|
71
77
|
<SingleSelectMenu
|
|
72
78
|
defaultIsOpen
|
|
73
|
-
|
|
79
|
+
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
74
80
|
>
|
|
75
81
|
<MenuContent>
|
|
76
82
|
<MenuGroup>
|
|
@@ -91,5 +97,34 @@ describe("MenuGroup", () => {
|
|
|
91
97
|
screen.getByRole("option", { name: menuItems[1].label })
|
|
92
98
|
).toBeInTheDocument();
|
|
93
99
|
});
|
|
100
|
+
|
|
101
|
+
// How do we do this accessibly in a UL?
|
|
102
|
+
xdescribe("renders custom content", () => {
|
|
103
|
+
test.each([
|
|
104
|
+
{
|
|
105
|
+
name: "a red Banner at the top of the group",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "hint text at the bottom of the group",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "text at the top and bottom of the group",
|
|
112
|
+
},
|
|
113
|
+
])("$name", () => {});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// To be implemented when items, customMenuItem and menuItemType props are added
|
|
117
|
+
xdescribe("when the items prop is provided", () => {
|
|
118
|
+
xit("hides the group if items is empty", () => {});
|
|
119
|
+
xit("renders items using MenuItem by default", () => {});
|
|
120
|
+
xit("renders items using customMenuItem when provided", () => {});
|
|
121
|
+
// use a .each to test each item type
|
|
122
|
+
xit("sets MenuItem types when menuItemType provided", () => {});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
xdescribe("when the items prop is not provided", () => {
|
|
126
|
+
xit("ignores the menuItemType prop", () => {});
|
|
127
|
+
xit("ignores the customMenuItem prop", () => {});
|
|
128
|
+
});
|
|
94
129
|
});
|
|
95
130
|
});
|
package/src/MenuGroup/styles.tsx
CHANGED
|
@@ -18,6 +18,7 @@ export const StyledMenuGroup = styled.li<TypeMenuGroupProps>`
|
|
|
18
18
|
}
|
|
19
19
|
list-style: none;
|
|
20
20
|
margin: 0;
|
|
21
|
+
|
|
21
22
|
${border}
|
|
22
23
|
${position}
|
|
23
24
|
${color}
|
|
@@ -28,10 +29,10 @@ export const StyledMenuGroup = styled.li<TypeMenuGroupProps>`
|
|
|
28
29
|
`;
|
|
29
30
|
|
|
30
31
|
export const StyledTitle = styled(Text.SmallSubHeadline)`
|
|
31
|
-
margin: ${({ theme }) => theme.space[
|
|
32
|
-
|
|
32
|
+
margin: ${({ theme }) => theme.space[400]} ${({ theme }) => theme.space[400]}
|
|
33
|
+
0px ${({ theme }) => theme.space[400]};
|
|
33
34
|
`;
|
|
34
35
|
export const StyledMenuGroupUl = styled.ul`
|
|
35
36
|
margin: 0;
|
|
36
|
-
padding:
|
|
37
|
+
padding: 0;
|
|
37
38
|
`;
|
|
@@ -3,9 +3,18 @@ import {
|
|
|
3
3
|
MenuHeaderContainer,
|
|
4
4
|
MenuTitleText,
|
|
5
5
|
MenuChildrenContainer,
|
|
6
|
+
MenuHeaderFeatures,
|
|
6
7
|
} from "./styles";
|
|
7
8
|
import type { TypeMenuHeaderProps } from "./MenuHeaderTypes";
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @link https://seeds.sproutsocial.com/components/menu-header/
|
|
12
|
+
*
|
|
13
|
+
* @description MenuHeader is a container used to hold header content within a Menu.
|
|
14
|
+
*
|
|
15
|
+
* @see {@link https://seeds.sproutsocial.com/components/menu-footer/ | MenuFooter}
|
|
16
|
+
*/
|
|
17
|
+
|
|
9
18
|
const MenuHeader = ({
|
|
10
19
|
children,
|
|
11
20
|
title,
|
|
@@ -16,11 +25,13 @@ const MenuHeader = ({
|
|
|
16
25
|
return (
|
|
17
26
|
<>
|
|
18
27
|
<MenuHeaderContainer $leftAction={!!leftAction} {...rest}>
|
|
19
|
-
{leftAction
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
<MenuHeaderFeatures $leftAction={!!leftAction}>
|
|
29
|
+
{leftAction && leftAction}
|
|
30
|
+
{title && (
|
|
31
|
+
<MenuTitleText $leftAction={!!leftAction}>{title}</MenuTitleText>
|
|
32
|
+
)}
|
|
33
|
+
{rightAction && rightAction}
|
|
34
|
+
</MenuHeaderFeatures>
|
|
24
35
|
{children && <MenuChildrenContainer>{children}</MenuChildrenContainer>}
|
|
25
36
|
</MenuHeaderContainer>
|
|
26
37
|
</>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { render, screen } from "@sproutsocial/seeds-react-testing-library";
|
|
2
2
|
import { MenuHeader } from "../MenuHeader";
|
|
3
|
+
import { MenuRoot } from "../../MenuRoot";
|
|
3
4
|
import Button from "@sproutsocial/seeds-react-button";
|
|
5
|
+
import Text from "@sproutsocial/seeds-react-text";
|
|
4
6
|
|
|
5
7
|
describe("MenuHeader API", () => {
|
|
6
8
|
it("renders a title when title prop is passed a string", () => {
|
|
@@ -15,12 +17,54 @@ describe("MenuHeader API", () => {
|
|
|
15
17
|
|
|
16
18
|
expect(menuHeader).not.toBeInTheDocument();
|
|
17
19
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const contextTests = [
|
|
21
|
+
{
|
|
22
|
+
children: "Text",
|
|
23
|
+
TestComponent: <Text>Find me!</Text>,
|
|
24
|
+
test: () => {
|
|
25
|
+
expect(screen.getByText("Hello world!")).toContainElement(
|
|
26
|
+
screen.getByText("Find me!")
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
// Implement when components are available
|
|
31
|
+
// { children: "Banner" },
|
|
32
|
+
// { children: "MenuHeaderInput" },
|
|
33
|
+
// { children: "Text and a Banner" },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
it.each(contextTests)(
|
|
37
|
+
"$children rendered when $children provided",
|
|
38
|
+
({ TestComponent, test }) => {
|
|
39
|
+
render(
|
|
40
|
+
<MenuHeader>
|
|
41
|
+
Hello world!
|
|
42
|
+
{TestComponent}
|
|
43
|
+
</MenuHeader>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
test();
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
it.each(contextTests)(
|
|
51
|
+
"$children rendered when $children provided in MenuRoot",
|
|
52
|
+
({ TestComponent, test }) => {
|
|
53
|
+
render(
|
|
54
|
+
<MenuRoot
|
|
55
|
+
isOpen
|
|
56
|
+
menuToggleElement={<Button onClick={jest.fn()}>Open</Button>}
|
|
57
|
+
>
|
|
58
|
+
<MenuHeader>
|
|
59
|
+
Hello world!
|
|
60
|
+
{TestComponent}
|
|
61
|
+
</MenuHeader>
|
|
62
|
+
</MenuRoot>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
test();
|
|
66
|
+
}
|
|
67
|
+
);
|
|
24
68
|
|
|
25
69
|
it("supports clickable actions", async () => {
|
|
26
70
|
const handleTestClick = jest.fn();
|
|
@@ -6,6 +6,7 @@ export const MenuHeaderTypeTest = () => (
|
|
|
6
6
|
title="Menu Header"
|
|
7
7
|
leftAction={<div>left action</div>}
|
|
8
8
|
rightAction={<div>right action</div>}
|
|
9
|
+
// eslint-disable-next-line react/no-children-prop
|
|
9
10
|
children={<div>children</div>}
|
|
10
11
|
/>
|
|
11
12
|
{/* left and right action accept strings */}
|
|
@@ -22,28 +22,9 @@ export const MenuHeaderContainer = styled.div<TypeMenuHeaderContainerProps>`
|
|
|
22
22
|
width: 100%;
|
|
23
23
|
font-size: ${({ theme }) => theme.typography[200].fontSize};
|
|
24
24
|
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
25
26
|
padding: ${({ theme }) => theme.space[350]} ${({ theme }) => theme.space[400]};
|
|
26
27
|
|
|
27
|
-
> a {
|
|
28
|
-
white-space: nowrap;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
&:first-child {
|
|
32
|
-
flex-grow: 1;
|
|
33
|
-
justify-self: start;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
&:last-child {
|
|
37
|
-
flex-grow: 1;
|
|
38
|
-
justify-self: end;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
${({ $leftAction, theme }) =>
|
|
42
|
-
$leftAction &&
|
|
43
|
-
`
|
|
44
|
-
padding-left: ${theme.space[350]};
|
|
45
|
-
`}
|
|
46
|
-
|
|
47
28
|
${border}
|
|
48
29
|
${color}
|
|
49
30
|
${flexbox}
|
|
@@ -60,16 +41,34 @@ export const MenuTitleText = styled.h3<TypeMenuHeaderContainerProps>`
|
|
|
60
41
|
flex-grow: 2;
|
|
61
42
|
justify-self: start;
|
|
62
43
|
|
|
44
|
+
${({ theme }) => theme.typography[200]}
|
|
45
|
+
|
|
63
46
|
${({ $leftAction, theme }) =>
|
|
64
47
|
$leftAction &&
|
|
65
48
|
`
|
|
66
49
|
padding-left: ${theme.space[350]};
|
|
67
50
|
`}
|
|
68
|
-
|
|
69
|
-
${({ theme }) => theme.typography[200]}
|
|
70
51
|
`;
|
|
71
52
|
|
|
72
53
|
export const MenuChildrenContainer = styled.div`
|
|
73
54
|
grid-column: 1 / -1;
|
|
74
55
|
margin-top: ${({ theme }) => theme.space[300]};
|
|
75
56
|
`;
|
|
57
|
+
|
|
58
|
+
export const MenuHeaderFeatures = styled.div<TypeMenuHeaderContainerProps>`
|
|
59
|
+
display: flex;
|
|
60
|
+
|
|
61
|
+
> a {
|
|
62
|
+
white-space: nowrap;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
&:first-child {
|
|
66
|
+
flex-grow: 1;
|
|
67
|
+
justify-self: start;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&:last-child {
|
|
71
|
+
flex-grow: 1;
|
|
72
|
+
justify-self: end;
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Feature: MenuItem
|
|
2
2
|
|
|
3
|
-
A configurable and interactive item for inclusion in a `Menu` component.
|
|
3
|
+
A configurable and interactive item for inclusion in a `Menu` component, with various input types if used in a listbox.
|
|
4
4
|
|
|
5
5
|
Rule: User Behavior
|
|
6
6
|
Background:
|
|
@@ -11,6 +11,17 @@ Feature: MenuItem
|
|
|
11
11
|
When the MenuItem is clicked
|
|
12
12
|
Then the MenuItem's onClick event should fire
|
|
13
13
|
|
|
14
|
+
Scenario Outline: MenuItem can be triggered via keyboard
|
|
15
|
+
Given a MenuItem is focused
|
|
16
|
+
And the MenuItem is not disabled
|
|
17
|
+
When the MenuItem is triggered via <interaction>
|
|
18
|
+
Then the MenuItem's onClick event should fire
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
| interaction |
|
|
22
|
+
| pressing the Enter key |
|
|
23
|
+
| pressing the Space key |
|
|
24
|
+
|
|
14
25
|
Scenario Outline: MenuItem is disabled
|
|
15
26
|
Given a MenuItem is disabled
|
|
16
27
|
When the MenuItem is triggered via <interaction>
|
|
@@ -21,18 +32,35 @@ Feature: MenuItem
|
|
|
21
32
|
| clicking |
|
|
22
33
|
| focusing and pressing the Enter key |
|
|
23
34
|
| focusing and pressing the Space key |
|
|
35
|
+
|
|
36
|
+
Scenario Outline: MenuItem can be checked
|
|
37
|
+
Given the MenuItem is in a Listbox
|
|
38
|
+
And the MenuItem is in the unchecked state
|
|
39
|
+
And the MenuItem is focused
|
|
40
|
+
And the MenuItem is not disabled
|
|
41
|
+
When the MenuItem is triggered via <interaction>
|
|
42
|
+
Then the MenuItem should be checked
|
|
24
43
|
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
Examples: Interactions
|
|
45
|
+
| interaction |
|
|
46
|
+
| clicking with mouse |
|
|
47
|
+
| pressing the Enter key |
|
|
48
|
+
| pressing the Space key |
|
|
49
|
+
|
|
50
|
+
Scenario Outline: MenuItem can be unchecked
|
|
51
|
+
Given the MenuItem is in a Listbox
|
|
52
|
+
And the MenuItem is in the checked state
|
|
53
|
+
And the MenuItem is focused
|
|
27
54
|
And the MenuItem is not disabled
|
|
28
55
|
When the MenuItem is triggered via <interaction>
|
|
29
|
-
Then the MenuItem
|
|
56
|
+
Then the MenuItem should be unchecked
|
|
30
57
|
|
|
31
|
-
Examples:
|
|
58
|
+
Examples: Interactions
|
|
32
59
|
| interaction |
|
|
60
|
+
| clicking with mouse |
|
|
33
61
|
| pressing the Enter key |
|
|
34
62
|
| pressing the Space key |
|
|
35
|
-
|
|
63
|
+
|
|
36
64
|
Rule: API
|
|
37
65
|
Background:
|
|
38
66
|
Given an engineer is developing using the component
|
|
@@ -63,26 +91,38 @@ Feature: MenuItem
|
|
|
63
91
|
| grid | gridGap |
|
|
64
92
|
| space | p |
|
|
65
93
|
|
|
94
|
+
Scenario Outline: MenuItem supports different input types
|
|
95
|
+
Given the MenuItem is in a Listbox
|
|
96
|
+
When the inputType is <inputTypeProp> on the MenuItem component
|
|
97
|
+
Then the MenuItem renders with the appropriate <component> type
|
|
98
|
+
|
|
99
|
+
Examples: Input Types
|
|
100
|
+
| inputTypeProp | component |
|
|
101
|
+
| checkbox | Checkbox |
|
|
102
|
+
| radio | Radio |
|
|
103
|
+
| switch | Switch |
|
|
104
|
+
|
|
105
|
+
Scenario: MenuItem defaults to an icon input type
|
|
106
|
+
Given the MenuItem is in a Listbox
|
|
107
|
+
When the MenuItem is implemented without an inputType prop specified
|
|
108
|
+
Then the MenuItem defaults to an icon input type
|
|
109
|
+
|
|
110
|
+
Scenario: MenuItem checked state can be enabled via a prop
|
|
111
|
+
Given the MenuItem is in a Listbox
|
|
112
|
+
When the selected prop is set to true
|
|
113
|
+
Then the MenuItem should appear in the checked state
|
|
114
|
+
|
|
66
115
|
Scenario Outline: MenuItem validates prop types
|
|
67
|
-
When any <
|
|
68
|
-
Then the MenuItem should check that each
|
|
116
|
+
When any <propName> is passed to the MenuItem
|
|
117
|
+
Then the MenuItem should check that each <propName> is of the expected <propType>
|
|
69
118
|
|
|
70
119
|
Examples: Prop Types
|
|
71
|
-
|
|
|
72
|
-
| children
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Examples: MenuItem States
|
|
82
|
-
| state |
|
|
83
|
-
| enabled |
|
|
84
|
-
| disabled |
|
|
85
|
-
| hovered |
|
|
86
|
-
| focused |
|
|
87
|
-
| active |
|
|
88
|
-
| pressed |
|
|
120
|
+
| propName | propType |
|
|
121
|
+
| children | ReactNode |
|
|
122
|
+
| disabled | boolean |
|
|
123
|
+
| elevation | low, medium, or high |
|
|
124
|
+
| href | string |
|
|
125
|
+
| onClick | React.MouseEventHandler |
|
|
126
|
+
| role | string |
|
|
127
|
+
| selected | boolean |
|
|
128
|
+
| inputType | checkbox, radio, or switch |
|
|
@@ -1,44 +1,63 @@
|
|
|
1
1
|
import React, { useContext } from "react";
|
|
2
2
|
import { MenuContext } from "../MenuContext";
|
|
3
|
-
import { MENU_ITEM_ROLES } from "../constants";
|
|
4
3
|
import type { TypeDefaultItemProps } from "../types";
|
|
5
|
-
import { MenuItemContainer } from "./styles";
|
|
6
4
|
import type { TypeMenuItemProps } from "./MenuItemTypes";
|
|
5
|
+
import { MenuItemContainer, StyledMenuItem } from "./styles";
|
|
6
|
+
import { useOptionProps } from "./useOptionProps";
|
|
7
|
+
|
|
8
|
+
// TODO: decide if we want to support inputs on menuitems
|
|
9
|
+
// const roles = {
|
|
10
|
+
// checkbox: "menuitemcheckbox",
|
|
11
|
+
// switch: "menuitemcheckbox",
|
|
12
|
+
// radio: "menuitemradio",
|
|
13
|
+
// } as const;
|
|
7
14
|
|
|
8
15
|
export const MenuItem = ({
|
|
9
16
|
id,
|
|
10
17
|
children,
|
|
11
|
-
role = "menuitem",
|
|
12
|
-
label,
|
|
13
18
|
onClick,
|
|
14
19
|
onKeyDown,
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
15
21
|
disabled = false,
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
16
23
|
ref,
|
|
24
|
+
href,
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
17
26
|
color,
|
|
18
27
|
...props
|
|
19
28
|
}: TypeMenuItemProps) => {
|
|
20
29
|
const { getItemProps, itemsMap, highlightedIndex, isListbox } =
|
|
21
30
|
useContext(MenuContext);
|
|
31
|
+
const { ...optionProps } = useOptionProps({
|
|
32
|
+
id,
|
|
33
|
+
children,
|
|
34
|
+
...props,
|
|
35
|
+
});
|
|
22
36
|
const item = (itemsMap[id] || { index: 0, id }) as TypeDefaultItemProps;
|
|
23
37
|
const highlighted = highlightedIndex === item?.index;
|
|
24
38
|
|
|
25
39
|
return (
|
|
26
|
-
<MenuItemContainer
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
<MenuItemContainer role="none">
|
|
41
|
+
<StyledMenuItem
|
|
42
|
+
id={id}
|
|
43
|
+
$highlighted={highlighted}
|
|
44
|
+
{...getItemProps?.({
|
|
45
|
+
...(isListbox ? {} : { role: "menuitem" }),
|
|
46
|
+
item,
|
|
47
|
+
index: item.index,
|
|
48
|
+
// This prop no longer supported in downshift
|
|
49
|
+
// disabled,
|
|
50
|
+
onClick,
|
|
51
|
+
onKeyDown,
|
|
52
|
+
})}
|
|
53
|
+
// use anchor tag if href is provided and button for menuitem
|
|
54
|
+
as={isListbox ? undefined : href ? "a" : "button"}
|
|
55
|
+
href={href}
|
|
56
|
+
// eslint-disable-next-line react/no-children-prop
|
|
57
|
+
children={children}
|
|
58
|
+
{...optionProps}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
42
61
|
</MenuItemContainer>
|
|
43
62
|
);
|
|
44
63
|
};
|
|
@@ -22,6 +22,14 @@ export interface TypeMenuItemStyledProps
|
|
|
22
22
|
TypeSpaceSystemProps,
|
|
23
23
|
TypeTypographySystemProps {}
|
|
24
24
|
|
|
25
|
+
export const INPUT_TYPES = {
|
|
26
|
+
CHECKBOX: "checkbox",
|
|
27
|
+
RADIO: "radio",
|
|
28
|
+
SWITCH: "switch",
|
|
29
|
+
ICON: "icon",
|
|
30
|
+
} as const;
|
|
31
|
+
export type InputType = (typeof INPUT_TYPES)[keyof typeof INPUT_TYPES];
|
|
32
|
+
|
|
25
33
|
export interface TypeMenuItemProps
|
|
26
34
|
extends TypeMenuItemStyledProps,
|
|
27
35
|
Omit<
|
|
@@ -29,9 +37,13 @@ export interface TypeMenuItemProps
|
|
|
29
37
|
"as" | "item" | keyof TypeMenuItemStyledProps | keyof TypeDefaultItemProps
|
|
30
38
|
>,
|
|
31
39
|
Omit<TypeDefaultItemProps, "index"> {
|
|
32
|
-
label?: string;
|
|
33
40
|
children: React.ReactNode;
|
|
34
41
|
disabled?: boolean;
|
|
35
42
|
role?: TypeMenuItemRoles;
|
|
36
43
|
$highlighted?: boolean;
|
|
44
|
+
|
|
45
|
+
// props for options
|
|
46
|
+
active?: boolean;
|
|
47
|
+
inputType?: InputType;
|
|
48
|
+
inputLabelId?: string;
|
|
37
49
|
}
|