Skip to content

Commit 3b3e1f6

Browse files
authored
feat(Menu)!: updates menu variants and usage guidance
Updates Menu variants and usage guidance according to figma. The action variant is now popover, the navigation variant is now fixed and the border has been removed, and the select variant is no more.
1 parent c716368 commit 3b3e1f6

File tree

10 files changed

+174
-133
lines changed

10 files changed

+174
-133
lines changed

packages/gamut/src/DataList/Controls/FilterControl.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export const FilterControl: React.FC<FilterProps> = ({
7474
maxHeight={300}
7575
overflowY="auto"
7676
width="max-content"
77-
variant="action"
77+
variant="popover"
7878
>
7979
{[SELECT_ALL, ...options].map((opt) => {
8080
const { text, value } = formatOption(opt);

packages/gamut/src/Menu/Menu.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const Menu = forwardRef<
88
Omit<ComponentProps<typeof List>, 'root'>
99
>(
1010
(
11-
{ children, variant = 'select', spacing = 'normal', role, ...rest },
11+
{ children, variant = 'popover', spacing = 'normal', role, ...rest },
1212
ref
1313
) => {
1414
const currentContext = useMenu({ variant, role, spacing });

packages/gamut/src/Menu/MenuContext.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import { ListProps } from '../List';
44

55
export interface MenuContextProps {
66
spacing: 'normal' | 'condensed';
7-
variant: 'select' | 'navigation' | 'action';
7+
variant: 'popover' | 'fixed';
88
depth: number;
99
role: ListProps['role'];
1010
}
1111

1212
export const MenuContext = createContext<MenuContextProps>({
1313
spacing: 'normal',
14-
variant: 'select',
14+
variant: 'popover',
1515
depth: 0,
1616
role: undefined,
1717
});

packages/gamut/src/Menu/MenuItem.tsx

+3-4
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ const getListItemType = (href: boolean, onClick: boolean) =>
1515
href ? 'link' : onClick ? 'button' : 'item';
1616

1717
const activePropnames = {
18-
navigation: 'active-navlink',
19-
action: 'active',
20-
select: 'selected',
18+
fixed: 'active-navlink',
19+
popover: 'active',
2120
};
2221

2322
const currentItemText = {
@@ -41,7 +40,7 @@ export const MenuItem = forwardRef<
4140
const computed = {
4241
...props,
4342
...rest,
44-
variant: variant === 'select' ? 'select' : 'link',
43+
variant: 'link',
4544
role: role === 'menu' ? 'menuitem' : undefined,
4645
[activeProp]: active,
4746
} as ListItemProps;

packages/gamut/src/Menu/MenuSeparator.tsx

-4
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,12 @@ import * as React from 'react';
22

33
import { Box, FlexBox } from '../Box';
44
import { FlexBoxProps } from '../Box/props';
5-
import { useMenuContext } from './MenuContext';
65

76
interface MenuSeperatorProps extends FlexBoxProps {
87
children?: never;
98
}
109

1110
export const MenuSeparator: React.FC<MenuSeperatorProps> = (props) => {
12-
const { variant } = useMenuContext();
13-
if (variant !== 'action') return null;
14-
1511
return (
1612
<FlexBox as="li" role="separator" height={8} fit center {...props}>
1713
<Box fit height="1px" bg="text-disabled" borderRadius="sm" mx={16} />

packages/gamut/src/Menu/__tests__/Menu.test.tsx

+7-14
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,17 @@ describe('Menu', () => {
3939
screen.getByRole('button');
4040
expect(screen.queryByRole('menuitem')).toBeNull();
4141
});
42-
it('renders menu separators when the variant is action', () => {
42+
it('renders menu separators when variant is popover', () => {
4343
renderView({
44-
variant: 'action',
44+
variant: 'popover',
4545
children: <MenuSeparator />,
4646
});
4747

4848
screen.getByRole('separator');
4949
});
50-
it('renders deep menu separators while the parent variant is action', () => {
50+
it('renders deep menu separators', () => {
5151
renderView({
52-
variant: 'action',
52+
variant: 'popover',
5353
children: (
5454
<Menu>
5555
<MenuSeparator />
@@ -59,20 +59,13 @@ describe('Menu', () => {
5959

6060
screen.getByRole('separator');
6161
});
62-
it('does not render separators when the variant is select + navigation', () => {
62+
it('renders menu separators when variant is fixed', () => {
6363
renderView({
64-
variant: 'select',
64+
variant: 'fixed',
6565
children: <MenuSeparator />,
6666
});
6767

68-
expect(screen.queryByRole('separator')).toBeNull();
69-
70-
renderView({
71-
variant: 'navigation',
72-
children: <MenuSeparator />,
73-
});
74-
75-
expect(screen.queryByRole('separator')).toBeNull();
68+
screen.getByRole('separator');
7669
});
7770
it('renders and icon only when specified', () => {
7871
renderView({

packages/gamut/src/Menu/elements.tsx

+14-3
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ export interface ListProps extends ListStyleProps, StyleStateProps {
3232
/** How offset spacing should be */
3333
spacing?: 'normal' | 'condensed';
3434
/** Menu variants for specific use cases and styles */
35-
variant?: 'select' | 'navigation' | 'action';
35+
variant?: 'popover' | 'fixed';
3636
/** is root menu */
3737
root?: boolean;
3838
/** bordered */
3939
as?: 'ul' | 'ol';
40+
showBorder?: boolean;
4041
}
4142

4243
const StyledList = styled('ul', styledOptions<'ul'>())<ListProps>(
@@ -54,6 +55,8 @@ const StyledList = styled('ul', styledOptions<'ul'>())<ListProps>(
5455
minWidth: 192,
5556
bg: 'background',
5657
p: 0,
58+
},
59+
showBorder: {
5760
border: 1,
5861
borderRadius: 'sm',
5962
},
@@ -65,8 +68,16 @@ const StyledList = styled('ul', styledOptions<'ul'>())<ListProps>(
6568
export const List = forwardRef<
6669
HTMLUListElement,
6770
ComponentProps<typeof StyledList>
68-
>(({ context = true, m = 0, root = true, ...rest }, ref) => (
69-
<StyledList context={context} m={m} root={root} ref={ref} {...rest} />
71+
>(({ context = true, m = 0, root = true, variant, ...rest }, ref) => (
72+
<StyledList
73+
context={context}
74+
m={m}
75+
root={root}
76+
showBorder={variant !== 'fixed'}
77+
ref={ref}
78+
variant={variant}
79+
{...rest}
80+
/>
7081
));
7182

7283
const interactiveVariants = system.variant({
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { Column, LayoutGrid } from '@codecademy/gamut';
21
import { Canvas, Controls, Meta } from '@storybook/blocks';
32

4-
import { ComponentHeader } from '~styleguide/blocks';
3+
import { ComponentHeader, ImageWrapper, LinkTo } from '~styleguide/blocks';
54

65
import * as MenuStories from './Menu.stories';
76

@@ -24,7 +23,8 @@ export const parameters = {
2423
<ComponentHeader {...parameters} />
2524

2625
## Usage
27-
While most Menus are used as layout elements, Menus can also be used as popover elements within components like Selects.
26+
27+
Use a Menu to organize and present a list of actions, options, or navigation links.
2828

2929
### Components
3030

@@ -34,71 +34,81 @@ While most Menus are used as layout elements, Menus can also be used as popover
3434

3535
### Best practices:
3636

37-
- A `Menu` should only have the `menu` role when it is a list of actions or functions a user can evoke.
38-
- For example - a popover menu that allows the user to copy text, paste text, or reset a workspace.
39-
- If your `Menu` is simply a list of links it should not have the `menu` role.
40-
- Adding the role of `menu` to the menu component will programmatically set the correct roles on its `MenuItem`s and `MenuSeparator`s.
37+
- The Popover menu has the `menu` role by default, which is intended for presenting actions or options. However, if it contains only navigation links, it should use the `<nav>` element for semantic and accessible navigation.
38+
- You can create floating menus by utilizing our <LinkTo id="Atoms/PopoverContainer">PopoverContainer</LinkTo>. Once you have your base positioning, you just need to adjust the y axis by 48 for the normal spacing or 32 for the condensed spacing for each of the MenuItems. You may also need to change the alignment of the PopoverContainer to ensure correct positioning.
39+
40+
### When NOT to use:
41+
42+
- Form Inputs - For selecting from a predefined set of choices within a form, use the SelectDropdown component.
43+
- Expandable Menus - For organizing multiple collapsible sections of navigation links in a structured layout, use the LayoutMenu component.
44+
- Switching between content - For toggling between multiple related views or content sections within the same context, use the Tabs component.
45+
46+
## Anatomy
47+
48+
<ImageWrapper src="./molecules/menuAnatomy.png" alt="Menu Anatomy diagram"/>
49+
50+
1. Leading Icon (optional)
51+
52+
- Use to visually reinforce the purpose of a menu item, improve scannability, or differentiate between actions.
53+
54+
2. Label
55+
56+
- Limit to 1-3 words.
57+
- For actions, use a verb that clearly explains what will happen.
58+
- When the menu item is linking to information, use a noun.
59+
- Keep the same word form for each grouping. (For example, all nouns or all verbs.)
60+
- Order and group by meaning or alphabetically.
61+
- Avoid placing words that look the same (for example, that start with the exact same letters) adjacent to one another.
4162

42-
If you would like to know more about the `menu` role - check out the MDN web docs [here](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/menu_role).
63+
3. Menu separator (optional)
64+
65+
- Use to group menu items into sections when there is a significant number of items, making lengthy menus easier to scan and navigate.
4366

4467
## Variants
4568

46-
<LayoutGrid gap={32} py={24}>
69+
### Popover
70+
71+
Use the Popover menu to present a temporary surface for actions, options, or links, ensuring access only when needed while keeping the interface clean and focused. The Popover menu has the `menu` role by default. But, if the menu contains only a list of navigation links, it should be nested in a `<nav>` element, which implicitly includes the `navigation` role.
4772

48-
<Column size={4} variant={false} mx={8}>
49-
### Select
50-
<Canvas of={MenuStories.Default} />
51-
A general purpose menu type that can be used for actions or navigation.
52-
</Column>
73+
<Canvas of={MenuStories.Popover} />
5374

54-
<Column size={4} variant={false}>
55-
### Action
56-
<Canvas of={MenuStories.Action} />
57-
These should be populated with MenuItems typed as buttons that execute some action.
58-
</Column>
75+
### Fixed
5976

60-
<Column size={4} variant={false}>
61-
### Navigation
62-
<Canvas of={MenuStories.Navigation} />
63-
These should be populated with MenuItems typed as links.
64-
</Column>
77+
Use the Fixed menu to provide persistent access to navigation links, allowing users to seamlessly move between sections of the interface without opening additional surfaces. Fixed menus should always use the `<nav>` element to ensure semantic and accessible navigation.
6578

66-
</LayoutGrid>
79+
<Canvas of={MenuStories.Fixed} />
6780

6881
## Spacing variants
82+
6983
Setting the Menu's `spacing` to `'condensed'` will reduce the padding around the MenuItems.
7084

71-
<LayoutGrid gap={32} py={24}>
85+
### Popover condensed
7286

73-
<Column size={4} variant={false} mx={8}>
74-
### Select condensed
75-
<Canvas of={MenuStories.SelectCondensed} />
76-
</Column>
87+
<Canvas of={MenuStories.PopoverCondensed} />
7788

78-
<Column size={4} variant={false}>
79-
### Action condensed
80-
<Canvas of={MenuStories.ActionCondensed} />
81-
</Column>
89+
### Fixed condensed
8290

83-
<Column size={4} variant={false}>
84-
### Navigation condensed
85-
<Canvas of={MenuStories.NavigationCondensed} />
86-
</Column>
91+
<Canvas of={MenuStories.FixedCondensed} />
8792

88-
</LayoutGrid>
93+
## Menu separator
8994

90-
## Popover menus
95+
Use the `MenuSeparator` component to group menu items into sections when there is a significant number of items, making lengthy menus easier to scan and navigate.
9196

92-
You can create popover menus by utilizing our **PopoverContainer**. Once you have your base positioning, you just need to adjust the `y` axis by `48` for the `normal` spacing or `32` for the `condensed` spacing for each of the `MenuItem`s. You may also need to change the `alignment` of the `PopoverContainer` to ensure correct positioning.
97+
<Canvas of={MenuStories.PopoverMenuSeparator} />
98+
<Canvas of={MenuStories.FixedMenuSeparator} />
9399

94-
Popover Menus that have complex functionalities or actions, like the example below, should have the `menu` role.
100+
## Floating menus
95101

96-
Click through each item to see how the `Menu`s appear (and disappear when the item is clicked again, or another item is selected.)
102+
You can create floating menus by utilizing our <LinkTo id="Atoms/PopoverContainer">PopoverContainer</LinkTo>. Once you have your base positioning, you just need to adjust the y axis by 48 for the normal spacing or 32 for the condensed spacing for each of the MenuItems. You may also need to change the alignment of the PopoverContainer to ensure correct positioning.
97103

98-
<Canvas of={MenuStories.Popover} />
104+
Floating menus that have complex functionalities or actions, like the example below, should have the menu role.
105+
106+
Click through each item to see how the `Menu`s appear (and disappear when the item is clicked again, or another item is selected.)
99107

108+
<Canvas of={MenuStories.FloatingMenuExample} />
100109

101110
## Playground
111+
102112
<Canvas sourceState="shown" of={MenuStories.Default} />
103113

104114
<Controls />

0 commit comments

Comments
 (0)