@sproutsocial/seeds-react-menu 0.5.0 → 1.0.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 (104) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +72 -0
  3. package/dist/esm/index.js +1529 -440
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/index.d.mts +275 -92
  6. package/dist/index.d.ts +275 -92
  7. package/dist/index.js +1558 -444
  8. package/dist/index.js.map +1 -1
  9. package/package.json +15 -12
  10. package/src/ActionMenu/ActionMenu.stories.tsx +422 -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 +188 -0
  21. package/src/Autocomplete/SingleAutocomplete.tsx +74 -0
  22. package/src/Autocomplete/__tests__/Autocomplete.test.tsx +207 -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 +28 -12
  26. package/src/MenuContent/MenuContentTypes.ts +6 -4
  27. package/src/MenuContent/__tests__/MenuContent.typetest.tsx +8 -8
  28. package/src/MenuContext.tsx +192 -37
  29. package/src/MenuFooter/MenuFooter.stories.tsx +202 -0
  30. package/src/MenuFooter/__tests__/MenuFooter.test.tsx +16 -11
  31. package/src/MenuFooter/styles.tsx +1 -1
  32. package/src/MenuGroup/MenuGroup.feature +16 -16
  33. package/src/MenuGroup/MenuGroup.stories.tsx +248 -0
  34. package/src/MenuGroup/MenuGroup.tsx +12 -7
  35. package/src/MenuGroup/MenuGroupTypes.ts +6 -2
  36. package/src/MenuGroup/__tests__/MenuGroup.test.tsx +6 -6
  37. package/src/MenuGroup/__tests__/MenuGroup.typetest.tsx +7 -7
  38. package/src/MenuGroup/styles.tsx +4 -2
  39. package/src/MenuHeader/MenuHeader.stories.tsx +276 -0
  40. package/src/MenuHeader/MenuHeader.tsx +12 -7
  41. package/src/MenuHeader/__tests__/MenuHeader.test.tsx +23 -13
  42. package/src/MenuHeader/styles.tsx +22 -14
  43. package/src/MenuItem/MenuItem.stories.tsx +507 -0
  44. package/src/MenuItem/MenuItem.tsx +23 -9
  45. package/src/MenuItem/MenuItemTypes.ts +9 -4
  46. package/src/MenuItem/__tests__/MenuItem.test.tsx +6 -6
  47. package/src/MenuItem/styles.tsx +23 -5
  48. package/src/MenuItem/useOptionProps.tsx +11 -9
  49. package/src/MenuLabel.tsx +2 -2
  50. package/src/MenuRoot/MenuRoot.tsx +52 -24
  51. package/src/MenuRoot/__tests__/MenuRoot.test.tsx +59 -32
  52. package/src/MenuSearchInput/MenuSearchInput.tsx +85 -0
  53. package/src/MenuSearchInput/__tests__/MenuSearchInput.test.tsx +157 -0
  54. package/src/MenuSearchInput/__tests__/MenuSearchInput.typetest.tsx +31 -0
  55. package/src/MenuSearchInput/index.ts +1 -0
  56. package/src/MenuSearchTokenInput/MenuSearchTokenInput.tsx +108 -0
  57. package/src/MenuSearchTokenInput/__tests__/MenuSearchTokenInput.test.tsx +247 -0
  58. package/src/MenuSearchTokenInput/__tests__/MenuSearchTokenInput.typetest.tsx +33 -0
  59. package/src/MenuSearchTokenInput/index.ts +1 -0
  60. package/src/MenuToggleButton/MenuToggleButton.stories.tsx +72 -0
  61. package/src/{MenuToggleButton.tsx → MenuToggleButton/MenuToggleButton.tsx} +6 -5
  62. package/src/MenuToggleButton/index.ts +1 -0
  63. package/src/MenuTokenInput.tsx +104 -0
  64. package/src/MultiSelectMenu/MultiSelectMenu.stories.tsx +222 -0
  65. package/src/MultiSelectMenu/MultiSelectMenu.tsx +38 -68
  66. package/src/MultiSelectMenu/MultiSelectMenuTypes.ts +8 -6
  67. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +46 -17
  68. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.typetest.tsx +6 -6
  69. package/src/NestedMenu/NestedMenu.feature +109 -0
  70. package/src/NestedMenu/NestedMenu.stories.tsx +157 -0
  71. package/src/NestedMenu/NestedMenu.tsx +102 -0
  72. package/src/NestedMenu/NestedMenuContent.tsx +75 -0
  73. package/src/NestedMenu/NestedMenuContext.tsx +56 -0
  74. package/src/NestedMenu/NestedMenuHeader.tsx +25 -0
  75. package/src/NestedMenu/NestedMenuItem.tsx +41 -0
  76. package/src/NestedMenu/NestedMenuTypes.ts +47 -0
  77. package/src/NestedMenu/__tests__/NestedMenu.test.tsx +31 -0
  78. package/src/NestedMenu/__tests__/NestedMenu.typetest.tsx +134 -0
  79. package/src/NestedMenu/index.ts +5 -0
  80. package/src/SingleSelectMenu/SingleSelectMenu.stories.tsx +233 -0
  81. package/src/SingleSelectMenu/SingleSelectMenu.tsx +23 -26
  82. package/src/SingleSelectMenu/SingleSelectMenuTypes.ts +7 -5
  83. package/src/SingleSelectMenu/__tests__/SingleSelectMenu.typetest.tsx +6 -6
  84. package/src/SubmenuItem/SubmenuItem.feature +82 -0
  85. package/src/SubmenuItem/SubmenuItem.stories.tsx +163 -0
  86. package/src/SubmenuItem/SubmenuItem.tsx +258 -0
  87. package/src/SubmenuItem/SubmenuItemTypes.ts +24 -0
  88. package/src/SubmenuItem/__tests__/SubmenuItem.test.tsx +3 -0
  89. package/src/SubmenuItem/__tests__/SubmenuItem.typetest.tsx +112 -0
  90. package/src/SubmenuItem/index.ts +2 -0
  91. package/src/hooks/useDownshiftDefaults.tsx +117 -0
  92. package/src/hooks/useItemFiltering.tsx +68 -0
  93. package/src/hooks/useMenuChildren.tsx +205 -131
  94. package/src/index.ts +6 -0
  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 -30
  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/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,21 @@ 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
