@sproutsocial/seeds-react-menu 1.2.0 → 1.2.2
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 +14 -0
- package/dist/esm/index.js +106 -76
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +120 -60
- package/dist/index.d.ts +120 -60
- package/dist/index.js +130 -105
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ActionMenu/ActionMenu.tsx +2 -2
- package/src/Autocomplete/Autocomplete.stories.tsx +7 -19
- package/src/Autocomplete/Autocomplete.tsx +2 -3
- package/src/Autocomplete/AutocompleteTypes.ts +16 -12
- package/src/Autocomplete/SingleAutocomplete.tsx +2 -3
- package/src/MenuContent/MenuContent.tsx +4 -4
- package/src/MenuContext.tsx +6 -1
- package/src/MenuFooter/MenuFooter.tsx +2 -2
- package/src/MenuGroup/MenuGroup.tsx +4 -4
- package/src/MenuHeader/MenuHeader.tsx +4 -2
- package/src/MenuLabel.tsx +32 -2
- package/src/MenuRoot/MenuRoot.tsx +46 -27
- package/src/MenuSearchInput/MenuSearchInput.tsx +5 -11
- package/src/MenuSearchTokenInput/MenuSearchTokenInput.tsx +6 -3
- package/src/MenuToggleButton/MenuToggleButton.tsx +11 -12
- package/src/MenuTokenInput.tsx +10 -4
- package/src/MultiSelectMenu/MultiSelectMenu.tsx +4 -3
- package/src/NestedMenu/NestedMenu.feature +2 -2
- package/src/NestedMenu/NestedMenu.tsx +3 -0
- package/src/NestedMenu/NestedMenuTypes.ts +3 -0
- package/src/SingleSelectMenu/SingleSelectMenu.tsx +5 -4
- package/src/hooks/useItemFiltering.tsx +2 -1
- package/src/hooks/useMenuChildren.tsx +1 -1
- package/src/types.ts +28 -15
package/package.json
CHANGED
|
@@ -78,8 +78,8 @@ const handleStateChange = (
|
|
|
78
78
|
* @description An ActionMenu contains a list of clickable items like buttons or links. ActionMenus do not hold a selection.
|
|
79
79
|
*
|
|
80
80
|
* **To select menu items:**
|
|
81
|
-
* @see {@link https://
|
|
82
|
-
* @see {@link https://
|
|
81
|
+
* @see {@link https://seeds.sproutsocial.com/components/single-select-menu SingleSelectMenu}
|
|
82
|
+
* @see {@link https://seeds.sproutsocial.com/components/multi-select-menu MultiSelectMenu}
|
|
83
83
|
*/
|
|
84
84
|
export const ActionMenu = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
85
85
|
children: childrenProp,
|
|
@@ -52,9 +52,7 @@ export const AutocompleteTextInputExample: Story = {
|
|
|
52
52
|
itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
|
|
53
53
|
menuToggleElement={
|
|
54
54
|
<>
|
|
55
|
-
<MenuLabel
|
|
56
|
-
Choose a book
|
|
57
|
-
</MenuLabel>
|
|
55
|
+
<MenuLabel mb={300}>Choose a book</MenuLabel>
|
|
58
56
|
<AutocompleteInput />
|
|
59
57
|
</>
|
|
60
58
|
}
|
|
@@ -80,9 +78,7 @@ export const AutocompleteWithItemsAsProps: Story = {
|
|
|
80
78
|
itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
|
|
81
79
|
menuToggleElement={
|
|
82
80
|
<>
|
|
83
|
-
<MenuLabel
|
|
84
|
-
Choose a book
|
|
85
|
-
</MenuLabel>
|
|
81
|
+
<MenuLabel mb={300}>Choose a book</MenuLabel>
|
|
86
82
|
<AutocompleteInput />
|
|
87
83
|
</>
|
|
88
84
|
}
|
|
@@ -104,11 +100,9 @@ export const AutocompleteTextWithButtonExample: Story = {
|
|
|
104
100
|
itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
|
|
105
101
|
menuToggleElement={
|
|
106
102
|
<>
|
|
107
|
-
<MenuLabel
|
|
108
|
-
Choose a book
|
|
109
|
-
</MenuLabel>
|
|
103
|
+
<MenuLabel mb={300}>Choose a book</MenuLabel>
|
|
110
104
|
<Box display="flex" alignItems="center" gap={200}>
|
|
111
|
-
<AutocompleteInput />
|
|
105
|
+
<AutocompleteInput style={{ flex: 2 }} />
|
|
112
106
|
<MenuToggleButton>
|
|
113
107
|
<Icon name="chevron-down" aria-label="Open Menu" />
|
|
114
108
|
</MenuToggleButton>
|
|
@@ -137,9 +131,7 @@ export const AutocompleteTokenInputExample: Story = {
|
|
|
137
131
|
itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
|
|
138
132
|
menuToggleElement={
|
|
139
133
|
<>
|
|
140
|
-
<MenuLabel
|
|
141
|
-
Choose a book
|
|
142
|
-
</MenuLabel>
|
|
134
|
+
<MenuLabel mb={300}>Choose a book</MenuLabel>
|
|
143
135
|
<AutocompleteTokenInput />
|
|
144
136
|
<MenuToggleButton mt={200} width="100%" p="0">
|
|
145
137
|
<Icon name="chevron-down" aria-label="Open Menu" />
|
|
@@ -170,9 +162,7 @@ export const AutocompleteSelectExample: Story = {
|
|
|
170
162
|
itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
|
|
171
163
|
menuToggleElement={
|
|
172
164
|
<>
|
|
173
|
-
<MenuLabel
|
|
174
|
-
Choose a book
|
|
175
|
-
</MenuLabel>
|
|
165
|
+
<MenuLabel mb={300}>Choose a book</MenuLabel>
|
|
176
166
|
<AutocompleteSelect />
|
|
177
167
|
</>
|
|
178
168
|
}
|
|
@@ -247,9 +237,7 @@ const TestControlledMultiAutocomplete = (args: CustomAutocompleteArgs) => {
|
|
|
247
237
|
itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
|
|
248
238
|
menuToggleElement={
|
|
249
239
|
<>
|
|
250
|
-
<MenuLabel
|
|
251
|
-
Choose a book
|
|
252
|
-
</MenuLabel>
|
|
240
|
+
<MenuLabel mb={300}>Choose a book</MenuLabel>
|
|
253
241
|
<AutocompleteTokenInput />
|
|
254
242
|
<MenuToggleButton mt={200} width="100%" p="0">
|
|
255
243
|
<Icon name="chevron-down" aria-label="Open Menu" />
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
3
|
import type { TypeMenuItemProps } from "../MenuItem";
|
|
4
|
-
import type { TypeMenuRootProps } from "../MenuRoot";
|
|
5
4
|
import type { TypeAutocompleteProps } from "./AutocompleteTypes";
|
|
6
5
|
import { SingleAutocomplete } from "./SingleAutocomplete";
|
|
7
6
|
import { MultiAutocomplete } from "./MultiAutocomplete";
|
|
@@ -9,11 +8,11 @@ import { MultiAutocomplete } from "./MultiAutocomplete";
|
|
|
9
8
|
/**
|
|
10
9
|
* @link https://seeds.sproutsocial.com/components/autocomplete/
|
|
11
10
|
*
|
|
12
|
-
* @description A Autocomplete allows selecting
|
|
11
|
+
* @description A Autocomplete allows selecting {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menuitem MenuItems} from a list or groups of lists to populate an input.
|
|
13
12
|
*
|
|
14
13
|
*/
|
|
15
14
|
export const Autocomplete = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
16
|
-
multiSelect,
|
|
15
|
+
multiSelect = false,
|
|
17
16
|
...rest
|
|
18
17
|
}: TypeAutocompleteProps<I>) => {
|
|
19
18
|
if (multiSelect) {
|
|
@@ -1,34 +1,38 @@
|
|
|
1
|
-
import type { TypeMenuRootOwnProps } from "../MenuRoot";
|
|
2
1
|
import type {
|
|
3
2
|
TypeDownshiftComboboxProps,
|
|
4
3
|
TypeDownshiftMultiSelectProps,
|
|
5
4
|
} from "../MenuContext";
|
|
6
5
|
import type { TypeGetIsItemVisibleFn } from "../hooks/useItemFiltering";
|
|
7
|
-
import type { TypeMenuRootProps } from "../MenuRoot";
|
|
8
6
|
import type { TypeMenuItemProps } from "../MenuItem";
|
|
9
7
|
import type {
|
|
10
8
|
TypeChildrenOrItems,
|
|
11
9
|
TypeHigherOrderComponentProps,
|
|
12
10
|
} from "../types";
|
|
13
11
|
|
|
14
|
-
export type
|
|
15
|
-
I extends TypeMenuItemProps = TypeMenuItemProps
|
|
16
|
-
> = TypeHigherOrderComponentProps &
|
|
17
|
-
TypeChildrenOrItems<I> &
|
|
12
|
+
export type TypeBaseAutocompleteProps = TypeHigherOrderComponentProps &
|
|
18
13
|
TypeDownshiftComboboxProps & {
|
|
14
|
+
/** A function that can be passed to override the default filtering logic */
|
|
19
15
|
getIsItemVisible?: TypeGetIsItemVisibleFn;
|
|
16
|
+
/** Set multiSelect to true to allow multiple selections.
|
|
17
|
+
* Default: false*/
|
|
18
|
+
multiSelect?: boolean;
|
|
20
19
|
};
|
|
21
20
|
|
|
21
|
+
export type TypeSingleAutocompleteProps<
|
|
22
|
+
I extends TypeMenuItemProps = TypeMenuItemProps
|
|
23
|
+
> = TypeBaseAutocompleteProps & {
|
|
24
|
+
multiSelect?: false;
|
|
25
|
+
} & TypeChildrenOrItems<I>;
|
|
26
|
+
|
|
22
27
|
export type TypeMultiAutocompleteProps<
|
|
23
28
|
I extends TypeMenuItemProps = TypeMenuItemProps
|
|
24
|
-
> =
|
|
29
|
+
> = TypeBaseAutocompleteProps &
|
|
25
30
|
TypeDownshiftMultiSelectProps & {
|
|
26
|
-
|
|
31
|
+
multiSelect?: true;
|
|
32
|
+
/** Determines whether a multiselect autocomplete will display selected items in the dropdown */
|
|
27
33
|
showSelectedItemsInDropdown?: boolean;
|
|
28
|
-
}
|
|
34
|
+
} & TypeChildrenOrItems<I>;
|
|
29
35
|
|
|
30
36
|
export type TypeAutocompleteProps<
|
|
31
37
|
I extends TypeMenuItemProps = TypeMenuItemProps
|
|
32
|
-
> =
|
|
33
|
-
| (TypeSingleAutocompleteProps<I> & { multiSelect?: false })
|
|
34
|
-
| (TypeMultiAutocompleteProps<I> & { multiSelect: true });
|
|
38
|
+
> = TypeSingleAutocompleteProps<I> | TypeMultiAutocompleteProps<I>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import React, { useEffect } from "react";
|
|
2
1
|
import { useCombobox } from "downshift";
|
|
3
2
|
import { useMenuChildren } from "../hooks/useMenuChildren";
|
|
4
3
|
import { itemToString as defaultItemToString } from "../utils/downshiftDefaults";
|
|
@@ -8,7 +7,7 @@ import { useItemFiltering } from "../hooks/useItemFiltering";
|
|
|
8
7
|
import { MenuRoot } from "../MenuRoot";
|
|
9
8
|
|
|
10
9
|
import type { TypeMenuItemProps } from "../MenuItem";
|
|
11
|
-
import type {
|
|
10
|
+
import type { TypeSingleAutocompleteProps } from "./AutocompleteTypes";
|
|
12
11
|
|
|
13
12
|
export const SingleAutocomplete = <
|
|
14
13
|
I extends TypeMenuItemProps = TypeMenuItemProps
|
|
@@ -21,7 +20,7 @@ export const SingleAutocomplete = <
|
|
|
21
20
|
itemToString = defaultItemToString,
|
|
22
21
|
getIsItemVisible,
|
|
23
22
|
...useComboboxProps
|
|
24
|
-
}:
|
|
23
|
+
}: TypeSingleAutocompleteProps<I>) => {
|
|
25
24
|
/** Get a list of menuItems and children */
|
|
26
25
|
const { allMenuItems, children } = useMenuChildren({
|
|
27
26
|
children: childrenProp,
|
|
@@ -6,9 +6,9 @@ import { useCallback } from "react";
|
|
|
6
6
|
import type { TypeMenuItemProps } from "../MenuItem";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* @link https://seeds.sproutsocial.com/components/menu
|
|
9
|
+
* @link https://seeds.sproutsocial.com/components/seeds-react-menu/#menucontent
|
|
10
10
|
*
|
|
11
|
-
* @description MenuContent is used to contain lists or grouped lists of {@link https://
|
|
11
|
+
* @description MenuContent is used to contain lists or grouped lists of {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menuitem MenuItems}.
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
14
|
* <ActionMenu>
|
|
@@ -24,8 +24,8 @@ import type { TypeMenuItemProps } from "../MenuItem";
|
|
|
24
24
|
* <MenuFooter>Footer</MenuFooter>
|
|
25
25
|
* </ActionMenu>
|
|
26
26
|
*
|
|
27
|
-
* @see {@link https://seeds.sproutsocial.com/components/menu
|
|
28
|
-
* @see {@link https://seeds.sproutsocial.com/components/menu
|
|
27
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menuitem MenuItem}
|
|
28
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menugroup MenuGroup}
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
const MenuContent = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
package/src/MenuContext.tsx
CHANGED
|
@@ -28,8 +28,11 @@ export interface TypeDownshiftMultiSelectProps
|
|
|
28
28
|
>
|
|
29
29
|
> {
|
|
30
30
|
// Multi Select Props with conflicting useSelect prop names
|
|
31
|
+
/** Optional override to the built-in getA11yStatusMessage function for multiple selection*/
|
|
31
32
|
getA11yMultiStatusMessage?: UseMultipleSelectionProps<TypeInternalItemProps>["getA11yStatusMessage"];
|
|
33
|
+
/** Optional override to the built-in onStateChange function for multiple selection*/
|
|
32
34
|
onMultiSelectStateChange?: UseMultipleSelectionProps<TypeInternalItemProps>["onStateChange"];
|
|
35
|
+
/** Optional override to the built-in stateReducer function for multiple selection*/
|
|
33
36
|
multiSelectStateReducer?: UseMultipleSelectionProps<TypeInternalItemProps>["stateReducer"];
|
|
34
37
|
}
|
|
35
38
|
|
|
@@ -125,7 +128,9 @@ export interface TypeMenuProviderProps {
|
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
export interface TypeMenuToggleContext extends TypeMenuContext {
|
|
128
|
-
ref?:
|
|
131
|
+
ref?:
|
|
132
|
+
| React.RefObject<any>
|
|
133
|
+
| ((arg0: React.ElementRef<any> | HTMLElement) => void);
|
|
129
134
|
toggle?: () => void;
|
|
130
135
|
show?: () => void;
|
|
131
136
|
hide?: () => void;
|
|
@@ -3,11 +3,11 @@ import type { TypeMenuFooterProps } from "./MenuFooterTypes";
|
|
|
3
3
|
import { StyledMenuFooterBox } from "./styles";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @link https://seeds.sproutsocial.com/components/menu
|
|
6
|
+
* @link https://seeds.sproutsocial.com/components/seeds-react-menu/#menufooter
|
|
7
7
|
*
|
|
8
8
|
* @description MenuFooter is a container used to hold footer content within a Menu.
|
|
9
9
|
*
|
|
10
|
-
* @see {@link https://seeds.sproutsocial.com/components/menu
|
|
10
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menuheader MenuHeader}
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
export const MenuFooter = ({ children, ...rest }: TypeMenuFooterProps) => {
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { TypeMenuItemProps } from "../MenuItem";
|
|
3
|
-
import type { TypeMenuRootProps } from "../MenuRoot";
|
|
4
3
|
import type { TypeMenuGroupProps } from "./MenuGroupTypes";
|
|
5
4
|
import { StyledMenuGroup, StyledTitle, StyledMenuGroupUl } from "./styles";
|
|
6
5
|
import { mapMenuItems } from "../hooks/useMenuChildren";
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
|
-
* @link https://seeds.sproutsocial.com/components/menu
|
|
8
|
+
* @link https://seeds.sproutsocial.com/components/seeds-react-menu/#menugroup
|
|
10
9
|
*
|
|
11
|
-
* @description MenuGroup is used to group lists of {@link https://
|
|
10
|
+
* @description MenuGroup is used to group lists of {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menuitem MenuItems}.
|
|
12
11
|
*
|
|
13
12
|
* @example
|
|
14
13
|
* <ActionMenu>
|
|
@@ -22,7 +21,8 @@ import { mapMenuItems } from "../hooks/useMenuChildren";
|
|
|
22
21
|
* </MenuContent>
|
|
23
22
|
* </ActionMenu>
|
|
24
23
|
*
|
|
25
|
-
* @see {@link https://seeds.sproutsocial.com/components/menu
|
|
24
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menuitem MenuItem}
|
|
25
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menucontent MenuContent}
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
const MenuGroup = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
@@ -8,11 +8,13 @@ import {
|
|
|
8
8
|
import type { TypeMenuHeaderProps } from "./MenuHeaderTypes";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* @link https://seeds.sproutsocial.com/components/menu
|
|
11
|
+
* @link https://seeds.sproutsocial.com/components/seeds-react-menu/#menuheader
|
|
12
12
|
*
|
|
13
13
|
* @description MenuHeader is a container used to hold header content within a Menu.
|
|
14
14
|
*
|
|
15
|
-
* @see {@link https://seeds.sproutsocial.com/components/menu
|
|
15
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menufooter MenuFooter}
|
|
16
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menusearchinput MenuSearchInput}
|
|
17
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menusearchtokeninput MenuSearchTokenInput}
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
20
|
const MenuHeader = ({
|
package/src/MenuLabel.tsx
CHANGED
|
@@ -2,10 +2,40 @@ import React from "react";
|
|
|
2
2
|
import { Label, type TypeLabelProps } from "@sproutsocial/seeds-react-label";
|
|
3
3
|
import { useMenuToggleContext } from "./MenuContext";
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export interface TypeMenuLabelProps extends Omit<TypeLabelProps, "htmlFor"> {
|
|
6
|
+
// The children and required types are redundant, but they power the MenuLabel PropTable.
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
/** Whether making a selection in the associated menu is required. */
|
|
9
|
+
required?: boolean;
|
|
10
|
+
/** ID of the associated form element. This prop is never valid to pass into MenuLabel
|
|
11
|
+
* because MenuLabel passes its own htmlFor prop to the underlying Label component.*/
|
|
12
|
+
htmlFor?: never;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @link https://seeds.sproutsocial.com/components/seeds-react-menu/#menulabel
|
|
17
|
+
*
|
|
18
|
+
* @description MenuLabel extends Label to create an accessible label for a Menu's toggle element.
|
|
19
|
+
* A MenuLabel will automatically be associated with the corresponding menuToggleElement,
|
|
20
|
+
* so an `htmlFor` prop is not necessary or allowed.
|
|
21
|
+
*
|
|
22
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menutogglebutton MenuToggleButton}
|
|
23
|
+
* @see {@link https://seeds.sproutsocial.com/components/label/ Label}
|
|
24
|
+
*/
|
|
25
|
+
export const MenuLabel = ({
|
|
26
|
+
children,
|
|
27
|
+
required = false,
|
|
28
|
+
...rest
|
|
29
|
+
}: TypeMenuLabelProps) => {
|
|
6
30
|
const { getLabelProps } = useMenuToggleContext();
|
|
7
31
|
return (
|
|
8
|
-
|
|
32
|
+
// This fallback id should never be used, but it is here to satisfy TypeScript since getLabelProps could be undefined.
|
|
33
|
+
<Label
|
|
34
|
+
htmlFor="fallback_menu_label"
|
|
35
|
+
required={required}
|
|
36
|
+
{...getLabelProps?.()}
|
|
37
|
+
{...rest}
|
|
38
|
+
>
|
|
9
39
|
{children}
|
|
10
40
|
</Label>
|
|
11
41
|
);
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useLayoutEffect,
|
|
5
|
+
type RefObject,
|
|
6
|
+
} from "react";
|
|
2
7
|
import styled from "styled-components";
|
|
3
8
|
import { Popout, type TypePopoutProps } from "@sproutsocial/seeds-react-popout";
|
|
9
|
+
import { mergeRefs } from "@sproutsocial/seeds-react-utilities";
|
|
4
10
|
|
|
5
11
|
import {
|
|
6
12
|
MenuContentProvider,
|
|
@@ -12,10 +18,16 @@ import {
|
|
|
12
18
|
import { MOTION_DURATION_FAST } from "@sproutsocial/seeds-motion/unitless";
|
|
13
19
|
|
|
14
20
|
export interface TypeMenuRootOwnProps {
|
|
21
|
+
/** The element that is or contains an element that will toggle the menu when clicked.
|
|
22
|
+
* May contain labels, buttons, inputs or other content as needed. */
|
|
15
23
|
menuToggleElement: React.ReactElement<any>;
|
|
24
|
+
/** Props to pass through to the underlying Popout.
|
|
25
|
+
* Any Popout prop is valid except those explicitly omitted. */
|
|
16
26
|
popoutProps?: Partial<
|
|
17
27
|
Omit<TypePopoutProps, "isOpen" | "setIsOpen" | "content" | "children">
|
|
18
28
|
>;
|
|
29
|
+
/** The width of the Popout content.
|
|
30
|
+
* Defaults to 200px or the width of the toggle element, whichever is greater.*/
|
|
19
31
|
width?: TypePopoutProps["width"];
|
|
20
32
|
}
|
|
21
33
|
export interface TypeMenuRootProps
|
|
@@ -102,16 +114,14 @@ export const MenuPopout = ({
|
|
|
102
114
|
};
|
|
103
115
|
|
|
104
116
|
/**
|
|
105
|
-
* @
|
|
106
|
-
*
|
|
107
|
-
* @description MenuRoot is a utility component used to handle {@link https://github.com/sproutsocial/seeds/blob/dev/seeds-react/seeds-react-menu/src/MenuContext.tsx | MenuContext}. This component should rarely, if ever, be used directly unless building a custom menu type.
|
|
117
|
+
* @description MenuRoot is a utility component used to handle React Context for our Menus. This component should rarely, if ever, be used directly unless building a custom menu type.
|
|
108
118
|
*/
|
|
109
119
|
|
|
110
120
|
export const MenuRoot = ({
|
|
111
121
|
children,
|
|
112
122
|
menuToggleElement,
|
|
113
123
|
popoutProps: { placement = "bottom", focusLockProps, ...popoutProps } = {},
|
|
114
|
-
width
|
|
124
|
+
width,
|
|
115
125
|
...contextProps
|
|
116
126
|
}: TypeMenuRootProps) => {
|
|
117
127
|
const {
|
|
@@ -121,17 +131,27 @@ export const MenuRoot = ({
|
|
|
121
131
|
inputValue,
|
|
122
132
|
hiddenItemsCount = 0,
|
|
123
133
|
items,
|
|
134
|
+
getMenuProps,
|
|
124
135
|
} = contextProps;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
|
|
137
|
+
const toggleElementRef = React.useRef<HTMLElement>(null);
|
|
138
|
+
const [finalWidth, setFinalWidth] = React.useState<typeof width>();
|
|
139
|
+
|
|
140
|
+
const min_width = 200;
|
|
141
|
+
|
|
142
|
+
useLayoutEffect(() => {
|
|
143
|
+
if (width) {
|
|
144
|
+
setFinalWidth(width);
|
|
145
|
+
} else if (toggleElementRef.current?.offsetWidth) {
|
|
146
|
+
const toggleWidth = toggleElementRef.current.offsetWidth;
|
|
147
|
+
setFinalWidth(Math.max(toggleWidth, min_width));
|
|
148
|
+
} else {
|
|
149
|
+
setFinalWidth(min_width);
|
|
150
|
+
}
|
|
151
|
+
}, [width, toggleElementRef.current?.offsetWidth]);
|
|
131
152
|
|
|
132
153
|
useEffect(() => {
|
|
133
154
|
if (!inputValue) return;
|
|
134
|
-
|
|
135
155
|
if (items && hiddenItemsCount >= items.length && isOpen) {
|
|
136
156
|
closeMenu?.();
|
|
137
157
|
}
|
|
@@ -139,6 +159,9 @@ export const MenuRoot = ({
|
|
|
139
159
|
|
|
140
160
|
const menuContext = useMenu(contextProps);
|
|
141
161
|
|
|
162
|
+
// Hack to suppress error: "downshift: You forgot to call the getMenuProps getter function on your component / element."
|
|
163
|
+
getMenuProps?.({}, { suppressRefError: true });
|
|
164
|
+
|
|
142
165
|
return (
|
|
143
166
|
<MenuPopout
|
|
144
167
|
placement={placement}
|
|
@@ -150,27 +173,23 @@ export const MenuRoot = ({
|
|
|
150
173
|
{children}
|
|
151
174
|
</MenuContentProvider>
|
|
152
175
|
}
|
|
176
|
+
width={finalWidth}
|
|
153
177
|
{...popoutProps}
|
|
154
178
|
>
|
|
155
179
|
{(toggleProps) => (
|
|
156
|
-
<MenuToggleProvider
|
|
180
|
+
<MenuToggleProvider
|
|
181
|
+
menuContext={{
|
|
182
|
+
...menuContext,
|
|
183
|
+
...toggleProps,
|
|
184
|
+
ref: mergeRefs<HTMLElement>(
|
|
185
|
+
toggleProps.ref,
|
|
186
|
+
toggleElementRef
|
|
187
|
+
) as RefObject<HTMLElement>,
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
157
190
|
{menuToggleElement}
|
|
158
191
|
</MenuToggleProvider>
|
|
159
192
|
)}
|
|
160
193
|
</MenuPopout>
|
|
161
194
|
);
|
|
162
195
|
};
|
|
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>
|
|
175
|
-
);
|
|
176
|
-
};
|
|
@@ -10,23 +10,17 @@ import {
|
|
|
10
10
|
} from "../MenuContext";
|
|
11
11
|
|
|
12
12
|
export interface TypeMenuSearchInputProps extends TypeInputProps {
|
|
13
|
-
|
|
13
|
+
/** A function that can be passed to override the default filtering logic */
|
|
14
14
|
getIsItemVisible?: TypeGetIsItemVisibleFn;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
* aria-activedescendant=""
|
|
20
|
-
* aria-autocomplete="list"
|
|
21
|
-
* aria-controls="downshift-:r1:-menu"
|
|
22
|
-
* aria-expanded="true"
|
|
23
|
-
* />
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @link https://seeds.sproutsocial.com/components/menu-header/
|
|
18
|
+
* @link https://seeds.sproutsocial.com/components/seeds-react-menu/#menusearchinput
|
|
28
19
|
*
|
|
29
20
|
* @description MenuSearchInput is search input that can be added to the MenuHeader to filter the items.
|
|
21
|
+
*
|
|
22
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menuheader MenuHeader}
|
|
23
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menusearchtokeninput MenuSearchTokenInput}
|
|
30
24
|
*/
|
|
31
25
|
export const MenuSearchInput = ({
|
|
32
26
|
getIsItemVisible,
|
|
@@ -14,14 +14,17 @@ import {
|
|
|
14
14
|
} from "../MenuTokenInput";
|
|
15
15
|
|
|
16
16
|
export interface TypeMenuSearchTokenInputProps extends TypeMenuTokenInputProps {
|
|
17
|
-
|
|
17
|
+
/** A function that can be passed to override the default filtering logic */
|
|
18
18
|
getIsItemVisible?: TypeGetIsItemVisibleFn;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* @link https://seeds.sproutsocial.com/components/
|
|
22
|
+
* @link https://seeds.sproutsocial.com/components/seeds-react-menu/#menusearchtokeninput
|
|
23
23
|
*
|
|
24
|
-
* @description MenuSearchTokenInput is
|
|
24
|
+
* @description MenuSearchTokenInput is a token input that can be added to the MenuHeader to filter the items.
|
|
25
|
+
*
|
|
26
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menuheader MenuHeader}
|
|
27
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menusearchinput MenuSearchInput}
|
|
25
28
|
*/
|
|
26
29
|
export const MenuSearchTokenInput = ({
|
|
27
30
|
getIsItemVisible,
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import React, { type ReactNode } from "react";
|
|
2
1
|
import { Button, type TypeButtonProps } from "@sproutsocial/seeds-react-button";
|
|
3
2
|
import { useMenuToggleContext } from "../MenuContext";
|
|
4
3
|
|
|
5
|
-
export interface TypeMenuToggleButtonProps extends
|
|
6
|
-
children: ReactNode;
|
|
7
|
-
}
|
|
4
|
+
export interface TypeMenuToggleButtonProps extends TypeButtonProps {}
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
/**
|
|
7
|
+
* @link https://seeds.sproutsocial.com/components/seeds-react-menu/#menutogglebutton
|
|
8
|
+
* @param props - MenuToggleButton accepts all the same props as Button.
|
|
9
|
+
* @description MenuToggleButton extends the Button component to create a toggle for a menu.
|
|
10
|
+
* @see {@link https://seeds.sproutsocial.com/components/button/ Button}
|
|
11
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menulabel MenuLabel}
|
|
12
|
+
*/
|
|
13
|
+
export const MenuToggleButton = (props: TypeMenuToggleButtonProps) => {
|
|
13
14
|
const { getToggleButtonProps, isListbox, getDropdownProps, ref, ariaProps } =
|
|
14
15
|
useMenuToggleContext();
|
|
15
16
|
return (
|
|
@@ -22,9 +23,7 @@ export const MenuToggleButton = ({
|
|
|
22
23
|
...ariaProps,
|
|
23
24
|
...(isListbox ? {} : { role: "button", "aria-haspopup": "menu" }),
|
|
24
25
|
})}
|
|
25
|
-
{...
|
|
26
|
-
|
|
27
|
-
{children}
|
|
28
|
-
</Button>
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
29
28
|
);
|
|
30
29
|
};
|
package/src/MenuTokenInput.tsx
CHANGED
|
@@ -26,13 +26,19 @@ const StyledTokenInputWrapper = styled.div`
|
|
|
26
26
|
`;
|
|
27
27
|
|
|
28
28
|
export interface TypeMenuTokenInputProps extends TypeTokenInputProps {
|
|
29
|
-
|
|
30
|
-
getTokenProps?: (
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
/** A function that can be passed to override the default token props */
|
|
30
|
+
getTokenProps?: (itemId: string) => Partial<TypeTokenSpec>;
|
|
31
|
+
/** Specify the menu context to use. MenuToggleContext is the default.
|
|
32
|
+
* This prop is only needed for specialized use cases. */
|
|
33
33
|
MenuContext?: typeof MenuToggleContext | typeof MenuContentContext;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* @description MenuTokenInput is the basis of the MenuSearchTokenInput and AutocompleteTokenInput components
|
|
38
|
+
*
|
|
39
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menusearchinput MenuSearchInput}
|
|
40
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menusearchtokeninput MenuSearchTokenInput}
|
|
41
|
+
*/
|
|
36
42
|
export const MenuTokenInput = ({
|
|
37
43
|
inputProps = {},
|
|
38
44
|
getTokenProps,
|
|
@@ -10,11 +10,12 @@ import type { TypeMultiSelectMenuProps } from "./MultiSelectMenuTypes";
|
|
|
10
10
|
import type { TypeInternalItemProps } from "../types";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @link https://seeds.sproutsocial.com/components/
|
|
13
|
+
* @link https://seeds.sproutsocial.com/components/multi-select-menu/
|
|
14
14
|
*
|
|
15
|
-
* @description A MultiSelectMenu allows multiple {@link https://
|
|
15
|
+
* @description A MultiSelectMenu allows multiple {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menuitem MenuItems} to be selected from a list or groups of lists.
|
|
16
16
|
*
|
|
17
|
-
* @see {@link https://seeds.sproutsocial.com/components/menu
|
|
17
|
+
* @see {@link https://seeds.sproutsocial.com/components/single-select-menu/ SingleSelectMenu}
|
|
18
|
+
* @see {@link https://seeds.sproutsocial.com/components/seeds-react-menu/#menugroup MenuGroup}
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
export const MultiSelectMenu = <
|
|
@@ -30,7 +30,7 @@ Feature: NestedMenu
|
|
|
30
30
|
|
|
31
31
|
Scenario: User can select a child menu without closing the menu
|
|
32
32
|
Given the NestedMenu is open
|
|
33
|
-
When
|
|
33
|
+
When a menu item that triggers a child menu is clicked
|
|
34
34
|
Then the menu will stay open
|
|
35
35
|
And the menu will not use its default open/close behavior
|
|
36
36
|
|
|
@@ -75,7 +75,7 @@ Feature: NestedMenu
|
|
|
75
75
|
|
|
76
76
|
Scenario: A Menu stays open when a menu item with a childMenuId is clicked
|
|
77
77
|
Given the NestedMenu is open
|
|
78
|
-
When
|
|
78
|
+
When a menu item that triggers a child menu is clicked
|
|
79
79
|
Then the menu will stay open
|
|
80
80
|
And the menu will not use its default open/close behavior
|
|
81
81
|
|
|
@@ -85,6 +85,9 @@ export const NestedMenu = <
|
|
|
85
85
|
onIsOpenChange: onNestedIsOpenChange,
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
+
// Hack to suppress error: "downshift: You forgot to call the getMenuProps getter function on your component / element."
|
|
89
|
+
rootMenuContextProps.getMenuProps?.({}, { suppressRefError: true });
|
|
90
|
+
|
|
88
91
|
/**
|
|
89
92
|
* Handling the state of the NestedMenu content
|
|
90
93
|
*/
|
|
@@ -27,13 +27,16 @@ export interface TypeNestedMenuHeaderProps extends TypeMenuHeaderProps {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface TypeNestedMenuItemProps extends TypeMenuItemProps {
|
|
30
|
+
/** The id of the menu to be displayed when this item is selected. */
|
|
30
31
|
childMenuId?: string;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export interface TypeNestedMenuProps<
|
|
34
35
|
I extends TypeMenuItemProps = TypeNestedMenuItemProps
|
|
35
36
|
> extends Omit<TypeActionMenuBaseProps, "MenuRootComponent"> {
|
|
37
|
+
/** The id of the menu to display when the NestedMenu is opened. */
|
|
36
38
|
initialMenuId: string;
|
|
39
|
+
/** An optional callback to fire when the back button is clicked.*/
|
|
37
40
|
onBackButtonClick?: (menuId: string) => void;
|
|
38
41
|
/** Sets the default localized label for the back arrow in all child menus.
|
|
39
42
|
* This label can be overridden by including backArrowLabel in a menu's menuHeaderProps. */
|