@sproutsocial/seeds-react-menu 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +15 -0
  3. package/dist/esm/index.js +1486 -440
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/index.d.mts +248 -92
  6. package/dist/index.d.ts +248 -92
  7. package/dist/index.js +1500 -429
  8. package/dist/index.js.map +1 -1
  9. package/package.json +4 -1
  10. package/src/ActionMenu/ActionMenu.stories.tsx +426 -0
  11. package/src/ActionMenu/ActionMenu.tsx +24 -27
  12. package/src/ActionMenu/ActionMenuTypes.ts +8 -7
  13. package/src/ActionMenu/__tests__/ActionMenu.test.tsx +17 -8
  14. package/src/ActionMenu/__tests__/ActionMenu.typetest.tsx +8 -8
  15. package/src/Autocomplete/Autocomplete.stories.tsx +38 -3
  16. package/src/Autocomplete/Autocomplete.tsx +15 -80
  17. package/src/Autocomplete/AutocompleteInput.tsx +18 -27
  18. package/src/Autocomplete/AutocompleteTokenInput.tsx +50 -0
  19. package/src/Autocomplete/AutocompleteTypes.ts +25 -12
  20. package/src/Autocomplete/MultiAutocomplete.tsx +189 -0
  21. package/src/Autocomplete/SingleAutocomplete.tsx +74 -0
  22. package/src/Autocomplete/__tests__/Autocomplete.test.tsx +171 -3
  23. package/src/Autocomplete/__tests__/Autocomplete.typetest.tsx +90 -8
  24. package/src/MenuContent/MenuContent.stories.tsx +164 -0
  25. package/src/MenuContent/MenuContent.tsx +26 -12
  26. package/src/MenuContent/MenuContentTypes.ts +6 -4
  27. package/src/MenuContent/__tests__/MenuContent.typetest.tsx +8 -8
  28. package/src/MenuContext.tsx +191 -37
  29. package/src/MenuFooter/MenuFooter.stories.tsx +202 -0
  30. package/src/MenuFooter/__tests__/MenuFooter.test.tsx +16 -11
  31. package/src/MenuGroup/MenuGroup.feature +16 -16
  32. package/src/MenuGroup/MenuGroup.stories.tsx +248 -0
  33. package/src/MenuGroup/MenuGroup.tsx +8 -7
  34. package/src/MenuGroup/MenuGroupTypes.ts +6 -2
  35. package/src/MenuGroup/__tests__/MenuGroup.test.tsx +6 -6
  36. package/src/MenuGroup/__tests__/MenuGroup.typetest.tsx +7 -7
  37. package/src/MenuGroup/styles.tsx +4 -2
  38. package/src/MenuHeader/MenuHeader.stories.tsx +276 -0
  39. package/src/MenuHeader/MenuHeader.tsx +3 -1
  40. package/src/MenuHeader/__tests__/MenuHeader.test.tsx +23 -13
  41. package/src/MenuHeader/styles.tsx +18 -14
  42. package/src/MenuItem/MenuItem.stories.tsx +507 -0
  43. package/src/MenuItem/MenuItem.tsx +18 -8
  44. package/src/MenuItem/MenuItemTypes.ts +9 -4
  45. package/src/MenuItem/__tests__/MenuItem.test.tsx +6 -6
  46. package/src/MenuItem/styles.tsx +21 -5
  47. package/src/MenuItem/useOptionProps.tsx +10 -8
  48. package/src/MenuLabel.tsx +2 -2
  49. package/src/MenuRoot/MenuRoot.tsx +37 -23
  50. package/src/MenuRoot/__tests__/MenuRoot.test.tsx +59 -32
  51. package/src/MenuSearchInput/MenuSearchInput.tsx +63 -0
  52. package/src/MenuSearchInput/__tests__/MenuSearchInput.test.tsx +157 -0
  53. package/src/MenuSearchInput/__tests__/MenuSearchInput.typetest.tsx +31 -0
  54. package/src/MenuSearchInput/index.ts +1 -0
  55. package/src/MenuSearchTokenInput/MenuSearchTokenInput.tsx +98 -0
  56. package/src/MenuSearchTokenInput/__tests__/MenuSearchTokenInput.test.tsx +247 -0
  57. package/src/MenuSearchTokenInput/__tests__/MenuSearchTokenInput.typetest.tsx +33 -0
  58. package/src/MenuSearchTokenInput/index.ts +1 -0
  59. package/src/MenuToggleButton/MenuToggleButton.stories.tsx +72 -0
  60. package/src/{MenuToggleButton.tsx → MenuToggleButton/MenuToggleButton.tsx} +6 -5
  61. package/src/MenuToggleButton/index.ts +1 -0
  62. package/src/MenuTokenInput.tsx +104 -0
  63. package/src/MultiSelectMenu/MultiSelectMenu.stories.tsx +214 -0
  64. package/src/MultiSelectMenu/MultiSelectMenu.tsx +38 -68
  65. package/src/MultiSelectMenu/MultiSelectMenuTypes.ts +8 -6
  66. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +46 -17
  67. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.typetest.tsx +6 -6
  68. package/src/NestedMenu/NestedMenu.feature +109 -0
  69. package/src/NestedMenu/NestedMenu.stories.tsx +157 -0
  70. package/src/NestedMenu/NestedMenu.tsx +102 -0
  71. package/src/NestedMenu/NestedMenuContent.tsx +71 -0
  72. package/src/NestedMenu/NestedMenuContext.tsx +56 -0
  73. package/src/NestedMenu/NestedMenuHeader.tsx +25 -0
  74. package/src/NestedMenu/NestedMenuItem.tsx +37 -0
  75. package/src/NestedMenu/NestedMenuTypes.ts +47 -0
  76. package/src/NestedMenu/__tests__/NestedMenu.test.tsx +31 -0
  77. package/src/NestedMenu/__tests__/NestedMenu.typetest.tsx +134 -0
  78. package/src/NestedMenu/index.ts +5 -0
  79. package/src/SingleSelectMenu/SingleSelectMenu.stories.tsx +227 -0
  80. package/src/SingleSelectMenu/SingleSelectMenu.tsx +23 -26
  81. package/src/SingleSelectMenu/SingleSelectMenuTypes.ts +7 -5
  82. package/src/SingleSelectMenu/__tests__/SingleSelectMenu.typetest.tsx +6 -6
  83. package/src/SubmenuItem/SubmenuItem.feature +82 -0
  84. package/src/SubmenuItem/SubmenuItem.stories.tsx +163 -0
  85. package/src/SubmenuItem/SubmenuItem.tsx +254 -0
  86. package/src/SubmenuItem/SubmenuItemTypes.ts +24 -0
  87. package/src/SubmenuItem/__tests__/SubmenuItem.test.tsx +3 -0
  88. package/src/SubmenuItem/__tests__/SubmenuItem.typetest.tsx +112 -0
  89. package/src/SubmenuItem/index.ts +2 -0
  90. package/src/hooks/useDownshiftDefaults.tsx +107 -0
  91. package/src/hooks/useItemFiltering.tsx +66 -0
  92. package/src/hooks/useMenuChildren.tsx +197 -128
  93. package/src/index.ts +6 -0
  94. package/src/menuTestingUtils.tsx +21 -10
  95. package/src/reducers/nestedMenuStateReducer.tsx +23 -0
  96. package/src/reducers/useComboboxStateReducer.tsx +26 -8
  97. package/src/reducers/useSelectStateReducer.tsx +24 -6
  98. package/src/storybookUtilities/menu-storybook-components.tsx +5 -3
  99. package/src/types.ts +38 -30
  100. package/src/utils/downshiftDefaults.ts +9 -0
  101. package/src/utils/getMultiSelectOverrides.ts +21 -0
  102. package/src/utils/reduceReducers.ts +19 -0
  103. package/src/utils/itemToString.ts +0 -5
