@sproutsocial/seeds-react-menu 0.4.1 → 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 +32 -0
- package/dist/esm/index.js +1553 -423
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +303 -122
- package/dist/index.d.ts +303 -122
- package/dist/index.js +1561 -405
- 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 +30 -26
- package/src/ActionMenu/ActionMenuTypes.ts +13 -8
- package/src/ActionMenu/__tests__/ActionMenu.test.tsx +17 -30
- package/src/ActionMenu/__tests__/ActionMenu.typetest.tsx +32 -3
- package/src/Autocomplete/Autocomplete.stories.tsx +61 -2
- package/src/Autocomplete/Autocomplete.tsx +15 -74
- package/src/Autocomplete/AutocompleteInput.tsx +18 -27
- package/src/Autocomplete/AutocompleteTokenInput.tsx +50 -0
- package/src/Autocomplete/AutocompleteTypes.ts +27 -11
- 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 +116 -3
- package/src/MenuContent/MenuContent.stories.tsx +164 -0
- package/src/MenuContent/MenuContent.tsx +35 -8
- package/src/MenuContent/MenuContentTypes.ts +7 -5
- package/src/MenuContent/__tests__/MenuContent.typetest.tsx +18 -2
- 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 +21 -21
- package/src/MenuGroup/MenuGroup.stories.tsx +248 -0
- package/src/MenuGroup/MenuGroup.tsx +9 -7
- package/src/MenuGroup/MenuGroupTypes.ts +8 -6
- package/src/MenuGroup/__tests__/MenuGroup.test.tsx +6 -6
- package/src/MenuGroup/__tests__/MenuGroup.typetest.tsx +23 -4
- 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 -9
- 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 +44 -67
- package/src/MultiSelectMenu/MultiSelectMenuTypes.ts +9 -5
- package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +46 -60
- package/src/MultiSelectMenu/__tests__/MultiSelectMenu.typetest.tsx +29 -2
- 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 +29 -25
- package/src/SingleSelectMenu/SingleSelectMenuTypes.ts +9 -5
- package/src/SingleSelectMenu/__tests__/SingleSelectMenu.typetest.tsx +32 -2
- 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/__tests__/Menu.test.tsx +38 -0
- package/src/hooks/useDownshiftDefaults.tsx +107 -0
- package/src/hooks/useItemFiltering.tsx +66 -0
- package/src/hooks/useMenuChildren.tsx +279 -0
- package/src/index.ts +7 -1
- 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 -5
- package/src/utils/downshiftDefaults.ts +9 -0
- package/src/utils/getMultiSelectOverrides.ts +21 -0
- package/src/utils/reduceReducers.ts +19 -0
- package/src/hooks/useItemsFromChildren.tsx +0 -131
- package/src/utils/itemToString.ts +0 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sproutsocial/seeds-react-menu",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Seeds React Menu",
|
|
5
5
|
"author": "Sprout Social, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,10 +28,13 @@
|
|
|
28
28
|
"@sproutsocial/seeds-react-switch": "^1.2.0",
|
|
29
29
|
"@sproutsocial/seeds-react-system-props": "^3.0.1",
|
|
30
30
|
"@sproutsocial/seeds-react-theme": "^2.2.0",
|
|
31
|
+
"@sproutsocial/seeds-react-token-input": "^1.0.1",
|
|
32
|
+
"@sproutsocial/seeds-react-utilities": "^4.1.0",
|
|
31
33
|
"downshift": "9.0.4",
|
|
32
34
|
"motion": "^11.11.17"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
37
|
+
"@sproutsocial/seeds-react-tooltip": "^1.0.1",
|
|
35
38
|
"@sproutsocial/eslint-config-seeds": "*",
|
|
36
39
|
"@sproutsocial/seeds-react-testing-library": "*",
|
|
37
40
|
"@sproutsocial/seeds-testing": "*",
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
+
import React from "react";
|
|
3
|
+
import styled from "styled-components";
|
|
4
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
5
|
+
import { action } from "@storybook/addon-actions";
|
|
6
|
+
|
|
7
|
+
import { Box } from "@sproutsocial/seeds-react-box";
|
|
8
|
+
import { Icon, type TypeIconName } from "@sproutsocial/seeds-react-icon";
|
|
9
|
+
import { Switch } from "@sproutsocial/seeds-react-switch";
|
|
10
|
+
import { Label } from "@sproutsocial/seeds-react-label";
|
|
11
|
+
import { Link } from "@sproutsocial/seeds-react-link";
|
|
12
|
+
import { StorybookMenuToggleButton } from "../storybookUtilities/menu-storybook-components";
|
|
13
|
+
import {
|
|
14
|
+
ActionMenu,
|
|
15
|
+
MenuContent,
|
|
16
|
+
MenuFooter,
|
|
17
|
+
MenuGroup,
|
|
18
|
+
MenuHeader,
|
|
19
|
+
MenuItem,
|
|
20
|
+
MenuSearchInput,
|
|
21
|
+
type TypeActionMenuProps,
|
|
22
|
+
} from "../index";
|
|
23
|
+
|
|
24
|
+
const StyledList = styled.ul`
|
|
25
|
+
list-style: none;
|
|
26
|
+
padding: 0px;
|
|
27
|
+
margin: 0px;
|
|
28
|
+
width: 240px;
|
|
29
|
+
|
|
30
|
+
li {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: row-reverse;
|
|
33
|
+
padding: 4px 0px 4px 0px;
|
|
34
|
+
justify-content: left;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
type ActionMenuStoryArgs = TypeActionMenuProps & {
|
|
38
|
+
"Trigger Text"?: string;
|
|
39
|
+
"Trigger Appearance"?: "primary" | "secondary";
|
|
40
|
+
icons: Array<string>;
|
|
41
|
+
};
|
|
42
|
+
type Story = StoryObj<ActionMenuStoryArgs>;
|
|
43
|
+
|
|
44
|
+
const meta: Meta<ActionMenuStoryArgs> = {
|
|
45
|
+
title: "Under Development/ActionMenu",
|
|
46
|
+
component: ActionMenu,
|
|
47
|
+
argTypes: {
|
|
48
|
+
"Trigger Appearance": {
|
|
49
|
+
options: ["primary", "secondary"],
|
|
50
|
+
control: { type: "select" },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
decorators: [
|
|
54
|
+
(Story) => (
|
|
55
|
+
<Box display="flex" gap="16px" flexDirection="column" maxWidth={300}>
|
|
56
|
+
<Story />
|
|
57
|
+
</Box>
|
|
58
|
+
),
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default meta;
|
|
63
|
+
|
|
64
|
+
const handleOnClick = (e: React.MouseEvent<HTMLElement>, itemName: string) => {
|
|
65
|
+
action(`${itemName}`)(e);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const ActionMenuSimple: Story = {
|
|
69
|
+
name: "Simple Action Menu",
|
|
70
|
+
args: {
|
|
71
|
+
"Trigger Text": "ActionMenu",
|
|
72
|
+
"Trigger Appearance": "secondary",
|
|
73
|
+
},
|
|
74
|
+
render: (args) => {
|
|
75
|
+
return (
|
|
76
|
+
<ActionMenu
|
|
77
|
+
{...args}
|
|
78
|
+
menuToggleElement={
|
|
79
|
+
<StorybookMenuToggleButton
|
|
80
|
+
buttonText={args["Trigger Text"]}
|
|
81
|
+
buttonProps={{ appearance: args["Trigger Appearance"] }}
|
|
82
|
+
/>
|
|
83
|
+
}
|
|
84
|
+
>
|
|
85
|
+
<MenuContent>
|
|
86
|
+
<MenuGroup id="actions">
|
|
87
|
+
<MenuItem onClick={(e) => handleOnClick(e, "Compose")} id="Compose">
|
|
88
|
+
Compose
|
|
89
|
+
</MenuItem>
|
|
90
|
+
<MenuItem
|
|
91
|
+
onClick={(e) => handleOnClick(e, "View Messages")}
|
|
92
|
+
id="View Messages"
|
|
93
|
+
>
|
|
94
|
+
View Messages
|
|
95
|
+
</MenuItem>
|
|
96
|
+
<MenuItem
|
|
97
|
+
onClick={(e) => handleOnClick(e, "Analyze post")}
|
|
98
|
+
id="Analyze post"
|
|
99
|
+
>
|
|
100
|
+
Analyze post
|
|
101
|
+
</MenuItem>
|
|
102
|
+
<MenuItem
|
|
103
|
+
href="https://sproutsocial.com"
|
|
104
|
+
target="_blank"
|
|
105
|
+
id="settings"
|
|
106
|
+
>
|
|
107
|
+
This Opens a Link
|
|
108
|
+
</MenuItem>
|
|
109
|
+
</MenuGroup>
|
|
110
|
+
</MenuContent>
|
|
111
|
+
</ActionMenu>
|
|
112
|
+
);
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const itemsAsProp = [
|
|
117
|
+
{
|
|
118
|
+
id: "compose",
|
|
119
|
+
onClick: (e) => handleOnClick(e, "Compose"),
|
|
120
|
+
children: "Compose",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "view-messages",
|
|
124
|
+
onClick: (e) => handleOnClick(e, "View Messages"),
|
|
125
|
+
children: "View Messages",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "analyze-post",
|
|
129
|
+
onClick: (e) => handleOnClick(e, "Analyze post"),
|
|
130
|
+
children: "Analyze post",
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
export const ActionMenuWithItemsAsProps: Story = {
|
|
134
|
+
name: "With Items as Props",
|
|
135
|
+
args: {
|
|
136
|
+
"Trigger Text": "ActionMenu",
|
|
137
|
+
"Trigger Appearance": "secondary",
|
|
138
|
+
},
|
|
139
|
+
render: (args) => {
|
|
140
|
+
return (
|
|
141
|
+
<ActionMenu
|
|
142
|
+
{...args}
|
|
143
|
+
menuToggleElement={
|
|
144
|
+
<StorybookMenuToggleButton
|
|
145
|
+
buttonText={args["Trigger Text"]}
|
|
146
|
+
buttonProps={{ appearance: args["Trigger Appearance"] }}
|
|
147
|
+
/>
|
|
148
|
+
}
|
|
149
|
+
menuItems={itemsAsProp}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const ActionMenuWithIcons: Story = {
|
|
156
|
+
name: "Using Icons",
|
|
157
|
+
args: {
|
|
158
|
+
"Trigger Text": "ActionMenu",
|
|
159
|
+
icons: [
|
|
160
|
+
"pencil-to-square-outline",
|
|
161
|
+
"envelope-outline",
|
|
162
|
+
"chart-area-outline",
|
|
163
|
+
],
|
|
164
|
+
"Trigger Appearance": "secondary",
|
|
165
|
+
},
|
|
166
|
+
render: (args) => {
|
|
167
|
+
return (
|
|
168
|
+
<ActionMenu
|
|
169
|
+
{...args}
|
|
170
|
+
menuToggleElement={
|
|
171
|
+
<StorybookMenuToggleButton
|
|
172
|
+
buttonText={args["Trigger Text"]}
|
|
173
|
+
buttonProps={{ appearance: args["Trigger Appearance"] }}
|
|
174
|
+
/>
|
|
175
|
+
}
|
|
176
|
+
>
|
|
177
|
+
<MenuContent>
|
|
178
|
+
<MenuGroup id="actions">
|
|
179
|
+
<MenuItem
|
|
180
|
+
onClick={(e) => handleOnClick(e, "Compose")}
|
|
181
|
+
key={0}
|
|
182
|
+
id="Compose"
|
|
183
|
+
>
|
|
184
|
+
<Box
|
|
185
|
+
display="flex"
|
|
186
|
+
alignItems="center"
|
|
187
|
+
justifyContent="space-between"
|
|
188
|
+
>
|
|
189
|
+
Compose
|
|
190
|
+
<Icon name={args["icons"][0] as TypeIconName} />
|
|
191
|
+
</Box>
|
|
192
|
+
</MenuItem>
|
|
193
|
+
<MenuItem
|
|
194
|
+
onClick={(e) => handleOnClick(e, "View Messages")}
|
|
195
|
+
key={1}
|
|
196
|
+
id="View Messages"
|
|
197
|
+
>
|
|
198
|
+
<Box
|
|
199
|
+
display="flex"
|
|
200
|
+
alignItems="center"
|
|
201
|
+
justifyContent="space-between"
|
|
202
|
+
>
|
|
203
|
+
View Messages
|
|
204
|
+
<Icon name={args["icons"][1] as TypeIconName} />
|
|
205
|
+
</Box>
|
|
206
|
+
</MenuItem>
|
|
207
|
+
<MenuItem
|
|
208
|
+
onClick={(e) => handleOnClick(e, "Analyze post")}
|
|
209
|
+
key={2}
|
|
210
|
+
id="Analyze post"
|
|
211
|
+
>
|
|
212
|
+
<Box
|
|
213
|
+
display="flex"
|
|
214
|
+
alignItems="center"
|
|
215
|
+
justifyContent="space-between"
|
|
216
|
+
>
|
|
217
|
+
Analyze Post
|
|
218
|
+
<Icon name={args["icons"][2] as TypeIconName} />
|
|
219
|
+
</Box>
|
|
220
|
+
</MenuItem>
|
|
221
|
+
</MenuGroup>
|
|
222
|
+
<MenuGroup id="settings">
|
|
223
|
+
<MenuItem
|
|
224
|
+
onClick={(e) => handleOnClick(e, "Settings")}
|
|
225
|
+
key={3}
|
|
226
|
+
id="Settings"
|
|
227
|
+
>
|
|
228
|
+
<Box
|
|
229
|
+
display="flex"
|
|
230
|
+
alignItems="center"
|
|
231
|
+
justifyContent="space-between"
|
|
232
|
+
>
|
|
233
|
+
Settings
|
|
234
|
+
<Icon name="gear-outline" />
|
|
235
|
+
</Box>
|
|
236
|
+
</MenuItem>
|
|
237
|
+
</MenuGroup>
|
|
238
|
+
</MenuContent>
|
|
239
|
+
</ActionMenu>
|
|
240
|
+
);
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const ActionMenuWithHeaderFooter: Story = {
|
|
245
|
+
name: "With Header & Footer",
|
|
246
|
+
args: {
|
|
247
|
+
"Trigger Text": "ActionMenu",
|
|
248
|
+
"Trigger Appearance": "secondary",
|
|
249
|
+
},
|
|
250
|
+
render: (args) => {
|
|
251
|
+
const [highContrast, setHighContrast] = React.useState(false);
|
|
252
|
+
const [darkMode, setDarkMode] = React.useState(false);
|
|
253
|
+
return (
|
|
254
|
+
<ActionMenu
|
|
255
|
+
{...args}
|
|
256
|
+
menuToggleElement={
|
|
257
|
+
<StorybookMenuToggleButton
|
|
258
|
+
buttonText={args["Trigger Text"]}
|
|
259
|
+
buttonProps={{ appearance: args["Trigger Appearance"] }}
|
|
260
|
+
/>
|
|
261
|
+
}
|
|
262
|
+
>
|
|
263
|
+
<MenuHeader title="Select one of the following" />
|
|
264
|
+
<MenuContent>
|
|
265
|
+
<MenuGroup id="actions">
|
|
266
|
+
<MenuItem
|
|
267
|
+
onClick={(e) => handleOnClick(e, "Compose")}
|
|
268
|
+
key={0}
|
|
269
|
+
id="Compose"
|
|
270
|
+
>
|
|
271
|
+
Compose
|
|
272
|
+
</MenuItem>
|
|
273
|
+
<MenuItem
|
|
274
|
+
onClick={(e) => handleOnClick(e, "View Messages")}
|
|
275
|
+
key={1}
|
|
276
|
+
id="View Messages"
|
|
277
|
+
>
|
|
278
|
+
View Messages
|
|
279
|
+
</MenuItem>
|
|
280
|
+
<MenuItem
|
|
281
|
+
onClick={(e) => handleOnClick(e, "Analyze post")}
|
|
282
|
+
key={2}
|
|
283
|
+
id="Analyze post"
|
|
284
|
+
>
|
|
285
|
+
Analyze post
|
|
286
|
+
</MenuItem>
|
|
287
|
+
</MenuGroup>
|
|
288
|
+
</MenuContent>
|
|
289
|
+
<MenuFooter>
|
|
290
|
+
<StyledList>
|
|
291
|
+
<li>
|
|
292
|
+
<Label htmlFor="menuSwitch1">High contrast</Label>
|
|
293
|
+
<Switch
|
|
294
|
+
mr={300}
|
|
295
|
+
id="menuSwitch1"
|
|
296
|
+
checked={highContrast}
|
|
297
|
+
onClick={() => setHighContrast(!highContrast)}
|
|
298
|
+
/>
|
|
299
|
+
</li>
|
|
300
|
+
<li>
|
|
301
|
+
<Label htmlFor="menuSwitch2">Dark mode</Label>
|
|
302
|
+
<Switch
|
|
303
|
+
mr={300}
|
|
304
|
+
id="menuSwitch2"
|
|
305
|
+
checked={darkMode}
|
|
306
|
+
onClick={() => setDarkMode(!darkMode)}
|
|
307
|
+
/>
|
|
308
|
+
</li>
|
|
309
|
+
<li>
|
|
310
|
+
<Link href="#" fontSize={200}>
|
|
311
|
+
Manage settings
|
|
312
|
+
</Link>
|
|
313
|
+
</li>
|
|
314
|
+
</StyledList>
|
|
315
|
+
</MenuFooter>
|
|
316
|
+
</ActionMenu>
|
|
317
|
+
);
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export const ActionMenuControlled: Story = {
|
|
322
|
+
name: "Controlled State",
|
|
323
|
+
args: {
|
|
324
|
+
"Trigger Text": "ActionMenu",
|
|
325
|
+
"Trigger Appearance": "secondary",
|
|
326
|
+
},
|
|
327
|
+
render: (args) => {
|
|
328
|
+
const [isOpen, setIsOpen] = React.useState<boolean>(false);
|
|
329
|
+
return (
|
|
330
|
+
<ActionMenu
|
|
331
|
+
{...args}
|
|
332
|
+
isOpen={isOpen}
|
|
333
|
+
onIsOpenChange={({ isOpen }) => setIsOpen(isOpen)}
|
|
334
|
+
menuToggleElement={
|
|
335
|
+
<StorybookMenuToggleButton
|
|
336
|
+
buttonText={args["Trigger Text"]}
|
|
337
|
+
buttonProps={{ appearance: args["Trigger Appearance"] }}
|
|
338
|
+
/>
|
|
339
|
+
}
|
|
340
|
+
>
|
|
341
|
+
<MenuContent>
|
|
342
|
+
<MenuGroup id="actions">
|
|
343
|
+
<MenuItem
|
|
344
|
+
onClick={(e) => handleOnClick(e, "Compose")}
|
|
345
|
+
key={0}
|
|
346
|
+
id="Compose"
|
|
347
|
+
>
|
|
348
|
+
Compose
|
|
349
|
+
</MenuItem>
|
|
350
|
+
<MenuItem
|
|
351
|
+
onClick={(e) => handleOnClick(e, "View Messages")}
|
|
352
|
+
key={1}
|
|
353
|
+
id="View Messages"
|
|
354
|
+
>
|
|
355
|
+
View Messages
|
|
356
|
+
</MenuItem>
|
|
357
|
+
<MenuItem
|
|
358
|
+
onClick={(e) => handleOnClick(e, "Analyze post")}
|
|
359
|
+
key={2}
|
|
360
|
+
id="Analyze post"
|
|
361
|
+
>
|
|
362
|
+
Analyze post
|
|
363
|
+
</MenuItem>
|
|
364
|
+
</MenuGroup>
|
|
365
|
+
</MenuContent>
|
|
366
|
+
</ActionMenu>
|
|
367
|
+
);
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
export const ActionMenuWithSearch: Story = {
|
|
372
|
+
name: "Action Menu with Search",
|
|
373
|
+
args: {
|
|
374
|
+
"Trigger Text": "ActionMenu",
|
|
375
|
+
"Trigger Appearance": "secondary",
|
|
376
|
+
},
|
|
377
|
+
render: (args) => {
|
|
378
|
+
return (
|
|
379
|
+
<ActionMenu
|
|
380
|
+
itemToString={(item) => `${item?.menuItemProps?.children || ""}`}
|
|
381
|
+
menuToggleElement={
|
|
382
|
+
<StorybookMenuToggleButton
|
|
383
|
+
buttonText={args["Trigger Text"]}
|
|
384
|
+
buttonProps={{ appearance: args["Trigger Appearance"] }}
|
|
385
|
+
/>
|
|
386
|
+
}
|
|
387
|
+
onStateChange={(changes) => action("onStateChange")(changes)}
|
|
388
|
+
{...(args as object)}
|
|
389
|
+
>
|
|
390
|
+
<MenuHeader>
|
|
391
|
+
<MenuSearchInput
|
|
392
|
+
id="single-select-search"
|
|
393
|
+
name="search"
|
|
394
|
+
type="search"
|
|
395
|
+
aria-label="Search Menu"
|
|
396
|
+
/>
|
|
397
|
+
</MenuHeader>
|
|
398
|
+
<MenuContent>
|
|
399
|
+
<MenuGroup id="actions">
|
|
400
|
+
<MenuItem
|
|
401
|
+
onClick={(e) => handleOnClick(e, "Compose")}
|
|
402
|
+
key={0}
|
|
403
|
+
id="Compose"
|
|
404
|
+
>
|
|
405
|
+
Compose
|
|
406
|
+
</MenuItem>
|
|
407
|
+
<MenuItem
|
|
408
|
+
onClick={(e) => handleOnClick(e, "View Messages")}
|
|
409
|
+
key={1}
|
|
410
|
+
id="View Messages"
|
|
411
|
+
>
|
|
412
|
+
View Messages
|
|
413
|
+
</MenuItem>
|
|
414
|
+
<MenuItem
|
|
415
|
+
onClick={(e) => handleOnClick(e, "Analyze post")}
|
|
416
|
+
key={2}
|
|
417
|
+
id="Analyze post"
|
|
418
|
+
>
|
|
419
|
+
Analyze post
|
|
420
|
+
</MenuItem>
|
|
421
|
+
</MenuGroup>
|
|
422
|
+
</MenuContent>
|
|
423
|
+
</ActionMenu>
|
|
424
|
+
);
|
|
425
|
+
},
|
|
426
|
+
};
|
|
@@ -4,20 +4,20 @@ import {
|
|
|
4
4
|
type UseSelectState,
|
|
5
5
|
type UseSelectStateChange,
|
|
6
6
|
} from "downshift";
|
|
7
|
-
import type {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "../
|
|
13
|
-
import {
|
|
14
|
-
|
|
7
|
+
import type { TypeInternalItemProps } from "../types";
|
|
8
|
+
import { useMenuChildren } from "../hooks/useMenuChildren";
|
|
9
|
+
import { useDownshiftDefaults } from "../hooks/useDownshiftDefaults";
|
|
10
|
+
import { reduceReducers } from "../utils/reduceReducers";
|
|
11
|
+
import { itemToString } from "../utils/downshiftDefaults";
|
|
12
|
+
import { MenuRoot, type TypeMenuRootProps } from "../MenuRoot";
|
|
13
|
+
import type { TypeMenuItemProps } from "../MenuItem";
|
|
14
|
+
|
|
15
15
|
import type { TypeActionMenuProps } from "./ActionMenuTypes";
|
|
16
16
|
|
|
17
17
|
// Manually fire the onClick handler when Enter or Space is pressed
|
|
18
18
|
const handleStateChange = (
|
|
19
|
-
changes: UseSelectStateChange<
|
|
20
|
-
currentSelectedItem: UseSelectState<
|
|
19
|
+
changes: UseSelectStateChange<TypeInternalItemProps>,
|
|
20
|
+
currentSelectedItem: UseSelectState<TypeInternalItemProps>["selectedItem"]
|
|
21
21
|
) => {
|
|
22
22
|
// if not Enter or Space key, do nothing
|
|
23
23
|
if (
|
|
@@ -81,30 +81,33 @@ const handleStateChange = (
|
|
|
81
81
|
* @see {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/SingleSelectMenu | SingleSelectMenu}
|
|
82
82
|
* @see {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/MultiSelectMenu | MultiSelectMenu}
|
|
83
83
|
*/
|
|
84
|
-
export const ActionMenu = ({
|
|
85
|
-
children,
|
|
84
|
+
export const ActionMenu = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
85
|
+
children: childrenProp,
|
|
86
|
+
menuItems,
|
|
87
|
+
MenuItemComponent,
|
|
86
88
|
stateReducer: externalStateReducer,
|
|
87
89
|
...useSelectProps
|
|
88
|
-
}: TypeActionMenuProps) => {
|
|
89
|
-
const { allMenuItems, menuItemsMap } =
|
|
90
|
+
}: TypeActionMenuProps<I>) => {
|
|
91
|
+
const { allMenuItems, menuItemsMap, children } = useMenuChildren({
|
|
92
|
+
children: childrenProp,
|
|
93
|
+
menuItems,
|
|
94
|
+
MenuItemComponent,
|
|
95
|
+
});
|
|
96
|
+
const { defaultDownshiftProps, ...restDefaults } = useDownshiftDefaults({
|
|
97
|
+
isCombobox: false,
|
|
98
|
+
});
|
|
90
99
|
const [currentSelectedItem, setCurrentSelectedItem] =
|
|
91
|
-
React.useState<UseSelectState<
|
|
100
|
+
React.useState<UseSelectState<TypeInternalItemProps>["selectedItem"]>(null);
|
|
92
101
|
|
|
93
102
|
const { isOpen, ...useSelectReturnProps } = useSelect({
|
|
94
|
-
...
|
|
103
|
+
...defaultDownshiftProps.select,
|
|
95
104
|
items: allMenuItems,
|
|
96
105
|
itemToString,
|
|
97
106
|
onStateChange: (changes) => handleStateChange(changes, currentSelectedItem),
|
|
98
|
-
stateReducer: (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
? externalStateReducer(state, {
|
|
103
|
-
...actionAndChanges,
|
|
104
|
-
changes: newState,
|
|
105
|
-
})
|
|
106
|
-
: newState;
|
|
107
|
-
},
|
|
107
|
+
stateReducer: reduceReducers(
|
|
108
|
+
defaultDownshiftProps.select.stateReducer,
|
|
109
|
+
externalStateReducer
|
|
110
|
+
),
|
|
108
111
|
...useSelectProps,
|
|
109
112
|
});
|
|
110
113
|
|
|
@@ -122,6 +125,7 @@ export const ActionMenu = ({
|
|
|
122
125
|
isOpen,
|
|
123
126
|
...useSelectProps,
|
|
124
127
|
...useSelectReturnProps,
|
|
128
|
+
...restDefaults,
|
|
125
129
|
}}
|
|
126
130
|
>
|
|
127
131
|
{children}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type TypeMenuRootOwnProps } from "../MenuRoot";
|
|
2
2
|
import type { TypeDownshiftSelectProps } from "../MenuContext";
|
|
3
|
+
import type { TypeMenuRootProps } from "../MenuRoot";
|
|
4
|
+
import type { TypeMenuItemProps } from "../MenuItem";
|
|
5
|
+
import type { TypeChildrenOrItems } from "../types";
|
|
3
6
|
|
|
4
7
|
type TypeInvalidMenuProps =
|
|
5
|
-
|
|
6
|
-
| "defaultSelectedItem"
|
|
7
|
-
|
|
8
|
-
export interface
|
|
8
|
+
// There is no selection state on action menus.
|
|
9
|
+
"initialSelectedItem" | "defaultSelectedItem" | "selectedItem";
|
|
10
|
+
|
|
11
|
+
export interface TypeActionMenuBaseProps
|
|
9
12
|
extends TypeMenuRootOwnProps,
|
|
10
|
-
Omit<TypeDownshiftSelectProps, TypeInvalidMenuProps> {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
Omit<TypeDownshiftSelectProps, TypeInvalidMenuProps> {}
|
|
14
|
+
|
|
15
|
+
export type TypeActionMenuProps<
|
|
16
|
+
I extends TypeMenuItemProps = TypeMenuItemProps
|
|
17
|
+
> = TypeActionMenuBaseProps & TypeChildrenOrItems<I>;
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
render,
|
|
5
5
|
screen,
|
|
6
6
|
waitFor,
|
|
7
|
+
act,
|
|
7
8
|
} from "@sproutsocial/seeds-react-testing-library";
|
|
8
9
|
import { PointerEventsCheckLevel } from "@testing-library/user-event";
|
|
9
10
|
import { type UserEvent } from "@testing-library/user-event";
|
|
@@ -11,28 +12,6 @@ import { ActionMenu } from "../ActionMenu";
|
|
|
11
12
|
import { MenuContent, MenuItem, MenuToggleButton } from "../../index";
|
|
12
13
|
|
|
13
14
|
describe("ActionMenu", () => {
|
|
14
|
-
it("should render the MenuItems when provided as children", async () => {
|
|
15
|
-
render(
|
|
16
|
-
<ActionMenu
|
|
17
|
-
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
18
|
-
>
|
|
19
|
-
<MenuContent>
|
|
20
|
-
<MenuItem id="test-item-1">Item 1</MenuItem>
|
|
21
|
-
<MenuItem id="test-item-2">Item 2</MenuItem>
|
|
22
|
-
</MenuContent>
|
|
23
|
-
</ActionMenu>
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
expect(screen.queryByRole("menu")).not.toBeInTheDocument();
|
|
27
|
-
|
|
28
|
-
const toggleBtn = screen.getByRole("button", { name: "Test Menu" });
|
|
29
|
-
fireEvent.click(toggleBtn);
|
|
30
|
-
|
|
31
|
-
await waitFor(() => expect(screen.getByRole("menu")).toBeVisible());
|
|
32
|
-
expect(screen.getByRole("menuitem", { name: "Item 1" })).toBeVisible();
|
|
33
|
-
expect(screen.getByRole("menuitem", { name: "Item 2" })).toBeVisible();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
15
|
test.each([
|
|
37
16
|
{
|
|
38
17
|
action: "click",
|
|
@@ -40,23 +19,29 @@ describe("ActionMenu", () => {
|
|
|
40
19
|
const firstItem = screen.getByRole("menuitem", {
|
|
41
20
|
name: "Item 1",
|
|
42
21
|
});
|
|
43
|
-
await
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
22
|
+
await act(async () => {
|
|
23
|
+
await user
|
|
24
|
+
.setup({
|
|
25
|
+
pointerEventsCheck: PointerEventsCheckLevel.Never,
|
|
26
|
+
})
|
|
27
|
+
.click(firstItem);
|
|
28
|
+
});
|
|
48
29
|
},
|
|
49
30
|
},
|
|
50
31
|
{
|
|
51
32
|
action: "pressed the Enter key",
|
|
52
33
|
activate: async (user: UserEvent) => {
|
|
53
|
-
await
|
|
34
|
+
await act(async () => {
|
|
35
|
+
await user.keyboard("{ArrowDown}{Enter}");
|
|
36
|
+
});
|
|
54
37
|
},
|
|
55
38
|
},
|
|
56
39
|
{
|
|
57
40
|
action: "pressed the Space key",
|
|
58
41
|
activate: async (user: UserEvent) => {
|
|
59
|
-
await
|
|
42
|
+
await act(async () => {
|
|
43
|
+
await user.keyboard("{ArrowDown} ");
|
|
44
|
+
});
|
|
60
45
|
},
|
|
61
46
|
},
|
|
62
47
|
])("should close after a MenuItem is $action", async ({ activate }) => {
|
|
@@ -75,7 +60,9 @@ describe("ActionMenu", () => {
|
|
|
75
60
|
);
|
|
76
61
|
|
|
77
62
|
const toggleBtn = screen.getByRole("button", { name: "Test Menu" });
|
|
78
|
-
await
|
|
63
|
+
await act(async () => {
|
|
64
|
+
await user.click(toggleBtn);
|
|
65
|
+
});
|
|
79
66
|
|
|
80
67
|
await waitFor(() => expect(screen.getByRole("menu")).toBeVisible());
|
|
81
68
|
|