@sproutsocial/seeds-react-menu 0.1.0 → 0.2.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 (54) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +17 -1
  3. package/dist/esm/index.js +209 -198
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/index.d.mts +103 -37
  6. package/dist/index.d.ts +103 -37
  7. package/dist/index.js +239 -229
  8. package/dist/index.js.map +1 -1
  9. package/package.json +6 -5
  10. package/src/ActionMenu/ActionMenu.feature +9 -3
  11. package/src/ActionMenu/ActionMenu.tsx +75 -42
  12. package/src/ActionMenu/__tests__/ActionMenu.test.tsx +36 -42
  13. package/src/ActionMenu/__tests__/ActionMenu.typetest.tsx +13 -13
  14. package/src/MenuContent/MenuContent.tsx +31 -1
  15. package/src/MenuContent/MenuContentTypes.ts +2 -2
  16. package/src/MenuContent/styles.tsx +10 -6
  17. package/src/MenuFooter/MenuFooter.feature +1 -1
  18. package/src/MenuFooter/MenuFooter.tsx +8 -0
  19. package/src/MenuFooter/__tests__/MenuFooter.test.tsx +53 -26
  20. package/src/MenuFooter/styles.tsx +1 -1
  21. package/src/MenuGroup/MenuGroup.feature +14 -26
  22. package/src/MenuGroup/MenuGroup.tsx +21 -1
  23. package/src/MenuGroup/__tests__/MenuGroup.test.tsx +40 -6
  24. package/src/MenuGroup/styles.tsx +4 -3
  25. package/src/MenuHeader/MenuHeader.tsx +16 -5
  26. package/src/MenuHeader/__tests__/MenuHeader.test.tsx +50 -6
  27. package/src/MenuHeader/styles.tsx +21 -22
  28. package/src/MenuItem/MenuItem.feature +66 -26
  29. package/src/MenuItem/MenuItem.tsx +35 -20
  30. package/src/MenuItem/MenuItemTypes.ts +13 -1
  31. package/src/MenuItem/__tests__/MenuItem.test.tsx +431 -154
  32. package/src/MenuItem/__tests__/MenuItem.typetest.tsx +57 -38
  33. package/src/MenuItem/styles.tsx +42 -3
  34. package/src/{MenuItemSelectable/MenuItemSelectable.tsx → MenuItem/useOptionProps.tsx} +31 -54
  35. package/src/MenuRoot/MenuRoot.tsx +12 -17
  36. package/src/MenuRoot/__tests__/MenuRoot.test.tsx +3 -3
  37. package/src/MenuRoot/__tests__/MenuRoot.typetest.tsx +10 -10
  38. package/src/MultiSelectMenu/MultiSelectMenu.tsx +8 -1
  39. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +19 -19
  40. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.typetest.tsx +10 -10
  41. package/src/SingleSelectMenu/SingleSelectMenu.feature +11 -0
  42. package/src/SingleSelectMenu/SingleSelectMenu.tsx +9 -0
  43. package/src/SingleSelectMenu/__tests__/SingleSelectMenu.test.tsx +11 -14
  44. package/src/SingleSelectMenu/__tests__/SingleSelectMenu.typetest.tsx +14 -14
  45. package/src/__tests__/Menu.test.tsx +20 -16
  46. package/src/hooks/useItemsFromChildren.tsx +1 -4
  47. package/src/index.ts +0 -1
  48. package/src/menuTestingUtils.tsx +7 -4
  49. package/src/MenuItemSelectable/MenuItemSelectable.feature +0 -99
  50. package/src/MenuItemSelectable/MenuItemSelectableTypes.ts +0 -15
  51. package/src/MenuItemSelectable/__tests__/MenuItemSelectable.test.tsx +0 -303
  52. package/src/MenuItemSelectable/__tests__/MenuItemSelectable.typetest.tsx +0 -56
  53. package/src/MenuItemSelectable/index.ts +0 -5
  54. package/src/MenuItemSelectable/styles.tsx +0 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-menu",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Seeds React Menu",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -19,14 +19,15 @@
