@sproutsocial/seeds-react-menu 0.2.2 → 0.3.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.
Files changed (38) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +18 -0
  3. package/dist/esm/index.js +18 -4
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/index.d.mts +70 -43
  6. package/dist/index.d.ts +70 -43
  7. package/dist/index.js +18 -4
  8. package/dist/index.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/ActionMenu/ActionMenu.tsx +2 -16
  11. package/src/ActionMenu/ActionMenuTypes.ts +12 -0
  12. package/src/ActionMenu/index.ts +1 -0
  13. package/src/{ComboBox/ComboBox.feature → Autocomplete/Autocomplete.feature} +16 -16
  14. package/src/Autocomplete/Autocomplete.tsx +65 -0
  15. package/src/Autocomplete/AutocompleteTypes.ts +8 -0
  16. package/src/Autocomplete/__tests__/Autocomplete.test.tsx +113 -0
  17. package/src/Autocomplete/__tests__/Autocomplete.typetest.tsx +78 -0
  18. package/src/Autocomplete/index.ts +2 -0
  19. package/src/MenuContent/MenuContent.tsx +3 -3
  20. package/src/MenuContext.tsx +65 -9
  21. package/src/MenuGroup/MenuGroup.feature +15 -15
  22. package/src/MenuGroup/MenuGroup.tsx +6 -2
  23. package/src/MenuGroup/MenuGroupTypes.ts +2 -0
  24. package/src/MenuGroup/__tests__/MenuGroup.test.tsx +144 -9
  25. package/src/MenuGroup/__tests__/MenuGroup.typetest.tsx +24 -12
  26. package/src/MenuItem/MenuItem.tsx +1 -1
  27. package/src/MenuItem/__tests__/MenuItem.test.tsx +4 -1
  28. package/src/MenuRoot/__tests__/MenuRoot.test.tsx +1 -1
  29. package/src/MultiSelectMenu/MultiSelectMenu.tsx +8 -0
  30. package/src/MultiSelectMenu/MultiSelectMenuTypes.ts +5 -1
  31. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +6 -8
  32. package/src/MultiSelectMenu/index.ts +1 -0
  33. package/src/SingleSelectMenu/SingleSelectMenuTypes.ts +2 -3
  34. package/src/SingleSelectMenu/__tests__/SingleSelectMenu.test.tsx +3 -3
  35. package/src/hooks/useItemsFromChildren.tsx +12 -5
  36. package/src/reducers/useComboboxStateReducer.tsx +26 -0
  37. package/src/types.ts +2 -0
  38. package/src/ComboBox/TokenInput.feature +0 -84
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-menu",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Seeds React Menu",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -1,7 +1,6 @@
1
1
  import React, { useEffect } from "react";
2
2
  import {
3
3
  useSelect,
4
- type UseSelectProps,
5
4
  type UseSelectState,
6
5
  type UseSelectStateChange,
7
6
  } from "downshift";
@@ -11,22 +10,9 @@ import {
11
10
  defaultUseSelectProps,
12
11
  useSelectStateReducer,
13
12
  } from "../reducers/useSelectStateReducer";
14
- import {
15
- MenuRoot,
16
- type TypeMenuRootProps,
17
- type TypeMenuRootOwnProps,
18
- } from "../MenuRoot";
13
+ import { MenuRoot } from "../MenuRoot";
19
14
  import { itemToString } from "../utils/itemToString";
20
-
21
- type TypeInvalidMenuProps =
22
- | "initialSelectedItem"
23
- | "defaultSelectedItem"
24
- | "selectedItem";
25
- export interface TypeActionMenuProps
26
- extends TypeMenuRootOwnProps,
27
- Partial<Omit<UseSelectProps<TypeDefaultItemProps>, TypeInvalidMenuProps>> {
28
- children: TypeMenuRootProps["children"];
29
- }
15
+ import type { TypeActionMenuProps } from "./ActionMenuTypes";
30
16
 
31
17
  // Manually fire the onClick handler when Enter or Space is pressed