@@ -0,0 +1,134 @@
1
+ import {
2
+ NestedMenu,
3
+ ActionMenu,
4
+ MenuToggleButton,
5
+ NestedMenuItem,
6
+ MenuItem,
7
+ NestedMenuContent,
8
+ type TypeNestedMenuItemProps,
9
+ } from "../../index";
10
+
11
+ export const NestedMenuTypeTest = () => (
12
+ <>
13
+ {/* Valid NestedMenu with all props */}
14
+ <NestedMenu
15
+ initialMenuId={"root"}
16
+ menuToggleElement={<button>Open</button>}
17
+ onBackButtonClick={(menuId) => console.log(menuId)}
18
+ // The menus can use different menu components and props
19
+ menus={{
20
+ root: {
21
+ MenuComponent: ActionMenu,
22
+ menuProps: {
23
+ // menuItems prop is supported
24
+ menuItems: [
25
+ { id: "item1", children: "Item 1", childMenuId: "child" },
26
+ { id: "item2", children: "Item 2" },
27
+ ],
28
+ MenuItemComponent: NestedMenuItem,
29
+ },
30
+ },
31
+ child: {
32
+ MenuComponent: ActionMenu,
33
+ menuProps: {
34
+ // children prop is supported
35
+ children: (
36
+ <NestedMenuContent>
37
+ <NestedMenuItem children="Child Item 1" id="child-1" />
38
+ </NestedMenuContent>
39
+ ),
40
+ },
41
+ },
42
+ }}
43
+ />
44
+
45
+ {/*Required/optional props*/}
46
+ <NestedMenu
47
+ // onBackButtonClick is not required
48
+ onBackButtonClick={undefined}
49
+ // @ts-expect-error initialMenuId is required
50
+ initialMenuId={undefined}
51
+ // @ts-expect-error menuToggleElement is required
52
+ menuToggleElement={undefined}
53
+ // @ts-expect-error menus is required
54
+ menus={undefined}
55
+ />
56
+
57
+ {/*Invalid props*/}
58
+ <NestedMenu
59
+ // @ts-expect-error initialMenuId must be a string
60
+ initialMenuId={3}
61
+ // @ts-expect-error menuToggleElement must be a React element, not a component
62
+ menuToggleElement={MenuToggleButton}
63
+ // @ts-expect-error menus must be an object, not an array
64
+ menus={[
65
+ {
66
+ MenuComponent: ActionMenu,
67
+ menuProps: {
68
+ menuItems: [
69
+ { id: "item1", children: "Item 1", childMenuId: "child" },
70
+ { id: "item2", children: "Item 2" },
71
+ ] as TypeNestedMenuItemProps[],
72
+ },
73
+ },
74
+ ]}
75
+ />
76
+
77
+ {/*menuToggleElement is not allowed on menuProps*/}
78
+ <NestedMenu
79
+ initialMenuId={"1"}
80
+ menuToggleElement={<span />}
81
+ menus={{
82
+ "1": {
83
+ MenuComponent: ActionMenu,
84
+ menuProps: {
85
+ menuItems: [
86
+ { id: "item1", children: "Item 1", childMenuId: "2" },
87
+ { id: "item2", children: "Item 2" },
88
+ ] as TypeNestedMenuItemProps[],
89
+ // @ts-expect-error menuToggleElement is not allowed here
90
+ menuToggleElement: <span />,
91
+ },
92
+ },
93
+ }}
94
+ />
95
+
96
+ {/*menuProps cannot have unexpected values*/}
97
+ <NestedMenu
98
+ initialMenuId={"1"}
99
+ menuToggleElement={<span />}
100
+ menus={{
101
+ "1": {
102
+ MenuComponent: ActionMenu,
103
+ menuProps: {
104
+ menuItems: [
105
+ { id: "item1", children: "Item 1", childMenuId: "2" },
106
+ { id: "item2", children: "Item 2" },
107
+ ] as TypeNestedMenuItemProps[],
108
+ // @ts-expect-error unexpected properties are not allowed
109
+ random: "asdf",
110
+ },
111
+ },
112
+ }}
113
+ />
114
+
115
+ {/*menuProps cannot combine menuItems and children*/}
116
+ <NestedMenu
117
+ initialMenuId={"1"}
118
+ menuToggleElement={<span />}
119
+ menus={{
120
+ "1": {
121
+ MenuComponent: ActionMenu,
122
+ // @ts-expect-error menuItems and children cannot be used together
123
+ menuProps: {
124
+ menuItems: [
125
+ { id: "item1", children: "Item 1", childMenuId: "2" },
126
+ { id: "item2", children: "Item 2" },
127
+ ] as TypeNestedMenuItemProps[],
128
+ children: "asdf",
129
+ },
130
+ },
131
+ }}
132
+ />
133
+ </>
134
+ );
@@ -0,0 +1,5 @@
1
+ export * from "./NestedMenu";
2
+ export * from "./NestedMenuItem";
3
+ export * from "./NestedMenuHeader";
4
+ export * from "./NestedMenuTypes";
5
+ export * from "./NestedMenuContent";
@@ -0,0 +1,227 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks */
2
+ import React from "react";
3
+ import type { Meta, StoryObj } from "@storybook/react";
4
+ import { action } from "@storybook/addon-actions";
5
+
6
+ import { Box } from "@sproutsocial/seeds-react-box";
7
+ import { Icon, type TypeIconName } from "@sproutsocial/seeds-react-icon";
8
+ import { StorybookMenuToggleButton } from "../storybookUtilities/menu-storybook-components";
9
+ import { TEST_BOOKS } from "../__fixtures__/menu-test-data";
10
+ import {
11
+ SingleSelectMenu,
12
+ MenuContent,
13
+ MenuGroup,
14
+ MenuItem,
15
+ MenuHeader,
16
+ MenuSearchInput,
17
+ type InputType,
18
+ type TypeInternalItemProps,
19
+ type TypeSingleSelectMenuBaseProps,
20
+ type TypeSingleSelectMenuProps,
21
+ } from "../index";
22
+
23
+ type CustomSingleSelectArgs = TypeSingleSelectMenuProps & {
24
+ inputType: InputType;
25
+ };
26
+
27
+ const meta: Meta<CustomSingleSelectArgs> = {
28
+ title: "Under Development/SingleSelectMenu",
29
+ component: SingleSelectMenu,
30
+ parameters: {
31
+ controls: {
32
+ expanded: true,
33
+ },
34
+ },
35
+ argTypes: {
36
+ inputType: {
37
+ options: ["radio", "checkbox", "icon", "switch", undefined],
38
+ control: { type: "select" },
39
+ description: "The type of input to use for the menu items",
40
+ table: {
41
+ category: "Menu Item Selectable",
42
+ defaultValue: { summary: "undefined" },
43
+ },
44
+ },
45
+ },
46
+ args: {
47
+ inputType: "radio",
48
+ },
49
+ decorators: [
50
+ (Story) => (
51
+ <Box display="flex" flexDirection="column" gap="16px" maxWidth={300}>
52
+ <Story />
53
+ </Box>
54
+ ),
55
+ ],
56
+ };
57
+
58
+ export default meta;
59
+
60
+ type Story = StoryObj<CustomSingleSelectArgs>;
61
+
62
+ export const SingleSelectMenuExample: Story = {
63
+ name: "Simple Single Select Menu",
64
+ render: ({ inputType, ...args }) => {
65
+ return (
66
+ <SingleSelectMenu
67
+ itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
68
+ menuToggleElement={<StorybookMenuToggleButton />}
69
+ {...(args as object)}
70
+ >
71
+ <MenuContent>
72
+ <MenuGroup id="books">
73
+ {TEST_BOOKS.map(({ id, title }) => (
74
+ <MenuItem key={id} id={id} inputType={inputType}>
75
+ {title}
76
+ </MenuItem>
77
+ ))}
78
+ </MenuGroup>
79
+ </MenuContent>
80
+ </SingleSelectMenu>
81
+ );
82
+ },
83
+ };
84
+
85
+ type permissionsObj = {
86
+ id: string;
87
+ index: number;
88
+ label: string;
89
+ iconName: TypeIconName;
90
+ };
91
+
92
+ const permissionsOptions: permissionsObj[] = [
93
+ { id: "no-access", index: 0, label: "No access", iconName: "circle-minus" },
94
+ { id: "read-only", index: 1, label: "Read only", iconName: "circle-half" },
95
+ {
96
+ id: "need-approval",
97
+ index: 2,
98
+ label: "Needs Approval",
99
+ iconName: "circle-half",
100
+ },
101
+ {
102
+ id: "can-reply",
103
+ index: 3,
104
+ label: "Can Reply",
105
+ iconName: "circle-half",
106
+ },
107
+ {
108
+ id: "full-publishing",
109
+ index: 4,
110
+ label: "Full Publishing",
111
+ iconName: "circle-fill",
112
+ },
113
+ ];
114
+
115
+ export const SingleSelectMenuWithItemsAsProps: Story = {
116
+ name: "With Items as Props",
117
+ render: ({ inputType, ...args }) => {
118
+ const items = TEST_BOOKS.map((book) => ({
119
+ id: book.id,
120
+ children: book.title,
121
+ inputType,
122
+ }));
123
+ return (
124
+ <SingleSelectMenu
125
+ {...args}
126
+ menuToggleElement={<StorybookMenuToggleButton />}
127
+ menuItems={items}
128
+ />
129
+ );
130
+ },
131
+ };
132
+
133
+ export const SingleSelectWithIcons: Story = {
134
+ name: "Single Select with icons",
135
+ render: ({ inputType, ...args }) => {
136
+ return (
137
+ <SingleSelectMenu
138
+ itemToString={(item) =>
139
+ item ? permissionsOptions[item.index].label : ""
140
+ }
141
+ menuToggleElement={
142
+ <StorybookMenuToggleButton buttonText="Select permissions" />
143
+ }
144
+ {...(args as object)}
145
+ >
146
+ <MenuContent>
147
+ <MenuGroup id="permissions">
148
+ {permissionsOptions.map(({ id, label, iconName }) => (
149
+ <MenuItem
150
+ key={id}
151
+ id={id}
152
+ display="flex"
153
+ alignContent="top"
154
+ inputType={inputType}
155
+ >
156
+ <Icon name={iconName} mr={300} />
157
+ {label}
158
+ </MenuItem>
159
+ ))}
160
+ </MenuGroup>
161
+ </MenuContent>
162
+ </SingleSelectMenu>
163
+ );
164
+ },
165
+ };
166
+
167
+ export const SingleSelectControlled: Story = {
168
+ name: "Controlled State",
169
+ render: ({ inputType, ...args }) => {
170
+ const [isOpen, setIsOpen] = React.useState<boolean>(false);
171
+ const [selectedItem, setSelectedItem] =
172
+ React.useState<TypeInternalItemProps | null>(null);
173
+ return (
174
+ <SingleSelectMenu
175
+ isOpen={isOpen}
176
+ onIsOpenChange={({ isOpen }) => setIsOpen(isOpen)}
177
+ selectedItem={selectedItem}
178
+ onSelectedItemChange={({ selectedItem: newSelectedItem }) => {
179
+ setSelectedItem(newSelectedItem);
180
+ }}
181
+ itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
182
+ menuToggleElement={<StorybookMenuToggleButton />}
183
+ {...(args as object)}
184
+ >
185
+ <MenuContent>
186
+ <MenuGroup id="books">
187
+ {TEST_BOOKS.map(({ id, title }) => (
188
+ <MenuItem key={id} id={id} inputType={inputType}>
189
+ {title}
190
+ </MenuItem>
191
+ ))}
192
+ </MenuGroup>
193
+ </MenuContent>
194
+ </SingleSelectMenu>
195
+ );
196
+ },
197
+ };
198
+
199
+ export const SingleSelectMenuWithSearch: Story = {
200
+ name: "Single Select Menu with Search",
201
+ render: ({ inputType, ...args }) => {
202
+ return (
203
+ <SingleSelectMenu
204
+ itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
205
+ menuToggleElement={<StorybookMenuToggleButton />}
206
+ onStateChange={(changes) => action("onStateChange")(changes)}
207
+ {...(args as object)}
208
+ >
209
+ <MenuHeader title="Select Book">
210
+ <MenuSearchInput
211
+ id="single-select-search"
212
+ name="search"
213
+ type="search"
214
+ aria-label="Search Books"
215
+ />
216
+ </MenuHeader>
217
+ <MenuContent>
218
+ {TEST_BOOKS.map(({ id, title }) => (
219
+ <MenuItem key={id} id={id} inputType={inputType}>
220
+ {title}
221
+ </MenuItem>
222
+ ))}
223
+ </MenuContent>
224
+ </SingleSelectMenu>
225
+ );
226
+ },
227
+ };
@@ -1,15 +1,12 @@
1
1
  import React from "react";
