From 11d34e08cb7fee2d68e52dbb8f0bb5a4320a3cbc Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Fri, 14 Jun 2024 18:25:44 -0400 Subject: [PATCH 01/11] moves menu callback position. updates tests --- packages/menu/src/Menu.spec.tsx | 37 ++++++++++++++++++------- packages/menu/src/Menu.stories.tsx | 4 ++- packages/menu/src/Menu/Menu.tsx | 41 ++++++++++++++++------------ packages/menu/src/Menu/Menu.types.ts | 6 ++-- 4 files changed, 57 insertions(+), 31 deletions(-) diff --git a/packages/menu/src/Menu.spec.tsx b/packages/menu/src/Menu.spec.tsx index ab9e3a7f90..2672c252df 100644 --- a/packages/menu/src/Menu.spec.tsx +++ b/packages/menu/src/Menu.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { act, - fireEvent, + prettyDOM, render, waitFor, waitForElementToBeRemoved, @@ -74,7 +74,10 @@ function renderMenu( async function openMenu() { userEvent.click(triggerEl); const menuElements = await findMenuElements(); - fireEvent.transitionEnd(menuElements.menuEl as Element); // JSDOM does not automatically fire these events + await waitForTransition(menuElements.menuEl); + await waitFor(() => { + expect(menuElements.menuItemElements[0]).toHaveFocus(); + }); return menuElements; } @@ -159,9 +162,18 @@ describe('packages/menu', () => { const { triggerEl, findMenuElements } = renderMenu({}); userEvent.click(triggerEl); const { menuEl, menuItemElements } = await findMenuElements(); + await waitForTransition(menuEl); + await waitFor(() => { + expect(menuItemElements[0]).toHaveFocus(); + }); + }); + + test('First item is focused when { usePortal: false }', async () => { + const { triggerEl, findMenuElements } = renderMenu({ usePortal: false }); + userEvent.click(triggerEl); + const { menuEl, menuItemElements } = await findMenuElements(); + await waitForTransition(menuEl); await waitFor(() => { - // JSDOM does not automatically fire these events - fireEvent.transitionEnd(menuEl as Element); expect(menuItemElements[0]).toHaveFocus(); }); }); @@ -220,7 +232,7 @@ describe('packages/menu', () => { if (key === 'tab') { userEvent.tab(); } else { - userEvent.type(menu, `{${key}}`); + userEvent.keyboard(`{${key}}`); } }; @@ -249,11 +261,16 @@ describe('packages/menu', () => { const { openMenu, triggerEl } = renderMenu({ usePortal: false, }); - const { menuEl } = await openMenu(); - + const { menuEl, menuItemElements } = await openMenu(); + await waitFor(() => { + expect(menuEl).toBeInTheDocument(); + expect(menuItemElements[0]).toHaveFocus(); + }); userEventInteraction(menuEl!, key); await waitForElementToBeRemoved(menuEl); - expect(triggerEl).toHaveFocus(); + await waitFor(() => { + expect(triggerEl).toHaveFocus(); + }); }); }); @@ -348,7 +365,7 @@ describe('packages/menu', () => { expect(firstItem).toHaveFocus(); - userEvent.keyboard('[Enter]'); + userEvent.type(menuEl!, '[Enter]'); await act(async () => await waitForTimeout()); expect(menuEl).toBeInTheDocument(); @@ -364,7 +381,7 @@ describe('packages/menu', () => { expect(firstItem).toHaveFocus(); - userEvent.keyboard('[Space]'); + userEvent.type(menuEl!, '[Space]'); await act(async () => await waitForTimeout()); expect(menuEl).toBeInTheDocument(); diff --git a/packages/menu/src/Menu.stories.tsx b/packages/menu/src/Menu.stories.tsx index 20361ed842..fdbd615952 100644 --- a/packages/menu/src/Menu.stories.tsx +++ b/packages/menu/src/Menu.stories.tsx @@ -55,7 +55,6 @@ export default { 'setOpen', 'as', 'portalRef', - 'usePortal', ], }, chromatic: { @@ -82,6 +81,9 @@ export default { renderDarkMenu: { control: 'boolean', }, + usePortal: { + control: 'boolean', + }, }, } satisfies StoryMetaType; diff --git a/packages/menu/src/Menu/Menu.tsx b/packages/menu/src/Menu/Menu.tsx index 4a3aec35f3..103cbf3069 100644 --- a/packages/menu/src/Menu/Menu.tsx +++ b/packages/menu/src/Menu/Menu.tsx @@ -1,4 +1,10 @@ -import React, { useCallback, useRef, useState } from 'react'; +import React, { + KeyboardEventHandler, + useCallback, + useRef, + useState, +} from 'react'; +import { prettyDOM } from '@testing-library/react'; import PropTypes from 'prop-types'; import { @@ -7,7 +13,11 @@ import { useInitDescendants, } from '@leafygreen-ui/descendants'; import { css, cx } from '@leafygreen-ui/emotion'; -import { useBackdropClick, useEventListener } from '@leafygreen-ui/hooks'; +import { + useBackdropClick, + useEventListener, + useForwardedRef, +} from '@leafygreen-ui/hooks'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; import { isDefined, keyMap, Theme } from '@leafygreen-ui/lib'; import Popover, { Align, Justify } from '@leafygreen-ui/popover'; @@ -70,11 +80,11 @@ export const Menu = React.forwardRef(function Menu( popoverZIndex, ...rest }: MenuProps, - forwardRef, + fwdRef, ) { const { theme, darkMode } = useDarkMode(darkModeProp); - const popoverRef = useRef(null); + const popoverRef = useForwardedRef(fwdRef, null); const triggerRef = useRef(null); const [uncontrolledOpen, uncontrolledSetOpen] = useState(initialOpen); @@ -120,7 +130,7 @@ export const Menu = React.forwardRef(function Menu( }; // Fired on global keyDown event - function handleKeyDown(e: KeyboardEvent) { + const handleKeyDown: KeyboardEventHandler = e => { switch (e.key) { case keyMap.ArrowDown: e.preventDefault(); // Prevents page scrolling @@ -150,9 +160,7 @@ export const Menu = React.forwardRef(function Menu( } break; } - } - - useEventListener('keydown', handleKeyDown, { enabled: open }); + }; const popoverProps = { popoverZIndex, @@ -185,14 +193,20 @@ export const Menu = React.forwardRef(function Menu( > } active={open} align={align} justify={justify} refEl={refEl} adjustOnMutation={adjustOnMutation} + // Need to stop propagation, otherwise Menu will closed automatically when clicked + onClick={e => e.stopPropagation()} + onKeyDown={handleKeyDown} onEntered={handlePopoverOpen} data-testid={LGIDs.root} data-lgid={LGIDs.root} + {...rest} {...popoverProps} >
(function Menu( }, className, )} - ref={forwardRef} > - {/* Need to stop propagation, otherwise Menu will closed automatically when clicked */} - {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events*/} -
    e.stopPropagation()} - ref={popoverRef} - > +
      {children}
diff --git a/packages/menu/src/Menu/Menu.types.ts b/packages/menu/src/Menu/Menu.types.ts index 0faaacb6b8..b61d6535a3 100644 --- a/packages/menu/src/Menu/Menu.types.ts +++ b/packages/menu/src/Menu/Menu.types.ts @@ -1,4 +1,4 @@ -import { ReactElement, ReactNode } from 'react'; +import { ComponentProps, ReactElement, ReactNode } from 'react'; import { InferredPolymorphicPropsWithRef, @@ -12,7 +12,9 @@ export type SubMenuType = ReactElement< InferredPolymorphicPropsWithRef >; -export interface MenuProps extends Omit { +export interface MenuProps + extends Omit, + ComponentProps<'div'> { /** * The menu items, or submenus * @type `` | `` | `` | `` From e19e2f3b330472af0667f5448af41a74252c7a6a Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Fri, 14 Jun 2024 18:29:45 -0400 Subject: [PATCH 02/11] stories cleanup --- packages/menu/src/Menu.spec.tsx | 1 - packages/menu/src/Menu/Menu.tsx | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/menu/src/Menu.spec.tsx b/packages/menu/src/Menu.spec.tsx index 2672c252df..0ee31fd1c3 100644 --- a/packages/menu/src/Menu.spec.tsx +++ b/packages/menu/src/Menu.spec.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { act, - prettyDOM, render, waitFor, waitForElementToBeRemoved, diff --git a/packages/menu/src/Menu/Menu.tsx b/packages/menu/src/Menu/Menu.tsx index 103cbf3069..c1808108ac 100644 --- a/packages/menu/src/Menu/Menu.tsx +++ b/packages/menu/src/Menu/Menu.tsx @@ -4,7 +4,6 @@ import React, { useRef, useState, } from 'react'; -import { prettyDOM } from '@testing-library/react'; import PropTypes from 'prop-types'; import { @@ -13,11 +12,7 @@ import { useInitDescendants, } from '@leafygreen-ui/descendants'; import { css, cx } from '@leafygreen-ui/emotion'; -import { - useBackdropClick, - useEventListener, - useForwardedRef, -} from '@leafygreen-ui/hooks'; +import { useBackdropClick, useForwardedRef } from '@leafygreen-ui/hooks'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; import { isDefined, keyMap, Theme } from '@leafygreen-ui/lib'; import Popover, { Align, Justify } from '@leafygreen-ui/popover'; From cd22136ab049e497b342ab4423c22c656553a52f Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Fri, 14 Jun 2024 18:31:52 -0400 Subject: [PATCH 03/11] Create two-deers-matter.md --- .changeset/two-deers-matter.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/two-deers-matter.md diff --git a/.changeset/two-deers-matter.md b/.changeset/two-deers-matter.md new file mode 100644 index 0000000000..77c0b7f8e8 --- /dev/null +++ b/.changeset/two-deers-matter.md @@ -0,0 +1,6 @@ +--- +'@leafygreen-ui/menu': major +--- + +- Updates Menu props to extend `div` props +- Moves `...rest` prop spread onto the `Popover` div (previously props were spread on the inner `ul` element) From c64dc769c9f02238167c33d7971efacb367a7364 Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Mon, 17 Jun 2024 13:16:42 -0400 Subject: [PATCH 04/11] update menu types --- packages/menu/src/Menu/Menu.tsx | 3 +-- packages/menu/src/Menu/Menu.types.ts | 4 ++-- packages/popover/src/Popover.types.ts | 1 + packages/split-button/src/Menu/Menu.tsx | 4 ++-- packages/split-button/src/SplitButton/SplitButton.types.ts | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/menu/src/Menu/Menu.tsx b/packages/menu/src/Menu/Menu.tsx index c1808108ac..f13455a919 100644 --- a/packages/menu/src/Menu/Menu.tsx +++ b/packages/menu/src/Menu/Menu.tsx @@ -188,8 +188,7 @@ export const Menu = React.forwardRef(function Menu( > } + ref={popoverRef} active={open} align={align} justify={justify} diff --git a/packages/menu/src/Menu/Menu.types.ts b/packages/menu/src/Menu/Menu.types.ts index b61d6535a3..7227742565 100644 --- a/packages/menu/src/Menu/Menu.types.ts +++ b/packages/menu/src/Menu/Menu.types.ts @@ -1,4 +1,4 @@ -import { ComponentProps, ReactElement, ReactNode } from 'react'; +import { ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react'; import { InferredPolymorphicPropsWithRef, @@ -14,7 +14,7 @@ export type SubMenuType = ReactElement< export interface MenuProps extends Omit, - ComponentProps<'div'> { + Omit, 'onClick'> { /** * The menu items, or submenus * @type `` | `` | `` | `` diff --git a/packages/popover/src/Popover.types.ts b/packages/popover/src/Popover.types.ts index 6f8389d492..bbaad72739 100644 --- a/packages/popover/src/Popover.types.ts +++ b/packages/popover/src/Popover.types.ts @@ -190,6 +190,7 @@ export type PopoverProps = { /** * Click event handler passed to the root div element within the portal container. */ + // TODO: This should be typed with `HTMLDivElement` onClick?: React.MouseEventHandler; /** diff --git a/packages/split-button/src/Menu/Menu.tsx b/packages/split-button/src/Menu/Menu.tsx index 63a0f54754..27ccc72a93 100644 --- a/packages/split-button/src/Menu/Menu.tsx +++ b/packages/split-button/src/Menu/Menu.tsx @@ -47,8 +47,8 @@ export const Menu = ({ }: MenuProps) => { const { theme } = useDarkMode(); const [uncontrolledOpen, uncontrolledSetOpen] = useState(false); - const buttonRef = useRef(null); - const menuRef = useRef(null); + const buttonRef = useRef(null); + const menuRef = useRef(null); // TODO: make hook const setOpen = diff --git a/packages/split-button/src/SplitButton/SplitButton.types.ts b/packages/split-button/src/SplitButton/SplitButton.types.ts index 09e891c4e2..53619f538f 100644 --- a/packages/split-button/src/SplitButton/SplitButton.types.ts +++ b/packages/split-button/src/SplitButton/SplitButton.types.ts @@ -60,6 +60,7 @@ export type SelectedMenuProps = Omit< | 'shouldClose' | 'darkMode' | 'onClick' + | 'onChange' | 'align' | 'justify' | 'className' From 0d144ab3df59cdbd074350f0830ee1848a8b0963 Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Thu, 20 Jun 2024 16:48:47 -0400 Subject: [PATCH 05/11] Update SplitButton.spec.tsx --- packages/split-button/src/SplitButton/SplitButton.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/split-button/src/SplitButton/SplitButton.spec.tsx b/packages/split-button/src/SplitButton/SplitButton.spec.tsx index a8ed8a1532..a8864bef8e 100644 --- a/packages/split-button/src/SplitButton/SplitButton.spec.tsx +++ b/packages/split-button/src/SplitButton/SplitButton.spec.tsx @@ -412,9 +412,9 @@ describe('packages/split-button', () => { label="label" menuItems={getMenuItems()} /> - {/* @ts-expect-error - href not allowed when as is div*/} Date: Thu, 20 Jun 2024 17:26:47 -0400 Subject: [PATCH 06/11] lint fix --- packages/split-button/src/SplitButton/SplitButton.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/split-button/src/SplitButton/SplitButton.spec.tsx b/packages/split-button/src/SplitButton/SplitButton.spec.tsx index a8864bef8e..46f53d6d38 100644 --- a/packages/split-button/src/SplitButton/SplitButton.spec.tsx +++ b/packages/split-button/src/SplitButton/SplitButton.spec.tsx @@ -414,7 +414,7 @@ describe('packages/split-button', () => { /> Date: Thu, 20 Jun 2024 16:43:31 -0400 Subject: [PATCH 07/11] Update SplitButton.spec.tsx --- .../src/SplitButton/SplitButton.spec.tsx | 84 +++++++++++-------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/packages/split-button/src/SplitButton/SplitButton.spec.tsx b/packages/split-button/src/SplitButton/SplitButton.spec.tsx index 46f53d6d38..4c5611d8c2 100644 --- a/packages/split-button/src/SplitButton/SplitButton.spec.tsx +++ b/packages/split-button/src/SplitButton/SplitButton.spec.tsx @@ -3,6 +3,7 @@ import { fireEvent, getAllByRole as globalGetAllByRole, render, + waitFor, waitForElementToBeRemoved, within, } from '@testing-library/react'; @@ -52,13 +53,19 @@ function renderSplitButton(props = {}) { menuEl: HTMLElement | null; menuItemElements: Array; }> { - const menuEl = await renderResult.findByTestId(menuTestId); - const menuItemElements = await within(menuEl).findAllByRole('menuitem'); - - return { - menuEl, - menuItemElements, - }; + try { + const menuEl = await renderResult.findByTestId(menuTestId); + const menuItemElements = await within(menuEl).findAllByRole('menuitem'); + return { + menuEl, + menuItemElements, + }; + } catch { + return { + menuEl: null, + menuItemElements: null, + }; + } } /** @@ -138,33 +145,33 @@ describe('packages/split-button', () => { }); describe('Menu', () => { - test('trigger opens the menu when clicked', () => { - const { menuTrigger, getByTestId } = renderSplitButton({}); + test('trigger opens the menu when clicked', async () => { + const { menuTrigger, findMenuElements } = renderSplitButton({}); - fireEvent.click(menuTrigger as HTMLElement); + userEvent.click(menuTrigger); - const menu = getByTestId(menuTestId); - expect(menu).toBeInTheDocument(); + const { menuEl } = await findMenuElements(); + expect(menuEl).toBeInTheDocument(); }); - test('disabled trigger does not open the menu when clicked', () => { - const { menuTrigger, queryByTestId } = renderSplitButton({ + test('disabled trigger does not open the menu when clicked', async () => { + const { menuTrigger, findMenuElements } = renderSplitButton({ disabled: true, }); - fireEvent.click(menuTrigger as HTMLElement); + userEvent.click(menuTrigger); - const menu = queryByTestId(menuTestId); - expect(menu).not.toBeInTheDocument(); + const { menuEl } = await findMenuElements(); + expect(menuEl).not.toBeInTheDocument(); }); - test('has correct menu items', () => { - const { menuTrigger, getByTestId } = renderSplitButton({}); + test('has correct menu items', async () => { + const { menuTrigger, findMenuElements } = renderSplitButton({}); - fireEvent.click(menuTrigger as HTMLElement); + userEvent.click(menuTrigger); - const menu = getByTestId(menuTestId); - expect(menu.childElementCount).toEqual(4); + const { menuItemElements } = await findMenuElements(); + expect(menuItemElements).toHaveLength(4); }); test('accepts a portalRef', () => { @@ -186,7 +193,7 @@ describe('packages/split-button', () => { const onChange = jest.fn(); const { menuTrigger, getByTestId } = renderSplitButton({ onChange }); - userEvent.click(menuTrigger as HTMLElement); + userEvent.click(menuTrigger); const menu = getByTestId(menuTestId); const options = globalGetAllByRole(menu, 'menuitem'); @@ -232,11 +239,16 @@ describe('packages/split-button', () => { const { openMenu, menuTrigger } = renderSplitButton({ usePortal: false, }); - const { menuEl } = await openMenu(); - + const { menuEl, menuItemElements } = await openMenu(); + await waitFor(() => { + expect(menuEl).toBeInTheDocument(); + expect(menuItemElements[0]).toHaveFocus(); + }); userEventInteraction(menuEl!, key); await waitForElementToBeRemoved(menuEl); - expect(menuTrigger).toHaveFocus(); + await waitFor(() => { + expect(menuTrigger).toHaveFocus(); + }); }); }); @@ -267,7 +279,7 @@ describe('packages/split-button', () => { menuItems, }); const { menuItemElements } = await openMenu(); - expect(menuItemElements[0]).toHaveFocus(); + expect(menuItemElements?.[0]).toHaveFocus(); userEvent.type(menuItemElements?.[0]!, `{${key}}`); expect(onClick).toHaveBeenCalled(); @@ -299,16 +311,16 @@ describe('packages/split-button', () => { describe('Down arrow', () => { test('highlights the next option in the menu', async () => { const { openMenu } = renderSplitButton({ menuItems }); - const { menuEl, menuItemElements } = await openMenu(); - userEvent.type(menuEl!, '{arrowdown}'); + const { menuItemElements } = await openMenu(); + userEvent.keyboard('{arrowdown}'); expect(menuItemElements[1]).toHaveFocus(); }); test('cycles highlight to the top', async () => { const { openMenu } = renderSplitButton({ menuItems }); - const { menuEl, menuItemElements } = await openMenu(); + const { menuItemElements } = await openMenu(); for (let i = 0; i < menuItemElements.length; i++) { - userEvent.type(menuEl!, '{arrowdown}'); + userEvent.keyboard('{arrowdown}'); } expect(menuItemElements[0]).toHaveFocus(); @@ -318,18 +330,18 @@ describe('packages/split-button', () => { describe('Up arrow', () => { test('highlights the previous option in the menu', async () => { const { openMenu } = renderSplitButton({ menuItems }); - const { menuEl, menuItemElements } = await openMenu(); + const { menuItemElements } = await openMenu(); - userEvent.type(menuEl!, '{arrowdown}'); - userEvent.type(menuEl!, '{arrowup}'); + userEvent.keyboard('{arrowdown}'); + userEvent.keyboard('{arrowup}'); expect(menuItemElements[0]).toHaveFocus(); }); test('cycles highlight to the bottom', async () => { const { openMenu } = renderSplitButton({ menuItems }); - const { menuEl, menuItemElements } = await openMenu(); + const { menuItemElements } = await openMenu(); const lastOption = menuItemElements[menuItemElements.length - 1]; - userEvent.type(menuEl!, '{arrowup}'); + userEvent.keyboard('{arrowup}'); expect(lastOption).toHaveFocus(); }); }); From 962aab3035b6b5dfc1da3f808bd4dc8aedb952dc Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Fri, 21 Jun 2024 12:56:21 -0400 Subject: [PATCH 08/11] update packages --- packages/menu/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/menu/package.json b/packages/menu/package.json index 454e3ac92c..d3ffcff680 100644 --- a/packages/menu/package.json +++ b/packages/menu/package.json @@ -22,7 +22,7 @@ "access": "public" }, "dependencies": { - "@leafygreen-ui/descendants": "^0.1.1", + "@leafygreen-ui/descendants": "^0.2.0", "@leafygreen-ui/emotion": "^4.0.8", "@leafygreen-ui/hooks": "^8.1.3", "@leafygreen-ui/icon": "^12.5.4", @@ -50,7 +50,7 @@ }, "devDependencies": { "@leafygreen-ui/button": "^21.2.0", - "@leafygreen-ui/testing-lib": "^0.5.0", + "@leafygreen-ui/testing-lib": "^0.6.0", "@lg-tools/storybook-utils": "^0.1.1", "@storybook/react": "^7.6.17" } From fa22964c9a5b008e6cc37984e7c7aa89f2896d23 Mon Sep 17 00:00:00 2001 From: Adam Thompson Date: Mon, 24 Jun 2024 12:05:10 -0400 Subject: [PATCH 09/11] pass through onClick & onKeyDown --- packages/menu/src/Menu/Menu.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/menu/src/Menu/Menu.tsx b/packages/menu/src/Menu/Menu.tsx index f13455a919..3556d640e6 100644 --- a/packages/menu/src/Menu/Menu.tsx +++ b/packages/menu/src/Menu/Menu.tsx @@ -1,5 +1,6 @@ import React, { KeyboardEventHandler, + MouseEventHandler, useCallback, useRef, useState, @@ -64,6 +65,8 @@ export const Menu = React.forwardRef(function Menu( setOpen: controlledSetOpen, darkMode: darkModeProp, renderDarkMenu = true, + onClick, + onKeyDown, children, className, refEl, @@ -117,6 +120,12 @@ export const Menu = React.forwardRef(function Menu( }, ); + const handleMenuClick: MouseEventHandler = e => { + // Need to stop propagation, otherwise Menu will closed automatically when clicked + e.stopPropagation(); + onClick?.(e); + }; + // Callback fired when the popover transition finishes. // Handling on this event ensures that the `descendants` elements // exist in the DOM before attempting to set `focus` @@ -155,6 +164,7 @@ export const Menu = React.forwardRef(function Menu( } break; } + onKeyDown?.(e); }; const popoverProps = { @@ -194,8 +204,7 @@ export const Menu = React.forwardRef(function Menu( justify={justify} refEl={refEl} adjustOnMutation={adjustOnMutation} - // Need to stop propagation, otherwise Menu will closed automatically when clicked - onClick={e => e.stopPropagation()} + onClick={handleMenuClick} onKeyDown={handleKeyDown} onEntered={handlePopoverOpen} data-testid={LGIDs.root} From b74aeec534a9b71fd60785b10951da52d657c625 Mon Sep 17 00:00:00 2001 From: Adam Thompson <2414030+TheSonOfThomp@users.noreply.github.com> Date: Tue, 25 Jun 2024 15:50:32 -0400 Subject: [PATCH 10/11] Enhances MenuGroup [LG-4346] (#2403) * 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 * fix stories * update packages * minor fixes add clarifying comments * Create shaggy-carrots-talk.md --------- Co-authored-by: Shaneeza --- .changeset/shaggy-carrots-talk.md | 5 ++ packages/menu/package.json | 2 + packages/menu/src/Menu.stories.tsx | 26 +++--- .../menu/src/MenuContext/GroupContext.tsx | 23 +++++ packages/menu/src/MenuContext/MenuContext.tsx | 20 ++++- .../menu/src/MenuContext/MenuContext.types.ts | 18 ---- .../menu/src/MenuContext/SubMenuContext.tsx | 23 +++++ packages/menu/src/MenuContext/index.ts | 15 +++- .../menu/src/MenuGroup/MenuGroup.stories.tsx | 90 +++++++++++++++++++ .../menu/src/MenuGroup/MenuGroup.styles.ts | 19 ++++ packages/menu/src/MenuGroup/MenuGroup.tsx | 59 +++++++++++- .../menu/src/MenuGroup/MenuGroup.types.ts | 10 +++ .../src/MenuItem/InternalMenuItemContent.tsx | 43 +++++---- .../menu/src/MenuItem/MenuItem.stories.tsx | 60 ++----------- packages/menu/src/MenuItem/MenuItem.styles.ts | 65 ++++++++++---- packages/menu/src/SubMenu/SubMenu.tsx | 8 +- packages/menu/src/SubMenu/index.ts | 1 - .../withMenuContextDecorator.testutils.tsx | 45 ++++++++++ packages/menu/tsconfig.json | 3 + 19 files changed, 407 insertions(+), 128 deletions(-) create mode 100644 .changeset/shaggy-carrots-talk.md create mode 100644 packages/menu/src/MenuContext/GroupContext.tsx delete mode 100644 packages/menu/src/MenuContext/MenuContext.types.ts create mode 100644 packages/menu/src/MenuContext/SubMenuContext.tsx create mode 100644 packages/menu/src/MenuGroup/MenuGroup.stories.tsx create mode 100644 packages/menu/src/MenuGroup/MenuGroup.styles.ts create mode 100644 packages/menu/src/testUtils/withMenuContextDecorator.testutils.tsx diff --git a/.changeset/shaggy-carrots-talk.md b/.changeset/shaggy-carrots-talk.md new file mode 100644 index 0000000000..7ea4888b69 --- /dev/null +++ b/.changeset/shaggy-carrots-talk.md @@ -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. \ No newline at end of file diff --git a/packages/menu/package.json b/packages/menu/package.json index d3ffcff680..44a435a801 100644 --- a/packages/menu/package.json +++ b/packages/menu/package.json @@ -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" diff --git a/packages/menu/src/Menu.stories.tsx b/packages/menu/src/Menu.stories.tsx index fdbd615952..bcc51c72e1 100644 --- a/packages/menu/src/Menu.stories.tsx +++ b/packages/menu/src/Menu.stories.tsx @@ -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) => { return css` @@ -65,7 +72,7 @@ export default { align: 'bottom', usePortal: true, darkMode: false, - renderDarkMenu: true, + renderDarkMenu: false, }, argTypes: { open: { @@ -134,14 +141,13 @@ export const LiveExample = { Delete - Lorem - Ipsum - Adipiscing - Cursus - Ullamcorper - Vulputate - Inceptos - Risus + + Lorem + Ipsum + Dolor + Sit + Amet + ); }, diff --git a/packages/menu/src/MenuContext/GroupContext.tsx b/packages/menu/src/MenuContext/GroupContext.tsx new file mode 100644 index 0000000000..10f0f7d013 --- /dev/null +++ b/packages/menu/src/MenuContext/GroupContext.tsx @@ -0,0 +1,23 @@ +import React, { createContext, PropsWithChildren, useContext } from 'react'; + +export interface MenuGroupContextData { + depth: number; + hasIcon: boolean; +} + +export const MenuGroupContext = createContext({ + depth: 0, + hasIcon: false, +}); + +export const MenuGroupProvider = ({ + children, + depth, + hasIcon = false, +}: PropsWithChildren) => ( + + {children} + +); + +export const useMenuGroupContext = () => useContext(MenuGroupContext); diff --git a/packages/menu/src/MenuContext/MenuContext.tsx b/packages/menu/src/MenuContext/MenuContext.tsx index 7a4a9a3b45..59e0caf472 100644 --- a/packages/menu/src/MenuContext/MenuContext.tsx +++ b/packages/menu/src/MenuContext/MenuContext.tsx @@ -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', @@ -15,5 +31,3 @@ export const MenuContext = createContext({ }); export const useMenuContext = () => useContext(MenuContext); - -export default MenuContext; diff --git a/packages/menu/src/MenuContext/MenuContext.types.ts b/packages/menu/src/MenuContext/MenuContext.types.ts deleted file mode 100644 index 3a41943903..0000000000 --- a/packages/menu/src/MenuContext/MenuContext.types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Descendant } from '@leafygreen-ui/descendants'; -import { Theme } from '@leafygreen-ui/lib'; - -import { HighlightReducerReturnType } from '../HighlightReducer/highlight.types'; - -export interface MenuContextData { - theme: Theme; - darkMode: boolean; - - /** The index of the currently highlighted (focused) item */ - // highlightIndex?: number; - highlight?: Descendant; - - setHighlight?: HighlightReducerReturnType['setHighlight']; - - /** Whether to a dark menu in light mode */ - renderDarkMenu?: boolean; -} diff --git a/packages/menu/src/MenuContext/SubMenuContext.tsx b/packages/menu/src/MenuContext/SubMenuContext.tsx new file mode 100644 index 0000000000..51e50bd6f2 --- /dev/null +++ b/packages/menu/src/MenuContext/SubMenuContext.tsx @@ -0,0 +1,23 @@ +import React, { createContext, PropsWithChildren, useContext } from 'react'; + +export interface SubMenuContextData { + depth: number; + hasIcon: boolean; +} + +export const SubMenuContext = createContext({ + depth: 0, + hasIcon: false, +}); + +export const SubMenuProvider = ({ + children, + depth, + hasIcon = false, +}: PropsWithChildren) => ( + + {children} + +); + +export const useSubMenuContext = () => useContext(SubMenuContext); diff --git a/packages/menu/src/MenuContext/index.ts b/packages/menu/src/MenuContext/index.ts index 86352e4de7..86c6880e74 100644 --- a/packages/menu/src/MenuContext/index.ts +++ b/packages/menu/src/MenuContext/index.ts @@ -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'; diff --git a/packages/menu/src/MenuGroup/MenuGroup.stories.tsx b/packages/menu/src/MenuGroup/MenuGroup.stories.tsx new file mode 100644 index 0000000000..9019f33d21 --- /dev/null +++ b/packages/menu/src/MenuGroup/MenuGroup.stories.tsx @@ -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; + +export const LiveExample = { + render: ({ glyph, ...args }) => ( +
+ + } + > + Apple + Banana + Carrot + + Jalapeño + Habanero + Ghost + + + + Lasagna + Haggis + }> + Jellybeans + Chocolate + Cotton Candy + + +
+ ), + parameters: { + chromatic: { + disableSnapshot: true, + }, + }, +} satisfies StoryObj; + +export const Generated = { + render: () => <>, + parameters: { + generate: { + combineArgs: { + darkMode: [false, true], + glyph: [undefined, ], + }, + decorator: withMenuContext(), + }, + }, +} satisfies StoryObj; diff --git a/packages/menu/src/MenuGroup/MenuGroup.styles.ts b/packages/menu/src/MenuGroup/MenuGroup.styles.ts new file mode 100644 index 0000000000..43cb204e26 --- /dev/null +++ b/packages/menu/src/MenuGroup/MenuGroup.styles.ts @@ -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; +`; diff --git a/packages/menu/src/MenuGroup/MenuGroup.tsx b/packages/menu/src/MenuGroup/MenuGroup.tsx index ed9da3b34f..74ca3bc097 100644 --- a/packages/menu/src/MenuGroup/MenuGroup.tsx +++ b/packages/menu/src/MenuGroup/MenuGroup.tsx @@ -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 * * ``` - - Hello World! + + Item 1 * ``` * @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 (
- {children} + {title && ( + + + + {title} + + + + )} + +
    + {children} +
+
); } diff --git a/packages/menu/src/MenuGroup/MenuGroup.types.ts b/packages/menu/src/MenuGroup/MenuGroup.types.ts index 9e72464469..4b70856400 100644 --- a/packages/menu/src/MenuGroup/MenuGroup.types.ts +++ b/packages/menu/src/MenuGroup/MenuGroup.types.ts @@ -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 `` | `` | `` | `` diff --git a/packages/menu/src/MenuItem/InternalMenuItemContent.tsx b/packages/menu/src/MenuItem/InternalMenuItemContent.tsx index 2f236e69c7..887f702560 100644 --- a/packages/menu/src/MenuItem/InternalMenuItemContent.tsx +++ b/packages/menu/src/MenuItem/InternalMenuItemContent.tsx @@ -7,15 +7,18 @@ import { PolymorphicAs, useInferredPolymorphic, } from '@leafygreen-ui/polymorphic'; -import { color, spacing } from '@leafygreen-ui/tokens'; +import { color } from '@leafygreen-ui/tokens'; -import { useMenuContext } from '../MenuContext'; -import { useSubMenuContext } from '../SubMenu'; +import { + useMenuContext, + useMenuGroupContext, + useSubMenuContext, +} from '../MenuContext'; import { getDarkInLightModeMenuItemStyles, getMenuItemStyles, - getSubMenuItemStyles, + getNestedMenuItemStyles, } from './MenuItem.styles'; import { MenuItemProps, Variant } from './MenuItem.types'; @@ -53,9 +56,16 @@ export const InternalMenuItemContent = React.forwardRef< const { as } = useInferredPolymorphic(asProp, rest, 'button'); const { theme, darkMode, highlight, renderDarkMenu } = useMenuContext(); - const { depth, hasIcon: parentHasIcon } = useSubMenuContext(); - const isSubMenuItem = depth > 0; - const highlighted = id === highlight?.id; + const { depth: submenuDepth, hasIcon: submenuHasIcon } = + useSubMenuContext(); + const { depth: groupDepth, hasIcon: groupHasIcon } = useMenuGroupContext(); + const isNested = !!(submenuDepth || groupDepth); + + // @ts-expect-error + // highlighted isn't a prop on this component, but could be passed in from MenuItem. + // Generally this will not be provided, but is permitted here to support isolated visual testing in Storybook + const forceHighlight = rest.highlighted; + const highlighted = id === highlight?.id || forceHighlight; const defaultAnchorProps = as === 'a' @@ -78,7 +88,7 @@ export const InternalMenuItemContent = React.forwardRef< darkMode={darkMode} showWedge highlighted={highlighted} - data-depth={depth} + data-depth={submenuDepth} className={cx( getMenuItemStyles({ active, @@ -89,7 +99,13 @@ export const InternalMenuItemContent = React.forwardRef< }), { - [getSubMenuItemStyles({ theme, parentHasIcon })]: isSubMenuItem, + [getNestedMenuItemStyles({ + theme, + submenuDepth, + submenuHasIcon, + groupDepth, + groupHasIcon, + })]: isNested, // TODO: Remove dark-in-light mode styles // after https://jira.mongodb.org/browse/LG-3974 @@ -104,7 +120,7 @@ export const InternalMenuItemContent = React.forwardRef< &:after { background-color: ${color.dark.border.secondary.default}; } - `]: theme === 'light' && renderDarkMenu && depth > 0, + `]: theme === 'light' && renderDarkMenu && submenuDepth > 0, }, className, )} @@ -116,13 +132,6 @@ export const InternalMenuItemContent = React.forwardRef< description={description} rightGlyph={rightGlyph} preserveIconSpace={false} - className={cx({ - [css` - position: relative; - padding-left: ${parentHasIcon ? spacing[900] : spacing[600]}px; - border-top: 1px solid transparent; - `]: depth > 0, - })} >
=> (Instance, ctx) => { - const { - args: { darkMode: darkModeProp, renderDarkMenu, highlighted, ...props }, - } = ctx ?? { - args: { - darkMode: false, - renderDarkMenu: false, - highlighted: false, - }, - }; - - const ref = useRef(null); - const [testDescendant, setTestDescendant] = useState(); - useEffect(() => { - setTestDescendant({ - ref, - element: ref.current, - id: ref?.current?.getAttribute('data-id'), - index: Number(ref?.current?.getAttribute('data-index')), - } as Descendant); - }, []); - const darkMode = (renderDarkMenu || darkModeProp) ?? false; - const theme = darkMode ? Theme.Dark : Theme.Light; - - return ( - -
    - -
-
- ); - }; - export default { title: 'Components/Menu/MenuItem', component: MenuItem, @@ -80,7 +32,7 @@ export default { combineArgs: { darkMode: [false, true], }, - decorator: _withMenuContext(), + decorator: withMenuContext(), }, }, } satisfies StoryMetaType>; @@ -111,7 +63,7 @@ export const LiveExample = { {children} ), - decorators: [_withMenuContext()], + decorators: [withMenuContext()], parameters: { chromatic: { disableSnapshot: true, diff --git a/packages/menu/src/MenuItem/MenuItem.styles.ts b/packages/menu/src/MenuItem/MenuItem.styles.ts index 809c5551a0..2ff3296837 100644 --- a/packages/menu/src/MenuItem/MenuItem.styles.ts +++ b/packages/menu/src/MenuItem/MenuItem.styles.ts @@ -1,6 +1,7 @@ import { css, cx } from '@leafygreen-ui/emotion'; import { descriptionClassName, + inputOptionContentClassName, leftGlyphClassName, titleClassName, } from '@leafygreen-ui/input-option'; @@ -131,24 +132,6 @@ export const getMenuItemStyles = ({ }, ); -export const getSubMenuItemStyles = ({ - theme, - parentHasIcon, -}: { - theme: Theme; - parentHasIcon: boolean; -}) => css` - &:after { - content: ''; - position: absolute; - top: 0; - right: 0; - left: ${parentHasIcon ? spacing[900] : spacing[600]}px; - height: 1px; - background-color: ${menuColor[theme].border.default}; - } -`; - export const getMenuItemContentStyles = ({ hasGlyph, }: { @@ -160,6 +143,52 @@ export const getMenuItemContentStyles = ({ `} `; +interface NestedItemStyleArgs { + theme: Theme; + submenuDepth: number; + groupDepth: number; + submenuHasIcon: boolean; + groupHasIcon: boolean; +} + +/** Styling for nested items */ +export const getNestedMenuItemStyles = ({ + theme, + submenuDepth, + submenuHasIcon, + groupDepth, + groupHasIcon, +}: NestedItemStyleArgs) => { + const submenuInset = + submenuDepth * (submenuHasIcon ? spacing[1000] : spacing[300]); + const groupInset = groupDepth * (groupHasIcon ? spacing[600] : spacing[300]); + const totalInset = submenuInset + groupInset; + + return cx( + { + // The inset border for submenu items + [css` + &:after { + content: ''; + position: absolute; + top: 0; + right: 0; + left: ${totalInset}px; + height: 1px; + background-color: ${menuColor[theme].border.default}; + } + `]: submenuDepth > 0, + }, + css` + .${inputOptionContentClassName} { + position: relative; + padding-left: ${totalInset}px; + border-top: 1px solid transparent; + } + `, + ); +}; + // TODO: Remove dark-in-light mode styles // after https://jira.mongodb.org/browse/LG-3974 export const getDarkInLightModeMenuItemStyles = ({ diff --git a/packages/menu/src/SubMenu/SubMenu.tsx b/packages/menu/src/SubMenu/SubMenu.tsx index 5e6895eb3e..6df913f4ac 100644 --- a/packages/menu/src/SubMenu/SubMenu.tsx +++ b/packages/menu/src/SubMenu/SubMenu.tsx @@ -20,7 +20,12 @@ import { } from '@leafygreen-ui/polymorphic'; import { LGIDs } from '../constants'; -import { MenuDescendantsContext, useMenuContext } from '../MenuContext'; +import { + MenuDescendantsContext, + SubMenuProvider, + useMenuContext, + useSubMenuContext, +} from '../MenuContext'; import { InternalMenuItemContent } from '../MenuItem/InternalMenuItemContent'; import { @@ -31,7 +36,6 @@ import { submenuToggleStyles, } from './SubMenu.styles'; import { InternalSubMenuProps } from './SubMenu.types'; -import { SubMenuProvider, useSubMenuContext } from './SubMenuContext'; import { useChildrenHeight } from './useChildrenHeight'; import { useControlledState } from './useControlledState'; diff --git a/packages/menu/src/SubMenu/index.ts b/packages/menu/src/SubMenu/index.ts index 3e99bbf493..35c15083e7 100644 --- a/packages/menu/src/SubMenu/index.ts +++ b/packages/menu/src/SubMenu/index.ts @@ -1,3 +1,2 @@ export { SubMenu } from './SubMenu'; export { InternalSubMenuProps, SubMenuProps } from './SubMenu.types'; -export { useSubMenuContext } from './SubMenuContext'; diff --git a/packages/menu/src/testUtils/withMenuContextDecorator.testutils.tsx b/packages/menu/src/testUtils/withMenuContextDecorator.testutils.tsx new file mode 100644 index 0000000000..710befdd6e --- /dev/null +++ b/packages/menu/src/testUtils/withMenuContextDecorator.testutils.tsx @@ -0,0 +1,45 @@ +/* eslint-disable react/jsx-key, react/display-name, react-hooks/rules-of-hooks */ +import React from 'react'; +import { InstanceDecorator } from '@lg-tools/storybook-utils'; + +import { css } from '@leafygreen-ui/emotion'; +import { Theme } from '@leafygreen-ui/lib'; + +import { Menu } from '../Menu'; +import { MenuContext } from '../MenuContext'; +import { MenuItem } from '../MenuItem'; + +/** + * Implements a MenuContext wrapper around each `MenuItem`, `SubMenu` or `MenuGroup` + */ +export const withMenuContext = + (): InstanceDecorator => (Instance, ctx) => { + const { + args: { darkMode: darkModeProp, renderDarkMenu }, + } = ctx ?? { + args: { + darkMode: false, + renderDarkMenu: false, + }, + }; + + const darkMode = (renderDarkMenu || darkModeProp) ?? false; + const theme = darkMode ? Theme.Dark : Theme.Light; + + return ( +
+ + + +
+ ); + }; diff --git a/packages/menu/tsconfig.json b/packages/menu/tsconfig.json index 8441ac11ab..ef8bcaf4c9 100644 --- a/packages/menu/tsconfig.json +++ b/packages/menu/tsconfig.json @@ -48,6 +48,9 @@ { "path": "../tokens" }, + { + "path": "../typography" + }, { "path": "../leafygreen-provider" } From c6d661e94ba8bbcc45c0fe3acd02b0f076cf4a8b Mon Sep 17 00:00:00 2001 From: Adam Thompson <2414030+TheSonOfThomp@users.noreply.github.com> Date: Tue, 25 Jun 2024 15:52:43 -0400 Subject: [PATCH 11/11] Revert "Enhances MenuGroup [LG-4346] (#2403)" (#2408) This reverts commit b74aeec534a9b71fd60785b10951da52d657c625. --- .changeset/shaggy-carrots-talk.md | 5 -- packages/menu/package.json | 2 - packages/menu/src/Menu.stories.tsx | 26 +++--- .../menu/src/MenuContext/GroupContext.tsx | 23 ----- packages/menu/src/MenuContext/MenuContext.tsx | 20 +---- .../menu/src/MenuContext/MenuContext.types.ts | 18 ++++ .../menu/src/MenuContext/SubMenuContext.tsx | 23 ----- packages/menu/src/MenuContext/index.ts | 15 +--- .../menu/src/MenuGroup/MenuGroup.stories.tsx | 90 ------------------- .../menu/src/MenuGroup/MenuGroup.styles.ts | 19 ---- packages/menu/src/MenuGroup/MenuGroup.tsx | 59 +----------- .../menu/src/MenuGroup/MenuGroup.types.ts | 10 --- .../src/MenuItem/InternalMenuItemContent.tsx | 43 ++++----- .../menu/src/MenuItem/MenuItem.stories.tsx | 60 +++++++++++-- packages/menu/src/MenuItem/MenuItem.styles.ts | 65 ++++---------- packages/menu/src/SubMenu/SubMenu.tsx | 8 +- packages/menu/src/SubMenu/index.ts | 1 + .../withMenuContextDecorator.testutils.tsx | 45 ---------- packages/menu/tsconfig.json | 3 - 19 files changed, 128 insertions(+), 407 deletions(-) delete mode 100644 .changeset/shaggy-carrots-talk.md delete mode 100644 packages/menu/src/MenuContext/GroupContext.tsx create mode 100644 packages/menu/src/MenuContext/MenuContext.types.ts delete mode 100644 packages/menu/src/MenuContext/SubMenuContext.tsx delete mode 100644 packages/menu/src/MenuGroup/MenuGroup.stories.tsx delete mode 100644 packages/menu/src/MenuGroup/MenuGroup.styles.ts delete mode 100644 packages/menu/src/testUtils/withMenuContextDecorator.testutils.tsx diff --git a/.changeset/shaggy-carrots-talk.md b/.changeset/shaggy-carrots-talk.md deleted file mode 100644 index 7ea4888b69..0000000000 --- a/.changeset/shaggy-carrots-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@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. \ No newline at end of file diff --git a/packages/menu/package.json b/packages/menu/package.json index 44a435a801..d3ffcff680 100644 --- a/packages/menu/package.json +++ b/packages/menu/package.json @@ -27,13 +27,11 @@ "@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" diff --git a/packages/menu/src/Menu.stories.tsx b/packages/menu/src/Menu.stories.tsx index bcc51c72e1..fdbd615952 100644 --- a/packages/menu/src/Menu.stories.tsx +++ b/packages/menu/src/Menu.stories.tsx @@ -20,14 +20,7 @@ import { TestUtils } from '@leafygreen-ui/popover'; const { getAlign, getJustify } = TestUtils; import { Size } from './types'; -import { - Menu, - MenuGroup, - MenuItem, - MenuProps, - MenuSeparator, - SubMenu, -} from '.'; +import { Menu, MenuItem, MenuProps, MenuSeparator, SubMenu } from '.'; const getDecoratorStyles = (args: Partial) => { return css` @@ -72,7 +65,7 @@ export default { align: 'bottom', usePortal: true, darkMode: false, - renderDarkMenu: false, + renderDarkMenu: true, }, argTypes: { open: { @@ -141,13 +134,14 @@ export const LiveExample = { Delete - - Lorem - Ipsum - Dolor - Sit - Amet - + Lorem + Ipsum + Adipiscing + Cursus + Ullamcorper + Vulputate + Inceptos + Risus ); }, diff --git a/packages/menu/src/MenuContext/GroupContext.tsx b/packages/menu/src/MenuContext/GroupContext.tsx deleted file mode 100644 index 10f0f7d013..0000000000 --- a/packages/menu/src/MenuContext/GroupContext.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React, { createContext, PropsWithChildren, useContext } from 'react'; - -export interface MenuGroupContextData { - depth: number; - hasIcon: boolean; -} - -export const MenuGroupContext = createContext({ - depth: 0, - hasIcon: false, -}); - -export const MenuGroupProvider = ({ - children, - depth, - hasIcon = false, -}: PropsWithChildren) => ( - - {children} - -); - -export const useMenuGroupContext = () => useContext(MenuGroupContext); diff --git a/packages/menu/src/MenuContext/MenuContext.tsx b/packages/menu/src/MenuContext/MenuContext.tsx index 59e0caf472..7a4a9a3b45 100644 --- a/packages/menu/src/MenuContext/MenuContext.tsx +++ b/packages/menu/src/MenuContext/MenuContext.tsx @@ -1,24 +1,8 @@ import { createContext, useContext } from 'react'; import { createDescendantsContext } from '@leafygreen-ui/descendants'; -import { Descendant } from '@leafygreen-ui/descendants'; -import { Theme } from '@leafygreen-ui/lib'; -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; -} +import { MenuContextData } from './MenuContext.types'; export const MenuDescendantsContext = createDescendantsContext( 'MenuDescendantsContext', @@ -31,3 +15,5 @@ export const MenuContext = createContext({ }); export const useMenuContext = () => useContext(MenuContext); + +export default MenuContext; diff --git a/packages/menu/src/MenuContext/MenuContext.types.ts b/packages/menu/src/MenuContext/MenuContext.types.ts new file mode 100644 index 0000000000..3a41943903 --- /dev/null +++ b/packages/menu/src/MenuContext/MenuContext.types.ts @@ -0,0 +1,18 @@ +import { Descendant } from '@leafygreen-ui/descendants'; +import { Theme } from '@leafygreen-ui/lib'; + +import { HighlightReducerReturnType } from '../HighlightReducer/highlight.types'; + +export interface MenuContextData { + theme: Theme; + darkMode: boolean; + + /** The index of the currently highlighted (focused) item */ + // highlightIndex?: number; + highlight?: Descendant; + + setHighlight?: HighlightReducerReturnType['setHighlight']; + + /** Whether to a dark menu in light mode */ + renderDarkMenu?: boolean; +} diff --git a/packages/menu/src/MenuContext/SubMenuContext.tsx b/packages/menu/src/MenuContext/SubMenuContext.tsx deleted file mode 100644 index 51e50bd6f2..0000000000 --- a/packages/menu/src/MenuContext/SubMenuContext.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React, { createContext, PropsWithChildren, useContext } from 'react'; - -export interface SubMenuContextData { - depth: number; - hasIcon: boolean; -} - -export const SubMenuContext = createContext({ - depth: 0, - hasIcon: false, -}); - -export const SubMenuProvider = ({ - children, - depth, - hasIcon = false, -}: PropsWithChildren) => ( - - {children} - -); - -export const useSubMenuContext = () => useContext(SubMenuContext); diff --git a/packages/menu/src/MenuContext/index.ts b/packages/menu/src/MenuContext/index.ts index 86c6880e74..86352e4de7 100644 --- a/packages/menu/src/MenuContext/index.ts +++ b/packages/menu/src/MenuContext/index.ts @@ -1,18 +1,5 @@ export { - type MenuGroupContext, - MenuGroupContextData, - MenuGroupProvider, - useMenuGroupContext, -} from './GroupContext'; -export { - MenuContext, - type MenuContextData, + default as MenuContext, MenuDescendantsContext, useMenuContext, } from './MenuContext'; -export { - SubMenuContext, - type SubMenuContextData, - SubMenuProvider, - useSubMenuContext, -} from './SubMenuContext'; diff --git a/packages/menu/src/MenuGroup/MenuGroup.stories.tsx b/packages/menu/src/MenuGroup/MenuGroup.stories.tsx deleted file mode 100644 index 9019f33d21..0000000000 --- a/packages/menu/src/MenuGroup/MenuGroup.stories.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* 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; - -export const LiveExample = { - render: ({ glyph, ...args }) => ( -
- - } - > - Apple - Banana - Carrot - - Jalapeño - Habanero - Ghost - - - - Lasagna - Haggis - }> - Jellybeans - Chocolate - Cotton Candy - - -
- ), - parameters: { - chromatic: { - disableSnapshot: true, - }, - }, -} satisfies StoryObj; - -export const Generated = { - render: () => <>, - parameters: { - generate: { - combineArgs: { - darkMode: [false, true], - glyph: [undefined, ], - }, - decorator: withMenuContext(), - }, - }, -} satisfies StoryObj; diff --git a/packages/menu/src/MenuGroup/MenuGroup.styles.ts b/packages/menu/src/MenuGroup/MenuGroup.styles.ts deleted file mode 100644 index 43cb204e26..0000000000 --- a/packages/menu/src/MenuGroup/MenuGroup.styles.ts +++ /dev/null @@ -1,19 +0,0 @@ -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; -`; diff --git a/packages/menu/src/MenuGroup/MenuGroup.tsx b/packages/menu/src/MenuGroup/MenuGroup.tsx index 74ca3bc097..ed9da3b34f 100644 --- a/packages/menu/src/MenuGroup/MenuGroup.tsx +++ b/packages/menu/src/MenuGroup/MenuGroup.tsx @@ -1,74 +1,23 @@ 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 * * ``` - - Item 1 + + Hello World! * ``` * @param props.children Content to appear inside of the MenuGroup. * */ -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); - +export function MenuGroup({ children, className, ...rest }: MenuGroupProps) { return (
- {title && ( - - - - {title} - - - - )} - -
    - {children} -
-
+ {children}
); } diff --git a/packages/menu/src/MenuGroup/MenuGroup.types.ts b/packages/menu/src/MenuGroup/MenuGroup.types.ts index 4b70856400..9e72464469 100644 --- a/packages/menu/src/MenuGroup/MenuGroup.types.ts +++ b/packages/menu/src/MenuGroup/MenuGroup.types.ts @@ -1,15 +1,5 @@ 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 `` | `` | `` | `` diff --git a/packages/menu/src/MenuItem/InternalMenuItemContent.tsx b/packages/menu/src/MenuItem/InternalMenuItemContent.tsx index 887f702560..2f236e69c7 100644 --- a/packages/menu/src/MenuItem/InternalMenuItemContent.tsx +++ b/packages/menu/src/MenuItem/InternalMenuItemContent.tsx @@ -7,18 +7,15 @@ import { PolymorphicAs, useInferredPolymorphic, } from '@leafygreen-ui/polymorphic'; -import { color } from '@leafygreen-ui/tokens'; +import { color, spacing } from '@leafygreen-ui/tokens'; -import { - useMenuContext, - useMenuGroupContext, - useSubMenuContext, -} from '../MenuContext'; +import { useMenuContext } from '../MenuContext'; +import { useSubMenuContext } from '../SubMenu'; import { getDarkInLightModeMenuItemStyles, getMenuItemStyles, - getNestedMenuItemStyles, + getSubMenuItemStyles, } from './MenuItem.styles'; import { MenuItemProps, Variant } from './MenuItem.types'; @@ -56,16 +53,9 @@ export const InternalMenuItemContent = React.forwardRef< const { as } = useInferredPolymorphic(asProp, rest, 'button'); const { theme, darkMode, highlight, renderDarkMenu } = useMenuContext(); - const { depth: submenuDepth, hasIcon: submenuHasIcon } = - useSubMenuContext(); - const { depth: groupDepth, hasIcon: groupHasIcon } = useMenuGroupContext(); - const isNested = !!(submenuDepth || groupDepth); - - // @ts-expect-error - // highlighted isn't a prop on this component, but could be passed in from MenuItem. - // Generally this will not be provided, but is permitted here to support isolated visual testing in Storybook - const forceHighlight = rest.highlighted; - const highlighted = id === highlight?.id || forceHighlight; + const { depth, hasIcon: parentHasIcon } = useSubMenuContext(); + const isSubMenuItem = depth > 0; + const highlighted = id === highlight?.id; const defaultAnchorProps = as === 'a' @@ -88,7 +78,7 @@ export const InternalMenuItemContent = React.forwardRef< darkMode={darkMode} showWedge highlighted={highlighted} - data-depth={submenuDepth} + data-depth={depth} className={cx( getMenuItemStyles({ active, @@ -99,13 +89,7 @@ export const InternalMenuItemContent = React.forwardRef< }), { - [getNestedMenuItemStyles({ - theme, - submenuDepth, - submenuHasIcon, - groupDepth, - groupHasIcon, - })]: isNested, + [getSubMenuItemStyles({ theme, parentHasIcon })]: isSubMenuItem, // TODO: Remove dark-in-light mode styles // after https://jira.mongodb.org/browse/LG-3974 @@ -120,7 +104,7 @@ export const InternalMenuItemContent = React.forwardRef< &:after { background-color: ${color.dark.border.secondary.default}; } - `]: theme === 'light' && renderDarkMenu && submenuDepth > 0, + `]: theme === 'light' && renderDarkMenu && depth > 0, }, className, )} @@ -132,6 +116,13 @@ export const InternalMenuItemContent = React.forwardRef< description={description} rightGlyph={rightGlyph} preserveIconSpace={false} + className={cx({ + [css` + position: relative; + padding-left: ${parentHasIcon ? spacing[900] : spacing[600]}px; + border-top: 1px solid transparent; + `]: depth > 0, + })} >
=> (Instance, ctx) => { + const { + args: { darkMode: darkModeProp, renderDarkMenu, highlighted, ...props }, + } = ctx ?? { + args: { + darkMode: false, + renderDarkMenu: false, + highlighted: false, + }, + }; + + const ref = useRef(null); + const [testDescendant, setTestDescendant] = useState(); + useEffect(() => { + setTestDescendant({ + ref, + element: ref.current, + id: ref?.current?.getAttribute('data-id'), + index: Number(ref?.current?.getAttribute('data-index')), + } as Descendant); + }, []); + const darkMode = (renderDarkMenu || darkModeProp) ?? false; + const theme = darkMode ? Theme.Dark : Theme.Light; + + return ( + +
    + +
+
+ ); + }; + export default { title: 'Components/Menu/MenuItem', component: MenuItem, @@ -32,7 +80,7 @@ export default { combineArgs: { darkMode: [false, true], }, - decorator: withMenuContext(), + decorator: _withMenuContext(), }, }, } satisfies StoryMetaType>; @@ -63,7 +111,7 @@ export const LiveExample = { {children} ), - decorators: [withMenuContext()], + decorators: [_withMenuContext()], parameters: { chromatic: { disableSnapshot: true, diff --git a/packages/menu/src/MenuItem/MenuItem.styles.ts b/packages/menu/src/MenuItem/MenuItem.styles.ts index 2ff3296837..809c5551a0 100644 --- a/packages/menu/src/MenuItem/MenuItem.styles.ts +++ b/packages/menu/src/MenuItem/MenuItem.styles.ts @@ -1,7 +1,6 @@ import { css, cx } from '@leafygreen-ui/emotion'; import { descriptionClassName, - inputOptionContentClassName, leftGlyphClassName, titleClassName, } from '@leafygreen-ui/input-option'; @@ -132,6 +131,24 @@ export const getMenuItemStyles = ({ }, ); +export const getSubMenuItemStyles = ({ + theme, + parentHasIcon, +}: { + theme: Theme; + parentHasIcon: boolean; +}) => css` + &:after { + content: ''; + position: absolute; + top: 0; + right: 0; + left: ${parentHasIcon ? spacing[900] : spacing[600]}px; + height: 1px; + background-color: ${menuColor[theme].border.default}; + } +`; + export const getMenuItemContentStyles = ({ hasGlyph, }: { @@ -143,52 +160,6 @@ export const getMenuItemContentStyles = ({ `} `; -interface NestedItemStyleArgs { - theme: Theme; - submenuDepth: number; - groupDepth: number; - submenuHasIcon: boolean; - groupHasIcon: boolean; -} - -/** Styling for nested items */ -export const getNestedMenuItemStyles = ({ - theme, - submenuDepth, - submenuHasIcon, - groupDepth, - groupHasIcon, -}: NestedItemStyleArgs) => { - const submenuInset = - submenuDepth * (submenuHasIcon ? spacing[1000] : spacing[300]); - const groupInset = groupDepth * (groupHasIcon ? spacing[600] : spacing[300]); - const totalInset = submenuInset + groupInset; - - return cx( - { - // The inset border for submenu items - [css` - &:after { - content: ''; - position: absolute; - top: 0; - right: 0; - left: ${totalInset}px; - height: 1px; - background-color: ${menuColor[theme].border.default}; - } - `]: submenuDepth > 0, - }, - css` - .${inputOptionContentClassName} { - position: relative; - padding-left: ${totalInset}px; - border-top: 1px solid transparent; - } - `, - ); -}; - // TODO: Remove dark-in-light mode styles // after https://jira.mongodb.org/browse/LG-3974 export const getDarkInLightModeMenuItemStyles = ({ diff --git a/packages/menu/src/SubMenu/SubMenu.tsx b/packages/menu/src/SubMenu/SubMenu.tsx index 6df913f4ac..5e6895eb3e 100644 --- a/packages/menu/src/SubMenu/SubMenu.tsx +++ b/packages/menu/src/SubMenu/SubMenu.tsx @@ -20,12 +20,7 @@ import { } from '@leafygreen-ui/polymorphic'; import { LGIDs } from '../constants'; -import { - MenuDescendantsContext, - SubMenuProvider, - useMenuContext, - useSubMenuContext, -} from '../MenuContext'; +import { MenuDescendantsContext, useMenuContext } from '../MenuContext'; import { InternalMenuItemContent } from '../MenuItem/InternalMenuItemContent'; import { @@ -36,6 +31,7 @@ import { submenuToggleStyles, } from './SubMenu.styles'; import { InternalSubMenuProps } from './SubMenu.types'; +import { SubMenuProvider, useSubMenuContext } from './SubMenuContext'; import { useChildrenHeight } from './useChildrenHeight'; import { useControlledState } from './useControlledState'; diff --git a/packages/menu/src/SubMenu/index.ts b/packages/menu/src/SubMenu/index.ts index 35c15083e7..3e99bbf493 100644 --- a/packages/menu/src/SubMenu/index.ts +++ b/packages/menu/src/SubMenu/index.ts @@ -1,2 +1,3 @@ export { SubMenu } from './SubMenu'; export { InternalSubMenuProps, SubMenuProps } from './SubMenu.types'; +export { useSubMenuContext } from './SubMenuContext'; diff --git a/packages/menu/src/testUtils/withMenuContextDecorator.testutils.tsx b/packages/menu/src/testUtils/withMenuContextDecorator.testutils.tsx deleted file mode 100644 index 710befdd6e..0000000000 --- a/packages/menu/src/testUtils/withMenuContextDecorator.testutils.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable react/jsx-key, react/display-name, react-hooks/rules-of-hooks */ -import React from 'react'; -import { InstanceDecorator } from '@lg-tools/storybook-utils'; - -import { css } from '@leafygreen-ui/emotion'; -import { Theme } from '@leafygreen-ui/lib'; - -import { Menu } from '../Menu'; -import { MenuContext } from '../MenuContext'; -import { MenuItem } from '../MenuItem'; - -/** - * Implements a MenuContext wrapper around each `MenuItem`, `SubMenu` or `MenuGroup` - */ -export const withMenuContext = - (): InstanceDecorator => (Instance, ctx) => { - const { - args: { darkMode: darkModeProp, renderDarkMenu }, - } = ctx ?? { - args: { - darkMode: false, - renderDarkMenu: false, - }, - }; - - const darkMode = (renderDarkMenu || darkModeProp) ?? false; - const theme = darkMode ? Theme.Dark : Theme.Light; - - return ( -
- - - -
- ); - }; diff --git a/packages/menu/tsconfig.json b/packages/menu/tsconfig.json index ef8bcaf4c9..8441ac11ab 100644 --- a/packages/menu/tsconfig.json +++ b/packages/menu/tsconfig.json @@ -48,9 +48,6 @@ { "path": "../tokens" }, - { - "path": "../typography" - }, { "path": "../leafygreen-provider" }