@sproutsocial/seeds-react-menu 0.4.1 → 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 (105) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +32 -0
  3. package/dist/esm/index.js +1553 -423
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/index.d.mts +303 -122
  6. package/dist/index.d.ts +303 -122
  7. package/dist/index.js +1561 -405
  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 +30 -26
  12. package/src/ActionMenu/ActionMenuTypes.ts +13 -8
  13. package/src/ActionMenu/__tests__/ActionMenu.test.tsx +17 -30
  14. package/src/ActionMenu/__tests__/ActionMenu.typetest.tsx +32 -3
  15. package/src/Autocomplete/Autocomplete.stories.tsx +61 -2
  16. package/src/Autocomplete/Autocomplete.tsx +15 -74
  17. package/src/Autocomplete/AutocompleteInput.tsx +18 -27
  18. package/src/Autocomplete/AutocompleteTokenInput.tsx +50 -0
  19. package/src/Autocomplete/AutocompleteTypes.ts +27 -11
  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 +116 -3
  24. package/src/MenuContent/MenuContent.stories.tsx +164 -0
  25. package/src/MenuContent/MenuContent.tsx +35 -8
  26. package/src/MenuContent/MenuContentTypes.ts +7 -5
  27. package/src/MenuContent/__tests__/MenuContent.typetest.tsx +18 -2
  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 +21 -21
  32. package/src/MenuGroup/MenuGroup.stories.tsx +248 -0
  33. package/src/MenuGroup/MenuGroup.tsx +9 -7
  34. package/src/MenuGroup/MenuGroupTypes.ts +8 -6
  35. package/src/MenuGroup/__tests__/MenuGroup.test.tsx +6 -6
  36. package/src/MenuGroup/__tests__/MenuGroup.typetest.tsx +23 -4
  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 -9
  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 +44 -67
  65. package/src/MultiSelectMenu/MultiSelectMenuTypes.ts +9 -5
  66. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +46 -60
  67. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.typetest.tsx +29 -2
  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 +29 -25
  81. package/src/SingleSelectMenu/SingleSelectMenuTypes.ts +9 -5
  82. package/src/SingleSelectMenu/__tests__/SingleSelectMenu.typetest.tsx +32 -2
  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/__tests__/Menu.test.tsx +38 -0
  91. package/src/hooks/useDownshiftDefaults.tsx +107 -0
  92. package/src/hooks/useItemFiltering.tsx +66 -0
  93. package/src/hooks/useMenuChildren.tsx +279 -0
  94. package/src/index.ts +7 -1
  95. package/src/menuTestingUtils.tsx +21 -10
  96. package/src/reducers/nestedMenuStateReducer.tsx +23 -0
  97. package/src/reducers/useComboboxStateReducer.tsx +26 -8
  98. package/src/reducers/useSelectStateReducer.tsx +24 -6
  99. package/src/storybookUtilities/menu-storybook-components.tsx +5 -3
  100. package/src/types.ts +38 -5
  101. package/src/utils/downshiftDefaults.ts +9 -0
  102. package/src/utils/getMultiSelectOverrides.ts +21 -0
  103. package/src/utils/reduceReducers.ts +19 -0
  104. package/src/hooks/useItemsFromChildren.tsx +0 -131
  105. package/src/utils/itemToString.ts +0 -5
