Skip to content

Commit

Permalink
Adam/menu group (#2409)
Browse files Browse the repository at this point in the history
* Create MenuGroup.stories.tsx

* mv submenuContext file

* Creates MenuGroup with title & context

* implements menu group

* creates menu group stories

* update depth dynamically

* Update MenuGroup.stories.tsx

* fix highlight stories

* Update withMenuContextDecorator.testutils.tsx

* install typography

* Restructure SubMenu & Implement new Light-in-Light mode [LG-4236, LG-4060, LG-3263, LG-3168, LG-3190] (#2380)

* create Menu.styles

* installs descendants in menu

* extract useMenuHeight

* init descendants

* pass onItemFocus from provider

* abstract out useUpdatedChildren

* creates useHighlightReducer

* cleanup reducer

* skip disabled elements

* implement descendant in submenu

* Update yarn.lock

* rm focus-visible styles

we always want focus

* fix menu item list style

* fix ts errors

* rm deprecated hooks

* rm debug text

* restructure test suite

* Create blue-crews-hope.md

* Updates stories

* adds controlled story

* modernizes spec file

* Update Menu.stories.tsx

* Update SplitButton.spec.tsx

* update split button pkg.json

* Update yarn.lock

* Delete getNewIndex.ts

* add // prettier-ignore

* mv HighlightReducer

Update getUpdatedIndex.ts

* Update .gitignore

* creates AriaLabelPropsWithChildren type

* uses AriaLabelPropsWithChildren in InputOption

* Create InputOptionContent generated story

* InputOptionContent use tokens, extend className

* inputOptionThemeStyles use color tokens

* Update titleClassName

* create & use InputOptionContext

* refactor inputOptionStyles

* fix inputoption icon placement & sizing

* update icon hover styles

* Update Avatar props (#2352)

* avatar accepts null text

* update generated stories

* changeset

* Update spotty-ghosts-play.md

* add turbo to clean (#2361)

* pr

* Update .gitignore

* create Menu.styles

* installs descendants in menu

* extract useMenuHeight

* init descendants

* pass onItemFocus from provider

* abstract out useUpdatedChildren

* creates useHighlightReducer

* cleanup reducer

* skip disabled elements

* implement descendant in submenu

* Update yarn.lock

* rm focus-visible styles

we always want focus

* fix menu item list style

* fix ts errors

* rm deprecated hooks

* rm debug text

* restructure test suite

* Create blue-crews-hope.md

* Updates stories

* adds controlled story

* modernizes spec file

* Update Menu.stories.tsx

* adds preserveIconSpace. Update unique classnames

* create Menu.styles

* installs descendants in menu

* extract useMenuHeight

* init descendants

* pass onItemFocus from provider

* abstract out useUpdatedChildren

* creates useHighlightReducer

* cleanup reducer

* skip disabled elements

* implement descendant in submenu

* Update yarn.lock

* rm focus-visible styles

we always want focus

* fix menu item list style

* fix ts errors

* rm deprecated hooks

* rm debug text

* restructure test suite

* Create blue-crews-hope.md

* Updates stories

* adds controlled story

* modernizes spec file

* Update Menu.stories.tsx

* Update SplitButton.spec.tsx

* update split button pkg.json

* Update yarn.lock

* Delete getNewIndex.ts

* add // prettier-ignore

* mv HighlightReducer

Update getUpdatedIndex.ts

* update icon hover styles

* pr

* Update package.json

* mv content

* WIP: implement input option

* update component exports

* Create big-wasps-fix.md

* Create shaggy-cheetahs-ring.md

* Update big-wasps-fix.md

* implements preserveIconSpace

* Renames selected -> checked

* creates separate InputOptionContent.stories

* Update big-wasps-fix.md

* updates menu item stories

* Implement active & destructive styles, add stories

* wip dark in light mode

* update active wedge to border.primary

* create DarkInLightMode story

* include darkMode in InputOptionContext

* fix renderDarkMenu stories

* spread args into InitialOpen story

* rm old highlight reducer

* rm unused descendant vars

* rm checked styles

* Update big-wasps-fix.md

* Create clean-apricots-provide.md

* typo

* fix bad merge

* revert wedge color to blue.base

* revert icon height to default

* use disabled prop on `Description`

* add style changes to changeset

* updates text highlight color targeting

* revert implementing of Label component

* add description to highlight story

* Update MenuItem.styles.ts

* fix menu item tests

* Update InputOption.style.ts

* waitForTransition accepts null arg

* WIP

* add ref to descendant object

* add ref to descendant object

* rm controls from controlled story

* Creates `useTraceUpdate` hook

* create stale descendant test

* update spec & stories

* do not register descendent if it doesn't exist

* Adds getDescendants function

* add documentation for `getDescendants`

* update docs

* use getDescendants within Menu

* fix stories TS

* add popover as dev dep

* Update package.json

* mv test utils

* Update yarn.lock

* Update useControlledState.ts

* Create SubMenu.stories.tsx

* sub menu uses menu item. create useChildrenHeight

* adds keydown to close submenu

* Update Menu.spec.tsx

* add serve & watch scripts

* disable active styles when highlighted

* update changesets

* add tests for AriaLabelPropsWithChildren

* update documentation

* Update .gitignore

* Update README.md

* fix nits

* add example to useTraceUpdate

* rename var

* fix testing lib version

* PolyRef x null. PolyProps x PropsWithRef

* use latest CLI

* update Submenu types

* Update styles.ts

* clean up submenu tests

* InternalMenuItemContent - Prevents nested buttons

* lgids

* test to ensure no nested buttons

* Create slimy-walls-cry.md

* Update RecursiveRecord.types.ts

* scaffold light mode styles

* updates menu light-mode styling

* updates dark in light mode styles

* update submenu indent styles

* cleanup highlight styles

* fix initial open logic

* add destructive styles to dark-in-light

* rm size from SB

* Update Menu.stories.tsx

* adds transition handler tests in submenu

* cleanup tests

* Adds tests for more complex menu interactions

* ensure focus remains on a submenu after opening

* add internal flags to descendants utils

* add getByIndex/id to descendants pkg

* refactor Highlight reducer

* Update SubMenu.tsx

* pass getDescendants into highlight reducer

* Updates Descendant index properties

* handle TransitionExiting in submenu

* resolves submenu focus bugs

* Update yarn.lock

* rm comments

* fixes generated stories

---------

Co-authored-by: Shaneeza <[email protected]>

* fix stories

* update packages

* minor fixes

add clarifying comments

* Create shaggy-carrots-talk.md

---------

Co-authored-by: Shaneeza <[email protected]>
  • Loading branch information
TheSonOfThomp and shaneeza authored Jun 25, 2024
1 parent e8b001a commit 470a769
Show file tree
Hide file tree
Showing 19 changed files with 407 additions and 128 deletions.
5 changes: 5 additions & 0 deletions .changeset/shaggy-carrots-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/menu': minor
---

Adds `title` and `glyph` props to `MenuGroup`. Providing a title to `MenuGroup` will visually indent the child `MenuItem` components, appearing nested within the group.
2 changes: 2 additions & 0 deletions packages/menu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
"@leafygreen-ui/hooks": "^8.1.3",
"@leafygreen-ui/icon": "^12.5.4",
"@leafygreen-ui/icon-button": "^15.0.21",
"@leafygreen-ui/input-option": "^1.1.4",
"@leafygreen-ui/lib": "^13.6.0",
"@leafygreen-ui/palette": "^4.0.9",
"@leafygreen-ui/popover": "^11.4.0",
"@leafygreen-ui/polymorphic": "^2.0.0",
"@leafygreen-ui/tokens": "^2.9.0",
"@leafygreen-ui/typography": "^19.2.0",
"lodash": "^4.17.21",
"polished": "^4.3.1",
"react-transition-group": "^4.4.5"
Expand Down
26 changes: 16 additions & 10 deletions packages/menu/src/Menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ import { TestUtils } from '@leafygreen-ui/popover';
const { getAlign, getJustify } = TestUtils;

import { Size } from './types';
import { Menu, MenuItem, MenuProps, MenuSeparator, SubMenu } from '.';
import {
Menu,
MenuGroup,
MenuItem,
MenuProps,
MenuSeparator,
SubMenu,
} from '.';

const getDecoratorStyles = (args: Partial<MenuProps>) => {
return css`
Expand Down Expand Up @@ -66,7 +73,7 @@ export default {
align: 'bottom',
usePortal: true,
darkMode: false,
renderDarkMenu: true,
renderDarkMenu: false,
},
argTypes: {
open: {
Expand Down Expand Up @@ -132,14 +139,13 @@ export const LiveExample = {
Delete
</MenuItem>
<MenuSeparator />
<MenuItem>Lorem</MenuItem>
<MenuItem>Ipsum</MenuItem>
<MenuItem>Adipiscing</MenuItem>
<MenuItem>Cursus</MenuItem>
<MenuItem>Ullamcorper</MenuItem>
<MenuItem>Vulputate</MenuItem>
<MenuItem>Inceptos</MenuItem>
<MenuItem>Risus</MenuItem>
<MenuGroup title="Lorem Ipsum">
<MenuItem>Lorem</MenuItem>
<MenuItem>Ipsum</MenuItem>
<MenuItem>Dolor</MenuItem>
<MenuItem>Sit</MenuItem>
<MenuItem>Amet</MenuItem>
</MenuGroup>
</Menu>
);
},
Expand Down
23 changes: 23 additions & 0 deletions packages/menu/src/MenuContext/GroupContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { createContext, PropsWithChildren, useContext } from 'react';

export interface MenuGroupContextData {
depth: number;
hasIcon: boolean;
}

export const MenuGroupContext = createContext<MenuGroupContextData>({
depth: 0,
hasIcon: false,
});

export const MenuGroupProvider = ({
children,
depth,
hasIcon = false,
}: PropsWithChildren<MenuGroupContextData>) => (
<MenuGroupContext.Provider value={{ depth, hasIcon }}>
{children}
</MenuGroupContext.Provider>
);

export const useMenuGroupContext = () => useContext(MenuGroupContext);
20 changes: 17 additions & 3 deletions packages/menu/src/MenuContext/MenuContext.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import { createContext, useContext } from 'react';

import { createDescendantsContext } from '@leafygreen-ui/descendants';
import { Descendant } from '@leafygreen-ui/descendants';
import { Theme } from '@leafygreen-ui/lib';

import { MenuContextData } from './MenuContext.types';
import { HighlightReducerReturnType } from '../HighlightReducer/highlight.types';

export interface MenuContextData {
theme: Theme;
darkMode: boolean;

/** The index of the currently highlighted (focused) item */
highlight?: Descendant;

/** Sets the current highlight by index or id */
setHighlight?: HighlightReducerReturnType['setHighlight'];

/** Whether to render a dark menu in light mode */
renderDarkMenu?: boolean;
}

export const MenuDescendantsContext = createDescendantsContext(
'MenuDescendantsContext',
Expand All @@ -15,5 +31,3 @@ export const MenuContext = createContext<MenuContextData>({
});

export const useMenuContext = () => useContext(MenuContext);

export default MenuContext;
18 changes: 0 additions & 18 deletions packages/menu/src/MenuContext/MenuContext.types.ts

This file was deleted.

23 changes: 23 additions & 0 deletions packages/menu/src/MenuContext/SubMenuContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { createContext, PropsWithChildren, useContext } from 'react';

export interface SubMenuContextData {
depth: number;
hasIcon: boolean;
}

export const SubMenuContext = createContext<SubMenuContextData>({
depth: 0,
hasIcon: false,
});

export const SubMenuProvider = ({
children,
depth,
hasIcon = false,
}: PropsWithChildren<SubMenuContextData>) => (
<SubMenuContext.Provider value={{ depth, hasIcon }}>
{children}
</SubMenuContext.Provider>
);

export const useSubMenuContext = () => useContext(SubMenuContext);
15 changes: 14 additions & 1 deletion packages/menu/src/MenuContext/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
export {
default as MenuContext,
type MenuGroupContext,
MenuGroupContextData,
MenuGroupProvider,
useMenuGroupContext,
} from './GroupContext';
export {
MenuContext,
type MenuContextData,
MenuDescendantsContext,
useMenuContext,
} from './MenuContext';
export {
SubMenuContext,
type SubMenuContextData,
SubMenuProvider,
useSubMenuContext,
} from './SubMenuContext';
90 changes: 90 additions & 0 deletions packages/menu/src/MenuGroup/MenuGroup.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* eslint-disable react/jsx-key */
import React from 'react';
import { StoryMetaType } from '@lg-tools/storybook-utils';
import { StoryObj } from '@storybook/react';

import { css } from '@leafygreen-ui/emotion';
import Icon, { glyphs } from '@leafygreen-ui/icon';

import { MenuItem } from '../MenuItem';
import { SubMenu } from '../SubMenu';
import { withMenuContext } from '../testUtils/withMenuContextDecorator.testutils';

import { MenuGroup } from './MenuGroup';

export default {
title: 'Components/Menu/MenuGroup',
component: MenuGroup,
parameters: {
default: null,
},
args: {
title: 'Group',
glyph: 'AllProducts',
darkMode: false,
},
argTypes: {
darkMode: {
control: 'boolean',
},
glyph: {
control: 'select',
options: [undefined, ...Object.keys(glyphs)],
},
},
decorators: [withMenuContext()],
} satisfies StoryMetaType<typeof MenuGroup>;

export const LiveExample = {
render: ({ glyph, ...args }) => (
<div
className={css`
width: 256px;
outline: 1px dashed gray;
`}
>
<MenuGroup
{...args}
glyph={
glyph && <Icon glyph={glyph as unknown as keyof typeof glyphs} />
}
>
<MenuItem>Apple</MenuItem>
<MenuItem>Banana</MenuItem>
<MenuItem>Carrot</MenuItem>
<SubMenu title="Peppers">
<MenuItem>Jalapeño</MenuItem>
<MenuItem>Habanero</MenuItem>
<MenuItem>Ghost</MenuItem>
</SubMenu>
</MenuGroup>
<MenuGroup title="Other">
<MenuItem>Lasagna</MenuItem>
<MenuItem>Haggis</MenuItem>
<SubMenu title="Sweets" glyph={<Icon glyph="Warning" />}>
<MenuItem>Jellybeans</MenuItem>
<MenuItem>Chocolate</MenuItem>
<MenuItem>Cotton Candy</MenuItem>
</SubMenu>
</MenuGroup>
</div>
),
parameters: {
chromatic: {
disableSnapshot: true,
},
},
} satisfies StoryObj<typeof MenuGroup>;

export const Generated = {
render: () => <></>,
parameters: {
generate: {
combineArgs: {
darkMode: [false, true],
glyph: [undefined, <Icon glyph="Beaker" />],
},
decorator: withMenuContext(),
},
},
} satisfies StoryObj<typeof MenuGroup>;
19 changes: 19 additions & 0 deletions packages/menu/src/MenuGroup/MenuGroup.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { css } from '@leafygreen-ui/emotion';
import { Theme } from '@leafygreen-ui/lib';
import { color } from '@leafygreen-ui/tokens';

import { menuColor } from '../styles';

export const getMenuGroupItemStyles = (theme: Theme) => css`
cursor: unset;
background-color: ${menuColor[theme].background.default};
`;

export const getMenuGroupTitleStyles = (theme: Theme) => css`
color: ${color[theme].text.secondary.default};
`;

export const menuGroupULStyles = css`
margin: 0;
padding: 0;
`;
59 changes: 55 additions & 4 deletions packages/menu/src/MenuGroup/MenuGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,74 @@
import * as React from 'react';
import PropTypes from 'prop-types';

import { useIdAllocator } from '@leafygreen-ui/hooks';
import { InputOption, InputOptionContent } from '@leafygreen-ui/input-option';
import { Overline } from '@leafygreen-ui/typography';

import {
MenuGroupProvider,
useMenuContext,
useMenuGroupContext,
} from '../MenuContext';

import {
getMenuGroupItemStyles,
getMenuGroupTitleStyles,
menuGroupULStyles,
} from './MenuGroup.styles';
import { MenuGroupProps } from './MenuGroup.types';

/**
* # MenuGroup
*
* ```
<MenuGroup>
<MenuGroup>Hello World!</MenuGroup>
<MenuGroup title="Hello World!">
<MenuItem>Item 1</MenuItem>
</MenuGroup>
* ```
* @param props.children Content to appear inside of the MenuGroup.
*
*/
export function MenuGroup({ children, className, ...rest }: MenuGroupProps) {
export function MenuGroup({
children,
className,
title,
glyph,
...rest
}: MenuGroupProps) {
const { theme, darkMode } = useMenuContext();
const id = useIdAllocator({ prefix: 'lg-menu-group' });
const { depth } = useMenuGroupContext();

const shouldRenderGroupHeader = !!title;
const hasIcon = shouldRenderGroupHeader && !!glyph;
// We only indent the child items if we render a title here,
// otherwise we just pass through
const nextGroupDepth = depth + (shouldRenderGroupHeader ? 1 : 0);

return (
<section {...rest} className={className}>
{children}
{title && (
<InputOption
id={id}
darkMode={darkMode}
as="div"
role="none"
isInteractive={false}
className={getMenuGroupItemStyles(theme)}
>
<InputOptionContent leftGlyph={glyph} preserveIconSpace={false}>
<Overline className={getMenuGroupTitleStyles(theme)}>
{title}
</Overline>
</InputOptionContent>
</InputOption>
)}
<MenuGroupProvider depth={nextGroupDepth} hasIcon={hasIcon}>
<ul role="menu" aria-labelledby={id} className={menuGroupULStyles}>
{children}
</ul>
</MenuGroupProvider>
</section>
);
}
Expand Down
10 changes: 10 additions & 0 deletions packages/menu/src/MenuGroup/MenuGroup.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { HTMLElementProps } from '@leafygreen-ui/lib';
export interface MenuGroupProps extends HTMLElementProps<'div'> {
/**
* Main text rendered in `MenuGroup`.
*/
title?: string;

/**
* Slot to pass in an Icon rendered to the left of the text.
*/
glyph?: React.ReactElement;

/**
* Content that will appear inside of MenuGroup component.
* @type `<MenuItem />` | `<SubMenu />` | `<MenuGroup />` | `<MenuSeparator />`
Expand Down
Loading

0 comments on commit 470a769

Please sign in to comment.