+ hiddenItemsCount?: number;
61
+ setHiddenItemsMap?: React.Dispatch<
62
+ React.SetStateAction<Record<string, boolean>>
63
+ >;
64
+ subMenuId?: string;
65
+ setSubMenuId?: (id: string) => void;
66
+ contentRef?: React.RefObject<HTMLUListElement>;
67
+ }
68
+
69
+ interface TypeAllDownshiftProps
56
70
  extends Omit<
57
71
  TypeDownshiftComboboxReturnValue,
58
72
  TypeDonwshiftSharedReturnValue
@@ -62,9 +76,6 @@ export interface TypeMenuContextProps
62
76
  Omit<TypeDownshiftSelectProps, TypeDonwshiftSharedProps>,
63
77
  TypeDownshiftMultiSelectReturnValue,
64
78
  TypeDownshiftMultiSelectProps {
65
- items: TypeDefaultItemProps[];
66
- itemsMap?: { [key: string]: TypeDefaultItemProps };
67
- isListbox?: boolean;
68
79
  onHighlightedIndexChange?:
69
80
  | TypeDownshiftComboboxProps["onHighlightedIndexChange"]
70
81
  | TypeDownshiftSelectProps["onHighlightedIndexChange"];
@@ -90,24 +101,72 @@ export interface TypeMenuContextProps
90
101
  | TypeDownshiftComboboxReturnValue["getMenuProps"]
91
102
  | TypeDownshiftSelectReturnValue["getMenuProps"];
92
103
  }
104
+ export interface TypeMenuContextProps
105
+ extends TypeAllDownshiftProps,
106
+ TypeMenuContextBaseProps {
107
+ items: TypeInternalItemProps[];
108
+ itemsMap?: { [key: string]: TypeInternalItemProps };
109
+ selectedItemIds?: { [key: string]: boolean };
110
+ isListbox?: boolean;
111
+ }
93
112
 
94
113
  export interface TypeMenuContext extends TypeMenuContextProps {
95
- itemsMap: { [key: string]: TypeDefaultItemProps };
114
+ itemsMap: { [key: string]: TypeInternalItemProps };
115
+ isItemSelected: (id: string) => boolean;
116
+ getInputComponentProps: (
117
+ props: Pick<TypeTokenInputProps, "inputProps"> &
118
+ Parameters<Required<TypeMenuContextProps>["getInputProps"]>[0]
119
+ ) => Omit<TypeDownshiftMergedInputProps<TypeTokenInputProps>, "onClick">;
96
120
  }
97
121
 