2
- import { useSelect, type UseSelectState } from "downshift";
2
+ import { useSelect } from "downshift";
3
+ import type { TypeMenuItemProps } from "../MenuItem";
3
4
  import { useMenuChildren } from "../hooks/useMenuChildren";
4
- import {
5
- defaultUseSelectProps,
6
- useSelectStateReducer,
7
- } from "../reducers/useSelectStateReducer";
8
- import { itemToString } from "../utils/itemToString";
9
- import { MenuRoot } from "../MenuRoot";
10
- import type { TypeDefaultItemProps } from "../types";
5
+ import { useDownshiftDefaults } from "../hooks/useDownshiftDefaults";
6
+ import { itemToString } from "../utils/downshiftDefaults";
7
+ import { reduceReducers } from "../utils/reduceReducers";
8
+ import { MenuRoot, type TypeMenuRootProps } from "../MenuRoot";
11
9
  import type { TypeSingleSelectMenuProps } from "./SingleSelectMenuTypes";
12
- import { MenuItem } from "../MenuItem/index";
13
10
 
14
11
  /**
15
12
  * @link https://seeds.sproutsocial.com/components/singleselect-menu/
@@ -20,37 +17,36 @@ import { MenuItem } from "../MenuItem/index";
20
17
  * @see {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/ActionMenu | ActionMenu}
21
18
  */