@@ -1,9 +1,4 @@
1
- import React, {
2
- createContext,
3
- useMemo,
4
- useContext,
5
- type RefObject,
6
- } from "react";
1
+ import React, { createContext, useMemo, useContext } from "react";
7
2
  import type {
8
3
  UseComboboxReturnValue,
9
4
  UseComboboxProps,
@@ -12,35 +7,40 @@ import type {
12
7
  UseMultipleSelectionReturnValue,
13
8
  UseMultipleSelectionProps,
14
9
  } from "downshift";
15
- import type { TypeDefaultItemProps, TypeNotEmptyChildren } from "./types";
10
+ import type { TypeTokenInputProps } from "@sproutsocial/seeds-react-token-input";
11
+ import type {
12
+ TypeInternalItemProps,
13
+ TypeNotEmptyChildren,
14
+ TypeDownshiftMergedInputProps,
15
+ } from "./types";
16
16
 
17
17
  export interface TypeDownshiftComboboxProps
18
- extends Partial<UseComboboxProps<TypeDefaultItemProps>> {}
18
+ extends Partial<UseComboboxProps<TypeInternalItemProps>> {}
19
19
 
20
20
  export interface TypeDownshiftSelectProps
21
- extends Partial<UseSelectProps<TypeDefaultItemProps>> {}
21
+ extends Partial<UseSelectProps<TypeInternalItemProps>> {}
22
22
 
23
23
  export interface TypeDownshiftMultiSelectProps
24
24
  extends Partial<
25
25
  Omit<
26
- UseMultipleSelectionProps<TypeDefaultItemProps>,
26
+ UseMultipleSelectionProps<TypeInternalItemProps>,
27
27
  "stateReducer" | "onStateChange" | "getA11yStatusMessage"
28
28
  >
29
29
  > {
30
30
  // Multi Select Props with conflicting useSelect prop names
31
- getA11yMultiStatusMessage?: UseMultipleSelectionProps<TypeDefaultItemProps>["getA11yStatusMessage"];
32
- onMultiSelectStateChange?: UseMultipleSelectionProps<TypeDefaultItemProps>["onStateChange"];
33
- multiSelectStateReducer?: UseMultipleSelectionProps<TypeDefaultItemProps>["stateReducer"];
31
+ getA11yMultiStatusMessage?: UseMultipleSelectionProps<TypeInternalItemProps>["getA11yStatusMessage"];
32
+ onMultiSelectStateChange?: UseMultipleSelectionProps<TypeInternalItemProps>["onStateChange"];
33
+ multiSelectStateReducer?: UseMultipleSelectionProps<TypeInternalItemProps>["stateReducer"];
34
34
  }
35
35
 
36
36
  export interface TypeDownshiftComboboxReturnValue
37
- extends Partial<UseComboboxReturnValue<TypeDefaultItemProps>> {}
37
+ extends Partial<UseComboboxReturnValue<TypeInternalItemProps>> {}
38
38
 
39
39
  export interface TypeDownshiftSelectReturnValue
40
- extends Partial<UseSelectReturnValue<TypeDefaultItemProps>> {}
40
+ extends Partial<UseSelectReturnValue<TypeInternalItemProps>> {}
41
41
 
42
42
  export interface TypeDownshiftMultiSelectReturnValue
43
- extends Partial<UseMultipleSelectionReturnValue<TypeDefaultItemProps>> {}
43
+ extends Partial<UseMultipleSelectionReturnValue<TypeInternalItemProps>> {}
44
44
 
45
45
  type TypeDonwshiftSharedReturnValue =
46
46
  | "getLabelProps"
@@ -52,7 +52,20 @@ type TypeDonwshiftSharedProps =
52
52
  | "onSelectedItemChange"
53
53
  | "onStateChange"
54
54
  | "stateReducer";
55
- export interface TypeMenuContextProps
55
+
56
+ export interface TypeMenuContextBaseProps {
57
+ itemsMap?: { [key: string]: TypeInternalItemProps };
58
+ isListbox?: boolean;
59
+ hiddenItemsMap?: Record<string, boolean>;
60
+ setHiddenItemsMap?: React.Dispatch<
61
+ React.SetStateAction<Record<string, boolean>>
62
+ >;
63
+ subMenuId?: string;
64
+ setSubMenuId?: (id: string) => void;
65
+ contentRef?: React.RefObject<HTMLUListElement>;
66
+ }
67
+
68
+ interface TypeAllDownshiftProps
56
69
  extends Omit<
57
70
  TypeDownshiftComboboxReturnValue,
58
71
  TypeDonwshiftSharedReturnValue
@@ -62,9 +75,6 @@ export interface TypeMenuContextProps
62
75
  Omit<TypeDownshiftSelectProps, TypeDonwshiftSharedProps>,
63
76
  TypeDownshiftMultiSelectReturnValue,
64
77
  TypeDownshiftMultiSelectProps {
65
- items: TypeDefaultItemProps[];
66
- itemsMap?: { [key: string]: TypeDefaultItemProps };
67
- isListbox?: boolean;
68
78
  onHighlightedIndexChange?:
69
79
  | TypeDownshiftComboboxProps["onHighlightedIndexChange"]
70
80
  | TypeDownshiftSelectProps["onHighlightedIndexChange"];
@@ -90,24 +100,72 @@ export interface TypeMenuContextProps
90
100
  | TypeDownshiftComboboxReturnValue["getMenuProps"]
91
101
  | TypeDownshiftSelectReturnValue["getMenuProps"];
92
102
  }
103
+ export interface TypeMenuContextProps
104
+ extends TypeAllDownshiftProps,
105
+ TypeMenuContextBaseProps {
106
+ items: TypeInternalItemProps[];
107
+ itemsMap?: { [key: string]: TypeInternalItemProps };
108
+ selectedItemIds?: { [key: string]: boolean };
109
+ isListbox?: boolean;
110
+ }
93
111
 
94
112
  export interface TypeMenuContext extends TypeMenuContextProps {
95
- itemsMap: { [key: string]: TypeDefaultItemProps };
113
+ itemsMap: { [key: string]: TypeInternalItemProps };
114
+ isItemSelected: (id: string) => boolean;
115
+ getInputComponentProps: (
116
+ props: Pick<TypeTokenInputProps, "inputProps"> &
117
+ Parameters<Required<TypeMenuContextProps>["getInputProps"]>[0]
118
+ ) => Omit<TypeDownshiftMergedInputProps<TypeTokenInputProps>, "onClick">;
96
119
  }
97
120
 
98
121
  export interface TypeMenuProviderProps {
99
122
  children: TypeNotEmptyChildren;
100
- menuProps: Partial<TypeMenuContextProps>;
123
+ menuContext: TypeMenuContext;
124
+ }
125
+
126
+ export interface TypeMenuToggleContext extends TypeMenuContext {
127
+ ref?: (arg0: React.ElementRef<any> | HTMLElement) => void;
128
+ toggle?: () => void;
129
+ show?: () => void;
130
+ hide?: () => void;
131
+ ariaProps?: Record<string, any>;
132
+ }
133
+
134
+ export interface TypeMenuToggleProviderProps extends TypeMenuProviderProps {
135
+ menuContext: TypeMenuToggleContext;
101
136
  }
102
137
 
103
138
  /**
104
139
  * Object for Initializing the Menu Context
105
140
  */
106
- const defaultMenuContext: TypeMenuContext = {
141
+ export const defaultMenuContext: TypeMenuContext = {
107
142
  items: [],
108
143
  itemsMap: {},
144
+ isItemSelected: () => false,
145
+ getInputComponentProps: () => ({}),
109
146
  };
110
147
 
148
+ export const getSelectedItemIds = <
149
+ T extends TypeMenuContextProps["selectedItems"] | undefined,
150
+ R = T extends undefined ? undefined : Record<string, boolean>
151
+ >(
152
+ selectedItems?: T
153
+ ): R =>
154
+ selectedItems?.reduce(
155
+ (selected, item) => ({ ...selected, [item.id]: true }),
156
+ {} as Record<string, boolean>
157
+ ) as R;
158
+
159
+ export const checkIfItemSelected = ({
160
+ id,
161
+ selectedItem,
162
+ selectedItemIds,
163
+ }: {
164
+ id: string;
165
+ selectedItem?: TypeInternalItemProps | null;
166
+ selectedItemIds?: TypeMenuContextProps["selectedItemIds"];
167
+ }) => (selectedItemIds ? !!selectedItemIds[id] : selectedItem?.id === id);
168
+
111
169
  /**
112
170
  * Hook for initializing Menu context
113
171
  */
@@ -116,10 +174,15 @@ export const useMenu = ({
116
174
  isListbox = false,
117
175
  isOpen = false,
118
176
  itemsMap: itemsMapFromProps,
177
+ selectedItemIds: selectedItemIdsFromProps,
178
+ selectedItem,
179
+ selectedItems,
180
+ getInputProps,
181
+ contentRef: contentRefFromProps,
119
182
  ...rest
120
183
  }: Partial<TypeMenuContextProps> = {}): TypeMenuContext => {
184
+ const contentRef = contentRefFromProps || React.createRef<HTMLUListElement>();
121
185
  const menuProps = useMemo(() => ({ ...rest }), [rest]);
122
-
123
186
  const itemsMap = useMemo(
124
187
  () =>
125
188
  itemsMapFromProps ||
@@ -132,45 +195,136 @@ export const useMenu = ({
132
195
  [items, itemsMapFromProps]
133
196
  );
134
197
 
198
+ const selectedItemIds = useMemo(
199
+ () => selectedItemIdsFromProps || getSelectedItemIds(selectedItems),
200
+ [selectedItems, selectedItemIdsFromProps]
201
+ );
202
+
203
+ const isItemSelected = React.useCallback(
204
+ (id: string) => checkIfItemSelected({ id, selectedItem, selectedItemIds }),
205
+ [selectedItem, selectedItemIds]
206
+ );
207
+
208
+ const getInputComponentProps = React.useCallback<
209
+ TypeMenuContext["getInputComponentProps"]
210
+ >(
211
+ ({ inputProps, ...props }) => {
212
+ if (!getInputProps) return {};
213
+ const {
214
+ ["aria-activedescendant"]: activedescendant,
215
+ ["aria-autocomplete"]: autocomplete,
216
+ ["aria-controls"]: controls,
217
+ ["aria-expanded"]: expanded,
218
+ ["aria-labelledby"]: labelledby,
219
+ role,
220
+ autoComplete,
221
+ onClick,
222
+ value,
223
+ ...restProps
224
+ } = getInputProps(props);
225
+ return {
226
+ ...restProps,
227
+ inputProps: {
228
+ ["aria-activedescendant"]: activedescendant,
229
+ ["aria-autocomplete"]: autocomplete,
230
+ ["aria-controls"]: controls,
231
+ ["aria-expanded"]: expanded,
232
+ ["aria-labelledby"]: labelledby,
233
+ role,
234
+ autoComplete,
235
+ onClick,
236
+ value,
237
+ ...inputProps,
238
+ },
239
+ };
240
+ },
241
+ [getInputProps]
242
+ );
243
+
135
244
  return {
136
245
  isOpen,
137
246
  isListbox,
138
247
  items,
139
248
  itemsMap,
249
+ selectedItem,
250
+ selectedItems,
251
+ selectedItemIds,
252
+ isItemSelected,
253
+ getInputProps,
254
+ getInputComponentProps,
255
+ contentRef,
140
256
  ...menuProps,
141
257
  };
142
258
  };
143
259
 
144
260
  /**
145
- * MenuContext factory, sets the type and the initial object
261
+ * MenuToggleContext factory, sets the type and the initial object
262
+ * @description Context for the Menu Toggle
146
263
  */
147
- export const MenuContext = createContext<TypeMenuContext>(defaultMenuContext);
264
+ export const MenuToggleContext =
265
+ createContext<TypeMenuToggleContext>(defaultMenuContext);
148
266
 
149
267
  /**
150
- * Provider Component sets the context values into the provider
268
+ * MenuToggleProvider Component sets the context values into the provider
269
+ * @description Context Provider for the Menu Toggle
151
270
  */
152
- export const MenuProvider = ({
271
+ export const MenuToggleProvider = ({
153
272
  children,
154
- menuProps,
155
- }: TypeMenuProviderProps) => {
156
- const filterContext = useMenu(menuProps);
273
+ menuContext,
274
+ }: TypeMenuToggleProviderProps) => {
275
+ return (
276
+ <MenuToggleContext.Provider value={{ ...menuContext }}>
277
+ {children}
278
+ </MenuToggleContext.Provider>
279
+ );
280
+ };
281
+
282
+ /**
283
+ * Utility hook for consuming MenuToggleContext
284
+ */
285
+ export const useMenuToggleContext = () => {
286
+ const menuContextValue = useContext(MenuToggleContext);
157
287
 
288
+ if (menuContextValue === null) {
289
+ throw new Error(
290
+ "useMenuToggleContext must be used within a <MenuToggleContext.Provider />"
291
+ );
292
+ }
293
+
294
+ return menuContextValue;
295
+ };
296
+
297
+ /**
298
+ * MenuContentContext factory, sets the type and the initial object
299
+ * @description Context for the Menu Content
300
+ */
301
+ export const MenuContentContext =
302
+ createContext<TypeMenuContext>(defaultMenuContext);
303
+
304
+ /**
305
+ * MenuContentProvider Component sets the context values into the provider
306
+ * @description Context Provider for the Menu Content
307
+ */
308
+ export const MenuContentProvider = ({
309
+ children,
310
+ menuContext,
311
+ }: TypeMenuProviderProps) => {
158
312
  return (
159
- <MenuContext.Provider value={{ ...filterContext }}>
313
+ <MenuContentContext.Provider value={{ ...menuContext }}>
160
314
  {children}
161
- </MenuContext.Provider>
315
+ </MenuContentContext.Provider>
162
316
  );
163
317
  };
164
318
 
165
319
  /**
166
- * Utility hook for consuming MenuContext
320
+ * Utility hook for consuming MenuContentContext
167
321
  */
168
- export const useMenuContext = (): TypeMenuContext => {
169
- const menuContextValue = useContext(MenuContext);
322
+ export const useMenuContentContext = () => {
323
+ const menuContextValue = useContext(MenuContentContext);
170
324
 
171
325
  if (menuContextValue === null) {
172
326
  throw new Error(
173
- "useMenuContext must be used within a <MenuContext.Provider />"
327
+ "useMenuContentContext must be used within a <MenuContentContext.Provider />"
174
328
  );
175
329
  }
176
330
 
@@ -0,0 +1,202 @@
1
+ import { useState } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { action } from "@storybook/addon-actions";
4
+
5
+ import { Box } from "@sproutsocial/seeds-react-box";
6
+ import { Button } from "@sproutsocial/seeds-react-button";
7
+ import { Label } from "@sproutsocial/seeds-react-label";
8
+ import { Link } from "@sproutsocial/seeds-react-link";
9
+ import { Switch } from "@sproutsocial/seeds-react-switch";
10
+ import { Text } from "@sproutsocial/seeds-react-text";
11
+
12
+ import { StorybookMenuToggleButton } from "../storybookUtilities/menu-storybook-components";
13
+ import { TEST_BOOKS } from "../__fixtures__/menu-test-data";
14
+ import {
15
+ MenuContent,
16
+ MenuGroup,
17
+ SingleSelectMenu,
18
+ MenuFooter,
19
+ MenuItem,
20
+ type TypeMenuFooterProps,
21
+ } from "../index";
22
+
23
+ interface MenuFooterStoryArgs extends TypeMenuFooterProps {
24
+ "Trigger Text"?: string;
25
+ componentOne?: string;
26
+ componentTwo?: string;
27
+ componentThree?: string;
28
+ }
29
+ type Story = StoryObj<MenuFooterStoryArgs>;
30
+
31
+ const optionsArr = ["Button Example", "Link Example", "Text Example", "none"];
32
+
33
+ const meta: Meta<MenuFooterStoryArgs> = {
34
+ title: "Under Development/Primitives/MenuFooter",
35
+ component: MenuFooter,
36
+ parameters: {
37
+ controls: { expanded: true, sort: "requiredFirst" },
38
+ },
39
+ argTypes: {
40
+ "Trigger Text": {
41
+ table: {
42
+ category: "Button",
43
+ },
44
+ description: "Change text of trigger Button",
45
+ control: "text",
46
+ },
47
+ componentOne: {
48
+ table: {
49
+ category: "MenuFooter",
50
+ },
51
+ description: "Example Child Component",
52
+ options: optionsArr,
53
+ control: { type: "select" },
54
+ },
55
+ componentTwo: {
56
+ table: {
57
+ category: "MenuFooter",
58
+ },
59
+ description: "Example Child Component",
60
+ options: optionsArr,
61
+ control: { type: "select" },
62
+ },
63
+ componentThree: {
64
+ table: {
65
+ category: "MenuFooter",
66
+ },
67
+ description: "Example Child Component",
68
+ options: optionsArr,
69
+ control: { type: "select" },
70
+ },
71
+ },
72
+ args: {
73
+ "Trigger Text": "Select a Book",
74
+ display: "flex",
75
+ flexDirection: "row",
76
+ alignItems: "center",
77
+ justifyContent: "space-between",
78
+ componentOne: optionsArr[0],
79
+ componentTwo: optionsArr[3],
80
+ componentThree: optionsArr[2],
81
+ },
82
+ };
83
+
84
+ export default meta;
85
+
86
+ const handleOnClick = (e: React.MouseEvent, itemName: string) => {
87
+ action(`${itemName}`)(e);
88
+ };
89
+
90
+ const StoryFooterExamples = ({ type }) => {
91
+ switch (type) {
92
+ case optionsArr[0]:
93
+ return (
94
+ <Button
95
+ appearance="primary"
96
+ display="flex"
97
+ justifyContent="center"
98
+ alignItems="center"
99
+ >
100
+ Save
101
+ </Button>
102
+ );
103
+ case optionsArr[1]:
104
+ return (
105
+ <Link mt="space.100" href="#">
106
+ Help
107
+ </Link>
108
+ );
109
+ case optionsArr[2]:
110
+ return <Text.SmallByline>Books will update on save.</Text.SmallByline>;
111
+ default:
112
+ case optionsArr[3]:
113
+ return null;
114
+ }
115
+ };
116
+
117
+ export const MenuFooterSandbox: Story = {
118
+ name: "Sandbox",
119
+ render: function MenuWithFooter(args) {
120
+ return (
121
+ <Box display="flex" gap="16px" flexDirection="column" maxWidth={300}>
122
+ <SingleSelectMenu
123
+ defaultIsOpen
124
+ menuToggleElement={<StorybookMenuToggleButton />}
125
+ >
126
+ <MenuContent>
127
+ <MenuGroup id="books">
128
+ {TEST_BOOKS.map((book) => (
129
+ <MenuItem
130
+ key={book.id}
131
+ onClick={(e) => handleOnClick(e, book.title)}
132
+ id={book.id}
133
+ >
134
+ {book.title}
135
+ </MenuItem>
136
+ ))}
137
+ </MenuGroup>
138
+ </MenuContent>
139
+ <MenuFooter
140
+ alignItems={args.alignItems}
141
+ justifyContent={args.justifyContent}
142
+ display={args.display}
143
+ flexDirection={args.flexDirection}
144
+ >
145
+ <StoryFooterExamples type={args.componentThree} />
146
+ <StoryFooterExamples type={args.componentOne} />
147
+ </MenuFooter>
148
+ </SingleSelectMenu>
149
+ </Box>
150
+ );
151
+ },
152
+ };
153
+
154
+ export const FooterwithActions: Story = {
155
+ name: "Footer Children",
156
+ parameters: {
157
+ controls: { exclude: ["componentOne", "componentTwo", "componentThree"] },
158
+ },
159
+ render: function FooterWithActions(args) {
160
+ const [checked, setChecked] = useState(false);
161
+ return (
162
+ <Box display="flex" gap="16px" flexDirection="column" maxWidth={300}>
163
+ <SingleSelectMenu
164
+ defaultIsOpen
165
+ menuToggleElement={<StorybookMenuToggleButton />}
166
+ >
167
+ <MenuContent>
168
+ <MenuGroup id="books">
169
+ {TEST_BOOKS.map((book) => (
170
+ <MenuItem
171
+ key={book.id}
172
+ onClick={(e) => handleOnClick(e, book.title)}
173
+ id={book.id}
174
+ >
175
+ {book.title}
176
+ </MenuItem>
177
+ ))}
178
+ </MenuGroup>
179
+ </MenuContent>
180
+ <MenuFooter display={args.display} flexDirection="column">
181
+ <Box display="flex" justifyContent="space-between">
182
+ <Box display="flex" justifyContent="flex-start">
183
+ <Switch
184
+ onClick={(event) => {
185
+ event?.preventDefault;
186
+ setChecked((d) => !d);
187
+ }}
188
+ checked={checked}
189
+ id="switch"
190
+ />
191
+ <Label htmlFor="switch" ml="10px">
192
+ Enable cliff notes
193
+ </Label>
194
+ </Box>
195
+ <Button appearance="primary">Save</Button>
196
+ </Box>
197
+ </MenuFooter>
198
+ </SingleSelectMenu>
199
+ </Box>
200
+ );
201
+ },
202
+ };
@@ -1,8 +1,13 @@
1
- import { render, screen } from "@sproutsocial/seeds-react-testing-library";
2
- import { MenuFooter, MenuRoot } from "../../index";
1
+ import {
2
+ render,
3
+ screen,
4
+ actRender,
5
+ } from "@sproutsocial/seeds-react-testing-library";
3
6
  import { Button } from "@sproutsocial/seeds-react-button";
4
7
  // import { Link } from "@sproutsocial/seeds-react-link";
5
8
  import { Switch } from "@sproutsocial/seeds-react-switch";
9
+ import { MenuFooter, MenuToggleButton } from "../../index";
10
+ import { TestWithMenuRoot } from "../../menuTestingUtils";
6
11
 
7
12
  describe("MenuFooter API", () => {
8
13
  const contextTests = [
@@ -63,8 +68,8 @@ describe("MenuFooter API", () => {
63
68
 
64
69
  it.each(contextTests)(
65
70
  "$children rendered when $children provided",
66
- ({ TestComponent, test }) => {
67
- render(
71
+ async ({ TestComponent, test }) => {
72
+ await actRender(
68
73
  <MenuFooter>
69
74
  Hello world!
70
75
  {TestComponent}
@@ -77,25 +82,25 @@ describe("MenuFooter API", () => {
77
82
 
78
83
  it.each(contextTests)(
79
84
  "$children rendered when $children provided in MenuRoot",
80
- ({ TestComponent, test }) => {
81
- render(
82
- <MenuRoot
85
+ async ({ TestComponent, test }) => {
86
+ await actRender(
87
+ <TestWithMenuRoot
83
88
  isOpen
84
- menuToggleElement={<Button onClick={jest.fn()}>Open</Button>}
89
+ menuToggleElement={<MenuToggleButton>Open</MenuToggleButton>}
85
90
  >
86
91
  <MenuFooter>
87
92
  Hello world!
88
93
  {TestComponent}
89
94
  </MenuFooter>
90
- </MenuRoot>
95
+ </TestWithMenuRoot>
91
96
  );
92
97
 
93
98
  test();
94
99
  }
95
100
  );
96
101
 
97
- it("extends Box", () => {
98
- render(<MenuFooter bg="#ff0000">Hello world!</MenuFooter>);
102
+ it("extends Box", async () => {
103
+ await actRender(<MenuFooter bg="#ff0000">Hello world!</MenuFooter>);
99
104
 
100
105
  const menuFooter = screen.getByText("Hello world!");
101
106
  expect(menuFooter).toHaveStyle("background: #ff0000");
@@ -32,9 +32,9 @@ Feature: Menu Group
32
32
  When the "title" prop is not set
33
33
  Then no title is visible at the top of the group
34
34
 
35
- Scenario: MenuGroup has no items provided
35
+ Scenario: MenuGroup has no menuItems provided
36
36
  When no children are provided to MenuGroup
37
- And no items prop is provided
37
+ And no menuItems prop is provided
38
38
  Then the MenuGroup will show a warning in the console
39
39
  And will not render the empty group
40
40
 
@@ -42,24 +42,24 @@ Feature: Menu Group
42
42
  When MenuItem components are passed as children to a MenuGroup
43
43
  Then the MenuGroup will render them inside of the group
44
44
 
45
- Scenario: MenuGroup items prop might be empty
46
- Given a list of items is empty
47
- When that list of items is provided to the items prop on a MenuGroup
45
+ Scenario: MenuGroup menuItems prop might be empty
46
+ Given a list of menuItems is empty
47
+ When that list of menuItems is provided to the menuItems prop on a MenuGroup
48
48
  Then the MenuGroup will show a warning in the console
49
49
  And will not render the empty group
50
50
 
51
- Scenario: MenuGroup can take items via a prop
52
- Given a list of items is not empty
53
- And the items are a valid object shape
54
- When that list of items is provided to the items prop on a MenuGroup
55
- Then the MenuGroup will render the items using the MenuItem component
51
+ Scenario: MenuGroup can take menuItems via a prop
52
+ Given a list of menuItems is not empty
53
+ And the menuItems are a valid object shape
54
+ When that list of menuItems is provided to the menuItems prop on a MenuGroup
55
+ Then the MenuGroup will render the menuItems using the MenuItem component
56
56
 
57
- Scenario: MenuGroup can use the items prop with a custom MenuItem
58
- Given a list of items is not empty
59
- And the items are a valid object shape
60
- When that list of items is provided to the items prop on a MenuGroup
57
+ Scenario: MenuGroup can use the menuItems prop with a custom MenuItem
58
+ Given a list of menuItems is not empty
59
+ And the menuItems are a valid object shape
60
+ When that list of menuItems is provided to the menuItems prop on a MenuGroup
61
61
  And a valid component is provided to the customMenuItem prop
62
- Then the MenuGroup will render the items using the customMenuItem component
62
+ Then the MenuGroup will render the menuItems using the customMenuItem component
63
63
 
64
64
  Scenario Outline: MenuGroup has custom content
65
65
  When <custom content> is present at the <location>
@@ -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 | tyepof MenuItem |
79
+ | propName | propType |
80
+ | children | ReactNode |
81
+ | title | string |
82
+ | menuItems | TypeMenuItemProps[] |
83
+ | isSingleSelect | boolean |
84
+ | MenuItemComponent | React.ComponentType<TypeMenuItemProps> |