98
122
  export interface TypeMenuProviderProps {
99
123
  children: TypeNotEmptyChildren;
100
- menuProps: Partial<TypeMenuContextProps>;
124
+ menuContext: TypeMenuContext;
125
+ }
126
+
127
+ export interface TypeMenuToggleContext extends TypeMenuContext {
128
+ ref?: (arg0: React.ElementRef<any> | HTMLElement) => void;
129
+ toggle?: () => void;
130
+ show?: () => void;
131
+ hide?: () => void;
132
+ ariaProps?: Record<string, any>;
133
+ }
134
+
135
+ export interface TypeMenuToggleProviderProps extends TypeMenuProviderProps {
136
+ menuContext: TypeMenuToggleContext;
101
137
  }
102
138
 
103
139
  /**
104
140
  * Object for Initializing the Menu Context
105
141
  */
106
- const defaultMenuContext: TypeMenuContext = {
142
+ export const defaultMenuContext: TypeMenuContext = {
107
143
  items: [],
108
144
  itemsMap: {},
145
+ isItemSelected: () => false,
146
+ getInputComponentProps: () => ({}),
109
147
  };
110
148
 
149
+ export const getSelectedItemIds = <
150
+ T extends TypeMenuContextProps["selectedItems"] | undefined,
151
+ R = T extends undefined ? undefined : Record<string, boolean>
152
+ >(
153
+ selectedItems?: T
154
+ ): R =>
155
+ selectedItems?.reduce(
156
+ (selected, item) => ({ ...selected, [item.id]: true }),
157
+ {} as Record<string, boolean>
158
+ ) as R;
159
+
160
+ export const checkIfItemSelected = ({
161
+ id,
162
+ selectedItem,
163
+ selectedItemIds,
164
+ }: {
165
+ id: string;
166
+ selectedItem?: TypeInternalItemProps | null;
167
+ selectedItemIds?: TypeMenuContextProps["selectedItemIds"];
168
+ }) => (selectedItemIds ? !!selectedItemIds[id] : selectedItem?.id === id);
169
+
111
170
  /**
112
171
  * Hook for initializing Menu context
113
172
  */
@@ -116,10 +175,15 @@ export const useMenu = ({
116
175
  isListbox = false,
117
176
  isOpen = false,
118
177
  itemsMap: itemsMapFromProps,
178
+ selectedItemIds: selectedItemIdsFromProps,
179
+ selectedItem,
180
+ selectedItems,
181
+ getInputProps,
182
+ contentRef: contentRefFromProps,
119
183
  ...rest
120
184
  }: Partial<TypeMenuContextProps> = {}): TypeMenuContext => {
185
+ const contentRef = contentRefFromProps || React.createRef<HTMLUListElement>();
121
186
  const menuProps = useMemo(() => ({ ...rest }), [rest]);
122
-
123
187
  const itemsMap = useMemo(
124
188
  () =>
125
189
  itemsMapFromProps ||
@@ -132,45 +196,136 @@ export const useMenu = ({
132
196
  [items, itemsMapFromProps]
133
197
  );
134
198
 
199
+ const selectedItemIds = useMemo(
200
+ () => selectedItemIdsFromProps || getSelectedItemIds(selectedItems),
201
+ [selectedItems, selectedItemIdsFromProps]
202
+ );
203
+
204
+ const isItemSelected = React.useCallback(
205
+ (id: string) => checkIfItemSelected({ id, selectedItem, selectedItemIds }),
206
+ [selectedItem?.id, selectedItemIds]
207
+ );
208
+
209
+ const getInputComponentProps = React.useCallback<
210
+ TypeMenuContext["getInputComponentProps"]
211
+ >(
212
+ ({ inputProps, ...props }) => {
213
+ if (!getInputProps) return {};
214
+ const {
215
+ ["aria-activedescendant"]: activedescendant,
216
+ ["aria-autocomplete"]: autocomplete,
217
+ ["aria-controls"]: controls,
218
+ ["aria-expanded"]: expanded,
219
+ ["aria-labelledby"]: labelledby,
220
+ role,
221
+ autoComplete,
222
+ onClick,
223
+ value,
224
+ ...restProps
225
+ } = getInputProps(props);
226
+ return {
227
+ ...restProps,
228
+ inputProps: {
229
+ ["aria-activedescendant"]: activedescendant,
230
+ ["aria-autocomplete"]: autocomplete,
231
+ ["aria-controls"]: controls,
232
+ ["aria-expanded"]: expanded,
233
+ ["aria-labelledby"]: labelledby,
234
+ role,
235
+ autoComplete,
236
+ onClick,
237
+ value,
238
+ ...inputProps,
239
+ },
240
+ };
241
+ },
242
+ [getInputProps]
243
+ );
244
+
135
245
  return {
136
246
  isOpen,
137
247
  isListbox,
138
248
  items,
139
249
  itemsMap,
250
+ selectedItem,
251
+ selectedItems,
252
+ selectedItemIds,
253
+ isItemSelected,
254
+ getInputProps,
255
+ getInputComponentProps,
256
+ contentRef,
140
257
  ...menuProps,
141
258
  };
142
259
  };
143
260
 
144
261
  /**
145
- * MenuContext factory, sets the type and the initial object
262
+ * MenuToggleContext factory, sets the type and the initial object
263
+ * @description Context for the Menu Toggle
146
264
  */
147
- export const MenuContext = createContext<TypeMenuContext>(defaultMenuContext);
265
+ export const MenuToggleContext =
266
+ createContext<TypeMenuToggleContext>(defaultMenuContext);
148
267
 
149
268
  /**
150
- * Provider Component sets the context values into the provider
269
+ * MenuToggleProvider Component sets the context values into the provider
270
+ * @description Context Provider for the Menu Toggle
151
271
  */
152
- export const MenuProvider = ({
272
+ export const MenuToggleProvider = ({
153
273
  children,
154
- menuProps,
155
- }: TypeMenuProviderProps) => {
156
- const filterContext = useMenu(menuProps);
274
+ menuContext,
275
+ }: TypeMenuToggleProviderProps) => {
276
+ return (
277
+ <MenuToggleContext.Provider value={{ ...menuContext }}>
278
+ {children}
279
+ </MenuToggleContext.Provider>
280
+ );
281
+ };
282
+
283
+ /**
284
+ * Utility hook for consuming MenuToggleContext
285
+ */
286
+ export const useMenuToggleContext = () => {
287
+ const menuContextValue = useContext(MenuToggleContext);
157
288
 
289
+ if (menuContextValue === null) {
290
+ throw new Error(
291
+ "useMenuToggleContext must be used within a <MenuToggleContext.Provider />"
292
+ );
293
+ }
294
+
295
+ return menuContextValue;
296
+ };
297
+
298
+ /**
299
+ * MenuContentContext factory, sets the type and the initial object
300
+ * @description Context for the Menu Content
301
+ */
302
+ export const MenuContentContext =
303
+ createContext<TypeMenuContext>(defaultMenuContext);
304
+
305
+ /**
306
+ * MenuContentProvider Component sets the context values into the provider
307
+ * @description Context Provider for the Menu Content
308
+ */
309
+ export const MenuContentProvider = ({
310
+ children,
311
+ menuContext,
312
+ }: TypeMenuProviderProps) => {
158
313
  return (
159
- <MenuContext.Provider value={{ ...filterContext }}>
314
+ <MenuContentContext.Provider value={{ ...menuContext }}>
160
315
  {children}
161
- </MenuContext.Provider>
316
+ </MenuContentContext.Provider>
162
317
  );
163
318
  };
164
319
 
165
320
  /**
166
- * Utility hook for consuming MenuContext
321
+ * Utility hook for consuming MenuContentContext
167
322
  */
168
- export const useMenuContext = (): TypeMenuContext => {
169
- const menuContextValue = useContext(MenuContext);
323
+ export const useMenuContentContext = () => {
324
+ const menuContextValue = useContext(MenuContentContext);
170
325
 
171
326
  if (menuContextValue === null) {
172
327
  throw new Error(
173
- "useMenuContext must be used within a <MenuContext.Provider />"
328
+ "useMenuContentContext must be used within a <MenuContentContext.Provider />"
174
329
  );
175
330
  }
176
331
 
@@ -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");
@@ -2,6 +2,6 @@ import styled from "styled-components";
2
2
  import { Box } from "@sproutsocial/seeds-react-box";
3
3
 
4
4
  export const StyledMenuFooterBox = styled(Box)`
5
- padding: ${(p) => p.theme.space[400]};
5
+ padding: ${({ theme }) => theme.space[350]} ${({ theme }) => theme.space[400]};
6
6
  border-top: 1px solid ${(p) => p.theme.colors.container.border.base};
7
7
  `;
@@ -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>
@@ -79,6 +79,6 @@ Feature: Menu Group
79
79
  | propName | propType |
80
80
  | children | ReactNode |
81
81
  | title | string |
82
- | items | TypeMenuItemProps[] |
82
+ | menuItems | TypeMenuItemProps[] |
83
83
  | isSingleSelect | boolean |
84
84
  | MenuItemComponent | React.ComponentType<TypeMenuItemProps> |