@sproutsocial/seeds-react-menu 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +86 -10
- package/CHANGELOG.md +44 -0
- package/dist/esm/index.js +537 -126
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +47 -16
- package/dist/index.d.ts +47 -16
- package/dist/index.js +602 -184
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/ActionMenu/ActionMenu.tsx +4 -3
- package/src/ActionMenu/ActionMenuTypes.ts +5 -4
- package/src/ActionMenu/__tests__/ActionMenu.test.tsx +1 -1
- package/src/Autocomplete/Autocomplete.stories.tsx +124 -32
- package/src/Autocomplete/AutocompleteSelect.tsx +24 -0
- package/src/Autocomplete/AutocompleteTypes.ts +5 -2
- package/src/Autocomplete/MultiAutocomplete.tsx +3 -2
- package/src/Autocomplete/SingleAutocomplete.tsx +4 -4
- package/src/Autocomplete/index.ts +2 -0
- package/src/ListboxToggleButton.tsx +65 -0
- package/src/MenuContent/MenuContentTypes.ts +1 -2
- package/src/MenuContent/styles.tsx +6 -9
- package/src/MenuGroup/__tests__/MenuGroup.test.tsx +1 -1
- package/src/MenuItem/__tests__/MenuItem.test.tsx +1 -3
- package/src/MenuRoot/MenuRoot.tsx +95 -15
- package/src/MultiSelectMenu/MultiSelectMenu.stories.tsx +0 -1
- package/src/MultiSelectMenu/MultiSelectMenu.tsx +3 -2
- package/src/MultiSelectMenu/MultiSelectMenuTypes.ts +5 -3
- package/src/NestedMenu/NestedMenu.feature +1 -1
- package/src/NestedMenu/NestedMenu.stories.tsx +25 -7
- package/src/NestedMenu/NestedMenu.tsx +217 -43
- package/src/NestedMenu/NestedMenuContext.tsx +16 -0
- package/src/NestedMenu/NestedMenuHeader.tsx +19 -6
- package/src/NestedMenu/NestedMenuItem.tsx +11 -8
- package/src/NestedMenu/NestedMenuPopout.tsx +55 -0
- package/src/NestedMenu/NestedMenuRoot.tsx +27 -0
- package/src/NestedMenu/NestedMenuTypes.ts +3 -2
- package/src/NestedMenu/__tests__/NestedMenu.test.tsx +210 -19
- package/src/SingleSelectMenu/SingleSelectMenu.stories.tsx +0 -1
- package/src/SingleSelectMenu/SingleSelectMenu.tsx +3 -2
- package/src/SingleSelectMenu/SingleSelectMenuTypes.ts +5 -3
- package/src/__tests__/Menu.test.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/storybookUtilities/menu-storybook-components.tsx +11 -31
- package/src/types.ts +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sproutsocial/seeds-react-menu",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Seeds React Menu",
|
|
5
5
|
"author": "Sprout Social, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,17 +21,17 @@
|
|
|
21
21
|
"@sproutsocial/seeds-react-button": "^1.2.1",
|
|
22
22
|
"@sproutsocial/seeds-react-checkbox": "^1.3.1",
|
|
23
23
|
"@sproutsocial/seeds-react-icon": "^1.1.2",
|
|
24
|
-
"@sproutsocial/seeds-react-input": "^1.4.
|
|
24
|
+
"@sproutsocial/seeds-react-input": "^1.4.2",
|
|
25
25
|
"@sproutsocial/seeds-react-label": "^1.0.1",
|
|
26
|
-
"@sproutsocial/seeds-react-popout": "^2.
|
|
26
|
+
"@sproutsocial/seeds-react-popout": "^2.2.0",
|
|
27
27
|
"@sproutsocial/seeds-react-radio": "^1.3.0",
|
|
28
28
|
"@sproutsocial/seeds-react-switch": "^1.2.1",
|
|
29
29
|
"@sproutsocial/seeds-react-system-props": "^3.0.2",
|
|
30
30
|
"@sproutsocial/seeds-react-theme": "^2.3.0",
|
|
31
|
-
"@sproutsocial/seeds-react-token-input": "^1.
|
|
31
|
+
"@sproutsocial/seeds-react-token-input": "^1.4.0",
|
|
32
32
|
"@sproutsocial/seeds-react-utilities": "^4.2.0",
|
|
33
33
|
"downshift": "9.0.4",
|
|
34
|
-
"motion": "^
|
|
34
|
+
"motion": "^12.6.3"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@sproutsocial/seeds-react-tooltip": "^1.0.1",
|
|
@@ -9,7 +9,7 @@ import { useMenuChildren } from "../hooks/useMenuChildren";
|
|
|
9
9
|
import { useDownshiftDefaults } from "../hooks/useDownshiftDefaults";
|
|
10
10
|
import { reduceReducers } from "../utils/reduceReducers";
|
|
11
11
|
import { itemToString } from "../utils/downshiftDefaults";
|
|
12
|
-
import { MenuRoot
|
|
12
|
+
import { MenuRoot } from "../MenuRoot";
|
|
13
13
|
import type { TypeMenuItemProps } from "../MenuItem";
|
|
14
14
|
|
|
15
15
|
import type { TypeActionMenuProps } from "./ActionMenuTypes";
|
|
@@ -86,6 +86,7 @@ export const ActionMenu = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
|
86
86
|
menuItems,
|
|
87
87
|
MenuItemComponent,
|
|
88
88
|
stateReducer: externalStateReducer,
|
|
89
|
+
MenuRootComponent = MenuRoot,
|
|
89
90
|
...useSelectProps
|
|
90
91
|
}: TypeActionMenuProps<I>) => {
|
|
91
92
|
const { allMenuItems, menuItemsMap, children } = useMenuChildren({
|
|
@@ -116,7 +117,7 @@ export const ActionMenu = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
|
116
117
|
}, [useSelectReturnProps.selectedItem]);
|
|
117
118
|
|
|
118
119
|
return (
|
|
119
|
-
<
|
|
120
|
+
<MenuRootComponent
|
|
120
121
|
{...{
|
|
121
122
|
isListbox: false,
|
|
122
123
|
items: allMenuItems,
|
|
@@ -129,7 +130,7 @@ export const ActionMenu = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
|
129
130
|
}}
|
|
130
131
|
>
|
|
131
132
|
{children}
|
|
132
|
-
</
|
|
133
|
+
</MenuRootComponent>
|
|
133
134
|
);
|
|
134
135
|
};
|
|
135
136
|
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { type TypeMenuRootOwnProps } from "../MenuRoot";
|
|
2
1
|
import type { TypeDownshiftSelectProps } from "../MenuContext";
|
|
3
|
-
import type { TypeMenuRootProps } from "../MenuRoot";
|
|
4
2
|
import type { TypeMenuItemProps } from "../MenuItem";
|
|
5
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
TypeChildrenOrItems,
|
|
5
|
+
TypeHigherOrderComponentProps,
|
|
6
|
+
} from "../types";
|
|
6
7
|
|
|
7
8
|
type TypeInvalidMenuProps =
|
|
8
9
|
// There is no selection state on action menus.
|
|
9
10
|
"initialSelectedItem" | "defaultSelectedItem" | "selectedItem";
|
|
10
11
|
|
|
11
12
|
export interface TypeActionMenuBaseProps
|
|
12
|
-
extends
|
|
13
|
+
extends TypeHigherOrderComponentProps,
|
|
13
14
|
Omit<TypeDownshiftSelectProps, TypeInvalidMenuProps> {}
|
|
14
15
|
|
|
15
16
|
export type TypeActionMenuProps<
|
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
screen,
|
|
6
6
|
waitFor,
|
|
7
7
|
act,
|
|
8
|
+
PointerEventsCheckLevel,
|
|
8
9
|
} from "@sproutsocial/seeds-react-testing-library";
|
|
9
|
-
import { PointerEventsCheckLevel } from "@testing-library/user-event";
|
|
10
10
|
import { type UserEvent } from "@testing-library/user-event";
|
|
11
11
|
import { ActionMenu } from "../ActionMenu";
|
|
12
12
|
import { MenuContent, MenuItem, MenuToggleButton } from "../../index";
|
|
@@ -5,7 +5,15 @@ import { action } from "@storybook/addon-actions";
|
|
|
5
5
|
|
|
6
6
|
import { Box } from "@sproutsocial/seeds-react-box";
|
|
7
7
|
import { Icon } from "@sproutsocial/seeds-react-icon";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
MenuContent,
|
|
10
|
+
MenuGroup,
|
|
11
|
+
MenuItem,
|
|
12
|
+
MenuLabel,
|
|
13
|
+
MenuToggleButton,
|
|
14
|
+
type TypeInternalItemProps,
|
|
15
|
+
AutocompleteSelect,
|
|
16
|
+
} from "../index";
|
|
9
17
|
// eslint-disable-next-line no-restricted-imports
|
|
10
18
|
import { TEST_BOOKS } from "../__fixtures__/menu-test-data";
|
|
11
19
|
|
|
@@ -154,34 +162,118 @@ export const AutocompleteTokenInputExample: Story = {
|
|
|
154
162
|
},
|
|
155
163
|
};
|
|
156
164
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
165
|
+
export const AutocompleteSelectExample: Story = {
|
|
166
|
+
name: "Autocomplete Select",
|
|
167
|
+
render: (args) => {
|
|
168
|
+
return (
|
|
169
|
+
<Autocomplete
|
|
170
|
+
itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
|
|
171
|
+
menuToggleElement={
|
|
172
|
+
<>
|
|
173
|
+
<MenuLabel htmlFor="my-combobox" mb={300}>
|
|
174
|
+
Choose a book
|
|
175
|
+
</MenuLabel>
|
|
176
|
+
<AutocompleteSelect />
|
|
177
|
+
</>
|
|
178
|
+
}
|
|
179
|
+
{...(args as object)}
|
|
180
|
+
>
|
|
181
|
+
<MenuContent>
|
|
182
|
+
{TEST_BOOKS.map(({ id, title }) => (
|
|
183
|
+
<MenuItem key={id} id={id}>
|
|
184
|
+
{title}
|
|
185
|
+
</MenuItem>
|
|
186
|
+
))}
|
|
187
|
+
</MenuContent>
|
|
188
|
+
</Autocomplete>
|
|
189
|
+
);
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const TestControlledSingleAutocomplete = ({
|
|
194
|
+
...args
|
|
195
|
+
}: CustomAutocompleteArgs) => {
|
|
196
|
+
const [isOpen, setIsOpen] = React.useState<boolean>(false);
|
|
197
|
+
const [selectedItem, setSelectedItem] =
|
|
198
|
+
React.useState<TypeInternalItemProps | null>(null);
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<Autocomplete
|
|
202
|
+
isOpen={isOpen}
|
|
203
|
+
onIsOpenChange={({ isOpen }) => setIsOpen(isOpen)}
|
|
204
|
+
selectedItem={selectedItem}
|
|
205
|
+
onSelectedItemChange={({ selectedItem: newSelectedItem }) => {
|
|
206
|
+
setSelectedItem(newSelectedItem);
|
|
207
|
+
}}
|
|
208
|
+
itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
|
|
209
|
+
menuToggleElement={<AutocompleteInput />}
|
|
210
|
+
{...(args as object)}
|
|
211
|
+
>
|
|
212
|
+
<MenuContent>
|
|
213
|
+
<MenuGroup id="books">
|
|
214
|
+
{TEST_BOOKS.map(({ id, title }) => (
|
|
215
|
+
<MenuItem key={id} id={id}>
|
|
216
|
+
{title}
|
|
217
|
+
</MenuItem>
|
|
218
|
+
))}
|
|
219
|
+
</MenuGroup>
|
|
220
|
+
</MenuContent>
|
|
221
|
+
</Autocomplete>
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export const AutocompleteControlled: Story = {
|
|
226
|
+
name: "Single Autocomplete Controlled State",
|
|
227
|
+
render: (args: CustomAutocompleteArgs) => (
|
|
228
|
+
<TestControlledSingleAutocomplete {...args} />
|
|
229
|
+
),
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const TestControlledMultiAutocomplete = (args: CustomAutocompleteArgs) => {
|
|
233
|
+
const [isOpen, setIsOpen] = React.useState<boolean>(false);
|
|
234
|
+
const [selectedItems, setSelectedItems] = React.useState<
|
|
235
|
+
TypeInternalItemProps[]
|
|
236
|
+
>([]);
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<Autocomplete
|
|
240
|
+
multiSelect
|
|
241
|
+
isOpen={isOpen}
|
|
242
|
+
onIsOpenChange={({ isOpen }) => setIsOpen(isOpen)}
|
|
243
|
+
selectedItems={selectedItems}
|
|
244
|
+
onSelectedItemsChange={({ selectedItems: newSelectedItems }) =>
|
|
245
|
+
setSelectedItems(newSelectedItems)
|
|
246
|
+
}
|
|
247
|
+
itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
|
|
248
|
+
menuToggleElement={
|
|
249
|
+
<>
|
|
250
|
+
<MenuLabel htmlFor="my-combobox" mb={300}>
|
|
251
|
+
Choose a book
|
|
252
|
+
</MenuLabel>
|
|
253
|
+
<AutocompleteTokenInput />
|
|
254
|
+
<MenuToggleButton mt={200} width="100%" p="0">
|
|
255
|
+
<Icon name="chevron-down" aria-label="Open Menu" />
|
|
256
|
+
</MenuToggleButton>
|
|
257
|
+
</>
|
|
258
|
+
}
|
|
259
|
+
{...(args as object)}
|
|
260
|
+
>
|
|
261
|
+
<MenuContent>
|
|
262
|
+
<MenuGroup id="books">
|
|
263
|
+
{TEST_BOOKS.map(({ id, title }) => (
|
|
264
|
+
<MenuItem key={id} id={id}>
|
|
265
|
+
{title}
|
|
266
|
+
</MenuItem>
|
|
267
|
+
))}
|
|
268
|
+
</MenuGroup>
|
|
269
|
+
</MenuContent>
|
|
270
|
+
</Autocomplete>
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export const MultiAutocompleteControlled: Story = {
|
|
275
|
+
name: "MultiAutocomplete Controlled State",
|
|
276
|
+
render: (args: CustomAutocompleteArgs) => (
|
|
277
|
+
<TestControlledMultiAutocomplete {...args} />
|
|
278
|
+
),
|
|
279
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Icon from "@sproutsocial/seeds-react-icon";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AutocompleteInput,
|
|
6
|
+
type TypeAutocompleteInputProps,
|
|
7
|
+
} from "./AutocompleteInput";
|
|
8
|
+
import styled from "styled-components";
|
|
9
|
+
import { Accessory } from "@sproutsocial/seeds-react-input";
|
|
10
|
+
|
|
11
|
+
const StyledAutocompleteInput = styled(AutocompleteInput)`
|
|
12
|
+
${Accessory} {
|
|
13
|
+
pointer-events: none;
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
export const AutocompleteSelect = (props: TypeAutocompleteInputProps) => {
|
|
18
|
+
return (
|
|
19
|
+
<StyledAutocompleteInput
|
|
20
|
+
elemAfter={<Icon name="chevron-down" />}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -6,11 +6,14 @@ import type {
|
|
|
6
6
|
import type { TypeGetIsItemVisibleFn } from "../hooks/useItemFiltering";
|
|
7
7
|
import type { TypeMenuRootProps } from "../MenuRoot";
|
|
8
8
|
import type { TypeMenuItemProps } from "../MenuItem";
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
TypeChildrenOrItems,
|
|
11
|
+
TypeHigherOrderComponentProps,
|
|
12
|
+
} from "../types";
|
|
10
13
|
|
|
11
14
|
export type TypeSingleAutocompleteProps<
|
|
12
15
|
I extends TypeMenuItemProps = TypeMenuItemProps
|
|
13
|
-
> =
|
|
16
|
+
> = TypeHigherOrderComponentProps &
|
|
14
17
|
TypeChildrenOrItems<I> &
|
|
15
18
|
TypeDownshiftComboboxProps & {
|
|
16
19
|
getIsItemVisible?: TypeGetIsItemVisibleFn;
|
|
@@ -26,6 +26,7 @@ export const MultiAutocomplete = <
|
|
|
26
26
|
stateReducer: externalStateReducer,
|
|
27
27
|
menuItems,
|
|
28
28
|
MenuItemComponent,
|
|
29
|
+
MenuRootComponent = MenuRoot,
|
|
29
30
|
itemToString = defaultItemToString,
|
|
30
31
|
itemToKey = defaultItemToKey,
|
|
31
32
|
getIsItemVisible = getDefaultGetIsItemVisible(itemToString),
|
|
@@ -160,7 +161,7 @@ export const MultiAutocomplete = <
|
|
|
160
161
|
});
|
|
161
162
|
|
|
162
163
|
return (
|
|
163
|
-
<
|
|
164
|
+
<MenuRootComponent
|
|
164
165
|
{...{
|
|
165
166
|
isListbox: true,
|
|
166
167
|
items: allMenuItems,
|
|
@@ -183,6 +184,6 @@ export const MultiAutocomplete = <
|
|
|
183
184
|
}}
|
|
184
185
|
>
|
|
185
186
|
{children}
|
|
186
|
-
</
|
|
187
|
+
</MenuRootComponent>
|
|
187
188
|
);
|
|
188
189
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
2
|
import { useCombobox } from "downshift";
|
|
3
3
|
import { useMenuChildren } from "../hooks/useMenuChildren";
|
|
4
4
|
import { itemToString as defaultItemToString } from "../utils/downshiftDefaults";
|
|
@@ -8,7 +8,6 @@ import { useItemFiltering } from "../hooks/useItemFiltering";
|
|
|
8
8
|
import { MenuRoot } from "../MenuRoot";
|
|
9
9
|
|
|
10
10
|
import type { TypeMenuItemProps } from "../MenuItem";
|
|
11
|
-
import type { TypeMenuRootProps } from "../MenuRoot";
|
|
12
11
|
import type { TypeAutocompleteProps } from "./AutocompleteTypes";
|
|
13
12
|
|
|
14
13
|
export const SingleAutocomplete = <
|
|
@@ -17,6 +16,7 @@ export const SingleAutocomplete = <
|
|
|
17
16
|
children: childrenProp,
|
|
18
17
|
stateReducer: externalStateReducer,
|
|
19
18
|
menuItems,
|
|
19
|
+
MenuRootComponent = MenuRoot,
|
|
20
20
|
MenuItemComponent,
|
|
21
21
|
itemToString = defaultItemToString,
|
|
22
22
|
getIsItemVisible,
|
|
@@ -53,7 +53,7 @@ export const SingleAutocomplete = <
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
return (
|
|
56
|
-
<
|
|
56
|
+
<MenuRootComponent
|
|
57
57
|
{...{
|
|
58
58
|
isListbox: true,
|
|
59
59
|
items: allMenuItems,
|
|
@@ -69,6 +69,6 @@ export const SingleAutocomplete = <
|
|
|
69
69
|
}}
|
|
70
70
|
>
|
|
71
71
|
{children}
|
|
72
|
-
</
|
|
72
|
+
</MenuRootComponent>
|
|
73
73
|
);
|
|
74
74
|
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import styled, { css } from "styled-components";
|
|
2
|
+
import { Icon } from "@sproutsocial/seeds-react-icon";
|
|
3
|
+
import {
|
|
4
|
+
MenuToggleButton,
|
|
5
|
+
type TypeMenuToggleButtonProps,
|
|
6
|
+
} from "./MenuToggleButton";
|
|
7
|
+
import { useMenuToggleContext } from "./MenuContext";
|
|
8
|
+
|
|
9
|
+
export interface TypeListboxToggleButtonProps
|
|
10
|
+
extends TypeMenuToggleButtonProps {
|
|
11
|
+
invalid?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const StyledListboxToggleButton = styled(
|
|
15
|
+
MenuToggleButton
|
|
16
|
+
)<TypeListboxToggleButtonProps>`
|
|
17
|
+
${(props) =>
|
|
18
|
+
props.invalid &&
|
|
19
|
+
css`
|
|
20
|
+
border-color: ${(props) => props.theme.colors.form.border.error};
|
|
21
|
+
`}
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
export interface TypeStyledChevronProps {
|
|
25
|
+
$isOpen?: boolean;
|
|
26
|
+
invalid?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const StyledChevron = styled.div<TypeStyledChevronProps>`
|
|
30
|
+
margin-left: ${({ theme }) => theme.space[300]};
|
|
31
|
+
${({ $isOpen }) =>
|
|
32
|
+
$isOpen &&
|
|
33
|
+
`
|
|
34
|
+
transform: rotate(-180deg);
|
|
35
|
+
`}
|
|
36
|
+
transition: transform 0.15s;
|
|
37
|
+
|
|
38
|
+
${({ invalid }) =>
|
|
39
|
+
invalid &&
|
|
40
|
+
css`
|
|
41
|
+
color: ${(props) => props.theme.colors.icon.error};
|
|
42
|
+
`}
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const StyledInnerButton = styled.div`
|
|
46
|
+
display: flex;
|
|
47
|
+
justify-content: space-between;
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
export const ListboxToggleButton = ({
|
|
51
|
+
children,
|
|
52
|
+
...buttonProps
|
|
53
|
+
}: TypeListboxToggleButtonProps) => {
|
|
54
|
+
const { isOpen } = useMenuToggleContext();
|
|
55
|
+
return (
|
|
56
|
+
<StyledListboxToggleButton appearance="secondary" {...buttonProps}>
|
|
57
|
+
<StyledInnerButton>
|
|
58
|
+
{children}
|
|
59
|
+
<StyledChevron $isOpen={isOpen} invalid={buttonProps.invalid}>
|
|
60
|
+
<Icon name="chevron-down-outline" fixedWidth aria-hidden />
|
|
61
|
+
</StyledChevron>
|
|
62
|
+
</StyledInnerButton>
|
|
63
|
+
</StyledListboxToggleButton>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { type HTMLMotionProps } from "motion/react";
|
|
2
1
|
import type { TypeMenuItemProps } from "../MenuItem";
|
|
3
2
|
import type { TypeChildrenOrItems } from "../types";
|
|
4
3
|
|
|
5
4
|
export type TypeMenuContentProps<
|
|
6
5
|
I extends TypeMenuItemProps = TypeMenuItemProps
|
|
7
|
-
> = Omit<
|
|
6
|
+
> = Omit<React.ComponentPropsWithoutRef<"ul">, "children"> & {
|
|
8
7
|
innerRef?: React.Ref<HTMLUListElement>;
|
|
9
8
|
} & TypeChildrenOrItems<I>;
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import styled from "styled-components";
|
|
2
|
-
import { motion } from "motion/react";
|
|
3
|
-
//import { focusRing } from "@sproutsocial/seeds-react-mixins";
|
|
4
2
|
|
|
5
|
-
export const StyledMenuContent = styled
|
|
3
|
+
export const StyledMenuContent = styled.ul`
|
|
6
4
|
margin: 0;
|
|
7
5
|
padding: 0;
|
|
8
|
-
`;
|
|
9
|
-
|
|
10
|
-
// Removing this for now because it's insanely intrusive and distracting to the user... interestingly the out of the box browser focus ring works better only appears when the content is actually focused.
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
&:focus {
|
|
8
|
+
outline: none;
|
|
9
|
+
box-shadow: none;
|
|
10
|
+
}
|
|
11
|
+
`;
|
|
@@ -5,11 +5,9 @@ import {
|
|
|
5
5
|
render,
|
|
6
6
|
waitFor,
|
|
7
7
|
act,
|
|
8
|
-
} from "@sproutsocial/seeds-react-testing-library";
|
|
9
|
-
import {
|
|
10
8
|
PointerEventsCheckLevel,
|
|
11
9
|
type UserEvent,
|
|
12
|
-
} from "@testing-library
|
|
10
|
+
} from "@sproutsocial/seeds-react-testing-library";
|
|
13
11
|
import type { ByRoleMatcher, ByRoleOptions } from "@testing-library/react";
|
|
14
12
|
import { theme } from "@sproutsocial/seeds-react-theme";
|
|
15
13
|
import {
|
|
@@ -3,12 +3,13 @@ import styled from "styled-components";
|
|
|
3
3
|
import { Popout, type TypePopoutProps } from "@sproutsocial/seeds-react-popout";
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
MenuToggleProvider,
|
|
7
6
|
MenuContentProvider,
|
|
8
|
-
|
|
7
|
+
MenuToggleProvider,
|
|
9
8
|
type TypeMenuContextProps,
|
|
10
9
|
type TypeMenuProviderProps,
|
|
10
|
+
useMenu,
|
|
11
11
|
} from "../MenuContext";
|
|
12
|
+
import { MOTION_DURATION_FAST } from "@sproutsocial/seeds-motion/unitless";
|
|
12
13
|
|
|
13
14
|
export interface TypeMenuRootOwnProps {
|
|
14
15
|
menuToggleElement: React.ReactElement<any>;
|
|
@@ -22,12 +23,84 @@ export interface TypeMenuRootProps
|
|
|
22
23
|
Pick<TypeMenuProviderProps, "children">,
|
|
23
24
|
TypeMenuRootOwnProps {}
|
|
24
25
|
|
|
25
|
-
const CustomPopoutContent = styled(Popout.Content)`
|
|
26
|
+
export const CustomPopoutContent = styled(Popout.Content)`
|
|
26
27
|
padding: 0;
|
|
27
28
|
margin-left: 0;
|
|
28
29
|
margin-right: 0;
|
|
29
30
|
`;
|
|
30
31
|
|
|
32
|
+
const menuAnimationConfig = {
|
|
33
|
+
initial: {
|
|
34
|
+
scale: 0,
|
|
35
|
+
opacity: 0,
|
|
36
|
+
originX: 0,
|
|
37
|
+
originY: 0,
|
|
38
|
+
},
|
|
39
|
+
animate: {
|
|
40
|
+
opacity: 1,
|
|
41
|
+
scale: 1,
|
|
42
|
+
originX: 0,
|
|
43
|
+
originY: 0,
|
|
44
|
+
|
|
45
|
+
transition: {
|
|
46
|
+
type: "tween",
|
|
47
|
+
duration: MOTION_DURATION_FAST,
|
|
48
|
+
ease: "circOut",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
exit: {
|
|
52
|
+
opacity: 0,
|
|
53
|
+
scale: 1,
|
|
54
|
+
transition: {
|
|
55
|
+
type: "tween",
|
|
56
|
+
duration: MOTION_DURATION_FAST,
|
|
57
|
+
ease: "circIn",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export interface TypeMenuPopoutProps
|
|
63
|
+
extends Pick<Partial<TypeMenuContextProps>, "openMenu" | "closeMenu">,
|
|
64
|
+
TypePopoutProps {}
|
|
65
|
+
|
|
66
|
+
export const MenuPopout = ({
|
|
67
|
+
content,
|
|
68
|
+
width = "300px",
|
|
69
|
+
isOpen,
|
|
70
|
+
openMenu,
|
|
71
|
+
closeMenu,
|
|
72
|
+
focusLockProps,
|
|
73
|
+
...popoutProps
|
|
74
|
+
}: TypeMenuPopoutProps) => {
|
|
75
|
+
const setIsOpen = useCallback(
|
|
76
|
+
(newIsOpen: boolean) => {
|
|
77
|
+
newIsOpen ? openMenu?.() : closeMenu?.();
|
|
78
|
+
},
|
|
79
|
+
[openMenu, closeMenu]
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Popout
|
|
84
|
+
isOpen={!!isOpen}
|
|
85
|
+
setIsOpen={setIsOpen}
|
|
86
|
+
content={(props) => (
|
|
87
|
+
<CustomPopoutContent width={popoutProps.fullWidth ? "initial" : width}>
|
|
88
|
+
{typeof content === "function" ? content(props) : content}
|
|
89
|
+
</CustomPopoutContent>
|
|
90
|
+
)}
|
|
91
|
+
focusLockProps={{
|
|
92
|
+
// correct jerking motion when scrolling while the Popout is open
|
|
93
|
+
// returnFocus: false,
|
|
94
|
+
crossFrame: false,
|
|
95
|
+
...focusLockProps,
|
|
96
|
+
}}
|
|
97
|
+
disableWrapperAria
|
|
98
|
+
animationConfig={menuAnimationConfig}
|
|
99
|
+
{...popoutProps}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
31
104
|
/**
|
|
32
105
|
* @link https://seeds.sproutsocial.com/components/menu-root/
|
|
33
106
|
*
|
|
@@ -67,23 +140,16 @@ export const MenuRoot = ({
|
|
|
67
140
|
const menuContext = useMenu(contextProps);
|
|
68
141
|
|
|
69
142
|
return (
|
|
70
|
-
<
|
|
71
|
-
menuPopout
|
|
143
|
+
<MenuPopout
|
|
72
144
|
placement={placement}
|
|
73
145
|
isOpen={!!isOpen}
|
|
74
|
-
|
|
146
|
+
openMenu={openMenu}
|
|
147
|
+
closeMenu={closeMenu}
|
|
75
148
|
content={
|
|
76
149
|
<MenuContentProvider menuContext={menuContext}>
|
|
77
|
-
|
|
150
|
+
{children}
|
|
78
151
|
</MenuContentProvider>
|
|
79
152
|
}
|
|
80
|
-
focusLockProps={{
|
|
81
|
-
// correct jerking motion when scrolling while the Popout is open
|
|
82
|
-
// returnFocus: false,
|
|
83
|
-
crossFrame: false,
|
|
84
|
-
...focusLockProps,
|
|
85
|
-
}}
|
|
86
|
-
disableWrapperAria
|
|
87
153
|
{...popoutProps}
|
|
88
154
|
>
|
|
89
155
|
{(toggleProps) => (
|
|
@@ -91,6 +157,20 @@ export const MenuRoot = ({
|
|
|
91
157
|
{menuToggleElement}
|
|
92
158
|
</MenuToggleProvider>
|
|
93
159
|
)}
|
|
94
|
-
</
|
|
160
|
+
</MenuPopout>
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const MenuRootNoPopout = ({
|
|
165
|
+
children,
|
|
166
|
+
menuToggleElement,
|
|
167
|
+
...contextProps
|
|
168
|
+
}: TypeMenuRootProps) => {
|
|
169
|
+
const menuContext = useMenu(contextProps);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<MenuContentProvider menuContext={menuContext}>
|
|
173
|
+
{children}
|
|
174
|
+
</MenuContentProvider>
|
|
95
175
|
);
|
|
96
176
|
};
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
|
|
4
4
|
import { Box } from "@sproutsocial/seeds-react-box";
|
|
5
|
-
import { action } from "@storybook/addon-actions";
|
|
6
5
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
7
6
|
import { StorybookMenuToggleButton } from "../storybookUtilities/menu-storybook-components";
|
|
8
7
|
import { TEST_BOOKS, EXTRA_BOOKS } from "../__fixtures__/menu-test-data";
|