19
19
  "dependencies": {
20
20
  "@sproutsocial/seeds-react-box": "*",
21
21
  "@sproutsocial/seeds-react-button": "*",
22
- "@sproutsocial/seeds-react-icon": "*",
23
22
  "@sproutsocial/seeds-react-checkbox": "*",
23
+ "@sproutsocial/seeds-react-icon": "*",
24
24
  "@sproutsocial/seeds-react-popout": "*",
25
25
  "@sproutsocial/seeds-react-radio": "*",
26
26
  "@sproutsocial/seeds-react-switch": "*",
27
27
  "@sproutsocial/seeds-react-system-props": "*",
28
28
  "@sproutsocial/seeds-react-theme": "*",
29
- "downshift": "9.0.4"
29
+ "downshift": "9.0.4",
30
+ "motion": "^11.11.17"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@sproutsocial/eslint-config-seeds": "*",
@@ -37,8 +38,8 @@
37
38
  "@types/styled-components": "^5.1.26",
38
39
  "react": "^18.0.0",
39
40
  "react-dom": "^18.0.0",
40
- "typescript": "^5.2.2",
41
- "tsup": "^8.0.2"
41
+ "tsup": "^8.0.2",
42
+ "typescript": "^5.2.2"
42
43
  },
43
44
  "peerDependencies": {
44
45
  "react": "^17.0.0 || ^18.0.0",
@@ -14,7 +14,13 @@ Feature: Action Menu
14
14
  When an array of MenuItems is passed to the ActionMenu as children
15
15
  Then the ActionMenu should render the MenuItems
16
16
 
17
- Scenario: ActionMenu automatically closes after an item is clicked
18
- When a MenuItem is clicked
17
+ Scenario: ActionMenu automatically closes after an item is selected
18
+ When a MenuItem is <selection action>
19
19
  And the corresponding onClick executes
20
- Then the ActionMenu should no longer render
20
+ Then the ActionMenu should no longer render
21
+
22
+ Examples: child components
23
+ | selection action |
24
+ | clicked |
25
+ | activated with space |
26
+ | activated with enter |
@@ -1,9 +1,9 @@
1
- import React from "react";
1
+ import React, { useEffect } from "react";
2
2
  import {
3
3
  useSelect,
4
4
  type UseSelectProps,
5
5
  type UseSelectState,
6
- type UseSelectSelectedItemChange,
6
+ type UseSelectStateChange,
7
7
  } from "downshift";
8
8
  import type { TypeDefaultItemProps } from "../types";
9
9
  import { useItemsFromChildren } from "../hooks/useItemsFromChildren";
@@ -28,58 +28,87 @@ export interface TypeActionMenuProps
28
28
  children: TypeMenuRootProps["children"];
29
29
  }
30
30
 
