@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
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,21 +4,20 @@ import {
|
|
|
4
4
|
type UseSelectState,
|
|
5
5
|
type UseSelectStateChange,
|
|
6
6
|
} from "downshift";
|
|
7
|
-
import type {
|
|
7
|
+
import type { TypeInternalItemProps } from "../types";
|
|
8
8
|
import { useMenuChildren } from "../hooks/useMenuChildren";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "../
|
|
13
|
-
import {
|
|
14
|
-
|
|
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
|
-
import { MenuItem } from "../MenuItem/index";
|
|
17
16
|
|
|
18
17
|
// Manually fire the onClick handler when Enter or Space is pressed
|
|
19
18
|
const handleStateChange = (
|
|
20
|
-
changes: UseSelectStateChange<
|
|
21
|
-
currentSelectedItem: UseSelectState<
|
|
19
|
+
changes: UseSelectStateChange<TypeInternalItemProps>,
|
|
20
|
+
currentSelectedItem: UseSelectState<TypeInternalItemProps>["selectedItem"]
|
|
22
21
|
) => {
|
|
23
22
|
// if not Enter or Space key, do nothing
|
|
24
23
|
if (
|
|
@@ -82,36 +81,33 @@ const handleStateChange = (
|
|
|
82
81
|
* @see {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/SingleSelectMenu | SingleSelectMenu}
|
|
83
82
|
* @see {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/MultiSelectMenu | MultiSelectMenu}
|
|
84
83
|
*/
|
|
85
|
-
export const ActionMenu = ({
|
|
84
|
+
export const ActionMenu = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
86
85
|
children: childrenProp,
|
|
87
|
-
|
|
88
|
-
MenuItemComponent
|
|
86
|
+
menuItems,
|
|
87
|
+
MenuItemComponent,
|
|
89
88
|
stateReducer: externalStateReducer,
|
|
90
89
|
...useSelectProps
|
|
91
|
-
}: TypeActionMenuProps) => {
|
|
90
|
+
}: TypeActionMenuProps<I>) => {
|
|
92
91
|
const { allMenuItems, menuItemsMap, children } = useMenuChildren({
|
|
93
92
|
children: childrenProp,
|
|
94
|
-
|
|
93
|
+
menuItems,
|
|
95
94
|
MenuItemComponent,
|
|
96
95
|
});
|
|
96
|
+
const { defaultDownshiftProps, ...restDefaults } = useDownshiftDefaults({
|
|
97
|
+
isCombobox: false,
|
|
98
|
+
});
|
|
97
99
|
const [currentSelectedItem, setCurrentSelectedItem] =
|
|
98
|
-
React.useState<UseSelectState<
|
|
100
|
+
React.useState<UseSelectState<TypeInternalItemProps>["selectedItem"]>(null);
|
|
99
101
|
|
|
100
102
|
const { isOpen, ...useSelectReturnProps } = useSelect({
|
|
101
|
-
...
|
|
103
|
+
...defaultDownshiftProps.select,
|
|
102
104
|
items: allMenuItems,
|
|
103
105
|
itemToString,
|
|
104
106
|
onStateChange: (changes) => handleStateChange(changes, currentSelectedItem),
|
|
105
|
-
stateReducer: (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
? externalStateReducer(state, {
|
|
110
|
-
...actionAndChanges,
|
|
111
|
-
changes: newState,
|
|
112
|
-
})
|
|
113
|
-
: newState;
|
|
114
|
-
},
|
|
107
|
+
stateReducer: reduceReducers(
|
|
108
|
+
defaultDownshiftProps.select.stateReducer,
|
|
109
|
+
externalStateReducer
|
|
110
|
+
),
|
|
115
111
|
...useSelectProps,
|
|
116
112
|
});
|
|
117
113
|
|
|
@@ -129,6 +125,7 @@ export const ActionMenu = ({
|
|
|
129
125
|
isOpen,
|
|
130
126
|
...useSelectProps,
|
|
131
127
|
...useSelectReturnProps,
|
|
128
|
+
...restDefaults,
|
|
132
129
|
}}
|
|
133
130
|
>
|
|
134
131
|
{children}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
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";
|
|
3
5
|
import type { TypeChildrenOrItems } from "../types";
|
|
4
6
|
|
|
5
7
|
type TypeInvalidMenuProps =
|
|
6
|
-
|
|
7
|
-
| "defaultSelectedItem"
|
|
8
|
-
| "selectedItem"
|
|
9
|
-
// We omit this so we can retype it.
|
|
10
|
-
| "items";
|
|
8
|
+
// There is no selection state on action menus.
|
|
9
|
+
"initialSelectedItem" | "defaultSelectedItem" | "selectedItem";
|
|
11
10
|
|
|
12
|
-
interface TypeActionMenuBaseProps
|
|
11
|
+
export interface TypeActionMenuBaseProps
|
|
13
12
|
extends TypeMenuRootOwnProps,
|
|
14
13
|
Omit<TypeDownshiftSelectProps, TypeInvalidMenuProps> {}
|
|
15
14
|
|
|
16
|
-
export type TypeActionMenuProps
|
|
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";
|
|
@@ -18,23 +19,29 @@ describe("ActionMenu", () => {
|
|
|
18
19
|
const firstItem = screen.getByRole("menuitem", {
|
|
19
20
|
name: "Item 1",
|
|
20
21
|
});
|
|
21
|
-
await
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
await act(async () => {
|
|
23
|
+
await user
|
|
24
|
+
.setup({
|
|
25
|
+
pointerEventsCheck: PointerEventsCheckLevel.Never,
|
|
26
|
+
})
|
|
27
|
+
.click(firstItem);
|
|
28
|
+
});
|
|
26
29
|
},
|
|
27
30
|
},
|
|
28
31
|
{
|
|
29
32
|
action: "pressed the Enter key",
|
|
30
33
|
activate: async (user: UserEvent) => {
|
|
31
|
-
await
|
|
34
|
+
await act(async () => {
|
|
35
|
+
await user.keyboard("{ArrowDown}{Enter}");
|
|
36
|
+
});
|
|
32
37
|
},
|
|
33
38
|
},
|
|
34
39
|
{
|
|
35
40
|
action: "pressed the Space key",
|
|
36
41
|
activate: async (user: UserEvent) => {
|
|
37
|
-
await
|
|
42
|
+
await act(async () => {
|
|
43
|
+
await user.keyboard("{ArrowDown} ");
|
|
44
|
+
});
|
|
38
45
|
},
|
|
39
46
|
},
|
|
40
47
|
])("should close after a MenuItem is $action", async ({ activate }) => {
|
|
@@ -53,7 +60,9 @@ describe("ActionMenu", () => {
|
|
|
53
60
|
);
|
|
54
61
|
|
|
55
62
|
const toggleBtn = screen.getByRole("button", { name: "Test Menu" });
|
|
56
|
-
await
|
|
63
|
+
await act(async () => {
|
|
64
|
+
await user.click(toggleBtn);
|
|
65
|
+
});
|
|
57
66
|
|
|
58
67
|
await waitFor(() => expect(screen.getByRole("menu")).toBeVisible());
|
|
59
68
|
|
|
@@ -20,15 +20,15 @@ export const ActionMenuTypeTest = () => (
|
|
|
20
20
|
<MenuContent>Test</MenuContent>
|
|
21
21
|
<MenuFooter>Test</MenuFooter>
|
|
22
22
|
</ActionMenu>
|
|
23
|
-
{/* Valid ActionMenu allows
|
|
23
|
+
{/* Valid ActionMenu allows menuItems to be passed using the menuItems prop */}
|
|
24
24
|
<ActionMenu
|
|
25
25
|
menuToggleElement={<>test</>}
|
|
26
|
-
|
|
26
|
+
menuItems={[{ children: "Item Name", id: "item-1" }]}
|
|
27
27
|
/>
|
|
28
|
-
{/* Valid ActionMenu allows
|
|
28
|
+
{/* Valid ActionMenu allows menuItems prop to be rendered using a MenuItemComponent prop */}
|
|
29
29
|
<ActionMenu
|
|
30
30
|
menuToggleElement={<>test</>}
|
|
31
|
-
|
|
31
|
+
menuItems={[{ children: "Item Name", id: "item-1" }]}
|
|
32
32
|
MenuItemComponent={MenuItem}
|
|
33
33
|
/>
|
|
34
34
|
{/* Valid ActionMenu accepts Downshift props and functions for useSelect other than selectedItem */}
|
|
@@ -51,16 +51,16 @@ export const ActionMenuTypeTest = () => (
|
|
|
51
51
|
>
|
|
52
52
|
Test
|
|
53
53
|
</ActionMenu>
|
|
54
|
-
{/* @ts-expect-error - when no children or items provided */}
|
|
55
|
-
<ActionMenu menuToggleElement={<>test</>} />
|
|
56
54
|
{/* @ts-expect-error - when both children and MenuItemComponent provided */}
|
|
57
55
|
<ActionMenu menuToggleElement={<>test</>} MenuItemComponent={MenuItem}>
|
|
58
56
|
Children
|
|
59
57
|
</ActionMenu>
|
|
60
|
-
{/* @ts-expect-error - when both children and
|
|
61
|
-
<ActionMenu menuToggleElement={<>test</>}
|
|
58
|
+
{/* @ts-expect-error - when both children and menuItems provided */}
|
|
59
|
+
<ActionMenu menuToggleElement={<>test</>} menuItems={[]}>
|
|
62
60
|
Children
|
|
63
61
|
</ActionMenu>
|
|
62
|
+
{/* @ts-expect-error - when no children or items provided */}
|
|
63
|
+
<ActionMenu menuToggleElement={<>test</>} />
|
|
64
64
|
{/* @ts-expect-error - when MenuItemComponent is of the wrong type */}
|
|
65
65
|
<ActionMenu menuToggleElement={<>test</>} MenuItemComponent={MenuContent}>
|
|
66
66
|
Children
|