Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Menu Props [LG-3259] #2399

Closed
wants to merge 11 commits into from
6 changes: 6 additions & 0 deletions .changeset/two-deers-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@leafygreen-ui/menu': major
---

- Updates Menu props to extend `div` props
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we leave this as-is for now, and revisit when we revisit how we handle Popover props holistically throughout the system. We can discuss at dev sync today if preferred

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we omit this from the queue does it break everything that comes after?

- Moves `...rest` prop spread onto the `Popover` div (previously props were spread on the inner `ul` element)
4 changes: 2 additions & 2 deletions packages/menu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
}
Expand Down
36 changes: 26 additions & 10 deletions packages/menu/src/Menu.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import {
act,
fireEvent,
render,
waitFor,
waitForElementToBeRemoved,
Expand Down Expand Up @@ -74,7 +73,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;
}

Expand Down Expand Up @@ -159,9 +161,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();
});
});
Expand Down Expand Up @@ -220,7 +231,7 @@ describe('packages/menu', () => {
if (key === 'tab') {
userEvent.tab();
} else {
userEvent.type(menu, `{${key}}`);
userEvent.keyboard(`{${key}}`);
}
};

Expand Down Expand Up @@ -249,11 +260,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();
});
});
});

Expand Down Expand Up @@ -348,7 +364,7 @@ describe('packages/menu', () => {

expect(firstItem).toHaveFocus();

userEvent.keyboard('[Enter]');
userEvent.type(menuEl!, '[Enter]');
shaneeza marked this conversation as resolved.
Show resolved Hide resolved

await act(async () => await waitForTimeout());
expect(menuEl).toBeInTheDocument();
Expand All @@ -364,7 +380,7 @@ describe('packages/menu', () => {

expect(firstItem).toHaveFocus();

userEvent.keyboard('[Space]');
userEvent.type(menuEl!, '[Space]');

await act(async () => await waitForTimeout());
expect(menuEl).toBeInTheDocument();
Expand Down
4 changes: 3 additions & 1 deletion packages/menu/src/Menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export default {
'setOpen',
'as',
'portalRef',
'usePortal',
],
},
chromatic: {
Expand All @@ -82,6 +81,9 @@ export default {
renderDarkMenu: {
control: 'boolean',
},
usePortal: {
control: 'boolean',
},
},
} satisfies StoryMetaType<typeof Menu>;

Expand Down
44 changes: 26 additions & 18 deletions packages/menu/src/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React, { useCallback, useRef, useState } from 'react';
import React, {
KeyboardEventHandler,
MouseEventHandler,
useCallback,
useRef,
useState,
} from 'react';
import PropTypes from 'prop-types';

import {
Expand All @@ -7,7 +13,7 @@ import {
useInitDescendants,
} from '@leafygreen-ui/descendants';
import { css, cx } from '@leafygreen-ui/emotion';
import { useBackdropClick, useEventListener } 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';
Expand Down Expand Up @@ -59,6 +65,8 @@ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(function Menu(
setOpen: controlledSetOpen,
darkMode: darkModeProp,
renderDarkMenu = true,
onClick,
onKeyDown,
children,
className,
refEl,
Expand All @@ -70,11 +78,11 @@ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(function Menu(
popoverZIndex,
...rest
}: MenuProps,
forwardRef,
fwdRef,
) {
const { theme, darkMode } = useDarkMode(darkModeProp);

const popoverRef = useRef<HTMLUListElement | null>(null);
const popoverRef = useForwardedRef(fwdRef, null);
const triggerRef = useRef<HTMLElement>(null);

const [uncontrolledOpen, uncontrolledSetOpen] = useState(initialOpen);
Expand Down Expand Up @@ -112,6 +120,12 @@ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(function Menu(
},
);

const handleMenuClick: MouseEventHandler<HTMLDivElement> = 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`
Expand All @@ -120,7 +134,7 @@ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(function Menu(
};

// Fired on global keyDown event
function handleKeyDown(e: KeyboardEvent) {
const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = e => {
switch (e.key) {
case keyMap.ArrowDown:
e.preventDefault(); // Prevents page scrolling
Expand Down Expand Up @@ -150,9 +164,8 @@ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(function Menu(
}
break;
}
}

useEventListener('keydown', handleKeyDown, { enabled: open });
onKeyDown?.(e);
};

const popoverProps = {
popoverZIndex,
Expand Down Expand Up @@ -185,14 +198,18 @@ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(function Menu(
>
<Popover
key="popover"
ref={popoverRef}
active={open}
align={align}
justify={justify}
refEl={refEl}
adjustOnMutation={adjustOnMutation}
onClick={handleMenuClick}
onKeyDown={handleKeyDown}
onEntered={handlePopoverOpen}
data-testid={LGIDs.root}
data-lgid={LGIDs.root}
{...rest}
{...popoverProps}
TheSonOfThomp marked this conversation as resolved.
Show resolved Hide resolved
>
<div
Expand All @@ -210,17 +227,8 @@ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(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*/}
<ul
{...rest}
className={scrollContainerStyle}
role="menu"
onClick={e => e.stopPropagation()}
ref={popoverRef}
>
<ul className={scrollContainerStyle} role="menu">
{children}
</ul>
</div>
Expand Down
6 changes: 4 additions & 2 deletions packages/menu/src/Menu/Menu.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReactElement, ReactNode } from 'react';
import { ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react';

import {
InferredPolymorphicPropsWithRef,
Expand All @@ -12,7 +12,9 @@ export type SubMenuType = ReactElement<
InferredPolymorphicPropsWithRef<PolymorphicAs, SubMenuProps>
>;

export interface MenuProps extends Omit<PopoverProps, 'active'> {
export interface MenuProps
extends Omit<PopoverProps, 'active'>,
Omit<ComponentPropsWithoutRef<'div'>, 'onClick'> {
/**
shaneeza marked this conversation as resolved.
Show resolved Hide resolved
* The menu items, or submenus
* @type `<MenuItem />` | `<SubMenu />` | `<MenuGroup />` | `<MenuSeparator />`
Expand Down
1 change: 1 addition & 0 deletions packages/popover/src/Popover.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/split-button/src/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export const Menu = ({
}: MenuProps) => {
const { theme } = useDarkMode();
const [uncontrolledOpen, uncontrolledSetOpen] = useState<boolean>(false);
const buttonRef = useRef<null | HTMLButtonElement>(null);
const menuRef = useRef<null | HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<HTMLDivElement>(null);

// TODO: make hook
const setOpen =
Expand Down
Loading
Loading