@sproutsocial/seeds-react-menu 0.5.0 → 0.6.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 +15 -0
- package/dist/esm/index.js +1486 -440
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +248 -92
- package/dist/index.d.ts +248 -92
- package/dist/index.js +1500 -429
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/src/ActionMenu/ActionMenu.stories.tsx +426 -0
- package/src/ActionMenu/ActionMenu.tsx +24 -27
- package/src/ActionMenu/ActionMenuTypes.ts +8 -7
- package/src/ActionMenu/__tests__/ActionMenu.test.tsx +17 -8
- package/src/ActionMenu/__tests__/ActionMenu.typetest.tsx +8 -8
- package/src/Autocomplete/Autocomplete.stories.tsx +38 -3
- package/src/Autocomplete/Autocomplete.tsx +15 -80
- package/src/Autocomplete/AutocompleteInput.tsx +18 -27
- package/src/Autocomplete/AutocompleteTokenInput.tsx +50 -0
- package/src/Autocomplete/AutocompleteTypes.ts +25 -12
- package/src/Autocomplete/MultiAutocomplete.tsx +189 -0
- package/src/Autocomplete/SingleAutocomplete.tsx +74 -0
- package/src/Autocomplete/__tests__/Autocomplete.test.tsx +171 -3
- package/src/Autocomplete/__tests__/Autocomplete.typetest.tsx +90 -8
- package/src/MenuContent/MenuContent.stories.tsx +164 -0
- package/src/MenuContent/MenuContent.tsx +26 -12
- package/src/MenuContent/MenuContentTypes.ts +6 -4
- package/src/MenuContent/__tests__/MenuContent.typetest.tsx +8 -8
- package/src/MenuContext.tsx +191 -37
- package/src/MenuFooter/MenuFooter.stories.tsx +202 -0
- package/src/MenuFooter/__tests__/MenuFooter.test.tsx +16 -11
- package/src/MenuGroup/MenuGroup.feature +16 -16
- package/src/MenuGroup/MenuGroup.stories.tsx +248 -0
- package/src/MenuGroup/MenuGroup.tsx +8 -7
- package/src/MenuGroup/MenuGroupTypes.ts +6 -2
- package/src/MenuGroup/__tests__/MenuGroup.test.tsx +6 -6
- package/src/MenuGroup/__tests__/MenuGroup.typetest.tsx +7 -7
- package/src/MenuGroup/styles.tsx +4 -2
- package/src/MenuHeader/MenuHeader.stories.tsx +276 -0
- package/src/MenuHeader/MenuHeader.tsx +3 -1
- package/src/MenuHeader/__tests__/MenuHeader.test.tsx +23 -13
- package/src/MenuHeader/styles.tsx +18 -14
- package/src/MenuItem/MenuItem.stories.tsx +507 -0
- package/src/MenuItem/MenuItem.tsx +18 -8
- package/src/MenuItem/MenuItemTypes.ts +9 -4
- package/src/MenuItem/__tests__/MenuItem.test.tsx +6 -6
- package/src/MenuItem/styles.tsx +21 -5
- package/src/MenuItem/useOptionProps.tsx +10 -8
- package/src/MenuLabel.tsx +2 -2
- package/src/MenuRoot/MenuRoot.tsx +37 -23
- package/src/MenuRoot/__tests__/MenuRoot.test.tsx +59 -32
- package/src/MenuSearchInput/MenuSearchInput.tsx +63 -0
- package/src/MenuSearchInput/__tests__/MenuSearchInput.test.tsx +157 -0
- package/src/MenuSearchInput/__tests__/MenuSearchInput.typetest.tsx +31 -0
- package/src/MenuSearchInput/index.ts +1 -0
- package/src/MenuSearchTokenInput/MenuSearchTokenInput.tsx +98 -0
- package/src/MenuSearchTokenInput/__tests__/MenuSearchTokenInput.test.tsx +247 -0
- package/src/MenuSearchTokenInput/__tests__/MenuSearchTokenInput.typetest.tsx +33 -0
- package/src/MenuSearchTokenInput/index.ts +1 -0
- package/src/MenuToggleButton/MenuToggleButton.stories.tsx +72 -0
- package/src/{MenuToggleButton.tsx → MenuToggleButton/MenuToggleButton.tsx} +6 -5
- package/src/MenuToggleButton/index.ts +1 -0
- package/src/MenuTokenInput.tsx +104 -0
- package/src/MultiSelectMenu/MultiSelectMenu.stories.tsx +214 -0
- package/src/MultiSelectMenu/MultiSelectMenu.tsx +38 -68
- package/src/MultiSelectMenu/MultiSelectMenuTypes.ts +8 -6
- package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +46 -17
- package/src/MultiSelectMenu/__tests__/MultiSelectMenu.typetest.tsx +6 -6
- package/src/NestedMenu/NestedMenu.feature +109 -0
- package/src/NestedMenu/NestedMenu.stories.tsx +157 -0
- package/src/NestedMenu/NestedMenu.tsx +102 -0
- package/src/NestedMenu/NestedMenuContent.tsx +71 -0
- package/src/NestedMenu/NestedMenuContext.tsx +56 -0
- package/src/NestedMenu/NestedMenuHeader.tsx +25 -0
- package/src/NestedMenu/NestedMenuItem.tsx +37 -0
- package/src/NestedMenu/NestedMenuTypes.ts +47 -0
- package/src/NestedMenu/__tests__/NestedMenu.test.tsx +31 -0
- package/src/NestedMenu/__tests__/NestedMenu.typetest.tsx +134 -0
- package/src/NestedMenu/index.ts +5 -0
- package/src/SingleSelectMenu/SingleSelectMenu.stories.tsx +227 -0
- package/src/SingleSelectMenu/SingleSelectMenu.tsx +23 -26
- package/src/SingleSelectMenu/SingleSelectMenuTypes.ts +7 -5
- package/src/SingleSelectMenu/__tests__/SingleSelectMenu.typetest.tsx +6 -6
- package/src/SubmenuItem/SubmenuItem.feature +82 -0
- package/src/SubmenuItem/SubmenuItem.stories.tsx +163 -0
- package/src/SubmenuItem/SubmenuItem.tsx +254 -0
- package/src/SubmenuItem/SubmenuItemTypes.ts +24 -0
- package/src/SubmenuItem/__tests__/SubmenuItem.test.tsx +3 -0
- package/src/SubmenuItem/__tests__/SubmenuItem.typetest.tsx +112 -0
- package/src/SubmenuItem/index.ts +2 -0
- package/src/hooks/useDownshiftDefaults.tsx +107 -0
- package/src/hooks/useItemFiltering.tsx +66 -0
- package/src/hooks/useMenuChildren.tsx +197 -128
- package/src/index.ts +6 -0
- package/src/menuTestingUtils.tsx +21 -10
- package/src/reducers/nestedMenuStateReducer.tsx +23 -0
- package/src/reducers/useComboboxStateReducer.tsx +26 -8
- package/src/reducers/useSelectStateReducer.tsx +24 -6
- package/src/storybookUtilities/menu-storybook-components.tsx +5 -3
- package/src/types.ts +38 -30
- package/src/utils/downshiftDefaults.ts +9 -0
- package/src/utils/getMultiSelectOverrides.ts +21 -0
- package/src/utils/reduceReducers.ts +19 -0
- package/src/utils/itemToString.ts +0 -5
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
render,
|
|
3
|
+
screen,
|
|
4
|
+
actRender,
|
|
5
|
+
act,
|
|
6
|
+
} from "@sproutsocial/seeds-react-testing-library";
|
|
4
7
|
import Button from "@sproutsocial/seeds-react-button";
|
|
5
8
|
import Text from "@sproutsocial/seeds-react-text";
|
|
9
|
+
import { MenuHeader } from "../MenuHeader";
|
|
10
|
+
import { MenuToggleButton } from "../../index";
|
|
11
|
+
import { TestWithMenuRoot } from "../../menuTestingUtils";
|
|
6
12
|
|
|
7
13
|
describe("MenuHeader API", () => {
|
|
8
14
|
it("renders a title when title prop is passed a string", () => {
|
|
@@ -49,17 +55,17 @@ describe("MenuHeader API", () => {
|
|
|
49
55
|
|
|
50
56
|
it.each(contextTests)(
|
|
51
57
|
"$children rendered when $children provided in MenuRoot",
|
|
52
|
-
({ TestComponent, test }) => {
|
|
53
|
-
|
|
54
|
-
<
|
|
58
|
+
async ({ TestComponent, test }) => {
|
|
59
|
+
await actRender(
|
|
60
|
+
<TestWithMenuRoot
|
|
55
61
|
isOpen
|
|
56
|
-
menuToggleElement={<
|
|
62
|
+
menuToggleElement={<MenuToggleButton>Open</MenuToggleButton>}
|
|
57
63
|
>
|
|
58
64
|
<MenuHeader>
|
|
59
65
|
Hello world!
|
|
60
66
|
{TestComponent}
|
|
61
67
|
</MenuHeader>
|
|
62
|
-
</
|
|
68
|
+
</TestWithMenuRoot>
|
|
63
69
|
);
|
|
64
70
|
|
|
65
71
|
test();
|
|
@@ -69,7 +75,7 @@ describe("MenuHeader API", () => {
|
|
|
69
75
|
it("supports clickable actions", async () => {
|
|
70
76
|
const handleTestClick = jest.fn();
|
|
71
77
|
|
|
72
|
-
const { user } =
|
|
78
|
+
const { user } = await actRender(
|
|
73
79
|
<MenuHeader
|
|
74
80
|
leftAction={<Button onClick={handleTestClick}>Left Action</Button>}
|
|
75
81
|
rightAction={<Button onClick={handleTestClick}>Right Action</Button>}
|
|
@@ -81,17 +87,21 @@ describe("MenuHeader API", () => {
|
|
|
81
87
|
const leftAction = screen.getByText("Left Action");
|
|
82
88
|
const rightAction = screen.getByText("Right Action");
|
|
83
89
|
|
|
84
|
-
await
|
|
90
|
+
await act(async () => {
|
|
91
|
+
await user.click(leftAction);
|
|
92
|
+
});
|
|
85
93
|
|
|
86
94
|
expect(handleTestClick).toHaveBeenCalledTimes(1);
|
|
87
95
|
|
|
88
|
-
await
|
|
96
|
+
await act(async () => {
|
|
97
|
+
await user.click(rightAction);
|
|
98
|
+
});
|
|
89
99
|
|
|
90
100
|
expect(handleTestClick).toHaveBeenCalledTimes(2);
|
|
91
101
|
});
|
|
92
102
|
|
|
93
|
-
it("can render actions on the left or right of the title", () => {
|
|
94
|
-
|
|
103
|
+
it("can render actions on the left or right of the title", async () => {
|
|
104
|
+
await actRender(
|
|
95
105
|
<MenuHeader
|
|
96
106
|
leftAction={<Button onClick={() => {}}>Left Action</Button>}
|
|
97
107
|
rightAction={<Button onClick={() => {}}>Right Action</Button>}
|
|
@@ -38,16 +38,15 @@ export const MenuTitleText = styled.h3<TypeMenuHeaderContainerProps>`
|
|
|
38
38
|
color: ${({ theme }) => theme.colors.text.headline};
|
|
39
39
|
font-weight: ${({ theme }) => theme.fontWeights.bold};
|
|
40
40
|
margin: 0px;
|
|
41
|
-
flex-grow:
|
|
42
|
-
justify-self: start;
|
|
41
|
+
flex-grow: 1;
|
|
43
42
|
|
|
44
43
|
${({ theme }) => theme.typography[200]}
|
|
45
44
|
|
|
46
45
|
${({ $leftAction, theme }) =>
|
|
47
46
|
$leftAction &&
|
|
48
47
|
`
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
padding-left: ${theme.space[350]};
|
|
49
|
+
`}
|
|
51
50
|
`;
|
|
52
51
|
|
|
53
52
|
export const MenuChildrenContainer = styled.div`
|
|
@@ -57,18 +56,23 @@ export const MenuChildrenContainer = styled.div`
|
|
|
57
56
|
|
|
58
57
|
export const MenuHeaderFeatures = styled.div<TypeMenuHeaderContainerProps>`
|
|
59
58
|
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: space-between;
|
|
61
|
+
width: 100%;
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
.right-action {
|
|
64
|
+
margin-left: auto; /* Pushes the right action to the right */
|
|
65
|
+
display: flex;
|
|
66
|
+
justify-content: flex-end;
|
|
67
|
+
flex-shrink: 0; /* Prevents it from shrinking */
|
|
68
|
+
max-width: 40%; /* Prevents it from taking too much space */
|
|
69
|
+
text-align: right;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
> a,
|
|
73
|
+
> button {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
word-break: break-word;
|
|
73
77
|
}
|
|
74
78
|
`;
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { action } from "@storybook/addon-actions";
|
|
4
|
+
|
|
5
|
+
import { Box } from "@sproutsocial/seeds-react-box";
|
|
6
|
+
import { Switch } from "@sproutsocial/seeds-react-switch";
|
|
7
|
+
import { Icon } from "@sproutsocial/seeds-react-icon";
|
|
8
|
+
import { Badge } from "@sproutsocial/seeds-react-badge";
|
|
9
|
+
import { Tooltip } from "@sproutsocial/seeds-react-tooltip";
|
|
10
|
+
import { VisuallyHidden } from "@sproutsocial/seeds-react-visually-hidden";
|
|
11
|
+
|
|
12
|
+
import { StorybookMenuToggleButton } from "../storybookUtilities/menu-storybook-components";
|
|
13
|
+
import { TEST_BOOKS } from "../__fixtures__/menu-test-data";
|
|
14
|
+
import {
|
|
15
|
+
MenuItem,
|
|
16
|
+
MenuLabel,
|
|
17
|
+
MenuToggleButton,
|
|
18
|
+
MenuHeader,
|
|
19
|
+
MenuContent,
|
|
20
|
+
ActionMenu,
|
|
21
|
+
SingleSelectMenu,
|
|
22
|
+
MultiSelectMenu,
|
|
23
|
+
MenuContentContext,
|
|
24
|
+
MenuGroup,
|
|
25
|
+
useMenu,
|
|
26
|
+
type TypeMenuItemProps,
|
|
27
|
+
} from "../index";
|
|
28
|
+
|
|
29
|
+
interface MenuItemStoryArgs extends TypeMenuItemProps {
|
|
30
|
+
"Include Switch Children"?: boolean;
|
|
31
|
+
text?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type Story = StoryObj<MenuItemStoryArgs>;
|
|
35
|
+
|
|
36
|
+
const meta: Meta<MenuItemStoryArgs> = {
|
|
37
|
+
title: "Under Development/Primitives/MenuItem",
|
|
38
|
+
component: MenuItem,
|
|
39
|
+
decorators: [
|
|
40
|
+
(Story) => (
|
|
41
|
+
<Box display="flex" flexDirection="column" gap="16px" maxWidth={300}>
|
|
42
|
+
<Story />
|
|
43
|
+
</Box>
|
|
44
|
+
),
|
|
45
|
+
],
|
|
46
|
+
argTypes: {
|
|
47
|
+
disabled: {
|
|
48
|
+
table: {
|
|
49
|
+
category: "MenuItem",
|
|
50
|
+
},
|
|
51
|
+
description: "disabled state",
|
|
52
|
+
control: { type: "boolean" },
|
|
53
|
+
},
|
|
54
|
+
"Include Switch Children": {
|
|
55
|
+
table: {
|
|
56
|
+
category: "MenuItem",
|
|
57
|
+
},
|
|
58
|
+
description: "Example Switch Child",
|
|
59
|
+
control: { type: "boolean" },
|
|
60
|
+
},
|
|
61
|
+
alignItems: {
|
|
62
|
+
table: {
|
|
63
|
+
category: "MenuItem",
|
|
64
|
+
},
|
|
65
|
+
description: "systemProps",
|
|
66
|
+
control: { type: "text" },
|
|
67
|
+
},
|
|
68
|
+
justifyContent: {
|
|
69
|
+
table: {
|
|
70
|
+
category: "MenuItem",
|
|
71
|
+
},
|
|
72
|
+
description: "systemProps",
|
|
73
|
+
control: { type: "text" },
|
|
74
|
+
},
|
|
75
|
+
display: {
|
|
76
|
+
table: {
|
|
77
|
+
category: "MenuItem",
|
|
78
|
+
},
|
|
79
|
+
description: "systemProps",
|
|
80
|
+
control: { type: "text" },
|
|
81
|
+
},
|
|
82
|
+
inputType: {
|
|
83
|
+
table: {
|
|
84
|
+
category: "MenuItem",
|
|
85
|
+
},
|
|
86
|
+
options: ["checkbox", "switch", "radio", undefined],
|
|
87
|
+
control: { type: "select" },
|
|
88
|
+
},
|
|
89
|
+
href: {
|
|
90
|
+
table: {
|
|
91
|
+
category: "MenuItem",
|
|
92
|
+
},
|
|
93
|
+
description: "href (makes an anchor tag)",
|
|
94
|
+
control: { type: "text" },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
args: {
|
|
98
|
+
disabled: false,
|
|
99
|
+
display: "flex",
|
|
100
|
+
alignItems: "center",
|
|
101
|
+
justifyContent: "start",
|
|
102
|
+
"Include Switch Children": false,
|
|
103
|
+
inputType: undefined,
|
|
104
|
+
href: undefined,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default meta;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* This custom onClick handler is used to log the event and the item name to the Action pane inside the Storybook UI.
|
|
112
|
+
*/
|
|
113
|
+
const handleOnClick = (e: React.MouseEvent<HTMLElement>, itemName: string) => {
|
|
114
|
+
action(itemName)(e);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Similar to how we stub events in SelectableMenuItem, the best way to implement the Switch in MenuItem is to stop the event from bubbling
|
|
119
|
+
* and allowing Downshift to handle it.
|
|
120
|
+
*/
|
|
121
|
+
const switchInputStub = (e: React.SyntheticEvent) => {
|
|
122
|
+
e.stopPropagation();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Story: Story for experimenting with different states of disabled and "switched" behavior with an action menu
|
|
127
|
+
*/
|
|
128
|
+
export const MenuWithItems: Story = {
|
|
129
|
+
render: function MenuWithItems(args) {
|
|
130
|
+
const [checked, setChecked] = useState({
|
|
131
|
+
compose: false,
|
|
132
|
+
view: false,
|
|
133
|
+
analyze: false,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const handleOnClick = (
|
|
137
|
+
e: React.MouseEvent<HTMLElement>,
|
|
138
|
+
itemName: keyof typeof checked
|
|
139
|
+
) => {
|
|
140
|
+
action(itemName)(e);
|
|
141
|
+
setChecked((prev) => ({
|
|
142
|
+
...prev,
|
|
143
|
+
[itemName]: !prev[itemName],
|
|
144
|
+
}));
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<ActionMenu
|
|
149
|
+
menuToggleElement={
|
|
150
|
+
<>
|
|
151
|
+
<VisuallyHidden>
|
|
152
|
+
<MenuLabel>Take an Action: </MenuLabel>
|
|
153
|
+
</VisuallyHidden>
|
|
154
|
+
<MenuToggleButton ml={10} appearance="secondary">
|
|
155
|
+
Launch Menu
|
|
156
|
+
</MenuToggleButton>
|
|
157
|
+
</>
|
|
158
|
+
}
|
|
159
|
+
>
|
|
160
|
+
<MenuHeader title="Sprout Menu" />
|
|
161
|
+
<MenuContent>
|
|
162
|
+
<MenuGroup id="group-1">
|
|
163
|
+
<MenuItem
|
|
164
|
+
onClick={(e) => handleOnClick(e, "compose")}
|
|
165
|
+
key={0}
|
|
166
|
+
id="compose"
|
|
167
|
+
disabled={args.disabled}
|
|
168
|
+
display={args.display}
|
|
169
|
+
alignItems={args.alignItems}
|
|
170
|
+
justifyContent={args.justifyContent}
|
|
171
|
+
>
|
|
172
|
+
{args["Include Switch Children"] && (
|
|
173
|
+
<Switch
|
|
174
|
+
onClick={switchInputStub}
|
|
175
|
+
checked={checked["compose"]}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
Compose Post
|
|
179
|
+
</MenuItem>
|
|
180
|
+
<MenuItem
|
|
181
|
+
onClick={(e) => handleOnClick(e, "view")}
|
|
182
|
+
key={1}
|
|
183
|
+
id="view"
|
|
184
|
+
disabled={args.disabled}
|
|
185
|
+
display={args.display}
|
|
186
|
+
alignItems={args.alignItems}
|
|
187
|
+
justifyContent={args.justifyContent}
|
|
188
|
+
>
|
|
189
|
+
{args["Include Switch Children"] && (
|
|
190
|
+
<Switch onClick={switchInputStub} checked={checked["view"]} />
|
|
191
|
+
)}
|
|
192
|
+
View Messages
|
|
193
|
+
</MenuItem>
|
|
194
|
+
<MenuItem
|
|
195
|
+
onClick={(e) => handleOnClick(e, "analyze")}
|
|
196
|
+
key={2}
|
|
197
|
+
id="analyze"
|
|
198
|
+
disabled={args.disabled}
|
|
199
|
+
display={args.display}
|
|
200
|
+
alignItems={args.alignItems}
|
|
201
|
+
justifyContent={args.justifyContent}
|
|
202
|
+
>
|
|
203
|
+
{args["Include Switch Children"] && (
|
|
204
|
+
<Switch
|
|
205
|
+
onClick={switchInputStub}
|
|
206
|
+
checked={checked["analyze"]}
|
|
207
|
+
/>
|
|
208
|
+
)}
|
|
209
|
+
Analyze Post
|
|
210
|
+
</MenuItem>
|
|
211
|
+
</MenuGroup>
|
|
212
|
+
</MenuContent>
|
|
213
|
+
</ActionMenu>
|
|
214
|
+
);
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Story: Story for experimenting with different visual children
|
|
220
|
+
*/
|
|
221
|
+
export const MenuWithIcons: Story = {
|
|
222
|
+
parameters: {
|
|
223
|
+
controls: { exclude: ["Include Switch Children"] },
|
|
224
|
+
},
|
|
225
|
+
args: {
|
|
226
|
+
justifyContent: "space-between",
|
|
227
|
+
},
|
|
228
|
+
render: function MenuWithIcons(args) {
|
|
229
|
+
return (
|
|
230
|
+
<ActionMenu
|
|
231
|
+
menuToggleElement={
|
|
232
|
+
<>
|
|
233
|
+
<VisuallyHidden>
|
|
234
|
+
<MenuLabel>Take an Action: </MenuLabel>
|
|
235
|
+
</VisuallyHidden>
|
|
236
|
+
<MenuToggleButton ml={10} appearance="secondary">
|
|
237
|
+
Launch Menu
|
|
238
|
+
</MenuToggleButton>
|
|
239
|
+
</>
|
|
240
|
+
}
|
|
241
|
+
>
|
|
242
|
+
<MenuHeader title="Sprout Menu" />
|
|
243
|
+
<MenuContent>
|
|
244
|
+
<MenuGroup id="group-1">
|
|
245
|
+
<MenuItem
|
|
246
|
+
onClick={(e) => handleOnClick(e, "settings")}
|
|
247
|
+
key={0}
|
|
248
|
+
id="settings"
|
|
249
|
+
disabled={args.disabled}
|
|
250
|
+
display={args.display}
|
|
251
|
+
alignItems={args.alignItems}
|
|
252
|
+
justifyContent={args.justifyContent}
|
|
253
|
+
>
|
|
254
|
+
<span>
|
|
255
|
+
<Icon name="gear" mr={350} />
|
|
256
|
+
Settings
|
|
257
|
+
</span>
|
|
258
|
+
<Badge
|
|
259
|
+
type="secondary"
|
|
260
|
+
badgeColor="red"
|
|
261
|
+
iconName="circle-exclamation"
|
|
262
|
+
>
|
|
263
|
+
Needs Attention
|
|
264
|
+
</Badge>
|
|
265
|
+
</MenuItem>
|
|
266
|
+
<MenuItem
|
|
267
|
+
onClick={(e) => handleOnClick(e, "messages")}
|
|
268
|
+
key={1}
|
|
269
|
+
id="messages"
|
|
270
|
+
disabled={args.disabled}
|
|
271
|
+
display={args.display}
|
|
272
|
+
alignItems={args.alignItems}
|
|
273
|
+
justifyContent={args.justifyContent}
|
|
274
|
+
>
|
|
275
|
+
<span>
|
|
276
|
+
<Icon name="envelope" mr={350} />
|
|
277
|
+
Messages
|
|
278
|
+
</span>
|
|
279
|
+
<Tooltip content="1 Message">
|
|
280
|
+
<Icon name="triangle-exclamation" />
|
|
281
|
+
</Tooltip>
|
|
282
|
+
</MenuItem>
|
|
283
|
+
<MenuItem
|
|
284
|
+
onClick={(e) => handleOnClick(e, "analytics")}
|
|
285
|
+
key={2}
|
|
286
|
+
id="analytics"
|
|
287
|
+
disabled={args.disabled}
|
|
288
|
+
display={args.display}
|
|
289
|
+
alignItems={args.alignItems}
|
|
290
|
+
justifyContent={args.justifyContent}
|
|
291
|
+
>
|
|
292
|
+
<span>
|
|
293
|
+
<Icon name="chart-arrow-up" mr={350} />
|
|
294
|
+
Analytics
|
|
295
|
+
</span>
|
|
296
|
+
<Icon name="link" />
|
|
297
|
+
</MenuItem>
|
|
298
|
+
</MenuGroup>
|
|
299
|
+
</MenuContent>
|
|
300
|
+
</ActionMenu>
|
|
301
|
+
);
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export const MenuItemsInListbox: Story = {
|
|
306
|
+
name: "Menu Items in Listbox",
|
|
307
|
+
decorators: [
|
|
308
|
+
(Story) => (
|
|
309
|
+
<Box display="flex" flexDirection="column" gap="16px" maxWidth={300}>
|
|
310
|
+
<Story />
|
|
311
|
+
</Box>
|
|
312
|
+
),
|
|
313
|
+
],
|
|
314
|
+
render: function MenuItemsInListbox(args) {
|
|
315
|
+
return (
|
|
316
|
+
<SingleSelectMenu
|
|
317
|
+
defaultIsOpen
|
|
318
|
+
initialIsOpen={false}
|
|
319
|
+
menuToggleElement={<StorybookMenuToggleButton />}
|
|
320
|
+
>
|
|
321
|
+
<MenuContent>
|
|
322
|
+
<MenuGroup id="books">
|
|
323
|
+
{TEST_BOOKS.map((book) => {
|
|
324
|
+
return (
|
|
325
|
+
<MenuItem
|
|
326
|
+
{...args}
|
|
327
|
+
key={book.id}
|
|
328
|
+
id={book.id}
|
|
329
|
+
inputType={args.inputType}
|
|
330
|
+
disabled={args.disabled}
|
|
331
|
+
display={args.display}
|
|
332
|
+
alignItems={args.alignItems}
|
|
333
|
+
justifyContent={args.justifyContent}
|
|
334
|
+
>
|
|
335
|
+
{book.title}
|
|
336
|
+
</MenuItem>
|
|
337
|
+
);
|
|
338
|
+
})}
|
|
339
|
+
</MenuGroup>
|
|
340
|
+
</MenuContent>
|
|
341
|
+
</SingleSelectMenu>
|
|
342
|
+
);
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
export const MenuItemsWithInputTypes: Story = {
|
|
347
|
+
name: "Menu Items With Input Types",
|
|
348
|
+
decorators: [
|
|
349
|
+
(Story) => (
|
|
350
|
+
<Box
|
|
351
|
+
display="flex"
|
|
352
|
+
justifyContent="space-between"
|
|
353
|
+
flexDirection="row"
|
|
354
|
+
width="100vw"
|
|
355
|
+
gap={300}
|
|
356
|
+
>
|
|
357
|
+
<Story />
|
|
358
|
+
</Box>
|
|
359
|
+
),
|
|
360
|
+
],
|
|
361
|
+
render: function MenuItemsWithInputTypes() {
|
|
362
|
+
return (
|
|
363
|
+
<>
|
|
364
|
+
<Box width="280px" mr={300}>
|
|
365
|
+
<SingleSelectMenu
|
|
366
|
+
isOpen
|
|
367
|
+
menuToggleElement={<MenuToggleButton>Open</MenuToggleButton>}
|
|
368
|
+
popoutProps={{ placement: "bottom-start" }}
|
|
369
|
+
>
|
|
370
|
+
<MenuContent>
|
|
371
|
+
<MenuGroup id="books">
|
|
372
|
+
{TEST_BOOKS.map((book) => {
|
|
373
|
+
return (
|
|
374
|
+
<MenuItem key={book.id} id={book.id}>
|
|
375
|
+
{book.title}
|
|
376
|
+
</MenuItem>
|
|
377
|
+
);
|
|
378
|
+
})}
|
|
379
|
+
</MenuGroup>
|
|
380
|
+
</MenuContent>
|
|
381
|
+
</SingleSelectMenu>
|
|
382
|
+
</Box>
|
|
383
|
+
<Box width="280px" mr={300}>
|
|
384
|
+
<MultiSelectMenu
|
|
385
|
+
isOpen
|
|
386
|
+
menuToggleElement={<MenuToggleButton>Open</MenuToggleButton>}
|
|
387
|
+
popoutProps={{ placement: "bottom-start" }}
|
|
388
|
+
>
|
|
389
|
+
<MenuContent>
|
|
390
|
+
<MenuGroup id="books">
|
|
391
|
+
{TEST_BOOKS.map((book) => {
|
|
392
|
+
return (
|
|
393
|
+
<MenuItem key={book.id} id={book.id} inputType="checkbox">
|
|
394
|
+
{book.title}
|
|
395
|
+
</MenuItem>
|
|
396
|
+
);
|
|
397
|
+
})}
|
|
398
|
+
</MenuGroup>
|
|
399
|
+
</MenuContent>
|
|
400
|
+
</MultiSelectMenu>
|
|
401
|
+
</Box>
|
|
402
|
+
<Box width="280px" mr={300}>
|
|
403
|
+
<SingleSelectMenu
|
|
404
|
+
isOpen
|
|
405
|
+
menuToggleElement={<MenuToggleButton>Open</MenuToggleButton>}
|
|
406
|
+
popoutProps={{ placement: "bottom-start" }}
|
|
407
|
+
>
|
|
408
|
+
<MenuContent>
|
|
409
|
+
<MenuGroup id="books">
|
|
410
|
+
{TEST_BOOKS.map((book) => {
|
|
411
|
+
return (
|
|
412
|
+
<MenuItem
|
|
413
|
+
key={book.id}
|
|
414
|
+
id={book.id}
|
|
415
|
+
inputType="radio"
|
|
416
|
+
inputLabelId={"test-label-" + book.id}
|
|
417
|
+
>
|
|
418
|
+
<label id={"test-label-" + book.id}></label>
|
|
419
|
+
{book.title}
|
|
420
|
+
</MenuItem>
|
|
421
|
+
);
|
|
422
|
+
})}
|
|
423
|
+
</MenuGroup>
|
|
424
|
+
</MenuContent>
|
|
425
|
+
</SingleSelectMenu>
|
|
426
|
+
</Box>
|
|
427
|
+
<Box width="280px" mr={300}>
|
|
428
|
+
<MultiSelectMenu
|
|
429
|
+
isOpen
|
|
430
|
+
menuToggleElement={<MenuToggleButton>Open</MenuToggleButton>}
|
|
431
|
+
popoutProps={{ placement: "bottom-start" }}
|
|
432
|
+
>
|
|
433
|
+
<MenuContent>
|
|
434
|
+
<MenuGroup id="books">
|
|
435
|
+
{TEST_BOOKS.map((book) => {
|
|
436
|
+
return (
|
|
437
|
+
<MenuItem key={book.id} id={book.id} inputType="switch">
|
|
438
|
+
{book.title}
|
|
439
|
+
</MenuItem>
|
|
440
|
+
);
|
|
441
|
+
})}
|
|
442
|
+
</MenuGroup>
|
|
443
|
+
</MenuContent>
|
|
444
|
+
</MultiSelectMenu>
|
|
445
|
+
</Box>
|
|
446
|
+
</>
|
|
447
|
+
);
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Story: Story for experimenting with different states of disabled and "switched" behavior with an action menu
|
|
453
|
+
*/
|
|
454
|
+
export const MenuItemOnly: Story = {
|
|
455
|
+
args: {
|
|
456
|
+
text: "Click Me",
|
|
457
|
+
},
|
|
458
|
+
parameters: {
|
|
459
|
+
controls: { exclude: ["Include Switch Children"] },
|
|
460
|
+
},
|
|
461
|
+
render: function MenuItemOnly(args) {
|
|
462
|
+
const handleOnClick = (e: React.MouseEvent<HTMLElement>, itemName) => {
|
|
463
|
+
action(itemName)(e);
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
return (
|
|
467
|
+
<MenuItem
|
|
468
|
+
{...args}
|
|
469
|
+
onClick={(e) => handleOnClick(e, "compose")}
|
|
470
|
+
key={0}
|
|
471
|
+
id="compose"
|
|
472
|
+
>
|
|
473
|
+
{args.text}
|
|
474
|
+
</MenuItem>
|
|
475
|
+
);
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Story: Story for experimenting with the selectable menu item
|
|
481
|
+
*/
|
|
482
|
+
export const ListboxMenuItemOnly: Story = {
|
|
483
|
+
name: "Selectable Menu Item Only",
|
|
484
|
+
render: function MenuItemSelectableOnly(args) {
|
|
485
|
+
const item = {
|
|
486
|
+
id: "any",
|
|
487
|
+
children: "Select Me!",
|
|
488
|
+
index: 0,
|
|
489
|
+
};
|
|
490
|
+
const menuContext = useMenu({
|
|
491
|
+
items: [item],
|
|
492
|
+
itemsMap: { item },
|
|
493
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
494
|
+
// @ts-expect-error - mocked function does not match real type
|
|
495
|
+
getItemProps: (props) => ({ role: "option", ...props }),
|
|
496
|
+
isListbox: true,
|
|
497
|
+
// ...context,
|
|
498
|
+
});
|
|
499
|
+
return (
|
|
500
|
+
<MenuContentContext.Provider value={menuContext}>
|
|
501
|
+
<MenuItem {...args} key="any" id="any">
|
|
502
|
+
Select Me!
|
|
503
|
+
</MenuItem>
|
|
504
|
+
</MenuContentContext.Provider>
|
|
505
|
+
);
|
|
506
|
+
},
|
|
507
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import { useMenuContentContext } from "../MenuContext";
|
|
2
|
+
import type { TypeInternalItemProps } from "../types";
|
|
3
3
|
import type { TypeMenuItemProps } from "./MenuItemTypes";
|
|
4
4
|
import { MenuItemContainer, StyledMenuItem } from "./styles";
|
|
5
5
|
import { useOptionProps } from "./useOptionProps";
|
|
@@ -17,42 +17,52 @@ export const MenuItem = ({
|
|
|
17
17
|
onClick,
|
|
18
18
|
onKeyDown,
|
|
19
19
|
disabled = false,
|
|
20
|
-
|
|
20
|
+
active,
|
|
21
|
+
innerRef,
|
|
21
22
|
href,
|
|
22
23
|
color,
|
|
23
24
|
...props
|
|
24
25
|
}: TypeMenuItemProps) => {
|
|
25
|
-
const {
|
|
26
|
-
|
|
26
|
+
const {
|
|
27
|
+
getItemProps,
|
|
28
|
+
itemsMap,
|
|
29
|
+
highlightedIndex,
|
|
30
|
+
isListbox,
|
|
31
|
+
hiddenItemsMap = {},
|
|
32
|
+
} = useMenuContentContext();
|
|
27
33
|
const { ...optionProps } = useOptionProps({
|
|
28
34
|
id,
|
|
29
35
|
children,
|
|
30
36
|
...props,
|
|
31
37
|
});
|
|
32
|
-
const item = (itemsMap[id] || { index: 0, id }) as
|
|
38
|
+
const item = (itemsMap[id] || { index: 0, id }) as TypeInternalItemProps;
|
|
33
39
|
|
|
34
40
|
const highlighted = highlightedIndex === item?.index;
|
|
35
41
|
|
|
36
|
-
return item.
|
|
42
|
+
return hiddenItemsMap[item.id] ? (
|
|
37
43
|
<></>
|
|
38
44
|
) : (
|
|
39
45
|
<MenuItemContainer role="none">
|
|
40
46
|
<StyledMenuItem
|
|
41
47
|
id={id}
|
|
42
48
|
$highlighted={highlighted}
|
|
49
|
+
$active={active}
|
|
43
50
|
{...getItemProps?.({
|
|
44
51
|
...(isListbox ? {} : { role: "menuitem" }),
|
|
45
52
|
item,
|
|
46
53
|
index: item.index,
|
|
47
|
-
//
|
|
54
|
+
// The disabled prop no longer supported in downshift. Use isItemDisabled instead.
|
|
48
55
|
// disabled,
|
|
49
56
|
onClick,
|
|
50
57
|
onKeyDown,
|
|
58
|
+
ref: innerRef,
|
|
51
59
|
})}
|
|
52
60
|
// use anchor tag if href is provided and button for menuitem
|
|
53
61
|
as={isListbox ? undefined : href ? "a" : "button"}
|
|
54
62
|
href={href}
|
|
55
63
|
children={children}
|
|
64
|
+
// Keyboard nav uses the arrow keys, not tab.
|
|
65
|
+
tabIndex={-1}
|
|
56
66
|
{...optionProps}
|
|
57
67
|
{...props}
|
|
58
68
|
/>
|