@sproutsocial/seeds-react-menu 0.1.0 → 0.2.1

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 (62) hide show
  1. package/.eslintignore +6 -0
  2. package/.eslintrc.js +6 -2
  3. package/.turbo/turbo-build.log +10 -10
  4. package/CHANGELOG.md +23 -1
  5. package/dist/esm/index.js +243 -217
  6. package/dist/esm/index.js.map +1 -1
  7. package/dist/index.d.mts +104 -38
  8. package/dist/index.d.ts +104 -38
  9. package/dist/index.js +272 -243
  10. package/dist/index.js.map +1 -1
  11. package/package.json +10 -7
  12. package/src/ActionMenu/ActionMenu.feature +9 -3
  13. package/src/ActionMenu/ActionMenu.tsx +76 -42
  14. package/src/ActionMenu/__tests__/ActionMenu.test.tsx +36 -42
  15. package/src/ActionMenu/__tests__/ActionMenu.typetest.tsx +13 -13
  16. package/src/MenuContent/MenuContent.tsx +31 -1
  17. package/src/MenuContent/MenuContentTypes.ts +3 -4
  18. package/src/MenuContent/styles.tsx +10 -6
  19. package/src/MenuContext.tsx +1 -6
  20. package/src/MenuFooter/MenuFooter.feature +1 -1
  21. package/src/MenuFooter/MenuFooter.tsx +8 -0
  22. package/src/MenuFooter/__tests__/MenuFooter.test.tsx +53 -26
  23. package/src/MenuFooter/styles.tsx +1 -1
  24. package/src/MenuGroup/MenuGroup.feature +14 -26
  25. package/src/MenuGroup/MenuGroup.tsx +21 -1
  26. package/src/MenuGroup/MenuGroupTypes.ts +1 -0
  27. package/src/MenuGroup/__tests__/MenuGroup.test.tsx +41 -6
  28. package/src/MenuGroup/styles.tsx +4 -3
  29. package/src/MenuHeader/MenuHeader.tsx +16 -5
  30. package/src/MenuHeader/__tests__/MenuHeader.test.tsx +50 -6
  31. package/src/MenuHeader/__tests__/MenuHeader.typetest.tsx +1 -0
  32. package/src/MenuHeader/styles.tsx +21 -22
  33. package/src/MenuItem/MenuItem.feature +66 -26
  34. package/src/MenuItem/MenuItem.tsx +39 -20
  35. package/src/MenuItem/MenuItemTypes.ts +13 -1
  36. package/src/MenuItem/__tests__/MenuItem.test.tsx +431 -154
  37. package/src/MenuItem/__tests__/MenuItem.typetest.tsx +57 -38
  38. package/src/MenuItem/styles.tsx +44 -3
  39. package/src/{MenuItemSelectable/MenuItemSelectable.tsx → MenuItem/useOptionProps.tsx} +32 -54
  40. package/src/MenuRoot/MenuRoot.feature +20 -10
  41. package/src/MenuRoot/MenuRoot.tsx +14 -18
  42. package/src/MenuRoot/__tests__/MenuRoot.test.tsx +168 -34
  43. package/src/MenuRoot/__tests__/MenuRoot.typetest.tsx +10 -10
  44. package/src/MultiSelectMenu/MultiSelectMenu.feature +2 -13
  45. package/src/MultiSelectMenu/MultiSelectMenu.tsx +9 -1
  46. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.test.tsx +154 -153
  47. package/src/MultiSelectMenu/__tests__/MultiSelectMenu.typetest.tsx +10 -10
  48. package/src/SingleSelectMenu/SingleSelectMenu.feature +11 -0
  49. package/src/SingleSelectMenu/SingleSelectMenu.tsx +10 -0
  50. package/src/SingleSelectMenu/__tests__/SingleSelectMenu.test.tsx +11 -14
  51. package/src/SingleSelectMenu/__tests__/SingleSelectMenu.typetest.tsx +14 -14
  52. package/src/__tests__/Menu.test.tsx +20 -16
  53. package/src/hooks/useItemsFromChildren.tsx +2 -4
  54. package/src/index.ts +0 -1
  55. package/src/menuTestingUtils.tsx +7 -4
  56. package/tsconfig.json +1 -1
  57. package/src/MenuItemSelectable/MenuItemSelectable.feature +0 -99
  58. package/src/MenuItemSelectable/MenuItemSelectableTypes.ts +0 -15
  59. package/src/MenuItemSelectable/__tests__/MenuItemSelectable.test.tsx +0 -303
  60. package/src/MenuItemSelectable/__tests__/MenuItemSelectable.typetest.tsx +0 -56
  61. package/src/MenuItemSelectable/index.ts +0 -5
  62. package/src/MenuItemSelectable/styles.tsx +0 -21
