Skip to content

Commit

Permalink
Update Menu item styling [LG-3188] (#2369)
Browse files Browse the repository at this point in the history
* 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 Menu.spec.tsx

* 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

---------

Co-authored-by: Shaneeza <[email protected]>
  • Loading branch information
TheSonOfThomp and shaneeza committed Jun 25, 2024
1 parent a83d96e commit f799a44
Show file tree
Hide file tree
Showing 15 changed files with 397 additions and 213 deletions.
7 changes: 7 additions & 0 deletions .changeset/clean-apricots-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@leafygreen-ui/menu': minor
---

- Internal changes: Implements `InputOption` within `MenuItem`.
- Aligns spacing & colors with other dropdown menus.
- Creates additional `MenuItem` generated stories
5 changes: 5 additions & 0 deletions .changeset/tender-avocados-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/testing-lib': patch
---

`waitForTransition` accepts `null` arg
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,4 @@ scripts/tmp.*
TODO.md

# PR train config (https://github.com/realyze/pr-train)
.pr-train.yml

.pr-train.yml
8 changes: 2 additions & 6 deletions packages/descendants/src/Descendants.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,8 @@ export const WithPopover = {
render: ({ open }) => {
return (
<Parent open={open}>
<TestDescendant data-testid="child" tabIndex={0}>
Apple
</TestDescendant>
<TestDescendant data-testid="child" tabIndex={0}>
Banana
</TestDescendant>
<TestDescendant tabIndex={0}>Apple</TestDescendant>
<TestDescendant tabIndex={0}>Banana</TestDescendant>
</Parent>
);
},
Expand Down
1 change: 1 addition & 0 deletions packages/menu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
},
"devDependencies": {
"@leafygreen-ui/button": "^21.2.0",
"@leafygreen-ui/testing-lib": "^0.5.0",
"@lg-tools/storybook-utils": "^0.1.1",
"@storybook/react": "^7.6.17"
}
Expand Down
38 changes: 20 additions & 18 deletions packages/menu/src/Menu.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { waitForTransition } from '@leafygreen-ui/testing-lib';

import { MenuProps } from './Menu';
import { Menu, MenuItem, MenuSeparator } from '.';

Expand Down Expand Up @@ -84,9 +86,9 @@ describe('packages/menu', () => {
expect(menuItem).toBeInTheDocument();
});

describe('controlled `open`', () => {
describe('when the `open` prop is `true`', () => {
const setOpen = jest.fn();
test('menu renders when `open` prop is set', () => {
test('menu renders', () => {
const { getByTestId } = renderMenu({ open: true, setOpen });
const menu = getByTestId(menuTestId);
expect(menu).toBeInTheDocument();
Expand All @@ -102,34 +104,34 @@ describe('packages/menu', () => {
const { findMenuElements } = renderMenu({ open: true, setOpen });
const { menuEl, menuItemElements } = await findMenuElements();

// JSDOM does not automatically fire these events
fireEvent.transitionEnd(menuEl as Element);
await waitForTransition(menuEl);

await waitFor(() => {
const firstItem = menuItemElements[0];
expect(firstItem).toHaveFocus();
});
});
});

test('uncontrolled if `open` prop is not set, with `setOpen` callback', async () => {
const { getByTestId, getByText } = renderMenu({
open: undefined,
setOpen,
trigger: defaultTrigger,
});
test('`open` prop is not set, but `setOpen` callback is provided', async () => {
const setOpen = jest.fn();
const { getByTestId, getByText } = renderMenu({
open: undefined,
setOpen,
trigger: defaultTrigger,
});

const button = getByTestId('menu-trigger');
userEvent.click(button);
const button = getByTestId('menu-trigger');
userEvent.click(button);

const menuItem = getByText('Item B');
const menuItem = getByText('Item B');

expect(menuItem).toBeInTheDocument();
expect(menuItem).toBeInTheDocument();

userEvent.click(button);
userEvent.click(button);

await waitForElementToBeRemoved(menuItem);
expect(menuItem).not.toBeInTheDocument();
});
await waitForElementToBeRemoved(menuItem);
expect(menuItem).not.toBeInTheDocument();
});
});

Expand Down
43 changes: 30 additions & 13 deletions packages/menu/src/Menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,42 +60,39 @@ export default {
'refEl',
'setOpen',
'as',
'portalRef',
'usePortal',
],
},
chromatic: {
disableSnapshot: true,
},
},
args: {
open: true,
align: 'bottom',
usePortal: true,
darkMode: false,
renderDarkMenu: true,
},
argTypes: {
open: {
control: 'boolean',
},
usePortal: {
control: 'boolean',
},
size: {
options: Object.values(Size),
control: 'select',
description:
'Size of the `MenuItem` component, can be `default` or `large`',
},
darkMode: storybookArgTypes.darkMode,
renderDarkMenu: {
control: 'boolean',
},
},
} satisfies StoryMetaType<typeof Menu>;

export const LiveExample = {
render: ({
open: _,
size,
darkMode,
...args
}: MenuProps & { size: MenuItemProps['size'] }) => {
render: ({ open, size, darkMode, ...args }) => {
return (
<Menu
darkMode={darkMode}
Expand All @@ -104,6 +101,7 @@ export const LiveExample = {
Menu
</Button>
}
open={open}
{...args}
>
<MenuItem size={size} glyph={<CloudIcon />}>
Expand Down Expand Up @@ -164,14 +162,15 @@ export const LiveExample = {
disableSnapshot: true,
},
},
} satisfies StoryObj<typeof Menu & { size: MenuItemProps['size'] }>;
} satisfies StoryObj<typeof Menu & { size?: MenuItemProps['size'] }>;

export const InitialOpen = {
render: () => {
render: args => {
return (
<Menu
initialOpen
trigger={<Button rightGlyph={<CaretDown />}>Menu</Button>}
{...args}
>
<MenuItem>Lorem</MenuItem>
<MenuItem>Ipsum</MenuItem>
Expand All @@ -187,14 +186,15 @@ export const InitialOpen = {
} satisfies StoryObj<typeof Menu>;

export const Controlled = {
render: () => {
render: args => {
const [open, setOpen] = useState(true);

return (
<Menu
open={open}
setOpen={setOpen}
trigger={<Button rightGlyph={<CaretDown />}>Menu</Button>}
{...args}
>
<MenuItem>Lorem</MenuItem>
<MenuItem>Ipsum</MenuItem>
Expand All @@ -203,6 +203,23 @@ export const Controlled = {
);
},
parameters: {
controls: {
exclude: [
...storybookExcludedControlParams,
'trigger',
'children',
'refEl',
'setOpen',
'as',
'portalRef',
'usePortal',
'align',
'darkMode',
'justify',
'renderDarkMenu',
'size',
],
},
chromatic: {
disableSnapshot: true,
},
Expand Down
10 changes: 8 additions & 2 deletions packages/menu/src/Menu/HighlightReducer/HighlightReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ const getInitialIndex = (descendants: DescendantsList<HTMLElement>) =>
*/
export const useHighlightReducer = (
descendants: DescendantsList<HTMLElement>,
onChange?: (newIndex: Index) => void,
onChange?: (
newIndex: Index,
updatedDescendants: DescendantsList<HTMLElement>,
) => void,
): [Index, Dispatch<Direction>] => {
// Initializes a new reducer function
const highlightReducerFunction: Reducer<Index, Direction> = (
_index,
direction,
) => getUpdatedIndex(direction, _index, descendants);

// Create the reducer
const [index, dispatch] = useReducer<Reducer<Index, Direction>>(
highlightReducerFunction,
getInitialIndex(descendants),
Expand All @@ -32,7 +37,8 @@ export const useHighlightReducer = (
*/
const updateIndex = (direction: Direction) => {
const updatedIndex = highlightReducerFunction(index, direction);
onChange?.(updatedIndex);

onChange?.(updatedIndex, descendants);
dispatch(direction);
};

Expand Down
11 changes: 7 additions & 4 deletions packages/menu/src/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(function Menu(
open: controlledOpen,
setOpen: controlledSetOpen,
darkMode: darkModeProp,
renderDarkMenu = true,
children,
className,
refEl,
Expand All @@ -69,15 +70,14 @@ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(function Menu(
}: MenuProps,
forwardRef,
) {
const { theme, darkMode } = useDarkMode(darkModeProp);
const renderDarkMode = renderDarkMenu || darkModeProp;
const { theme, darkMode } = useDarkMode(renderDarkMode);

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

const [uncontrolledOpen, uncontrolledSetOpen] = useState(initialOpen);

const { descendants, dispatch } = useInitDescendants();

const setOpen =
(typeof controlledOpen === 'boolean' && controlledSetOpen) ||
uncontrolledSetOpen;
Expand All @@ -96,13 +96,16 @@ export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(function Menu(

useBackdropClick(handleClose, [popoverRef, triggerRef], open);

const { descendants, dispatch, getDescendants } = useInitDescendants();

// Tracks the currently highlighted (focused) item index
// Fires `.focus()` when the index is updated
const [highlightIndex, updateHighlightIndex] = useHighlightReducer(
descendants,
index => {
if (isDefined(index)) {
descendants[index]?.element?.focus();
const descendantElement = getDescendants()[index]?.ref.current;
descendantElement?.focus();
}
},
);
Expand Down
7 changes: 7 additions & 0 deletions packages/menu/src/Menu/Menu.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,11 @@ export interface MenuProps extends Omit<PopoverProps, 'active'> {
* id passed to the menu dropdown.
*/
id?: string;

/**
* Whether the menu should always render dark, regardless of the theme context
*
* @default {true}
*/
renderDarkMenu?: boolean;
}
Loading

0 comments on commit f799a44

Please sign in to comment.