22
19
 
23
- export const SingleSelectMenu = ({
20
+ export const SingleSelectMenu = <
21
+ I extends TypeMenuItemProps = TypeMenuItemProps
22
+ >({
24
23
  children: childrenProp,
25
24
  stateReducer: externalStateReducer,
26
- items,
27
- MenuItemComponent = MenuItem,
25
+ menuItems,
26
+ MenuItemComponent,
28
27
  ...useSelectProps
29
- }: TypeSingleSelectMenuProps) => {
30
- /** Get a list of items from the children */
28
+ }: TypeSingleSelectMenuProps<I>) => {
29
+ /** Get a list of menuItems from the children */
31
30
  const { allMenuItems, menuItemsMap, children } = useMenuChildren({
32
31
  children: childrenProp,
33
- items,
32
+ menuItems,
34
33
  MenuItemComponent,
35
34
  });
35
+ const { defaultDownshiftProps, ...restDefaults } = useDownshiftDefaults({
36
+ isCombobox: false,
37
+ });
36
38
 
37
39
  /**
38
40
  * Destructure Downshift props and call the core useSelect with items and state handler
39
41
  */
40
42
  const { isOpen, ...useSelectReturnProps } = useSelect({
41
- ...defaultUseSelectProps,
43
+ ...defaultDownshiftProps.select,
42
44
  items: allMenuItems,
43
45
  itemToString,
44
- stateReducer: (state, actionAndChanges) => {
45
- let newState: Partial<UseSelectState<TypeDefaultItemProps>>;
46
- newState = useSelectStateReducer(state, actionAndChanges);
47
- return externalStateReducer
48
- ? externalStateReducer(state, {
49
- ...actionAndChanges,
50
- changes: newState,
51
- })
52
- : newState;
53
- },
46
+ stateReducer: reduceReducers(
47
+ defaultDownshiftProps.select.stateReducer,
48
+ externalStateReducer
49
+ ),
54
50
  ...useSelectProps,
55
51
  });
56
52
 
@@ -64,6 +60,7 @@ export const SingleSelectMenu = ({
64
60
  isOpen,
65
61
  ...useSelectProps,
66
62
  ...useSelectReturnProps,
63
+ ...restDefaults,
67
64
  }}
68
65
  >
69
66
  {children}
@@ -1,10 +1,12 @@
1
- import type { TypeMenuRootOwnProps } from "../MenuRoot";
1
+ import type { TypeMenuRootOwnProps, TypeMenuRootProps } from "../MenuRoot";
2
2
  import type { TypeDownshiftSelectProps } from "../MenuContext";
3
+ import type { TypeMenuItemProps } from "../MenuItem";
3
4
  import type { TypeChildrenOrItems } from "../types";
4
5
 
5
- interface TypeSingleSelectMenuBaseProps
6
+ export interface TypeSingleSelectMenuBaseProps
6
7
  extends TypeMenuRootOwnProps,
7
- Omit<TypeDownshiftSelectProps, "items"> {}
8
+ TypeDownshiftSelectProps {}
8
9
 
9
- export type TypeSingleSelectMenuProps = TypeSingleSelectMenuBaseProps &
10
- TypeChildrenOrItems;
10
+ export type TypeSingleSelectMenuProps<
11
+ I extends TypeMenuItemProps = TypeMenuItemProps
12
+ > = TypeSingleSelectMenuBaseProps & TypeChildrenOrItems<I>;
@@ -20,15 +20,15 @@ export const SingleSelectMenuTypeTest = () => (
20
20
  <MenuContent>Test</MenuContent>
21
21
  <MenuFooter>Test</MenuFooter>
22
22
  </SingleSelectMenu>
23
- {/* Valid SingleSelectMenu allows items to be passed using the items prop */}
23
+ {/* Valid SingleSelectMenu allows menuItems to be passed using the menuItems prop */}
24
24
  <SingleSelectMenu
25
25
  menuToggleElement={<>test</>}
26
- items={[{ children: "Item Name", id: "item-1" }]}
26
+ menuItems={[{ children: "Item Name", id: "item-1" }]}
27
27
  />
28
- {/* Valid SingleSelectMenu allows items prop to be rendered using a MenuItemComponent prop */}
28
+ {/* Valid SingleSelectMenu allows menuItems prop to be rendered using a MenuItemComponent prop */}
29
29
  <SingleSelectMenu
30
30
  menuToggleElement={<>test</>}
31
- items={[{ children: "Item Name", id: "item-1" }]}
31
+ menuItems={[{ children: "Item Name", id: "item-1" }]}
32
32
  MenuItemComponent={MenuItem}
33
33
  />
34
34
  {/* Valid SingleSelectMenu accepts Downshift props and functions for useSelect */}
@@ -61,8 +61,8 @@ export const SingleSelectMenuTypeTest = () => (
61
61
  >
62
62
  Children
63
63
  </SingleSelectMenu>
64
- {/* @ts-expect-error - when both children and items provided */}
65
- <SingleSelectMenu menuToggleElement={<>test</>} items={[]}>
64
+ {/* @ts-expect-error - when both children and menuItems provided */}
65
+ <SingleSelectMenu menuToggleElement={<>test</>} menuItems={[]}>
66
66
  Children
67
67
  </SingleSelectMenu>
68
68
  <SingleSelectMenu
@@ -0,0 +1,82 @@
1
+ Scenario: SubmenuItem is expandable via click
2
+ Given a SubmenuItem is not disabled
3
+ When the SubmenuItem is clicked
4
+ Then the submenu should be displayed
5
+
6
+ Scenario Outline: SubmenuItem can be expanded via keyboard
7
+ Given a SubmenuItem is focused
8
+ And the SubmenuItem is not disabled
9
+ When the SubmenuItem is triggered via <interaction>
10
+ Then the submenu should be displayed
11
+
12
+ Examples:
13
+ | interaction |
14
+ | pressing the Enter key |
15
+ | pressing the Space key |
16
+ | pressing the Right Arrow key |
17
+
18
+ Scenario Outline: SubmenuItem is disabled
19
+ Given a SubmenuItem is disabled
20
+ When the SubmenuItem is triggered via <interaction>
21
+ Then no events should fire
22
+ And the submenu should not be displayed
23
+
24
+ Examples:
25
+ | interaction |
26
+ | clicking |
27
+ | focusing and pressing the Enter key |
28
+ | focusing and pressing the Space key |
29
+ | focusing and pressing the Right Arrow key |
30
+
31
+ Scenario: SubmenuItem displays an arrow indicator
32
+ Given a SubmenuItem is rendered
33
+ Then it should display an arrow icon indicating a submenu
34
+
35
+ Scenario: SubmenuItem can be collapsed via keyboard
36
+ Given a SubmenuItem is expanded
37
+ And the submenu is displayed
38
+ When the Left Arrow key is pressed
39
+ Then the submenu should be hidden
40
+
41
+ Rule: API
42
+ Background:
43
+ Given an engineer is developing using the component
44
+
45
+ Scenario Outline: SubmenuItem accepts children
46
+ When <child component(s)> provided
47
+ Then the SubmenuItem should render the <component(s)>
48
+
49
+ Examples: child components
50
+ | child component(s) | component(s) |
51
+ | a Link is | Link |
52
+ | a Text is | Text |
53
+ | an Icon is | Icon |
54
+ | a Text then an Icon are | Text and Icon |
55
+ | an Icon and Text are | Icon and Text |
56
+
57
+ Scenario Outline: A SubmenuItem accepts system props
58
+ When a system prop from the category <systemPropCategory> is passed a value
59
+ Then the SubmenuItem will have <systemPropName> styling
60
+
61
+ Examples: System Props
62
+ | systemPropCategory | systemPropName |
63
+ | color | bg |
64
+ | border | borderColor |
65
+ | layout | display |
66
+ | position | zIndex |
67
+ | flexbox | flex |
68
+ | grid | gridGap |
69
+ | space | p |
70
+
71
+ Scenario Outline: SubmenuItem validates prop types
72
+ When any <propName> is passed to the SubmenuItem
73
+ Then the SubmenuItem should check that each <propName> is of the expected <propType>
74
+
75
+ Examples: Prop Types
76
+ | propName | propType |
77
+ | children | ReactNode |
78
+ | disabled | boolean |
79
+ | elevation | low, medium, or high |
80
+ | href | string |
81
+ | onClick | React.MouseEventHandler |
82
+ | role | string |