@@ -6,30 +6,20 @@ Feature: Menu Group
6
6
  Background:
7
7
  Given an end user is using the component
8
8
 
9
- Scenario: MenuGroup is accessible to screen readers when menu opened
10
- Given a screen reader is being used
11
- When a menu is opened
12
- And the first group receives focus
13
- Then the first group is announced by the screen reader
14
-
15
- Scenario: MenuGroup is accessible to screen readers when there are 2 groups
16
- Given a screen reader is being used
17
- When a navigating between two groups
18
- Then the new group is announced by the screen reader
19
-
20
- Scenario Outline: MenuGroup uses select type
21
- When the MenuGroup has <group type>
22
- And MenuContext is set to <menu type>
23
- Then <selection behavior>
24
-
25
- Examples:
26
- | group type | menu type | selection behavior |
27
- | single | single | the menu uses the default behavior |
28
- | multiple | multiple | the menu uses the default behavior |
29
- | single | multiple | the group behaves like a single select |
30
- | multiple | single | the group throws an error and uses default behavior |
31
- | default | single | the menu uses the default behavior |
32
- | default | multiple | the menu uses the default behavior |
9
+ Scenario: MenuGroup has isSingleSelect true
10
+ When the MenuGroup isSingleSelect is true
11
+ And MenuContext is set to multiple selection
12
+ Then the group behaves like a single select
13
+
14
+ Scenario: MenuGroup has isSingleSelect undefined
15
+ When the MenuGroup isSingleSelect is undefined
16
+ And MenuContext is set to multiple selection
17
+ Then the group behaves like a multi select
18
+
19
+ Scenario: MenuGroup has isSingleSelect false
20
+ When the MenuGroup isSingleSelect is false
21
+ And MenuContext is set to multiple selection
22
+ Then the group behaves like a multi select
33
23
 
34
24
  Rule: API
35
25
  Background:
@@ -98,8 +88,6 @@ Feature: Menu Group
98
88
  | a red Banner | top | a red Banner at the top of the group |
99
89
  | hint text | bottom | hint text at the bottom of the group |
100
90
  | text | top and bottom | text at the top and bottom of the group |
101
- | no content | top | no content at the top of the group |
102
- | no content | bottom | no content at the bottom of the group |
103
91
 
104
92
  Scenario Outline: MenuRoot checks its types
105
93
  When <propName> is passed a value
@@ -2,6 +2,26 @@ import React from "react";
2
2
  import type { TypeMenuGroupProps } from "./MenuGroupTypes";
3
3
  import { StyledMenuGroup, StyledTitle, StyledMenuGroupUl } from "./styles";
4
4
 
