@sproutsocial/seeds-react-menu 0.4.1 → 0.5.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 +17 -0
- package/dist/esm/index.js +221 -137
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +126 -101
- package/dist/index.d.ts +126 -101
- package/dist/index.js +236 -151
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ActionMenu/ActionMenu.tsx +10 -3
- package/src/ActionMenu/ActionMenuTypes.ts +10 -6
- package/src/ActionMenu/__tests__/ActionMenu.test.tsx +0 -22
- package/src/ActionMenu/__tests__/ActionMenu.typetest.tsx +32 -3
- package/src/Autocomplete/Autocomplete.stories.tsx +24 -0
- package/src/Autocomplete/Autocomplete.tsx +10 -4
- package/src/Autocomplete/AutocompleteTypes.ts +6 -3
- package/src/Autocomplete/__tests__/Autocomplete.typetest.tsx +34 -3
- package/src/MenuContent/MenuContent.tsx +14 -1
- package/src/MenuContent/MenuContentTypes.ts +5 -5
- package/src/MenuContent/__tests__/MenuContent.typetest.tsx +17 -1
- package/src/MenuGroup/MenuGroup.feature +6 -6
- package/src/MenuGroup/MenuGroup.tsx +2 -1
- package/src/MenuGroup/MenuGroupTypes.ts +4 -6
- package/src/MenuGroup/__tests__/MenuGroup.typetest.tsx +23 -4
- package/src/MenuItem/MenuItem.tsx +0 -1
- package/src/MultiSelectMenu/MultiSelectMenu.tsx +10 -3
- package/src/MultiSelectMenu/MultiSelectMenuTypes.ts +8 -6
- package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +0 -43
- package/src/MultiSelectMenu/__tests__/MultiSelectMenu.typetest.tsx +29 -2
- package/src/SingleSelectMenu/SingleSelectMenu.tsx +10 -3
- package/src/SingleSelectMenu/SingleSelectMenuTypes.ts +7 -5
- package/src/SingleSelectMenu/__tests__/SingleSelectMenu.typetest.tsx +32 -2
- package/src/__tests__/Menu.test.tsx +38 -0
- package/src/hooks/useMenuChildren.tsx +210 -0
- package/src/index.ts +1 -1
- package/src/types.ts +26 -1
- package/src/hooks/useItemsFromChildren.tsx +0 -131
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type UseSelectStateChange,
|
|
6
6
|
} from "downshift";
|
|
7
7
|
import type { TypeDefaultItemProps } from "../types";
|
|
8
|
-
import {
|
|
8
|
+
import { useMenuChildren } from "../hooks/useMenuChildren";
|
|
9
9
|
import {
|
|
10
10
|
defaultUseSelectProps,
|
|
11
11
|
useSelectStateReducer,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { MenuRoot } from "../MenuRoot";
|
|
14
14
|
import { itemToString } from "../utils/itemToString";
|
|
15
15
|
import type { TypeActionMenuProps } from "./ActionMenuTypes";
|
|
16
|
+
import { MenuItem } from "../MenuItem/index";
|
|
16
17
|
|
|
17
18
|
// Manually fire the onClick handler when Enter or Space is pressed
|
|
18
19
|
const handleStateChange = (
|
|
@@ -82,11 +83,17 @@ const handleStateChange = (
|
|
|
82
83
|
* @see {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/MultiSelectMenu | MultiSelectMenu}
|
|
83
84
|
*/
|
|
84
85
|
export const ActionMenu = ({
|
|
85
|
-
children,
|
|
86
|
+
children: childrenProp,
|
|
87
|
+
items,
|
|
88
|
+
MenuItemComponent = MenuItem,
|
|
86
89
|
stateReducer: externalStateReducer,
|
|
87
90
|
...useSelectProps
|
|
88
91
|
}: TypeActionMenuProps) => {
|
|
89
|
-
const { allMenuItems, menuItemsMap } =
|
|
92
|
+
const { allMenuItems, menuItemsMap, children } = useMenuChildren({
|
|
93
|
+
children: childrenProp,
|
|
94
|
+
items,
|
|
95
|
+
MenuItemComponent,
|
|
96
|
+
});
|
|
90
97
|
const [currentSelectedItem, setCurrentSelectedItem] =
|
|
91
98
|
React.useState<UseSelectState<TypeDefaultItemProps>["selectedItem"]>(null);
|
|
92
99
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type TypeMenuRootOwnProps } from "../MenuRoot";
|
|
2
2
|
import type { TypeDownshiftSelectProps } from "../MenuContext";
|
|
3
|
+
import type { TypeChildrenOrItems } from "../types";
|
|
3
4
|
|
|
4
5
|
type TypeInvalidMenuProps =
|
|
5
6
|
| "initialSelectedItem"
|
|
6
7
|
| "defaultSelectedItem"
|
|
7
|
-
| "selectedItem"
|
|
8
|
-
|
|
8
|
+
| "selectedItem"
|
|
9
|
+
// We omit this so we can retype it.
|
|
10
|
+
| "items";
|
|
11
|
+
|
|
12
|
+
interface TypeActionMenuBaseProps
|
|
9
13
|
extends TypeMenuRootOwnProps,
|
|
10
|
-
Omit<TypeDownshiftSelectProps, TypeInvalidMenuProps> {
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
Omit<TypeDownshiftSelectProps, TypeInvalidMenuProps> {}
|
|
15
|
+
|
|
16
|
+
export type TypeActionMenuProps = TypeActionMenuBaseProps & TypeChildrenOrItems;
|
|
@@ -11,28 +11,6 @@ import { ActionMenu } from "../ActionMenu";
|
|
|
11
11
|
import { MenuContent, MenuItem, MenuToggleButton } from "../../index";
|
|
12
12
|
|
|
13
13
|
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
14
|
test.each([
|
|
37
15
|
{
|
|
38
16
|
action: "click",
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ActionMenu,
|
|
3
|
+
MenuContent,
|
|
4
|
+
MenuHeader,
|
|
5
|
+
MenuFooter,
|
|
6
|
+
MenuItem,
|
|
7
|
+
} from "../../index";
|
|
2
8
|
|
|
3
9
|
export const ActionMenuTypeTest = () => (
|
|
4
10
|
<>
|
|
@@ -14,6 +20,17 @@ export const ActionMenuTypeTest = () => (
|
|
|
14
20
|
<MenuContent>Test</MenuContent>
|
|
15
21
|
<MenuFooter>Test</MenuFooter>
|
|
16
22
|
</ActionMenu>
|
|
23
|
+
{/* Valid ActionMenu allows items to be passed using the items prop */}
|
|
24
|
+
<ActionMenu
|
|
25
|
+
menuToggleElement={<>test</>}
|
|
26
|
+
items={[{ children: "Item Name", id: "item-1" }]}
|
|
27
|
+
/>
|
|
28
|
+
{/* Valid ActionMenu allows items prop to be rendered using a MenuItemComponent prop */}
|
|
29
|
+
<ActionMenu
|
|
30
|
+
menuToggleElement={<>test</>}
|
|
31
|
+
items={[{ children: "Item Name", id: "item-1" }]}
|
|
32
|
+
MenuItemComponent={MenuItem}
|
|
33
|
+
/>
|
|
17
34
|
{/* Valid ActionMenu accepts Downshift props and functions for useSelect other than selectedItem */}
|
|
18
35
|
<ActionMenu
|
|
19
36
|
menuToggleElement={<>test</>}
|
|
@@ -34,8 +51,20 @@ export const ActionMenuTypeTest = () => (
|
|
|
34
51
|
>
|
|
35
52
|
Test
|
|
36
53
|
</ActionMenu>
|
|
37
|
-
{/* @ts-expect-error - when no children provided */}
|
|
38
|
-
<ActionMenu menuToggleElement=
|
|
54
|
+
{/* @ts-expect-error - when no children or items provided */}
|
|
55
|
+
<ActionMenu menuToggleElement={<>test</>} />
|
|
56
|
+
{/* @ts-expect-error - when both children and MenuItemComponent provided */}
|
|
57
|
+
<ActionMenu menuToggleElement={<>test</>} MenuItemComponent={MenuItem}>
|
|
58
|
+
Children
|
|
59
|
+
</ActionMenu>
|
|
60
|
+
{/* @ts-expect-error - when both children and items provided */}
|
|
61
|
+
<ActionMenu menuToggleElement={<>test</>} items={[]}>
|
|
62
|
+
Children
|
|
63
|
+
</ActionMenu>
|
|
64
|
+
{/* @ts-expect-error - when MenuItemComponent is of the wrong type */}
|
|
65
|
+
<ActionMenu menuToggleElement={<>test</>} MenuItemComponent={MenuContent}>
|
|
66
|
+
Children
|
|
67
|
+
</ActionMenu>
|
|
39
68
|
|
|
40
69
|
<ActionMenu
|
|
41
70
|
menuToggleElement={<>test</>}
|
|
@@ -62,6 +62,30 @@ export const AutocompleteTextInputExample: Story = {
|
|
|
62
62
|
},
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
+
export const AutocompleteWithItemsAsProps: Story = {
|
|
66
|
+
name: "With Items as Props",
|
|
67
|
+
render: (args) => {
|
|
68
|
+
return (
|
|
69
|
+
<Autocomplete
|
|
70
|
+
itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
|
|
71
|
+
menuToggleElement={
|
|
72
|
+
<>
|
|
73
|
+
<MenuLabel htmlFor="my-combobox" mb={300}>
|
|
74
|
+
Choose a book
|
|
75
|
+
</MenuLabel>
|
|
76
|
+
<AutocompleteInput />
|
|
77
|
+
</>
|
|
78
|
+
}
|
|
79
|
+
items={TEST_BOOKS.map(({ id, title }) => ({
|
|
80
|
+
children: title,
|
|
81
|
+
id,
|
|
82
|
+
}))}
|
|
83
|
+
{...(args as object)}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
65
89
|
export const AutocompleteTextWithButtonExample: Story = {
|
|
66
90
|
name: "Text Input w/ Button",
|
|
67
91
|
render: (args) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useCombobox, type UseComboboxState } from "downshift";
|
|
3
|
-
import {
|
|
3
|
+
import { useMenuChildren } from "../hooks/useMenuChildren";
|
|
4
4
|
import {
|
|
5
5
|
defaultUseComboboxProps,
|
|
6
6
|
useComboboxStateReducer,
|
|
@@ -9,6 +9,7 @@ import { itemToString as defaultItemToString } from "../utils/itemToString";
|
|
|
9
9
|
import { MenuRoot } from "../MenuRoot";
|
|
10
10
|
import type { TypeAutocompleteProps } from "./AutocompleteTypes";
|
|
11
11
|
import type { TypeDefaultItemProps } from "../types";
|
|
12
|
+
import { MenuItem } from "../MenuItem/index";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* @link https://seeds.sproutsocial.com/components/multiselect-menu/
|
|
@@ -19,15 +20,20 @@ import type { TypeDefaultItemProps } from "../types";
|
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
export const Autocomplete = ({
|
|
22
|
-
children,
|
|
23
|
+
children: childrenProp,
|
|
23
24
|
stateReducer: externalStateReducer,
|
|
25
|
+
items: itemsProp,
|
|
26
|
+
MenuItemComponent = MenuItem,
|
|
24
27
|
itemToString = defaultItemToString,
|
|
25
28
|
getIsItemVisible = (item, inputValue) =>
|
|
26
29
|
itemToString(item).toLowerCase().startsWith(inputValue.toLowerCase()),
|
|
27
30
|
...useComboboxProps
|
|
28
31
|
}: TypeAutocompleteProps) => {
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
const { allMenuItems, children } = useMenuChildren({
|
|
33
|
+
children: childrenProp,
|
|
34
|
+
items: itemsProp,
|
|
35
|
+
MenuItemComponent,
|
|
36
|
+
});
|
|
31
37
|
const [items, setItems] = React.useState(allMenuItems);
|
|
32
38
|
|
|
33
39
|
/**
|
|
@@ -3,13 +3,16 @@ import type {
|
|
|
3
3
|
TypeDownshiftComboboxProps,
|
|
4
4
|
TypeMenuContext,
|
|
5
5
|
} from "../MenuContext";
|
|
6
|
+
import type { TypeChildrenOrItems } from "../types";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
interface TypeAutocompleteBaseProps
|
|
8
9
|
extends TypeMenuRootOwnProps,
|
|
9
|
-
TypeDownshiftComboboxProps {
|
|
10
|
-
children: TypeMenuRootProps["children"];
|
|
10
|
+
Omit<TypeDownshiftComboboxProps, "items"> {
|
|
11
11
|
getIsItemVisible?: (
|
|
12
12
|
item: TypeMenuContext["items"][number],
|
|
13
13
|
inputValue: string
|
|
14
14
|
) => boolean;
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
export type TypeAutocompleteProps = TypeAutocompleteBaseProps &
|
|
18
|
+
TypeChildrenOrItems;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Autocomplete,
|
|
4
|
+
MenuContent,
|
|
5
|
+
MenuHeader,
|
|
6
|
+
MenuFooter,
|
|
7
|
+
MenuItem,
|
|
8
|
+
} from "../../index";
|
|
3
9
|
|
|
4
10
|
export const AutocompleteTypeTest = () => (
|
|
5
11
|
<>
|
|
@@ -15,6 +21,17 @@ export const AutocompleteTypeTest = () => (
|
|
|
15
21
|
<MenuContent>Test</MenuContent>
|
|
16
22
|
<MenuFooter>Test</MenuFooter>
|
|
17
23
|
</Autocomplete>
|
|
24
|
+
{/* Valid Autocomplete allows items to be passed using the items prop */}
|
|
25
|
+
<Autocomplete
|
|
26
|
+
menuToggleElement={<>test</>}
|
|
27
|
+
items={[{ children: "Item Name", id: "item-1" }]}
|
|
28
|
+
/>
|
|
29
|
+
{/* Valid Autocomplete allows items to be rendered using MenuItemComponent */}
|
|
30
|
+
<Autocomplete
|
|
31
|
+
menuToggleElement={<>test</>}
|
|
32
|
+
items={[{ children: "Item Name", id: "item-1" }]}
|
|
33
|
+
MenuItemComponent={MenuItem}
|
|
34
|
+
/>
|
|
18
35
|
{/* Valid Autocomplete allows getIsItemVisible function */}
|
|
19
36
|
<Autocomplete menuToggleElement={<>test</>} getIsItemVisible={() => true}>
|
|
20
37
|
Test
|
|
@@ -39,8 +56,22 @@ export const AutocompleteTypeTest = () => (
|
|
|
39
56
|
>
|
|
40
57
|
Test
|
|
41
58
|
</Autocomplete>
|
|
42
|
-
{/* @ts-expect-error - when no children provided */}
|
|
43
|
-
<Autocomplete menuToggleElement=
|
|
59
|
+
{/* @ts-expect-error - when no children or items provided */}
|
|
60
|
+
<Autocomplete menuToggleElement={<>test</>} />
|
|
61
|
+
{/* @ts-expect-error - when both children and items provided */}
|
|
62
|
+
<Autocomplete menuToggleElement={<>test</>} items={[]}>
|
|
63
|
+
Test
|
|
64
|
+
</Autocomplete>
|
|
65
|
+
{/* @ts-expect-error - when MenuItemComponent is passed with children */}
|
|
66
|
+
<Autocomplete menuToggleElement={<>test</>} MenuItemComponent={MenuItem}>
|
|
67
|
+
Test
|
|
68
|
+
</Autocomplete>
|
|
69
|
+
<Autocomplete
|
|
70
|
+
menuToggleElement={<>test</>}
|
|
71
|
+
items={[]}
|
|
72
|
+
/* @ts-expect-error - when MenuItemComponent has the wrong type */
|
|
73
|
+
MenuItemComponent={MenuFooter}
|
|
74
|
+
></Autocomplete>
|
|
44
75
|
<Autocomplete
|
|
45
76
|
menuToggleElement={<>test</>}
|
|
46
77
|
/* @ts-expect-error - isOpen can't be a string */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { TypeMenuContentProps } from "./MenuContentTypes";
|
|
2
2
|
import { StyledMenuContent } from "./styles";
|
|
3
3
|
import { useMenuContext } from "../MenuContext";
|
|
4
|
+
import { MenuItem } from "../MenuItem/index";
|
|
5
|
+
import { mapMenuItems } from "../hooks/useMenuChildren";
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* @link https://seeds.sproutsocial.com/components/menu-content/
|
|
@@ -27,11 +29,22 @@ import { useMenuContext } from "../MenuContext";
|
|
|
27
29
|
|
|
28
30
|
const MenuContent = ({
|
|
29
31
|
id = "menu_content",
|
|
30
|
-
children,
|
|
32
|
+
children: childrenProp,
|
|
33
|
+
items,
|
|
34
|
+
MenuItemComponent = MenuItem,
|
|
31
35
|
...rest
|
|
32
36
|
}: TypeMenuContentProps) => {
|
|
33
37
|
const { getToggleButtonProps, getMenuProps, isListbox } = useMenuContext();
|
|
34
38
|
|
|
39
|
+
const children =
|
|
40
|
+
childrenProp || !items || !items.length
|
|
41
|
+
? childrenProp
|
|
42
|
+
: mapMenuItems({ items, MenuItemComponent });
|
|
43
|
+
|
|
44
|
+
if (!children) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
return (
|
|
36
49
|
<StyledMenuContent
|
|
37
50
|
autoFocus
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { type HTMLMotionProps } from "motion/react";
|
|
3
|
-
import type {
|
|
2
|
+
import type { TypeChildrenOrItems } from "../types";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
interface TypeMenuContentBaseProps extends HTMLMotionProps<"ul"> {}
|
|
5
|
+
|
|
6
|
+
export type TypeMenuContentProps = TypeMenuContentBaseProps &
|
|
7
|
+
TypeChildrenOrItems;
|
|
@@ -4,6 +4,13 @@ export const MenuContentTypeTest = () => (
|
|
|
4
4
|
<>
|
|
5
5
|
{/* MenuContent allows children */}
|
|
6
6
|
<MenuContent>Test</MenuContent>
|
|
7
|
+
{/* MenuContent allows items prop*/}
|
|
8
|
+
<MenuContent items={[{ id: "1", children: "1" }]} />
|
|
9
|
+
{/* MenuContent allows items prop with MenuItemComponent*/}
|
|
10
|
+
<MenuContent
|
|
11
|
+
items={[{ id: "1", children: "1" }]}
|
|
12
|
+
MenuItemComponent={MenuItem}
|
|
13
|
+
/>
|
|
7
14
|
{/* MenuContent allows children with MenuItems */}
|
|
8
15
|
<MenuContent>
|
|
9
16
|
<MenuItem id="1">Test 1</MenuItem>
|
|
@@ -12,7 +19,16 @@ export const MenuContentTypeTest = () => (
|
|
|
12
19
|
{/* MenuContent accepts ul props */}
|
|
13
20
|
<MenuContent aria-label="test label">Test</MenuContent>
|
|
14
21
|
|
|
15
|
-
{/* @ts-expect-error - MenuContent requires children */}
|
|
22
|
+
{/* @ts-expect-error - MenuContent requires children or items */}
|
|
16
23
|
<MenuContent />
|
|
24
|
+
{/* @ts-expect-error - when both children and items provided */}
|
|
25
|
+
<MenuContent items={[]}>Test</MenuContent>
|
|
26
|
+
{/* @ts-expect-error - when MenuItemComponent is passed with children */}
|
|
27
|
+
<MenuContent MenuItemComponent={MenuItem}>Test</MenuContent>
|
|
28
|
+
<MenuContent
|
|
29
|
+
items={[]}
|
|
30
|
+
/* @ts-expect-error - when MenuItemComponent has the wrong type */
|
|
31
|
+
MenuItemComponent={({ text }) => <div>{text}</div>}
|
|
32
|
+
></MenuContent>
|
|
17
33
|
</>
|
|
18
34
|
);
|
|
@@ -76,9 +76,9 @@ Feature: Menu Group
|
|
|
76
76
|
Then MenuRoot expects it to be of type <propType>
|
|
77
77
|
|
|
78
78
|
Examples: Prop Types
|
|
79
|
-
| propName | propType
|
|
80
|
-
| children | ReactNode
|
|
81
|
-
| title | string
|
|
82
|
-
| items | TypeMenuItemProps[]
|
|
83
|
-
| isSingleSelect | boolean
|
|
84
|
-
| MenuItemComponent |
|
|
79
|
+
| propName | propType |
|
|
80
|
+
| children | ReactNode |
|
|
81
|
+
| title | string |
|
|
82
|
+
| items | TypeMenuItemProps[] |
|
|
83
|
+
| isSingleSelect | boolean |
|
|
84
|
+
| MenuItemComponent | React.ComponentType<TypeMenuItemProps> |
|
|
@@ -2,6 +2,7 @@ import React from "react";
|
|
|
2
2
|
import type { TypeMenuGroupProps } from "./MenuGroupTypes";
|
|
3
3
|
import { StyledMenuGroup, StyledTitle, StyledMenuGroupUl } from "./styles";
|
|
4
4
|
import { MenuItem } from "../MenuItem";
|
|
5
|
+
import { mapMenuItems } from "../hooks/useMenuChildren";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @link https://seeds.sproutsocial.com/components/menu-group/
|
|
@@ -40,7 +41,7 @@ export const MenuGroup = ({
|
|
|
40
41
|
const children =
|
|
41
42
|
childrenProp || !items || !items.length
|
|
42
43
|
? childrenProp
|
|
43
|
-
:
|
|
44
|
+
: mapMenuItems({ items, MenuItemComponent });
|
|
44
45
|
|
|
45
46
|
if (!children) {
|
|
46
47
|
return null;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
1
|
import type {
|
|
3
2
|
TypeBorderSystemProps,
|
|
4
3
|
TypeColorSystemProps,
|
|
@@ -9,7 +8,7 @@ import type {
|
|
|
9
8
|
TypeSpaceSystemProps,
|
|
10
9
|
TypeTypographySystemProps,
|
|
11
10
|
} from "@sproutsocial/seeds-react-system-props";
|
|
12
|
-
import {
|
|
11
|
+
import type { TypeChildrenOrItems } from "../types";
|
|
13
12
|
|
|
14
13
|
export interface TypeMenuGroupStyledProps
|
|
15
14
|
extends TypeBorderSystemProps,
|
|
@@ -21,14 +20,13 @@ export interface TypeMenuGroupStyledProps
|
|
|
21
20
|
TypeSpaceSystemProps,
|
|
22
21
|
TypeTypographySystemProps {}
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
interface TypeBaseMenuGroupProps
|
|
25
24
|
extends TypeMenuGroupStyledProps,
|
|
26
25
|
Omit<React.ComponentPropsWithoutRef<"ul">, "color"> {
|
|
27
26
|
id: string;
|
|
28
27
|
isSingleSelect?: boolean;
|
|
29
28
|
title?: string;
|
|
30
|
-
children?: ReactNode;
|
|
31
|
-
items?: TypeMenuItemProps[];
|
|
32
|
-
MenuItemComponent?: typeof MenuItem;
|
|
33
29
|
titleAs?: string | React.ComponentType<any>;
|
|
34
30
|
}
|
|
31
|
+
|
|
32
|
+
export type TypeMenuGroupProps = TypeBaseMenuGroupProps & TypeChildrenOrItems;
|
|
@@ -10,6 +10,14 @@ export const MenuGroupTypeTest = () => (
|
|
|
10
10
|
<MenuItem id="1">Test 1</MenuItem>
|
|
11
11
|
<MenuItem id="2">Test 2</MenuItem>
|
|
12
12
|
</MenuGroup>
|
|
13
|
+
{/* MenuGroup allows items prop */}
|
|
14
|
+
<MenuGroup id="group" items={[{ id: "1", children: "1" }]} />
|
|
15
|
+
{/* MenuGroup allows items prop with MenuItemComponent */}
|
|
16
|
+
<MenuGroup
|
|
17
|
+
id="group"
|
|
18
|
+
items={[{ id: "1", children: "1" }]}
|
|
19
|
+
MenuItemComponent={MenuItem}
|
|
20
|
+
/>
|
|
13
21
|
{/* MenuGroup allows isSingleSelect prop */}
|
|
14
22
|
<MenuGroup id="group-1" isSingleSelect={true}>
|
|
15
23
|
<MenuItem id="1">Test 1</MenuItem>
|
|
@@ -34,10 +42,21 @@ export const MenuGroupTypeTest = () => (
|
|
|
34
42
|
<MenuGroup id="group" border="1px solid black" bg="white" width="150px">
|
|
35
43
|
Test
|
|
36
44
|
</MenuGroup>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
|
|
46
|
+
{/* @ts-expect-error - MenuGroup requires children or items */}
|
|
47
|
+
<MenuGroup id="group" />
|
|
48
|
+
{/* @ts-expect-error - MenuGroup does not allow children and items together */}
|
|
49
|
+
<MenuGroup id="group" items={[{ id: "1", children: "1" }]}>
|
|
50
|
+
Test
|
|
51
|
+
</MenuGroup>
|
|
52
|
+
{/* @ts-expect-error - MenuGroup does not allow MenuItemComponent without items */}
|
|
53
|
+
<MenuGroup id="group" MenuItemComponent={MenuItem}>
|
|
54
|
+
Test
|
|
55
|
+
</MenuGroup>
|
|
56
|
+
{/* @ts-expect-error - MenuGroup validates the type of MenuItemComponent */}
|
|
57
|
+
<MenuGroup id="group" MenuItemComponent={Text}>
|
|
58
|
+
Test
|
|
59
|
+
</MenuGroup>
|
|
41
60
|
|
|
42
61
|
{/* @ts-expect-error - MenuGroup title is invalid as number */}
|
|
43
62
|
<MenuGroup id="group" title={123}>
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
useMultipleSelection,
|
|
5
5
|
type UseSelectState,
|
|
6
6
|
} from "downshift";
|
|
7
|
-
import {
|
|
7
|
+
import { useMenuChildren } from "../hooks/useMenuChildren";
|
|
8
8
|
import {
|
|
9
9
|
defaultUseSelectProps,
|
|
10
10
|
useSelectStateReducer,
|
|
@@ -13,6 +13,7 @@ import { itemToString } from "../utils/itemToString";
|
|
|
13
13
|
import { MenuRoot } from "../MenuRoot";
|
|
14
14
|
import type { TypeMultiSelectMenuProps } from "./MultiSelectMenuTypes";
|
|
15
15
|
import type { TypeDefaultItemProps } from "../types";
|
|
16
|
+
import { MenuItem } from "../MenuItem/index";
|
|
16
17
|
|
|
17
18
|
const selectReducerForMultiSelect: TypeMultiSelectMenuProps["stateReducer"] = (
|
|
18
19
|
state,
|
|
@@ -43,14 +44,20 @@ const selectReducerForMultiSelect: TypeMultiSelectMenuProps["stateReducer"] = (
|
|
|
43
44
|
*/
|
|
44
45
|
|
|
45
46
|
export const MultiSelectMenu = ({
|
|
46
|
-
children,
|
|
47
|
+
children: childrenProp,
|
|
48
|
+
items,
|
|
49
|
+
MenuItemComponent = MenuItem,
|
|
47
50
|
stateReducer: externalStateReducer,
|
|
48
51
|
getA11yMultiStatusMessage,
|
|
49
52
|
onMultiSelectStateChange,
|
|
50
53
|
multiSelectStateReducer,
|
|
51
54
|
...useSelectProps
|
|
52
55
|
}: TypeMultiSelectMenuProps) => {
|
|
53
|
-
const { allMenuItems, menuItemsMap } =
|
|
56
|
+
const { allMenuItems, menuItemsMap, children } = useMenuChildren({
|
|
57
|
+
children: childrenProp,
|
|
58
|
+
items,
|
|
59
|
+
MenuItemComponent,
|
|
60
|
+
});
|
|
54
61
|
|
|
55
62
|
const multiSelectOverrides = Object.entries({
|
|
56
63
|
...useSelectProps,
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TypeMenuRootOwnProps } from "../MenuRoot";
|
|
2
2
|
import type {
|
|
3
3
|
TypeDownshiftSelectProps,
|
|
4
4
|
TypeDownshiftMultiSelectProps,
|
|
5
5
|
} from "../MenuContext";
|
|
6
|
+
import type { TypeChildrenOrItems } from "../types";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
interface TypeMultiSelectMenuBaseProps
|
|
8
9
|
extends TypeMenuRootOwnProps,
|
|
9
|
-
TypeDownshiftSelectProps,
|
|
10
|
-
TypeDownshiftMultiSelectProps {
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Omit<TypeDownshiftSelectProps, "items">,
|
|
11
|
+
Omit<TypeDownshiftMultiSelectProps, "items"> {}
|
|
12
|
+
|
|
13
|
+
export type TypeMultiSelectMenuProps = TypeMultiSelectMenuBaseProps &
|
|
14
|
+
TypeChildrenOrItems;
|
|
@@ -25,49 +25,6 @@ describe("MultiSelectMenu", () => {
|
|
|
25
25
|
cleanup();
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
it("should render the MenuItems when provided as children", async () => {
|
|
29
|
-
const { user } = render(
|
|
30
|
-
<MultiSelectMenu
|
|
31
|
-
menuToggleElement={
|
|
32
|
-
<MenuToggleButton aria-label="testable menu" role="button">
|
|
33
|
-
Testable Menu
|
|
34
|
-
</MenuToggleButton>
|
|
35
|
-
}
|
|
36
|
-
>
|
|
37
|
-
<MenuContent>
|
|
38
|
-
<MenuGroup id="group">
|
|
39
|
-
{menuItems.map((item) => (
|
|
40
|
-
<MenuItem key={item.id} id={item.id} label={item.label}>
|
|
41
|
-
{item.label}
|
|
42
|
-
</MenuItem>
|
|
43
|
-
))}
|
|
44
|
-
</MenuGroup>
|
|
45
|
-
</MenuContent>
|
|
46
|
-
</MultiSelectMenu>
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
// Check that the menu is closed
|
|
50
|
-
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
51
|
-
|
|
52
|
-
const toggleButton = screen.getByRole("button", { name: "testable menu" });
|
|
53
|
-
|
|
54
|
-
// Open Menu
|
|
55
|
-
await user.click(toggleButton);
|
|
56
|
-
|
|
57
|
-
await waitFor(() => {
|
|
58
|
-
// Check that the menu is open
|
|
59
|
-
expect(screen.getByRole("listbox")).toBeInTheDocument();
|
|
60
|
-
// Check that the menu items are present
|
|
61
|
-
expect(
|
|
62
|
-
screen.getByRole("option", { name: "Item 1" })
|
|
63
|
-
).toBeInTheDocument();
|
|
64
|
-
expect(
|
|
65
|
-
// Check that the menu items are present
|
|
66
|
-
screen.getByRole("option", { name: "Item 2" })
|
|
67
|
-
).toBeInTheDocument();
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
28
|
it("should allow for multiple items to be selected", async () => {
|
|
72
29
|
const { user } = render(
|
|
73
30
|
<MultiSelectMenu
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
MultiSelectMenu,
|
|
4
4
|
MenuContent,
|
|
5
5
|
MenuHeader,
|
|
6
|
+
MenuItem,
|
|
6
7
|
MenuFooter,
|
|
7
8
|
} from "../../index";
|
|
8
9
|
|
|
@@ -20,6 +21,17 @@ export const MultiSelectMenuTypeTest = () => (
|
|
|
20
21
|
<MenuContent>Test</MenuContent>
|
|
21
22
|
<MenuFooter>Test</MenuFooter>
|
|
22
23
|
</MultiSelectMenu>
|
|
24
|
+
{/* Valid MultiSelectMenu allows items to be passed using the items prop */}
|
|
25
|
+
<MultiSelectMenu
|
|
26
|
+
menuToggleElement={<>test</>}
|
|
27
|
+
items={[{ children: "Item Name", id: "item-1" }]}
|
|
28
|
+
/>
|
|
29
|
+
{/* Valid MultiSelectMenu allows items prop to be rendered using a MenuItemComponent prop */}
|
|
30
|
+
<MultiSelectMenu
|
|
31
|
+
menuToggleElement={<>test</>}
|
|
32
|
+
items={[{ children: "Item Name", id: "item-1" }]}
|
|
33
|
+
MenuItemComponent={MenuItem}
|
|
34
|
+
/>
|
|
23
35
|
{/* Valid MultiSelectMenu accepts Downshift props and functions for useSelect */}
|
|
24
36
|
<MultiSelectMenu
|
|
25
37
|
menuToggleElement={<>test</>}
|
|
@@ -52,8 +64,23 @@ export const MultiSelectMenuTypeTest = () => (
|
|
|
52
64
|
>
|
|
53
65
|
Test
|
|
54
66
|
</MultiSelectMenu>
|
|
55
|
-
{/* @ts-expect-error - when no children provided */}
|
|
56
|
-
<MultiSelectMenu menuToggleElement=
|
|
67
|
+
{/* @ts-expect-error - when no children or items provided */}
|
|
68
|
+
<MultiSelectMenu menuToggleElement={<>test</>} />
|
|
69
|
+
{/* @ts-expect-error - when both children and MenuItemComponent provided */}
|
|
70
|
+
<MultiSelectMenu menuToggleElement={<>test</>} MenuItemComponent={MenuItem}>
|
|
71
|
+
Children
|
|
72
|
+
</MultiSelectMenu>
|
|
73
|
+
{/* @ts-expect-error - when both children and items provided */}
|
|
74
|
+
<MultiSelectMenu menuToggleElement={<>test</>} items={[]}>
|
|
75
|
+
Children
|
|
76
|
+
</MultiSelectMenu>
|
|
77
|
+
<MultiSelectMenu
|
|
78
|
+
menuToggleElement={<>test</>}
|
|
79
|
+
/* @ts-expect-error - when MenuItemComponent is of the wrong type */
|
|
80
|
+
MenuItemComponent={MenuContent}
|
|
81
|
+
>
|
|
82
|
+
Children
|
|
83
|
+
</MultiSelectMenu>
|
|
57
84
|
|
|
58
85
|
<MultiSelectMenu
|
|
59
86
|
menuToggleElement={<>test</>}
|