31
- export const ActionMenu = ({
32
- children,
33
- stateReducer: externalStateReducer,
34
- ...useSelectProps
35
- }: TypeActionMenuProps) => {
36
- const { allMenuItems, menuItemsMap } = useItemsFromChildren({ children });
31
+ // Manually fire the onClick handler when Enter or Space is pressed
32
+ const handleStateChange = (
33
+ changes: UseSelectStateChange<TypeDefaultItemProps>,
34
+ currentSelectedItem: UseSelectState<TypeDefaultItemProps>["selectedItem"]
35
+ ) => {
36
+ // if not Enter or Space key, do nothing
37
+ if (
38
+ !(
39
+ changes.type === useSelect.stateChangeTypes.ToggleButtonKeyDownEnter ||
40
+ changes.type === useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton
41
+ )
42
+ ) {
43
+ return;
44
+ }
37
45
 
38
- // Manually fire the onClick handler when Enter or Space is pressed
39
- const handleStateChange = (
40
- changes: UseSelectSelectedItemChange<TypeDefaultItemProps>
41
- ) => {
42
- const { selectedItem } = changes;
46
+ const selectedItem = changes.selectedItem || currentSelectedItem;
43
47
 
44
- // if selectedItem is disabled do nothing
45
- if (selectedItem.element?.props?.disabled) {
46
- return;
47
- }
48
+ // if selectedItem empty, do nothing
49
+ if (!selectedItem) return;
48
50
 
49
- // Listen for Enter or Space key
50
- if (
51
- changes.type === useSelect.stateChangeTypes.ToggleButtonKeyDownEnter ||
52
- changes.type === useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton
53
- ) {
54
- // Determine if Enter key was pressed
55
- const isEnterKeyDown =
56
- changes.type === useSelect.stateChangeTypes.ToggleButtonKeyDownEnter;
51
+ // if selectedItem is disabled, do nothing
52
+ if (selectedItem.element?.props?.disabled) {
53
+ return;
54
+ }
57
55
 
58
- // selectedItem props
59
- const selectedItemProps = selectedItem.element?.props;
56
+ // Determine if Enter key was pressed
57
+ const isEnterKeyDown =
58
+ changes.type === useSelect.stateChangeTypes.ToggleButtonKeyDownEnter;
60
59
 
61
- // Create a synthetic event to pass into the onClick
62
- const syntheticKeyboardEvent = new KeyboardEvent("keydown", {
63
- key: isEnterKeyDown ? "Enter" : " ",
64
- code: isEnterKeyDown ? "Enter" : "Space",
65
- bubbles: true,
66
- cancelable: true,
67
- view: window,
68
- });
60
+ // selectedItem props
61
+ const selectedItemProps = selectedItem.element?.props;
69
62
 
70
- // Manually call the onClick handler with a simulated event
71
- // Cast it to React.MouseEvent to match onClick expectations
72
- selectedItemProps?.onClick?.(
73
- syntheticKeyboardEvent as unknown as React.MouseEvent<HTMLElement>
74
- );
63
+ if (selectedItemProps?.onClick) {
64
+ // Create a synthetic event to pass into the onClick
65
+ const syntheticKeyboardEvent = new KeyboardEvent("keydown", {
66
+ key: isEnterKeyDown ? "Enter" : " ",
67
+ code: isEnterKeyDown ? "Enter" : "Space",
68
+ bubbles: true,
69
+ cancelable: true,
70
+ view: window,
71
+ });
72
+
73
+ // Manually call the onClick handler with a simulated event
74
+ // Cast it to React.MouseEvent to match onClick expectations
75
+ selectedItemProps?.onClick?.(
76
+ syntheticKeyboardEvent as unknown as React.MouseEvent<HTMLElement>
77
+ );
78
+ }
79
+
80
+ if (selectedItemProps?.href) {
81
+ if (selectedItemProps.target) {
82
+ window.open(selectedItemProps.href, selectedItemProps.target);
83
+ } else {
84
+ window.location.href = selectedItemProps?.href;
75
85
  }
76
- };
86
+ }
87
+ };
88
+
89
+ /**
90
+ * @link https://seeds.sproutsocial.com/components/action-menu/
91
+ *
92
+ * @description An ActionMenu contains a list of clickable items like buttons or links. ActionMenus do not hold a selection.
93
+ *
94
+ * **To select menu items:**
95
+ * @see {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/SingleSelectMenu | SingleSelectMenu}
96
+ * @see {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/MultiSelectMenu | MultiSelectMenu}
97
+ */
98
+ export const ActionMenu = ({
99
+ children,
100
+ stateReducer: externalStateReducer,
101
+ ...useSelectProps
102
+ }: TypeActionMenuProps) => {
103
+ const { allMenuItems, menuItemsMap } = useItemsFromChildren({ children });
104
+ const [currentSelectedItem, setCurrentSelectedItem] =
105
+ React.useState<UseSelectState<TypeDefaultItemProps>["selectedItem"]>(null);
77
106
 
78
107
  const { isOpen, ...useSelectReturnProps } = useSelect({
79
108
  ...defaultUseSelectProps,
80
109
  items: allMenuItems,
81
110
  itemToString,
82
- onSelectedItemChange: handleStateChange,
111
+ onStateChange: (changes) => handleStateChange(changes, currentSelectedItem),
83
112
  stateReducer: (state, actionAndChanges) => {
84
113
  let newState: Partial<UseSelectState<TypeDefaultItemProps>>;
85
114
  newState = useSelectStateReducer(state, actionAndChanges);
@@ -93,6 +122,10 @@ export const ActionMenu = ({
93
122
  ...useSelectProps,
94
123
  });
95
124
 
125
+ useEffect(() => {
126
+ setCurrentSelectedItem(useSelectReturnProps.selectedItem);
127
+ }, [useSelectReturnProps.selectedItem]);
128
+
96
129
  return (
97
130
  <MenuRoot
98
131
  {...{
@@ -5,18 +5,16 @@ import {
5
5
  screen,
6
6
  waitFor,
7
7
  } from "@sproutsocial/seeds-react-testing-library";
8
+ import { PointerEventsCheckLevel } from "@testing-library/user-event";
9
+ import { type UserEvent } from "@testing-library/user-event";
8
10
  import { ActionMenu } from "../ActionMenu";
9
11
  import { MenuContent, MenuItem, MenuToggleButton } from "../../index";
10
12
 
11
13
  describe("ActionMenu", () => {
12
- it.skip("extends Menu", () => {
13
- // run shared tests for all menus
14
- });
15
-
16
14
  it("should render the MenuItems when provided as children", async () => {
17
15
  render(
18
16
  <ActionMenu
19
- triggerElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
17
+ menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
20
18
  >
21
19
  <MenuContent>
22
20
  <MenuItem id="test-item-1">Item 1</MenuItem>
@@ -35,11 +33,37 @@ describe("ActionMenu", () => {
35
33
  expect(screen.getByRole("menuitem", { name: "Item 2" })).toBeVisible();
36
34
  });
37
35
 
38
- it("should close after a MenuItem is clicked", async () => {
36
+ test.each([
37
+ {
38
+ action: "click",
39
+ activate: async (user: UserEvent) => {
40
+ const firstItem = screen.getByRole("menuitem", {
41
+ name: "Item 1",
42
+ });
43
+ await user
44
+ .setup({
45
+ pointerEventsCheck: PointerEventsCheckLevel.Never,
46
+ })
47
+ .click(firstItem);
48
+ },
49
+ },
50
+ {
51
+ action: "pressed the Enter key",
52
+ activate: async (user: UserEvent) => {
53
+ await user.keyboard("{ArrowDown}{Enter}");
54
+ },
55
+ },
56
+ {
57
+ action: "pressed the Space key",
58
+ activate: async (user: UserEvent) => {
59
+ await user.keyboard("{ArrowDown} ");
60
+ },
61
+ },
62
+ ])("should close after a MenuItem is $action", async ({ activate }) => {
39
63
  const item1MockClick = jest.fn();
40
- render(
64
+ const { user } = render(
41
65
  <ActionMenu
42
- triggerElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
66
+ menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
43
67
  >
44
68
  <MenuContent>
45
69
  <MenuItem id="test-item-1" onClick={item1MockClick}>
@@ -51,49 +75,19 @@ describe("ActionMenu", () => {
51
75
  );
52
76
 
53
77
  const toggleBtn = screen.getByRole("button", { name: "Test Menu" });
54
- fireEvent.click(toggleBtn);
78
+ await user.click(toggleBtn);
55
79
 
56
80
  await waitFor(() => expect(screen.getByRole("menu")).toBeVisible());
57
81
 
58
82
  const item1 = screen.getByRole("menuitem", { name: "Item 1" });
59
83
  expect(item1).toBeVisible();
60
- fireEvent.click(item1);
61
-
62
- await waitFor(() => {
63
- expect(item1MockClick).toHaveBeenCalled();
64
- expect(screen.queryByRole("menu")).not.toBeInTheDocument();
65
- });
66
- });
67
-
68
- // TODO: Should be fixed after onClick bug fixed on menu items
69
- it.skip("should close after a MenuItem is accessed by the keyboard", async () => {
70
- const item1MockClick = jest.fn();
71
- const { user } = render(
72
- <ActionMenu
73
- triggerElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
74
- >
75
- <MenuContent>
76
- <MenuItem id="test-item-1" onClick={item1MockClick}>
77
- Item 1
78
- </MenuItem>
79
- <MenuItem id="test-item-2">Item 2</MenuItem>
80
- </MenuContent>
81
- </ActionMenu>
82
- );
83
-
84
- expect(screen.queryByRole("menu")).not.toBeInTheDocument();
85
- const toggleBtn = screen.getByRole("button", { name: "Test Menu" });
86
- fireEvent.click(toggleBtn);
87
-
88
- await waitFor(() => expect(screen.getByRole("menu")).toBeVisible());
89
84
 
90
- await user.keyboard("{arrowdown}{enter}");
85
+ await activate(user);
91
86
 
92
- await waitFor(() => {
93
- expect(item1MockClick).toHaveBeenCalled();
94
- });
95
87
  await waitFor(() => {
96
88
  expect(screen.queryByRole("menu")).not.toBeInTheDocument();
97
89
  });
90
+
91
+ expect(item1MockClick).toHaveBeenCalled();
98
92
  });
99
93
  });
@@ -3,20 +3,20 @@ import { ActionMenu, MenuContent, MenuHeader, MenuFooter } from "../../index";
3
3
  export const ActionMenuTypeTest = () => (
4
4
  <>
5
5
  {/* Valid ActionMenu with minimal required props */}
6
- <ActionMenu triggerElement={<>test</>}>Test</ActionMenu>
6
+ <ActionMenu menuToggleElement={<>test</>}>Test</ActionMenu>
7
7
  {/* Valid ActionMenu allows MenuContent by itself as a child */}
8
- <ActionMenu triggerElement={<>test</>}>
8
+ <ActionMenu menuToggleElement={<>test</>}>
9
9
  <MenuContent>Test</MenuContent>
10
10
  </ActionMenu>
11
11
  {/* Valid ActionMenu allows multiple children without fragment */}
12
- <ActionMenu triggerElement={<>test</>}>
12
+ <ActionMenu menuToggleElement={<>test</>}>
13
13
  <MenuHeader>Test</MenuHeader>
14
14
  <MenuContent>Test</MenuContent>
15
15
  <MenuFooter>Test</MenuFooter>
16
16
  </ActionMenu>
17
17
  {/* Valid ActionMenu accepts Downshift props and functions for useSelect other than selectedItem */}
18
18
  <ActionMenu
19
- triggerElement={<>test</>}
19
+ menuToggleElement={<>test</>}
20
20
  onSelectedItemChange={() => {}}
21
21
  stateReducer={(c) => c}
22
22
  isOpen
@@ -24,21 +24,21 @@ export const ActionMenuTypeTest = () => (
24
24
  Test
25
25
  </ActionMenu>
26
26
  {/* Valid ActionMenu accepts Downshift props and functions for useMultipleSelection */}
27
- <ActionMenu triggerElement={<>test</>}>Test</ActionMenu>
27
+ <ActionMenu menuToggleElement={<>test</>}>Test</ActionMenu>
28
28
 
29
- {/* @ts-expect-error - triggerElement is a required prop */}
29
+ {/* @ts-expect-error - menuToggleElement is a required prop */}
30
30
  <ActionMenu>Test</ActionMenu>
31
31
  <ActionMenu
32
- /* @ts-expect-error - triggerElement must be a valid react element */
33
- triggerElement="test"
32
+ /* @ts-expect-error - menuToggleElement must be a valid react element */
33
+ menuToggleElement="test"
34
34
  >
35
35
  Test
36
36
  </ActionMenu>
37
37
  {/* @ts-expect-error - when no children provided */}
38
- <ActionMenu triggerElement="test" />
38
+ <ActionMenu menuToggleElement="test" />
39
39
 
40
40
  <ActionMenu
41
- triggerElement={<>test</>}
41
+ menuToggleElement={<>test</>}
42
42
  /* @ts-expect-error - isOpen can't be a string */
43
43
  isOpen="false"
44
44
  /* @ts-expect-error - onSelectedItemChange can't be a string */
@@ -49,21 +49,21 @@ export const ActionMenuTypeTest = () => (
49
49
  Test
50
50
  </ActionMenu>
51
51
  <ActionMenu
52
- triggerElement={<>test</>}
52
+ menuToggleElement={<>test</>}
53
53
  /* @ts-expect-error - selectedItem is never allowed for ActionMenu */
54
54
  selectedItem={null}
55
55
  >
56
56
  Test
57
57
  </ActionMenu>
58
58
  <ActionMenu
59
- triggerElement={<>test</>}
59
+ menuToggleElement={<>test</>}
60
60
  /* @ts-expect-error - initialSelectedItem is never allowed for ActionMenu */
61
61
  initialSelectedItem={null}
62
62
  >
63
63
  Test
64
64
  </ActionMenu>
65
65
  <ActionMenu
66
- triggerElement={<>test</>}
66
+ menuToggleElement={<>test</>}
67
67
  /* @ts-expect-error - defaultSelectedItem is never allowed for ActionMenu */
68
68
  defaultSelectedItem={null}
69
69
  >
@@ -2,12 +2,35 @@ import type { TypeMenuContentProps } from "./MenuContentTypes";
2
2
  import { StyledMenuContent } from "./styles";
3
3
  import { useMenuContext } from "../MenuContext";
4
4
 
5
+ /**
6
+ * @link https://seeds.sproutsocial.com/components/menu-content/
7
+ *
8
+ * @description MenuContent is used to contain lists or grouped lists of {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/MenuItem | MenuItems}.
9
+ *
10
+ * @example
11
+ * <ActionMenu>
12
+ * <MenuHeader>Header</MenuHeader>
13
+ * <MenuContent>
14
+ * <MenuGroup>
15
+ * <MenuItem>Item 1</MenuItem>
16
+ * </MenuGroup>
17
+ * <MenuGroup>
18
+ * <MenuItem>Item 2</MenuItem>
19
+ * </MenuGroup>
20
+ * </MenuContent>
21
+ * <MenuFooter>Footer</MenuFooter>
22
+ * </ActionMenu>
23
+ *
24
+ * @see {@link https://seeds.sproutsocial.com/components/menu-item/ | MenuItem}
25
+ * @see {@link https://seeds.sproutsocial.com/components/menu-group/ | MenuGroup}
26
+ */
27
+
5
28
  const MenuContent = ({
6
29
  id = "menu_content",
7
30
  children,
8
31
  ...rest
9
32
  }: TypeMenuContentProps) => {
10
- const { getToggleButtonProps } = useMenuContext();
33
+ const { getToggleButtonProps, getMenuProps, isListbox } = useMenuContext();
11
34
 
12
35
  return (
13
36
  <StyledMenuContent
@@ -18,6 +41,13 @@ const MenuContent = ({
18
41
  onKeyDown={getToggleButtonProps?.().onKeyDown}
19
42
  // apply active descendent updates to container for proper accessibility
20
43
  aria-activedescendant={getToggleButtonProps?.()["aria-activedescendant"]}
44
+ {...getMenuProps?.(
45
+ {
46
+ ...(isListbox ? {} : { role: "menu" }),
47
+ },
48
+ // Suppress ref error for now
49
+ { suppressRefError: true }
50
+ )}
21
51
  {...rest}
22
52
  >
23
53
  {children}
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
+ import { type HTMLMotionProps } from "motion/react";
2
3
  import type { ReactNode, RefObject } from "react";
3
4
 
4
- export interface TypeMenuContentProps
5
- extends React.ButtonHTMLAttributes<HTMLUListElement> {
5
+ export interface TypeMenuContentProps extends HTMLMotionProps<"ul"> {
6
6
  children: ReactNode;
7
7
  }
@@ -1,10 +1,14 @@
1
1
  import styled from "styled-components";
2
- import { focusRing } from "@sproutsocial/seeds-react-mixins";
2
+ import { motion } from "motion/react";
3
+ //import { focusRing } from "@sproutsocial/seeds-react-mixins";
3
4
 
4
- export const StyledMenuContent = styled.ul`
5
+ export const StyledMenuContent = styled(motion.ul)`
5
6
  margin: 0;
6
- padding: ${({ theme }) => theme.space[300]} 0;
7
- &:focus {
8
- ${focusRing}
9
- }
7
+ padding: 0;
10
8
  `;
9
+
10
+ // Removing this for now because it's insanely intrusive and distracting to the user... interestingly the out of the box browser focus ring works better only appears when the content is actually focused.
11
+
12
+ // &:focus {
13
+ // ${focusRing}
14
+ // }
@@ -11,7 +11,7 @@ Feature: Menu Footer
11
11
  Then the MenuFooter should render the <component(s)>
12
12
 
13
13
  Examples: child components
14
- | child component(s) | component(s) |
14
+ | child component(s) | component(s) |
15
15
  | a Link is | Link |
16
16
  | a Switch is | Switch |
17
17
  | a Button is | Button |
@@ -2,6 +2,14 @@ import React from "react";
2
2
  import type { TypeMenuFooterProps } from "./MenuFooterTypes";
3
3
  import { StyledMenuFooterBox } from "./styles";
4
4
 
5
+ /**
6
+ * @link https://seeds.sproutsocial.com/components/menu-footer/
7
+ *
8
+ * @description MenuFooter is a container used to hold footer content within a Menu.
9
+ *
10
+ * @see {@link https://seeds.sproutsocial.com/components/menu-header/ | MenuHeader}
11
+ */
12
+
5
13
  export const MenuFooter = ({ children, ...rest }: TypeMenuFooterProps) => {
6
14
  return <StyledMenuFooterBox {...rest}>{children}</StyledMenuFooterBox>;
7
15
  };
@@ -1,35 +1,69 @@
1
1
  import { render, screen } from "@sproutsocial/seeds-react-testing-library";
2
2
  import { MenuFooter, MenuRoot } from "../../index";
3
3
  import { Button } from "@sproutsocial/seeds-react-button";
4
+ // import { Link } from "@sproutsocial/seeds-react-link";
4
5
  import { Switch } from "@sproutsocial/seeds-react-switch";
5
6
 
6
7
  describe("MenuFooter API", () => {
7
8
  const contextTests = [
8
9
  {
9
- child: "Button",
10
- TestComponent: <Button role="button">Find me!</Button>,
10
+ children: "Link",
11
+ // TestComponent: <Link>Find me!</Link>,
12
+ TestComponent: (
13
+ <Button as="a" href="https://sproutsocial.com">
14
+ Find me!
15
+ </Button>
16
+ ),
17
+ test: () => {
18
+ expect(screen.getByText("Hello world!")).toContainElement(
19
+ screen.getByRole("link", { name: "Find me!" })
20
+ );
21
+ },
11
22
  },
12
23
  {
13
- child: "Switcher",
24
+ children: "Switch",
14
25
  TestComponent: (
15
- <Switch
16
- role="button"
17
- checked={false}
18
- onClick={() => {}}
19
- aria-label="Find me!"
20
- />
26
+ <Switch checked={false} onClick={() => {}} aria-label="Find me!" />
21
27
  ),
28
+ test: () => {
29
+ expect(screen.getByText("Hello world!")).toContainElement(
30
+ screen.getByRole("switch", { name: "Find me!" })
31
+ );
32
+ },
33
+ },
34
+ {
35
+ children: "Button",
36
+ TestComponent: <Button>Find me!</Button>,
37
+ test: () => {
38
+ expect(screen.getByText("Hello world!")).toContainElement(
39
+ screen.getByRole("button", { name: "Find me!" })
40
+ );
41
+ },
22
42
  },
23
43
  {
24
- child: "Link",
25
- TestComponent: <button role="button">Find me!</button>,
44
+ children: "Link and Button",
45
+ TestComponent: (
46
+ <>
47
+ <Button>Find me!</Button>
48
+ <Button as="a" href="https://sproutsocial.com">
49
+ Find me too!
50
+ </Button>
51
+ </>
52
+ ),
53
+ test: () => {
54
+ expect(screen.getByText("Hello world!")).toContainElement(
55
+ screen.getByRole("button", { name: "Find me!" })
56
+ );
57
+ expect(screen.getByText("Hello world!")).toContainElement(
58
+ screen.getByRole("link", { name: "Find me too!" })
59
+ );
60
+ },
26
61
  },
27
62
  ];
28
63
 
29
64
  it.each(contextTests)(
30
- "$child contains children",
31
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
32
- ({ child, TestComponent }) => {
65
+ "$children rendered when $children provided",
66
+ ({ TestComponent, test }) => {
33
67
  render(
34
68
  <MenuFooter>
35
69
  Hello world!
@@ -37,21 +71,17 @@ describe("MenuFooter API", () => {
37
71
  </MenuFooter>
38
72
  );
39
73
 
40
- const testBtn = screen.getByRole("button");
41
-
42
- const parentComponent = screen.getByText("Hello world!");
43
- expect(parentComponent).toContainElement(testBtn);
74
+ test();
44
75
  }
45
76
  );
46
77
 
47
78
  it.each(contextTests)(
48
- "$child contains children when in MenuRoot",
49
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
50
- ({ child, TestComponent }) => {
79
+ "$children rendered when $children provided in MenuRoot",
80
+ ({ TestComponent, test }) => {
51
81
  render(
52
82
  <MenuRoot
53
83
  isOpen
54
- triggerElement={<Button onClick={jest.fn()}>Open</Button>}
84
+ menuToggleElement={<Button onClick={jest.fn()}>Open</Button>}
55
85
  >
56
86
  <MenuFooter>
57
87
  Hello world!
@@ -60,10 +90,7 @@ describe("MenuFooter API", () => {
60
90
  </MenuRoot>
61
91
  );
62
92
 
63
- const testBtn = screen.getByRole("button", { name: "Find me!" });
64
-
65
- const parentComponent = screen.getByText("Hello world!");
66
- expect(parentComponent).toContainElement(testBtn);
93
+ test();
67
94
  }
68
95
  );
69
96
 
@@ -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[350]};
5
+ padding: ${(p) => p.theme.space[400]};
6
6
  border-top: 1px solid ${(p) => p.theme.colors.container.border.base};
7
7
  `;