5
+ /**
6
+ * @link https://seeds.sproutsocial.com/components/menu-group/
7
+ *
8
+ * @description MenuGroup is used to group lists of {@link https://github.com/sproutsocial/seeds/tree/dev/seeds-react/seeds-react-menu/src/MenuItem | MenuItems}.
9
+ *
10
+ * @example
11
+ * <ActionMenu>
12
+ * <MenuContent>
13
+ * <MenuGroup title="Group 1">
14
+ * <MenuItem>Item 1</MenuItem>
15
+ * </MenuGroup>
16
+ * <MenuGroup title="Group 1">
17
+ * <MenuItem>Item 2</MenuItem>
18
+ * </MenuGroup>
19
+ * </MenuContent>
20
+ * </ActionMenu>
21
+ *
22
+ * @see {@link https://seeds.sproutsocial.com/components/menu-item/ | MenuItem}
23
+ */
24
+
5
25
  export const MenuGroup = ({
6
26
  titleAs = "h3",
7
27
  children,
@@ -23,7 +43,7 @@ export const MenuGroup = ({
23
43
  {...rest}
24
44
  >
25
45
  {title && (
26
- <StyledTitle as={titleAs} id={titleId}>
46
+ <StyledTitle role="heading" as={titleAs} id={titleId}>
27
47
  {title}
28
48
  </StyledTitle>
29
49
  )}
@@ -25,5 +25,6 @@ export interface TypeMenuGroupProps
25
25
  Omit<React.ComponentPropsWithoutRef<"ul">, "color"> {
26
26
  title?: string;
27
27
  children: ReactNode;
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
29
  titleAs?: string | React.ComponentType<any>;
29
30
  }
@@ -15,8 +15,10 @@ const menuItems = [
15
15
 
16
16
  describe("MenuGroup", () => {
17
17
  xdescribe("User Behavior", () => {
18
- xit("The first group should be accessible to screen readers", () => {});
19
- xit("User is able to navigate between groups", () => {});
18
+ // To be enabled when isSingleSelect prop added
19
+ xit("behaves as it normally would when isSingleSelect is undefined", () => {});
20
+ xit("behaves as it normally would when isSingleSelect is false", () => {});
21
+ xit("behaves like a single select when isSingleSelect is true", () => {});
20
22
  });
21
23
  describe("API", () => {
22
24
  it("renders a group with a title", () => {
@@ -24,7 +26,7 @@ describe("MenuGroup", () => {
24
26
  render(
25
27
  <SingleSelectMenu
26
28
  defaultIsOpen
27
- triggerElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
29
+ menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
28
30
  >
29
31
  <MenuContent>
30
32
  <MenuGroup title={menuTitle}>
@@ -41,13 +43,17 @@ describe("MenuGroup", () => {
41
43
  // Find associated label for the group. (Menu Group uses aria-labelledby to point to heading element)
42
44
  const menuGroup = screen.getByRole("group", { name: menuTitle });
43
45
  expect(menuGroup).toBeInTheDocument();
46
+ expect(
47
+ // eslint-disable-next-line testing-library/prefer-presence-queries
48
+ screen.queryByRole("heading", { name: menuTitle })
49
+ ).toBeInTheDocument();
44
50
  });
45
51
 
46
52
  it("when no title is provided, it should not render a title", () => {
47
53
  render(
48
54
  <SingleSelectMenu
49
55
  defaultIsOpen
50
- triggerElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
56
+ menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
51
57
  >
52
58
  <MenuContent>
53
59
  <MenuGroup>
@@ -66,11 +72,11 @@ describe("MenuGroup", () => {
66
72
  expect(menuTitle).not.toBeInTheDocument();
67
73
  });
68
74
 
69
- it("Renders children correctly", () => {
75
+ it("renders MenuItem children correctly", () => {
70
76
  render(
71
77
  <SingleSelectMenu
72
78
  defaultIsOpen
73
- triggerElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
79
+ menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
74
80
  >
75
81
  <MenuContent>
76
82
  <MenuGroup>
@@ -91,5 +97,34 @@ describe("MenuGroup", () => {
91
97
  screen.getByRole("option", { name: menuItems[1].label })
92
98
  ).toBeInTheDocument();
93
99
  });
100
+
101
+ // How do we do this accessibly in a UL?
102
+ xdescribe("renders custom content", () => {
103
+ test.each([
104
+ {
105
+ name: "a red Banner at the top of the group",
106
+ },
107
+ {
108
+ name: "hint text at the bottom of the group",
109
+ },
110
+ {
111
+ name: "text at the top and bottom of the group",
112
+ },
113
+ ])("$name", () => {});
114
+ });
115
+
116
+ // To be implemented when items, customMenuItem and menuItemType props are added
117
+ xdescribe("when the items prop is provided", () => {
118
+ xit("hides the group if items is empty", () => {});
119
+ xit("renders items using MenuItem by default", () => {});
120
+ xit("renders items using customMenuItem when provided", () => {});
121
+ // use a .each to test each item type
122
+ xit("sets MenuItem types when menuItemType provided", () => {});
123
+ });
124
+
125
+ xdescribe("when the items prop is not provided", () => {
126
+ xit("ignores the menuItemType prop", () => {});
127
+ xit("ignores the customMenuItem prop", () => {});
128
+ });
94
129
  });
95
130
  });
@@ -18,6 +18,7 @@ export const StyledMenuGroup = styled.li<TypeMenuGroupProps>`
18
18
  }
19
19
  list-style: none;
20
20
  margin: 0;
21
+
21
22
  ${border}
22
23
  ${position}
23
24
  ${color}
@@ -28,10 +29,10 @@ export const StyledMenuGroup = styled.li<TypeMenuGroupProps>`
28
29
  `;
29
30
 
30
31
  export const StyledTitle = styled(Text.SmallSubHeadline)`
31
- margin: ${({ theme }) => theme.space[200]} 0px
32
- ${({ theme }) => theme.space[300]} ${({ theme }) => theme.space[300]};
32
+ margin: ${({ theme }) => theme.space[400]} ${({ theme }) => theme.space[400]}
33
+ 0px ${({ theme }) => theme.space[400]};
33
34
  `;
34
35
  export const StyledMenuGroupUl = styled.ul`
35
36
  margin: 0;
36
- padding: ${({ theme }) => theme.space[300]};
37
+ padding: 0;
37
38
  `;
@@ -3,9 +3,18 @@ import {
3
3
  MenuHeaderContainer,
4
4
  MenuTitleText,
5
5
  MenuChildrenContainer,
6
+ MenuHeaderFeatures,
6
7
  } from "./styles";
7
8
  import type { TypeMenuHeaderProps } from "./MenuHeaderTypes";
8
9
 
10
+ /**
11
+ * @link https://seeds.sproutsocial.com/components/menu-header/
12
+ *
13
+ * @description MenuHeader is a container used to hold header content within a Menu.
14
+ *
15
+ * @see {@link https://seeds.sproutsocial.com/components/menu-footer/ | MenuFooter}
16
+ */
17
+
9
18
  const MenuHeader = ({
10
19
  children,
11
20
  title,
@@ -16,11 +25,13 @@ const MenuHeader = ({
16
25
  return (
17
26
  <>
18
27
  <MenuHeaderContainer $leftAction={!!leftAction} {...rest}>
19
- {leftAction && leftAction}
20
- {title && (
21
- <MenuTitleText $leftAction={!!leftAction}>{title}</MenuTitleText>
22
- )}
23
- {rightAction && rightAction}
28
+ <MenuHeaderFeatures $leftAction={!!leftAction}>
29
+ {leftAction && leftAction}
30
+ {title && (
31
+ <MenuTitleText $leftAction={!!leftAction}>{title}</MenuTitleText>
32
+ )}
33
+ {rightAction && rightAction}
34
+ </MenuHeaderFeatures>
24
35
  {children && <MenuChildrenContainer>{children}</MenuChildrenContainer>}
25
36
  </MenuHeaderContainer>
26
37
  </>
@@ -1,6 +1,8 @@
1
1
  import { render, screen } from "@sproutsocial/seeds-react-testing-library";
2
2
  import { MenuHeader } from "../MenuHeader";
3
+ import { MenuRoot } from "../../MenuRoot";
3
4
  import Button from "@sproutsocial/seeds-react-button";
5
+ import Text from "@sproutsocial/seeds-react-text";
4
6
 
5
7
  describe("MenuHeader API", () => {
6
8
  it("renders a title when title prop is passed a string", () => {
@@ -15,12 +17,54 @@ describe("MenuHeader API", () => {
15
17
 
16
18
  expect(menuHeader).not.toBeInTheDocument();
17
19
  });
18
-
19
- it("supports children", () => {
20
- render(<MenuHeader>Hello World</MenuHeader>);
21
-
22
- expect(screen.getByText("Hello World")).toBeInTheDocument();
23
- });
20
+ const contextTests = [
21
+ {
22
+ children: "Text",
23
+ TestComponent: <Text>Find me!</Text>,
24
+ test: () => {
25
+ expect(screen.getByText("Hello world!")).toContainElement(
26
+ screen.getByText("Find me!")
27
+ );
28
+ },
29
+ },
30
+ // Implement when components are available
31
+ // { children: "Banner" },
32
+ // { children: "MenuHeaderInput" },
33
+ // { children: "Text and a Banner" },
34
+ ];
35
+
36
+ it.each(contextTests)(
37
+ "$children rendered when $children provided",
38
+ ({ TestComponent, test }) => {
39
+ render(
40
+ <MenuHeader>
41
+ Hello world!
42
+ {TestComponent}
43
+ </MenuHeader>
44
+ );
45
+
46
+ test();
47
+ }
48
+ );
49
+
50
+ it.each(contextTests)(
51
+ "$children rendered when $children provided in MenuRoot",
52
+ ({ TestComponent, test }) => {
53
+ render(
54
+ <MenuRoot
55
+ isOpen
56
+ menuToggleElement={<Button onClick={jest.fn()}>Open</Button>}
57
+ >
58
+ <MenuHeader>
59
+ Hello world!
60
+ {TestComponent}
61
+ </MenuHeader>
62
+ </MenuRoot>
63
+ );
64
+
65
+ test();
66
+ }
67
+ );
24
68
 
25
69
  it("supports clickable actions", async () => {
26
70
  const handleTestClick = jest.fn();
@@ -6,6 +6,7 @@ export const MenuHeaderTypeTest = () => (
6
6
  title="Menu Header"
7
7
  leftAction={<div>left action</div>}
8
8
  rightAction={<div>right action</div>}
9
+ // eslint-disable-next-line react/no-children-prop
9
10
  children={<div>children</div>}
10
11
  />
11
12
  {/* left and right action accept strings */}
@@ -22,28 +22,9 @@ export const MenuHeaderContainer = styled.div<TypeMenuHeaderContainerProps>`
22
22
  width: 100%;
23
23
  font-size: ${({ theme }) => theme.typography[200].fontSize};
24
24
  display: flex;
25
+ flex-direction: column;
25
26
  padding: ${({ theme }) => theme.space[350]} ${({ theme }) => theme.space[400]};
26
27
 
27
- > a {
28
- white-space: nowrap;
29
- }
30
-
31
- &:first-child {
32
- flex-grow: 1;
33
- justify-self: start;
34
- }
35
-
36
- &:last-child {
37
- flex-grow: 1;
38
- justify-self: end;
39
- }
40
-
41
- ${({ $leftAction, theme }) =>
42
- $leftAction &&
43
- `
44
- padding-left: ${theme.space[350]};
45
- `}
46
-
47
28
  ${border}
48
29
  ${color}
49
30
  ${flexbox}
@@ -60,16 +41,34 @@ export const MenuTitleText = styled.h3<TypeMenuHeaderContainerProps>`
60
41
  flex-grow: 2;
61
42
  justify-self: start;
62
43
 
44
+ ${({ theme }) => theme.typography[200]}
45
+
63
46
  ${({ $leftAction, theme }) =>
64
47
  $leftAction &&
65
48
  `
66
49
  padding-left: ${theme.space[350]};
67
50
  `}
68
-
69
- ${({ theme }) => theme.typography[200]}
70
51
  `;
71
52
 
72
53
  export const MenuChildrenContainer = styled.div`
73
54
  grid-column: 1 / -1;
74
55
  margin-top: ${({ theme }) => theme.space[300]};
75
56
  `;
57
+
58
+ export const MenuHeaderFeatures = styled.div<TypeMenuHeaderContainerProps>`
59
+ display: flex;
60
+
61
+ > a {
62
+ white-space: nowrap;
63
+ }
64
+
65
+ &:first-child {
66
+ flex-grow: 1;
67
+ justify-self: start;
68
+ }
69
+
70
+ &:last-child {
71
+ flex-grow: 1;
72
+ justify-self: end;
73
+ }
74
+ `;
@@ -1,6 +1,6 @@
1
1
  Feature: MenuItem
2
2
 
3
- A configurable and interactive item for inclusion in a `Menu` component.
3
+ A configurable and interactive item for inclusion in a `Menu` component, with various input types if used in a listbox.
4
4
 
5
5
  Rule: User Behavior
6
6
  Background:
@@ -11,6 +11,17 @@ Feature: MenuItem
11
11
  When the MenuItem is clicked
12
12
  Then the MenuItem's onClick event should fire
13
13
 
14
+ Scenario Outline: MenuItem can be triggered via keyboard
15
+ Given a MenuItem is focused
16
+ And the MenuItem is not disabled
17
+ When the MenuItem is triggered via <interaction>
18
+ Then the MenuItem's onClick event should fire
19
+
20
+ Examples:
21
+ | interaction |
22
+ | pressing the Enter key |
23
+ | pressing the Space key |
24
+
14
25
  Scenario Outline: MenuItem is disabled
15
26
  Given a MenuItem is disabled
16
27
  When the MenuItem is triggered via <interaction>
@@ -21,18 +32,35 @@ Feature: MenuItem
21
32
  | clicking |
22
33
  | focusing and pressing the Enter key |
23
34
  | focusing and pressing the Space key |
35
+
36
+ Scenario Outline: MenuItem can be checked
37
+ Given the MenuItem is in a Listbox
38
+ And the MenuItem is in the unchecked state
39
+ And the MenuItem is focused
40
+ And the MenuItem is not disabled
41
+ When the MenuItem is triggered via <interaction>
42
+ Then the MenuItem should be checked
24
43
 
25
- Scenario Outline: MenuItem can be triggered via keyboard
26
- Given a MenuItem is focused
44
+ Examples: Interactions
45
+ | interaction |
46
+ | clicking with mouse |
47
+ | pressing the Enter key |
48
+ | pressing the Space key |
49
+
50
+ Scenario Outline: MenuItem can be unchecked
51
+ Given the MenuItem is in a Listbox
52
+ And the MenuItem is in the checked state
53
+ And the MenuItem is focused
27
54
  And the MenuItem is not disabled
28
55
  When the MenuItem is triggered via <interaction>
29
- Then the MenuItem's onClick event should fire
56
+ Then the MenuItem should be unchecked
30
57
 
31
- Examples:
58
+ Examples: Interactions
32
59
  | interaction |
60
+ | clicking with mouse |
33
61
  | pressing the Enter key |
34
62
  | pressing the Space key |
35
-
63
+
36
64
  Rule: API
37
65
  Background:
38
66
  Given an engineer is developing using the component
@@ -63,26 +91,38 @@ Feature: MenuItem
63
91
  | grid | gridGap |
64
92
  | space | p |
65
93
 
94
+ Scenario Outline: MenuItem supports different input types
95
+ Given the MenuItem is in a Listbox
96
+ When the inputType is <inputTypeProp> on the MenuItem component
97
+ Then the MenuItem renders with the appropriate <component> type
98
+
99
+ Examples: Input Types
100
+ | inputTypeProp | component |
101
+ | checkbox | Checkbox |
102
+ | radio | Radio |
103
+ | switch | Switch |
104
+
105
+ Scenario: MenuItem defaults to an icon input type
106
+ Given the MenuItem is in a Listbox
107
+ When the MenuItem is implemented without an inputType prop specified
108
+ Then the MenuItem defaults to an icon input type
109
+
110
+ Scenario: MenuItem checked state can be enabled via a prop
111
+ Given the MenuItem is in a Listbox
112
+ When the selected prop is set to true
113
+ Then the MenuItem should appear in the checked state
114
+
66
115
  Scenario Outline: MenuItem validates prop types
67
- When any <prop> is passed to the MenuItem
68
- Then the MenuItem should check that each prop is of the expected <propType>
116
+ When any <propName> is passed to the MenuItem
117
+ Then the MenuItem should check that each <propName> is of the expected <propType>
69
118
 
70
119
  Examples: Prop Types
71
- | prop | propType |
72
- | children | node |
73
- | onClick | func |
74
- | disabled | bool |
75
- | className | string |
76
-
77
- Scenario Outline: Screen readers should read MenuItem states
78
- Given a MenuItem is <state>
79
- Then screen readers should read the MenuItem as <state>
80
-
81
- Examples: MenuItem States
82
- | state |
83
- | enabled |
84
- | disabled |
85
- | hovered |
86
- | focused |
87
- | active |
88
- | pressed |
120
+ | propName | propType |
121
+ | children | ReactNode |
122
+ | disabled | boolean |
123
+ | elevation | low, medium, or high |
124
+ | href | string |
125
+ | onClick | React.MouseEventHandler |
126
+ | role | string |
127
+ | selected | boolean |
128
+ | inputType | checkbox, radio, or switch |
@@ -1,44 +1,63 @@
1
1
  import React, { useContext } from "react";
2
2
  import { MenuContext } from "../MenuContext";
3
- import { MENU_ITEM_ROLES } from "../constants";
4
3
  import type { TypeDefaultItemProps } from "../types";
5
- import { MenuItemContainer } from "./styles";
6
4
  import type { TypeMenuItemProps } from "./MenuItemTypes";
5
+ import { MenuItemContainer, StyledMenuItem } from "./styles";
6
+ import { useOptionProps } from "./useOptionProps";
7
+
8
+ // TODO: decide if we want to support inputs on menuitems
9
+ // const roles = {
10
+ // checkbox: "menuitemcheckbox",
11
+ // switch: "menuitemcheckbox",
12
+ // radio: "menuitemradio",
13
+ // } as const;
7
14
 
8
15
  export const MenuItem = ({
9
16
  id,
10
17
  children,
11
- role = "menuitem",
12
- label,
13
18
  onClick,
14
19
  onKeyDown,
20
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
15
21
  disabled = false,
22
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
16
23
  ref,
24
+ href,
25
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
17
26
  color,
18
27
  ...props
19
28
  }: TypeMenuItemProps) => {
20
29
  const { getItemProps, itemsMap, highlightedIndex, isListbox } =
21
30
  useContext(MenuContext);
31
+ const { ...optionProps } = useOptionProps({
32
+ id,
33
+ children,
34
+ ...props,
35
+ });
22
36
  const item = (itemsMap[id] || { index: 0, id }) as TypeDefaultItemProps;
23
37
  const highlighted = highlightedIndex === item?.index;
24
38
 
25
39
  return (
26
- <MenuItemContainer
27
- id={id}
28
- tabIndex={role !== MENU_ITEM_ROLES.LISTITEM && !disabled ? -1 : undefined}
29
- $highlighted={highlighted}
30
- {...getItemProps?.({
31
- ...(isListbox ? {} : { role: "menuitem" }),
32
- item,
33
- index: item.index,
34
- // This prop no longer supported in downshift
35
- // disabled,
36
- onClick,
37
- onKeyDown,
38
- })}
39
- {...props}
40
- >
41
- {children}
40
+ <MenuItemContainer role="none">
41
+ <StyledMenuItem
42
+ id={id}
43
+ $highlighted={highlighted}
44
+ {...getItemProps?.({
45
+ ...(isListbox ? {} : { role: "menuitem" }),
46
+ item,
47
+ index: item.index,
48
+ // This prop no longer supported in downshift
49
+ // disabled,
50
+ onClick,
51
+ onKeyDown,
52
+ })}
53
+ // use anchor tag if href is provided and button for menuitem
54
+ as={isListbox ? undefined : href ? "a" : "button"}
55
+ href={href}
56
+ // eslint-disable-next-line react/no-children-prop
57
+ children={children}
58
+ {...optionProps}
59
+ {...props}
60
+ />
42
61
  </MenuItemContainer>
43
62
  );
44
63
  };
@@ -22,6 +22,14 @@ export interface TypeMenuItemStyledProps
22
22
  TypeSpaceSystemProps,
23
23
  TypeTypographySystemProps {}
24
24
 
25
+ export const INPUT_TYPES = {
26
+ CHECKBOX: "checkbox",
27
+ RADIO: "radio",
28
+ SWITCH: "switch",
29
+ ICON: "icon",
30
+ } as const;
31
+ export type InputType = (typeof INPUT_TYPES)[keyof typeof INPUT_TYPES];
32
+
25
33
  export interface TypeMenuItemProps
26
34
  extends TypeMenuItemStyledProps,
27
35
  Omit<
@@ -29,9 +37,13 @@ export interface TypeMenuItemProps
29
37
  "as" | "item" | keyof TypeMenuItemStyledProps | keyof TypeDefaultItemProps
30
38
  >,
31
39
  Omit<TypeDefaultItemProps, "index"> {
32
- label?: string;
33
40
  children: React.ReactNode;
34
41
  disabled?: boolean;
35
42
  role?: TypeMenuItemRoles;
36
43
  $highlighted?: boolean;
44
+
45
+ // props for options
46
+ active?: boolean;
47
+ inputType?: InputType;
48
+ inputLabelId?: string;
37
49
  }