32
18
  const handleStateChange = (
@@ -0,0 +1,12 @@
1
+ import { type TypeMenuRootProps, type TypeMenuRootOwnProps } from "../MenuRoot";
2
+ import type { TypeDownshiftSelectProps } from "../MenuContext";
3
+
4
+ type TypeInvalidMenuProps =
5
+ | "initialSelectedItem"
6
+ | "defaultSelectedItem"
7
+ | "selectedItem";
8
+ export interface TypeActionMenuProps
9
+ extends TypeMenuRootOwnProps,
10
+ Omit<TypeDownshiftSelectProps, TypeInvalidMenuProps> {
11
+ children: TypeMenuRootProps["children"];
12
+ }
@@ -1 +1,2 @@
1
1
  export * from "./ActionMenu";
2
+ export * from "./ActionMenuTypes";
@@ -1,4 +1,4 @@
1
- Feature: ComboBox
1
+ Feature: Autocomplete
2
2
 
3
3
  A component that allows users to search, select, and manage items with autocomplete functionality.
4
4
 
@@ -8,31 +8,31 @@ Feature: ComboBox
8
8
  Given an end user is using the component
9
9
 
10
10
  Scenario: User can autocomplete text inputs
11
- Given the ComboBox Input is focused
11
+ Given the Autocomplete Input is focused
12
12
  When text is typed into the input
13
13
  Then a list of matching items is displayed
14
14
  And an item can be selected from the list
15
15
 
16
16
  Scenario: User can add a new value
17
- Given the Combobox Input has a value
17
+ Given the Autocomplete Input has a value
18
18
  When new values are allowed
19
19
  And the value has no match
20
20
  Then a new value can be created
21
21
 
22
22
  Scenario: User gets a warning on error
23
- Given the Combobox Input has a value
23
+ Given the Autocomplete Input has a value
24
24
  When the value is invalid
25
25
  Then a warning message appears
26
26
 
27
27
  Scenario: User can select an item from the list
28
- Given the ComboBox Input has text
28
+ Given the Autocomplete Input has text
29
29
  When an item is selected
30
30
  Then the item is marked as selected
31
31
  And the input reflects the selected item
32
32
 
33
33
  Scenario: Screen reader announces combobox
34
34
  Given a screen reader is being used
35
- When the Combobox input is focused
35
+ When the Autocomplete input is focused
36
36
  Then the screen reader announces it as a "combobox"
37
37
 
38
38
  Scenario: Component handles input change
@@ -40,7 +40,7 @@ Feature: ComboBox
40
40
  Then the onChange callback fires
41
41
 
42
42
  Scenario Outline: Custom open/close behavior
43
- Given the ComboBox is rendered with custom behavior
43
+ Given the Autocomplete is rendered with custom behavior
44
44
  When the `<action>` is performed
45
45
  Then the `<handler>` is executed
46
46
 
@@ -51,7 +51,7 @@ Feature: ComboBox
51
51
  | input blurred | onBlur |
52
52
 
53
53
  Scenario Outline: Autocomplete suggestions are displayed
54
- Given the ComboBox is rendered with items
54
+ Given the Autocomplete is rendered with items
55
55
  When <text> is typed into the input
56
56
  Then matching <suggestion> items are displayed
57
57
 
@@ -65,21 +65,21 @@ Feature: ComboBox
65
65
  Background:
66
66
  Given an engineer is developing using the component
67
67
 
68
- Scenario: ComboBox extends Menu
69
- When Menu features are used in ComboBox
70
- Then the ComboBox responds as a Menu would
68
+ Scenario: Autocomplete extends Menu
69
+ When Menu features are used in Autocomplete
70
+ Then the Autocomplete responds as a Menu would
71
71
 
72
- Scenario: ComboBox accepts selected item
73
- When a selected item is passed to the ComboBox
72
+ Scenario: Autocomplete accepts selected item
73
+ When a selected item is passed to the Autocomplete
74
74
  Then the input reflects the selected item
75
75
 
76
- Scenario: ComboBox hasWarning
76
+ Scenario: Autocomplete hasWarning
77
77
  When the input's validation has an error
78
78
  Then 'hasWarning' becomes true
79
79
 
80
- Scenario Outline: ComboBox checks its types
80
+ Scenario Outline: Autocomplete checks its types
81
81
  When <propName> is passed a value
82
- Then ComboBox expects it to be of type <propType>
82
+ Then Autocomplete expects it to be of type <propType>
83
83
 
84
84
  Examples: Prop Types
85
85
  | propName | propType |
@@ -0,0 +1,65 @@
1
+ import React from "react";
2
+ import { useCombobox, type UseComboboxState } from "downshift";
3
+ import { useItemsFromChildren } from "../hooks/useItemsFromChildren";
4
+ import {
5
+ defaultUseComboboxProps,
6
+ useComboboxStateReducer,
7
+ } from "../reducers/useComboboxStateReducer";
8
+ import { itemToString } from "../utils/itemToString";
9
+ import { MenuRoot } from "../MenuRoot";
10
+ import type { TypeAutocompleteProps } from "./AutocompleteTypes";
11
+ import type { TypeDefaultItemProps } from "../types";
12
+
13
+ /**
14
+ * @link https://seeds.sproutsocial.com/components/multiselect-menu/
15
+ *
16
+ * @description A Autocomplete allows multiple {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/MenuItem | MenuItems} to be selected from a list or groups of lists.
17
+ *
18
+ * @see {@link https://seeds.sproutsocial.com/components/menu-group/ | MenuGroup}
19
+ */
20
+
21
+ export const Autocomplete = ({
22
+ children,
23
+ stateReducer: externalStateReducer,
24
+ ...useComboboxProps
25
+ }: TypeAutocompleteProps) => {
26
+ const { allMenuItems, menuItemsMap } = useItemsFromChildren({ children });
27
+
28
+ const { isOpen, ...useComboboxReturnProps } = useCombobox({
29
+ ...defaultUseComboboxProps,
30
+ // onInputValueChange({ inputValue }) {
31
+ // setItems(books.filter(getBooksFilter(inputValue)));
32
+ // },
33
+ items: allMenuItems,
34
+ itemToString,
35
+ selectedItem: null,
36
+ defaultHighlightedIndex: 0,
37
+ stateReducer: (state, actionAndChanges) => {
38
+ let newState: Partial<UseComboboxState<TypeDefaultItemProps>>;
39
+ newState = useComboboxStateReducer(state, actionAndChanges);
40
+ return externalStateReducer
41
+ ? externalStateReducer(state, {
42
+ ...actionAndChanges,
43
+ changes: newState,
44
+ })
45
+ : newState;
46
+ },
47
+ ...useCombobox,
48
+ });
49
+
50
+ return (
51
+ <MenuRoot
52
+ {...{
53
+ isListbox: true,
54
+ items: allMenuItems,
55
+ itemsMap: menuItemsMap,
56
+ itemToString,
57
+ isOpen,
58
+ ...useComboboxProps,
59
+ ...useComboboxReturnProps,
60
+ }}
61
+ >
62
+ {children}
63
+ </MenuRoot>
64
+ );
65
+ };
@@ -0,0 +1,8 @@
1
+ import type { TypeMenuRootProps, TypeMenuRootOwnProps } from "../MenuRoot";
2
+ import type { TypeDownshiftComboboxProps } from "../MenuContext";
3
+
4
+ export interface TypeAutocompleteProps
5
+ extends TypeMenuRootOwnProps,
6
+ TypeDownshiftComboboxProps {
7
+ children: TypeMenuRootProps["children"];
8
+ }
@@ -0,0 +1,113 @@
1
+ // import React from "react";
2
+ // import {
3
+ // render,
4
+ // screen,
5
+ // fireEvent,
6
+ // waitFor,
7
+ // } from "@sproutsocial/seeds-react-testing-library";
8
+ // import { Autocomplete } from "../Autocomplete";
9
+
10
+ xdescribe("Autocomplete Component", () => {
11
+ it("should render the Autocomplete component", () => {
12
+ // render(<Autocomplete />);
13
+ // const autocompleteElement = screen.getByRole("combobox");
14
+ // expect(autocompleteElement).toBeInTheDocument();
15
+ });
16
+
17
+ it("should display suggestions when typing", async () => {
18
+ // render(<Autocomplete />);
19
+ // const inputElement = screen.getByRole("combobox");
20
+ // fireEvent.change(inputElement, { target: { value: "test" } });
21
+ // await waitFor(() => {
22
+ // const suggestions = screen.getAllByRole("option");
23
+ // expect(suggestions.length).toBeGreaterThan(0);
24
+ // });
25
+ });
26
+
27
+ it("should select a suggestion when clicked", async () => {
28
+ // render(<Autocomplete />);
29
+ // const inputElement = screen.getByRole("combobox");
30
+ // fireEvent.change(inputElement, { target: { value: "test" } });
31
+ // await waitFor(() => {
32
+ // const suggestion = screen.getByText("test suggestion");
33
+ // fireEvent.click(suggestion);
34
+ // expect(inputElement.value).toBe("test suggestion");
35
+ // });
36
+ });
37
+
38
+ it("should allow adding a new value when no match is found", async () => {
39
+ // render(<Autocomplete allowNewValues />);
40
+ // const inputElement = screen.getByRole("combobox");
41
+ // fireEvent.change(inputElement, { target: { value: "new value" } });
42
+ // await waitFor(() => {
43
+ // const newValueOption = screen.getByText('Add "new value"');
44
+ // fireEvent.click(newValueOption);
45
+ // expect(inputElement.value).toBe("new value");
46
+ // });
47
+ });
48
+
49
+ it("should display a warning message when the value is invalid", async () => {
50
+ // render(<Autocomplete />);
51
+ // const inputElement = screen.getByRole("combobox");
52
+ // fireEvent.change(inputElement, { target: { value: "invalid value" } });
53
+ // await waitFor(() => {
54
+ // const warningMessage = screen.getByText("Invalid value");
55
+ // expect(warningMessage).toBeInTheDocument();
56
+ // });
57
+ });
58
+
59
+ it("should mark an item as selected and reflect it in the input", async () => {
60
+ // render(<Autocomplete />);
61
+ // const inputElement = screen.getByRole("combobox");
62
+ // fireEvent.change(inputElement, { target: { value: "test" } });
63
+ // await waitFor(() => {
64
+ // const suggestion = screen.getByText("test suggestion");
65
+ // fireEvent.click(suggestion);
66
+ // expect(inputElement.value).toBe("test suggestion");
67
+ // expect(suggestion).toHaveAttribute("aria-selected", "true");
68
+ // });
69
+ });
70
+
71
+ it('should announce the input as a "combobox" for screen readers', () => {
72
+ // render(<Autocomplete />);
73
+ // const inputElement = screen.getByRole("combobox");
74
+ // expect(inputElement).toHaveAttribute("aria-haspopup", "listbox");
75
+ });
76
+
77
+ it("should fire the onChange callback when the input value changes", () => {
78
+ // const handleChange = jest.fn();
79
+ // render(<Autocomplete onChange={handleChange} />);
80
+ // const inputElement = screen.getByRole("combobox");
81
+ // fireEvent.change(inputElement, { target: { value: "test" } });
82
+ // expect(handleChange).toHaveBeenCalledWith("test");
83
+ });
84
+
85
+ it.each`
86
+ action | handler
87
+ ${"item selected"} | ${"onSelect"}
88
+ ${"input focused"} | ${"onFocus"}
89
+ ${"input blurred"} | ${"onBlur"}
90
+ `(
91
+ "should execute $handler when $action is performed",
92
+ async ({ action, handler }) => {
93
+ // const handlerMock = jest.fn();
94
+ // const props = { [handler]: handlerMock };
95
+ // render(<Autocomplete {...props} />);
96
+ // const inputElement = screen.getByRole("combobox");
97
+ // if (action === "item selected") {
98
+ // fireEvent.change(inputElement, { target: { value: "test" } });
99
+ // await waitFor(() => {
100
+ // const suggestion = screen.getByText("test suggestion");
101
+ // fireEvent.click(suggestion);
102
+ // expect(handlerMock).toHaveBeenCalled();
103
+ // });
104
+ // } else if (action === "input focused") {
105
+ // fireEvent.focus(inputElement);
106
+ // expect(handlerMock).toHaveBeenCalled();
107
+ // } else if (action === "input blurred") {
108
+ // fireEvent.blur(inputElement);
109
+ // expect(handlerMock).toHaveBeenCalled();
110
+ // }
111
+ }
112
+ );
113
+ });
@@ -0,0 +1,78 @@
1
+ import * as React from "react";
2
+ import {
3
+ MultiSelectMenu,
4
+ MenuContent,
5
+ MenuHeader,
6
+ MenuFooter,
7
+ } from "../../index";
8
+
9
+ export const MultiSelectMenuTypeTest = () => (
10
+ <>
11
+ {/* Valid MultiSelectMenu with minimal required props */}
12
+ <MultiSelectMenu menuToggleElement={<>test</>}>Test</MultiSelectMenu>
13
+ {/* Valid MultiSelectMenu allows MenuContent by itself as a child */}
14
+ <MultiSelectMenu menuToggleElement={<>test</>}>
15
+ <MenuContent>Test</MenuContent>
16
+ </MultiSelectMenu>
17
+ {/* Valid MultiSelectMenu allows multiple children without fragment */}
18
+ <MultiSelectMenu menuToggleElement={<>test</>}>
19
+ <MenuHeader>Test</MenuHeader>
20
+ <MenuContent>Test</MenuContent>
21
+ <MenuFooter>Test</MenuFooter>
22
+ </MultiSelectMenu>
23
+ {/* Valid MultiSelectMenu accepts Downshift props and functions for useSelect */}
24
+ <MultiSelectMenu
25
+ menuToggleElement={<>test</>}
26
+ onSelectedItemChange={() => {}}
27
+ selectedItem={null}
28
+ stateReducer={(c) => c}
29
+ onStateChange={() => {}}
30
+ getA11yStatusMessage={() => ""}
31
+ isOpen
32
+ >
33
+ Test
34
+ </MultiSelectMenu>
35
+ {/* Valid MultiSelectMenu accepts Downshift props and functions for useMultipleSelection */}
36
+ <MultiSelectMenu
37
+ menuToggleElement={<>test</>}
38
+ onSelectedItemsChange={() => {}}
39
+ selectedItems={[]}
40
+ multiSelectStateReducer={(c) => c}
41
+ onMultiSelectStateChange={() => {}}
42
+ getA11yMultiStatusMessage={() => ""}
43
+ >
44
+ Test
45
+ </MultiSelectMenu>
46
+
47
+ {/* @ts-expect-error - menuToggleElement is a required prop */}
48
+ <MultiSelectMenu>Test</MultiSelectMenu>
49
+ <MultiSelectMenu
50
+ /* @ts-expect-error - menuToggleElement must be a valid react element */
51
+ menuToggleElement="test"
52
+ >
53
+ Test
54
+ </MultiSelectMenu>
55
+ {/* @ts-expect-error - when no children provided */}
56
+ <MultiSelectMenu menuToggleElement="test" />
57
+
58
+ <MultiSelectMenu
59
+ menuToggleElement={<>test</>}
60
+ /* @ts-expect-error - isOpen can't be a string */
61
+ isOpen="false"
62
+ /* @ts-expect-error - onSelectedItemChange can't be a string */
63
+ onSelectedItemChange="test"
64
+ /* @ts-expect-error - selectedItem can't be a string */
65
+ selectedItem="test"
66
+ /* @ts-expect-error - stateReducer must have a return value */
67
+ stateReducer={() => {}}
68
+ /* @ts-expect-error - onMultiSelectStateChange can't be a string */
69
+ onMultiSelectStateChange="test"
70
+ /* @ts-expect-error - selectedItems can't be a string */
71
+ selectedItems="test"
72
+ /* @ts-expect-error - multiSelectStateReducer must have a return value */
73
+ multiSelectStateReducer="test"
74
+ >
75
+ Test
76
+ </MultiSelectMenu>
77
+ </>
78
+ );
@@ -0,0 +1,2 @@
1
+ export * from "./Autocomplete";
2
+ export * from "./AutocompleteTypes";
@@ -11,10 +11,10 @@ import { useMenuContext } from "../MenuContext";
11
11
  * <ActionMenu>
12
12
  * <MenuHeader>Header</MenuHeader>
13
13
  * <MenuContent>
14
- * <MenuGroup>
14
+ * <MenuGroup id="1">
15
15
  * <MenuItem>Item 1</MenuItem>
16
16
  * </MenuGroup>
17
- * <MenuGroup>
17
+ * <MenuGroup id="2">
18
18
  * <MenuItem>Item 2</MenuItem>
19
19
  * </MenuGroup>
20
20
  * </MenuContent>
@@ -38,7 +38,7 @@ const MenuContent = ({
38
38
  id={id}
39
39
  tabIndex={0}
40
40
  // apply onKeyDown logic for downshift keyboard accessibility support
41
- onKeyDown={getToggleButtonProps?.().onKeyDown}
41
+ onKeyDown={getToggleButtonProps?.()["onKeyDown"] || undefined}
42
42
  // apply active descendent updates to container for proper accessibility
43
43
  aria-activedescendant={getToggleButtonProps?.()["aria-activedescendant"]}
44
44
  {...getMenuProps?.(
@@ -5,6 +5,8 @@ import React, {
5
5
  type RefObject,
6
6
  } from "react";
7
7
  import type {
8
+ UseComboboxReturnValue,
9
+ UseComboboxProps,
8
10
  UseSelectReturnValue,
9
11
  UseSelectProps,
10
12
  UseMultipleSelectionReturnValue,
@@ -12,26 +14,80 @@ import type {
12
14
  } from "downshift";
13
15
  import type { TypeDefaultItemProps, TypeNotEmptyChildren } from "./types";
14
16
 
17
+ export interface TypeDownshiftComboboxProps
18
+ extends Partial<UseComboboxProps<TypeDefaultItemProps>> {}
19
+
20
+ export interface TypeDownshiftSelectProps
21
+ extends Partial<UseSelectProps<TypeDefaultItemProps>> {}
22
+
15
23
  export interface TypeDownshiftMultiSelectProps
16
- extends Partial<UseSelectProps<TypeDefaultItemProps>>,
17
- Partial<
18
- Omit<
19
- UseMultipleSelectionProps<TypeDefaultItemProps>,
20
- "stateReducer" | "onStateChange" | "getA11yStatusMessage"
21
- >
22
- > {
24
+ extends Partial<
25
+ Omit<
26
+ UseMultipleSelectionProps<TypeDefaultItemProps>,
27
+ "stateReducer" | "onStateChange" | "getA11yStatusMessage"
28
+ >
29
+ > {
23
30
  // Multi Select Props with conflicting useSelect prop names
24
31
  getA11yMultiStatusMessage?: UseMultipleSelectionProps<TypeDefaultItemProps>["getA11yStatusMessage"];
25
32
  onMultiSelectStateChange?: UseMultipleSelectionProps<TypeDefaultItemProps>["onStateChange"];
26
33
  multiSelectStateReducer?: UseMultipleSelectionProps<TypeDefaultItemProps>["stateReducer"];
27
34
  }
28
35
 
36
+ export interface TypeDownshiftComboboxReturnValue
37
+ extends Partial<UseComboboxReturnValue<TypeDefaultItemProps>> {}
38
+
39
+ export interface TypeDownshiftSelectReturnValue
40
+ extends Partial<UseSelectReturnValue<TypeDefaultItemProps>> {}
41
+
42
+ export interface TypeDownshiftMultiSelectReturnValue
43
+ extends Partial<UseMultipleSelectionReturnValue<TypeDefaultItemProps>> {}
44
+
45
+ type TypeDonwshiftSharedReturnValue =
46
+ | "getLabelProps"
47
+ | "getToggleButtonProps"
48
+ | "getMenuProps";
49
+ type TypeDonwshiftSharedProps =
50
+ | "onHighlightedIndexChange"
51
+ | "onIsOpenChange"
52
+ | "onSelectedItemChange"
53
+ | "onStateChange"
54
+ | "stateReducer";
29
55
  export interface TypeMenuContextProps
30
- extends Partial<UseSelectReturnValue<TypeDefaultItemProps>>,
31
- Partial<UseMultipleSelectionReturnValue<TypeDefaultItemProps>>,
56
+ extends Omit<
57
+ TypeDownshiftComboboxReturnValue,
58
+ TypeDonwshiftSharedReturnValue
59
+ >,
60
+ Omit<TypeDownshiftComboboxProps, TypeDonwshiftSharedProps>,
61
+ Omit<TypeDownshiftSelectReturnValue, TypeDonwshiftSharedReturnValue>,
62
+ Omit<TypeDownshiftSelectProps, TypeDonwshiftSharedProps>,
63
+ TypeDownshiftMultiSelectReturnValue,
32
64
  TypeDownshiftMultiSelectProps {
33
65
  items: TypeDefaultItemProps[];
34
66
  isListbox?: boolean;
67
+ onHighlightedIndexChange?:
68
+ | TypeDownshiftComboboxProps["onHighlightedIndexChange"]
69
+ | TypeDownshiftSelectProps["onHighlightedIndexChange"];
70
+ onIsOpenChange?:
71
+ | TypeDownshiftComboboxProps["onIsOpenChange"]
72
+ | TypeDownshiftSelectProps["onIsOpenChange"];
73
+ onSelectedItemChange?:
74
+ | TypeDownshiftComboboxProps["onSelectedItemChange"]
75
+ | TypeDownshiftSelectProps["onSelectedItemChange"];
76
+ onStateChange?:
77
+ | TypeDownshiftComboboxProps["onStateChange"]
78
+ | TypeDownshiftSelectProps["onStateChange"];
79
+ stateReducer?:
80
+ | TypeDownshiftComboboxProps["stateReducer"]
81
+ | TypeDownshiftSelectProps["stateReducer"];
82
+ getLabelProps?:
83
+ | TypeDownshiftComboboxReturnValue["getLabelProps"]
84
+ | TypeDownshiftSelectReturnValue["getLabelProps"];
85
+ getToggleButtonProps?:
86
+ | TypeDownshiftComboboxReturnValue["getToggleButtonProps"]
87
+ | TypeDownshiftSelectReturnValue["getToggleButtonProps"];
88
+ getMenuProps?:
89
+ | TypeDownshiftComboboxReturnValue["getMenuProps"]
90
+ | TypeDownshiftSelectReturnValue["getMenuProps"];
35
91
  }
36
92
 
37
93
  export interface TypeMenuContext extends TypeMenuContextProps {
@@ -6,20 +6,19 @@ Feature: Menu Group
6
6
  Background:
7
7
  Given an end user is using the component
8
8
 
9
- Scenario: MenuGroup has isSingleSelect true
10
- When the MenuGroup isSingleSelect is true
11
- And MenuContext is set to multiple selection
12
- Then the group behaves like a single select
9
+ Scenario Outline: MenuGroup can be single select
10
+ When the MenuGroup has isSingleSelect set to <value>
11
+ And MenuContext is set to <menu type>
12
+ Then <selection behavior>
13
13
 
14
- Scenario: MenuGroup has isSingleSelect undefined
15
- When the MenuGroup isSingleSelect is undefined
16
- And MenuContext is set to multiple selection
17
- Then the group behaves like a multi select
18
-
19
- Scenario: MenuGroup has isSingleSelect false
20
- When the MenuGroup isSingleSelect is false
21
- And MenuContext is set to multiple selection
22
- Then the group behaves like a multi select
14
+ Examples:
15
+ | value | menu type | selection behavior |
16
+ | true | single | the group behaves like a single select |
17
+ | false | single | the group behaves like a single select |
18
+ | undefined | single | the group behaves like a single select |
19
+ | true | multiple | the group behaves like a single select |
20
+ | false | multiple | the group behaves like a multi select |
21
+ | undefined | multiple | the group behaves like a multi select |
23
22
 
24
23
  Rule: API
25
24
  Background:
@@ -59,14 +58,14 @@ Feature: Menu Group
59
58
  Given a list of items is not empty
60
59
  And the items are a valid object shape
61
60
  When that list of items is provided to the items prop on a MenuGroup
62
- And a valid component is provided to the customMenuItem prop
61
+ And a valid component is provided to the customMenuItem prop
63
62
  Then the MenuGroup will render the items using the customMenuItem component
64
63
 
65
64
  Scenario: MenuGroup ignores the menuItemType without items prop
66
65
  When a MenuGroup has menuItemType set
67
66
  And is not provided with a valid items prop
68
67
  Then the menuItemType will be ignored
69
-
68
+
70
69
  Scenario Outline: MenuGroup can set the type for all MenuItems
71
70
  When menuItemType is set to <menu item type>
72
71
  And MenuGroup is provided with a valid items prop
@@ -98,5 +97,6 @@ Feature: Menu Group
98
97
  | children | ReactNode |
99
98
  | title | string |
100
99
  | items | TypeItem[] |
100
+ | isSingleSelect | boolean |
101
101
  | menuItemType | TypeMenuItemProps['type'] |
102
102
  | customMenuItem | tyepof MenuItem |
@@ -10,10 +10,10 @@ import { StyledMenuGroup, StyledTitle, StyledMenuGroupUl } from "./styles";
10
10
  * @example
11
11
  * <ActionMenu>
12
12
  * <MenuContent>
13
- * <MenuGroup title="Group 1">
13
+ * <MenuGroup title="Group 1" id="1">
14
14
  * <MenuItem>Item 1</MenuItem>
15
15
  * </MenuGroup>
16
- * <MenuGroup title="Group 1">
16
+ * <MenuGroup title="Group 2" id="2">
17
17
  * <MenuItem>Item 2</MenuItem>
18
18
  * </MenuGroup>
19
19
  * </MenuContent>
@@ -27,6 +27,8 @@ export const MenuGroup = ({
27
27
  children,
28
28
  title,
29
29
  color,
30
+ isSingleSelect,
31
+ id,
30
32
  ...rest
31
33
  }: TypeMenuGroupProps) => {
32
34
  // Generate a unique ID for the title
@@ -35,6 +37,8 @@ export const MenuGroup = ({
35
37
  return (
36
38
  <StyledMenuGroup
37
39
  role="group"
40
+ id={id}
41
+ isSingleSelect={isSingleSelect}
38
42
  aria-labelledby={title ? titleId : undefined}
39
43
  // TODO: fix this type since `color` should be valid here. TS can't resolve the correct type.
40
44
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -23,6 +23,8 @@ export interface TypeMenuGroupStyledProps
23
23
  export interface TypeMenuGroupProps
24
24
  extends TypeMenuGroupStyledProps,
25
25
  Omit<React.ComponentPropsWithoutRef<"ul">, "color"> {
26
+ id: string;
27
+ isSingleSelect?: boolean;
26
28
  title?: string;
27
29
  children: ReactNode;
28
30
  titleAs?: string | React.ComponentType<any>;