From 0a43c652369a53d2237fa78a1d4c0fee133da329 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 21 Oct 2024 18:05:23 +0200 Subject: [PATCH 1/7] Internal DropdownMenu(v2)? => Menu rename --- packages/components/src/menu/README.md | 44 +- .../components/src/menu/checkbox-item.tsx | 28 +- packages/components/src/menu/context.tsx | 8 +- packages/components/src/menu/group-label.tsx | 16 +- packages/components/src/menu/group.tsx | 16 +- packages/components/src/menu/index.tsx | 131 ++--- .../components/src/menu/item-help-text.tsx | 10 +- packages/components/src/menu/item-label.tsx | 10 +- packages/components/src/menu/item.tsx | 26 +- packages/components/src/menu/radio-item.tsx | 28 +- packages/components/src/menu/separator.tsx | 18 +- .../src/menu/stories/index.story.tsx | 539 +++++++----------- packages/components/src/menu/styles.ts | 34 +- packages/components/src/menu/test/index.tsx | 330 ++++------- packages/components/src/menu/types.ts | 22 +- packages/components/src/private-apis.ts | 4 +- 16 files changed, 538 insertions(+), 726 deletions(-) diff --git a/packages/components/src/menu/README.md b/packages/components/src/menu/README.md index 771b5b74b24bf7..b9fb782cd24436 100644 --- a/packages/components/src/menu/README.md +++ b/packages/components/src/menu/README.md @@ -1,51 +1,51 @@ -# `DropdownMenuV2` +# `Menu`
This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
-`DropdownMenuV2` displays a menu to the user (such as a set of actions or functions) triggered by a button. +`Menu` displays a menu to the user (such as a set of actions or functions) triggered by a button. ## Design guidelines ### Usage -#### When to use a DropdownMenu +#### When to use a `Menu` -Use a DropdownMenu when you want users to: +Use a `Menu` when you want users to: - Choose an action or change a setting from a list, AND - Only see the available choices contextually. -`DropdownMenu` is a React component to render an expandable menu of buttons. It is similar in purpose to a `` element, with the distinction that it does not maintain a value. Instead, each option behaves as an action button. -If you need to display all the available options at all times, consider using a Toolbar instead. Use a `DropdownMenu` to display a list of actions after the user interacts with a button. +If you need to display all the available options at all times, consider using a Toolbar instead. Use a `Menu` to display a list of actions after the user interacts with a button. **Do** -Use a `DropdownMenu` to display a list of actions after the user interacts with an icon. +Use a `Menu` to display a list of actions after the user interacts with an icon. -**Don’t** use a `DropdownMenu` for important actions that should always be visible. Use a `Toolbar` instead. +**Don’t** use a `Menu` for important actions that should always be visible. Use a `Toolbar` instead. **Don’t** -Don’t use a `DropdownMenu` for frequently used actions. Use a `Toolbar` instead. +Don’t use a `Menu` for frequently used actions. Use a `Toolbar` instead. #### Behavior -Generally, the parent button should indicate that interacting with it will show a `DropdownMenu`. +Generally, the parent button should indicate that interacting with it will show a `Menu`. -The parent button should retain the same visual styling regardless of whether the `DropdownMenu` is displayed or not. +The parent button should retain the same visual styling regardless of whether the `Menu` is displayed or not. #### Placement -The `DropdownMenu` should typically appear directly below, or below and to the left of, the parent button. If there isn’t enough space below to display the full `DropdownMenu`, it can be displayed instead above the parent button. +The `Menu` should typically appear directly below, or below and to the left of, the parent button. If there isn’t enough space below to display the full `Menu`, it can be displayed instead above the parent button. ## Development guidelines This component is still highly experimental, and it's not normally accessible to consumers of the `@wordpress/components` package. -The component exposes a set of components that are meant to be used in combination with each other in order to implement a `DropdownMenu` correctly. +The component exposes a set of components that are meant to be used in combination with each other in order to implement a `Menu` correctly. -### `DropdownMenuV2` +### `Menu` The root component, used to specify the menu's trigger and its contents. @@ -112,7 +112,7 @@ The skidding of the popover along the anchor element. Can be set to negative val - Required: no - Default: `0` for root-level menus, `-8` for nested menus -### `DropdownMenuV2.Item` +### `Menu.Item` Used to render a menu item. @@ -152,7 +152,7 @@ Determines if the element is disabled. - Required: no - Default: `false` -### `DropdownMenuV2.CheckboxItem` +### `Menu.CheckboxItem` Used to render a checkbox item. @@ -218,7 +218,7 @@ Event handler called when the checked state of the checkbox menu item changes. - Required: no -### `DropdownMenuV2.RadioItem` +### `Menu.RadioItem` Used to render a radio item. @@ -283,7 +283,7 @@ Event handler called when the checked radio menu item changes. - Required: no -### `DropdownMenuV2.ItemLabel` +### `Menu.ItemLabel` Used to render the menu item's label. @@ -297,7 +297,7 @@ The label contents. - Required: yes -### `DropdownMenuV2.ItemHelpText` +### `Menu.ItemHelpText` Used to render the menu item's help text. @@ -311,7 +311,7 @@ The help text contents. - Required: yes -### `DropdownMenuV2.Group` +### `Menu.Group` Used to group menu items. @@ -325,7 +325,7 @@ The contents of the group. - Required: yes -### `DropdownMenuV2.GroupLabel` +### `Menu.GroupLabel` Used to render a group label. The label text should be kept as short as possible. @@ -339,6 +339,6 @@ The contents of the group label. - Required: yes -### `DropdownMenuV2.Separator` +### `Menu.Separator` Used to render a visual separator. diff --git a/packages/components/src/menu/checkbox-item.tsx b/packages/components/src/menu/checkbox-item.tsx index bcbc920cbb7205..b9a9b8105e517e 100644 --- a/packages/components/src/menu/checkbox-item.tsx +++ b/packages/components/src/menu/checkbox-item.tsx @@ -13,33 +13,33 @@ import { Icon, check } from '@wordpress/icons'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { DropdownMenuContext } from './context'; -import type { DropdownMenuCheckboxItemProps } from './types'; +import { MenuContext } from './context'; +import type { MenuCheckboxItemProps } from './types'; import * as Styled from './styles'; import { useTemporaryFocusVisibleFix } from './use-temporary-focus-visible-fix'; -export const DropdownMenuCheckboxItem = forwardRef< +export const MenuCheckboxItem = forwardRef< HTMLDivElement, - WordPressComponentProps< DropdownMenuCheckboxItemProps, 'div', false > ->( function DropdownMenuCheckboxItem( + WordPressComponentProps< MenuCheckboxItemProps, 'div', false > +>( function MenuCheckboxItem( { suffix, children, onBlur, hideOnClick = false, ...props }, ref ) { // TODO: Remove when https://github.com/ariakit/ariakit/issues/4083 is fixed const focusVisibleFixProps = useTemporaryFocusVisibleFix( { onBlur } ); - const dropdownMenuContext = useContext( DropdownMenuContext ); + const menuContext = useContext( MenuContext ); return ( - } // Override some ariakit inline styles style={ { width: 'auto', height: 'auto' } } @@ -47,17 +47,17 @@ export const DropdownMenuCheckboxItem = forwardRef< - - + + { children } - + { suffix && ( { suffix } ) } - - + + ); } ); diff --git a/packages/components/src/menu/context.tsx b/packages/components/src/menu/context.tsx index b285843327267f..1205015c57cbee 100644 --- a/packages/components/src/menu/context.tsx +++ b/packages/components/src/menu/context.tsx @@ -6,8 +6,8 @@ import { createContext } from '@wordpress/element'; /** * Internal dependencies */ -import type { DropdownMenuContext as DropdownMenuContextType } from './types'; +import type { MenuContext as MenuContextType } from './types'; -export const DropdownMenuContext = createContext< - DropdownMenuContextType | undefined ->( undefined ); +export const MenuContext = createContext< MenuContextType | undefined >( + undefined +); diff --git a/packages/components/src/menu/group-label.tsx b/packages/components/src/menu/group-label.tsx index 7d838ef9fa620a..71c5c7de69941e 100644 --- a/packages/components/src/menu/group-label.tsx +++ b/packages/components/src/menu/group-label.tsx @@ -7,18 +7,18 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { DropdownMenuContext } from './context'; +import { MenuContext } from './context'; import { Text } from '../text'; -import type { DropdownMenuGroupLabelProps } from './types'; +import type { MenuGroupLabelProps } from './types'; import * as Styled from './styles'; -export const DropdownMenuGroupLabel = forwardRef< +export const MenuGroupLabel = forwardRef< HTMLDivElement, - WordPressComponentProps< DropdownMenuGroupLabelProps, 'div', false > ->( function DropdownMenuGroup( props, ref ) { - const dropdownMenuContext = useContext( DropdownMenuContext ); + WordPressComponentProps< MenuGroupLabelProps, 'div', false > +>( function MenuGroup( props, ref ) { + const menuContext = useContext( MenuContext ); return ( - } { ...props } - store={ dropdownMenuContext?.store } + store={ menuContext?.store } /> ); } ); diff --git a/packages/components/src/menu/group.tsx b/packages/components/src/menu/group.tsx index f2bf79015bc691..f9a4138fe43580 100644 --- a/packages/components/src/menu/group.tsx +++ b/packages/components/src/menu/group.tsx @@ -7,20 +7,20 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { DropdownMenuContext } from './context'; -import type { DropdownMenuGroupProps } from './types'; +import { MenuContext } from './context'; +import type { MenuGroupProps } from './types'; import * as Styled from './styles'; -export const DropdownMenuGroup = forwardRef< +export const MenuGroup = forwardRef< HTMLDivElement, - WordPressComponentProps< DropdownMenuGroupProps, 'div', false > ->( function DropdownMenuGroup( props, ref ) { - const dropdownMenuContext = useContext( DropdownMenuContext ); + WordPressComponentProps< MenuGroupProps, 'div', false > +>( function MenuGroup( props, ref ) { + const menuContext = useContext( MenuContext ); return ( - ); } ); diff --git a/packages/components/src/menu/index.tsx b/packages/components/src/menu/index.tsx index 50c4f3069d51b5..ee5d9a6f8c9df4 100644 --- a/packages/components/src/menu/index.tsx +++ b/packages/components/src/menu/index.tsx @@ -22,23 +22,20 @@ import { chevronRightSmall } from '@wordpress/icons'; */ import { useContextSystem, contextConnect } from '../context'; import type { WordPressComponentProps } from '../context'; -import type { - DropdownMenuContext as DropdownMenuContextType, - DropdownMenuProps, -} from './types'; +import type { MenuContext as MenuContextType, MenuProps } from './types'; import * as Styled from './styles'; -import { DropdownMenuContext } from './context'; -import { DropdownMenuItem } from './item'; -import { DropdownMenuCheckboxItem } from './checkbox-item'; -import { DropdownMenuRadioItem } from './radio-item'; -import { DropdownMenuGroup } from './group'; -import { DropdownMenuGroupLabel } from './group-label'; -import { DropdownMenuSeparator } from './separator'; -import { DropdownMenuItemLabel } from './item-label'; -import { DropdownMenuItemHelpText } from './item-help-text'; - -const UnconnectedDropdownMenu = ( - props: WordPressComponentProps< DropdownMenuProps, 'div', false >, +import { MenuContext } from './context'; +import { MenuItem } from './item'; +import { MenuCheckboxItem } from './checkbox-item'; +import { MenuRadioItem } from './radio-item'; +import { MenuGroup } from './group'; +import { MenuGroupLabel } from './group-label'; +import { MenuSeparator } from './separator'; +import { MenuItemLabel } from './item-label'; +import { MenuItemHelpText } from './item-help-text'; + +const UnconnectedMenu = ( + props: WordPressComponentProps< MenuProps, 'div', false >, ref: React.ForwardedRef< HTMLDivElement > ) => { const { @@ -62,11 +59,12 @@ const UnconnectedDropdownMenu = ( // Rest ...otherProps - } = useContextSystem< - typeof props & Pick< DropdownMenuContextType, 'variant' > - >( props, 'DropdownMenu' ); + } = useContextSystem< typeof props & Pick< MenuContextType, 'variant' > >( + props, + 'Menu' + ); - const parentContext = useContext( DropdownMenuContext ); + const parentContext = useContext( MenuContext ); const computedDirection = isRTL() ? 'rtl' : 'ltr'; @@ -91,7 +89,7 @@ const UnconnectedDropdownMenu = ( } } - const dropdownMenuStore = Ariakit.useMenuStore( { + const menuStore = Ariakit.useMenuStore( { parent: parentContext?.store, open, defaultOpen, @@ -104,25 +102,25 @@ const UnconnectedDropdownMenu = ( } ); const contextValue = useMemo( - () => ( { store: dropdownMenuStore, variant } ), - [ dropdownMenuStore, variant ] + () => ( { store: menuStore, variant } ), + [ menuStore, variant ] ); // Extract the side from the applied placement — useful for animations. // Using `currentPlacement` instead of `placement` to make sure that we // use the final computed placement (including "flips" etc). const appliedPlacementSide = useStoreState( - dropdownMenuStore, + menuStore, 'currentPlacement' ).split( '-' )[ 0 ]; if ( - dropdownMenuStore.parent && - ! ( isValidElement( trigger ) && DropdownMenuItem === trigger.type ) + menuStore.parent && + ! ( isValidElement( trigger ) && MenuItem === trigger.type ) ) { // eslint-disable-next-line no-console console.warn( - 'For nested DropdownMenus, the `trigger` should always be a `DropdownMenuItem`.' + 'For nested Menus, the `trigger` should always be a `MenuItem`.' ); } @@ -153,9 +151,9 @@ const UnconnectedDropdownMenu = ( { /* Menu trigger */ } ) } > - + { children } - + ); }; -export const DropdownMenuV2 = Object.assign( - contextConnect( UnconnectedDropdownMenu, 'DropdownMenu' ), - { - Context: Object.assign( DropdownMenuContext, { - displayName: 'DropdownMenuV2.Context', - } ), - Item: Object.assign( DropdownMenuItem, { - displayName: 'DropdownMenuV2.Item', - } ), - RadioItem: Object.assign( DropdownMenuRadioItem, { - displayName: 'DropdownMenuV2.RadioItem', - } ), - CheckboxItem: Object.assign( DropdownMenuCheckboxItem, { - displayName: 'DropdownMenuV2.CheckboxItem', - } ), - Group: Object.assign( DropdownMenuGroup, { - displayName: 'DropdownMenuV2.Group', - } ), - GroupLabel: Object.assign( DropdownMenuGroupLabel, { - displayName: 'DropdownMenuV2.GroupLabel', - } ), - Separator: Object.assign( DropdownMenuSeparator, { - displayName: 'DropdownMenuV2.Separator', - } ), - ItemLabel: Object.assign( DropdownMenuItemLabel, { - displayName: 'DropdownMenuV2.ItemLabel', - } ), - ItemHelpText: Object.assign( DropdownMenuItemHelpText, { - displayName: 'DropdownMenuV2.ItemHelpText', - } ), - } -); - -export default DropdownMenuV2; +export const Menu = Object.assign( contextConnect( UnconnectedMenu, 'Menu' ), { + Context: Object.assign( MenuContext, { + displayName: 'Menu.Context', + } ), + Item: Object.assign( MenuItem, { + displayName: 'Menu.Item', + } ), + RadioItem: Object.assign( MenuRadioItem, { + displayName: 'Menu.RadioItem', + } ), + CheckboxItem: Object.assign( MenuCheckboxItem, { + displayName: 'Menu.CheckboxItem', + } ), + Group: Object.assign( MenuGroup, { + displayName: 'Menu.Group', + } ), + GroupLabel: Object.assign( MenuGroupLabel, { + displayName: 'Menu.GroupLabel', + } ), + Separator: Object.assign( MenuSeparator, { + displayName: 'Menu.Separator', + } ), + ItemLabel: Object.assign( MenuItemLabel, { + displayName: 'Menu.ItemLabel', + } ), + ItemHelpText: Object.assign( MenuItemHelpText, { + displayName: 'Menu.ItemHelpText', + } ), +} ); + +export default Menu; diff --git a/packages/components/src/menu/item-help-text.tsx b/packages/components/src/menu/item-help-text.tsx index 0408d20dfbd40d..0ccc8f7461a8ff 100644 --- a/packages/components/src/menu/item-help-text.tsx +++ b/packages/components/src/menu/item-help-text.tsx @@ -9,15 +9,11 @@ import { forwardRef } from '@wordpress/element'; import type { WordPressComponentProps } from '../context'; import * as Styled from './styles'; -export const DropdownMenuItemHelpText = forwardRef< +export const MenuItemHelpText = forwardRef< HTMLSpanElement, WordPressComponentProps< { children: React.ReactNode }, 'span', true > ->( function DropdownMenuItemHelpText( props, ref ) { +>( function MenuItemHelpText( props, ref ) { return ( - + ); } ); diff --git a/packages/components/src/menu/item-label.tsx b/packages/components/src/menu/item-label.tsx index a1f9391af2f92d..458f69558eafbc 100644 --- a/packages/components/src/menu/item-label.tsx +++ b/packages/components/src/menu/item-label.tsx @@ -9,15 +9,11 @@ import { forwardRef } from '@wordpress/element'; import type { WordPressComponentProps } from '../context'; import * as Styled from './styles'; -export const DropdownMenuItemLabel = forwardRef< +export const MenuItemLabel = forwardRef< HTMLSpanElement, WordPressComponentProps< { children: React.ReactNode }, 'span', true > ->( function DropdownMenuItemLabel( props, ref ) { +>( function MenuItemLabel( props, ref ) { return ( - + ); } ); diff --git a/packages/components/src/menu/item.tsx b/packages/components/src/menu/item.tsx index 2680603db22aa7..f8ae670846f55f 100644 --- a/packages/components/src/menu/item.tsx +++ b/packages/components/src/menu/item.tsx @@ -7,44 +7,44 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import type { DropdownMenuItemProps } from './types'; +import type { MenuItemProps } from './types'; import * as Styled from './styles'; -import { DropdownMenuContext } from './context'; +import { MenuContext } from './context'; import { useTemporaryFocusVisibleFix } from './use-temporary-focus-visible-fix'; -export const DropdownMenuItem = forwardRef< +export const MenuItem = forwardRef< HTMLDivElement, - WordPressComponentProps< DropdownMenuItemProps, 'div', false > ->( function DropdownMenuItem( + WordPressComponentProps< MenuItemProps, 'div', false > +>( function MenuItem( { prefix, suffix, children, onBlur, hideOnClick = true, ...props }, ref ) { // TODO: Remove when https://github.com/ariakit/ariakit/issues/4083 is fixed const focusVisibleFixProps = useTemporaryFocusVisibleFix( { onBlur } ); - const dropdownMenuContext = useContext( DropdownMenuContext ); + const menuContext = useContext( MenuContext ); return ( - { prefix } - - + + { children } - + { suffix && ( { suffix } ) } - - + + ); } ); diff --git a/packages/components/src/menu/radio-item.tsx b/packages/components/src/menu/radio-item.tsx index 547d8f257cdf4b..3848d2062c0c25 100644 --- a/packages/components/src/menu/radio-item.tsx +++ b/packages/components/src/menu/radio-item.tsx @@ -13,8 +13,8 @@ import { Icon } from '@wordpress/icons'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { DropdownMenuContext } from './context'; -import type { DropdownMenuRadioItemProps } from './types'; +import { MenuContext } from './context'; +import type { MenuRadioItemProps } from './types'; import * as Styled from './styles'; import { SVG, Circle } from '@wordpress/primitives'; import { useTemporaryFocusVisibleFix } from './use-temporary-focus-visible-fix'; @@ -25,28 +25,28 @@ const radioCheck = ( ); -export const DropdownMenuRadioItem = forwardRef< +export const MenuRadioItem = forwardRef< HTMLDivElement, - WordPressComponentProps< DropdownMenuRadioItemProps, 'div', false > ->( function DropdownMenuRadioItem( + WordPressComponentProps< MenuRadioItemProps, 'div', false > +>( function MenuRadioItem( { suffix, children, onBlur, hideOnClick = false, ...props }, ref ) { // TODO: Remove when https://github.com/ariakit/ariakit/issues/4083 is fixed const focusVisibleFixProps = useTemporaryFocusVisibleFix( { onBlur } ); - const dropdownMenuContext = useContext( DropdownMenuContext ); + const menuContext = useContext( MenuContext ); return ( - } // Override some ariakit inline styles style={ { width: 'auto', height: 'auto' } } @@ -54,17 +54,17 @@ export const DropdownMenuRadioItem = forwardRef< - - + + { children } - + { suffix && ( { suffix } ) } - - + + ); } ); diff --git a/packages/components/src/menu/separator.tsx b/packages/components/src/menu/separator.tsx index bc5aff7ae26118..5d0110016d9c4a 100644 --- a/packages/components/src/menu/separator.tsx +++ b/packages/components/src/menu/separator.tsx @@ -7,21 +7,21 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { DropdownMenuContext } from './context'; -import type { DropdownMenuSeparatorProps } from './types'; +import { MenuContext } from './context'; +import type { MenuSeparatorProps } from './types'; import * as Styled from './styles'; -export const DropdownMenuSeparator = forwardRef< +export const MenuSeparator = forwardRef< HTMLHRElement, - WordPressComponentProps< DropdownMenuSeparatorProps, 'hr', false > ->( function DropdownMenuSeparator( props, ref ) { - const dropdownMenuContext = useContext( DropdownMenuContext ); + WordPressComponentProps< MenuSeparatorProps, 'hr', false > +>( function MenuSeparator( props, ref ) { + const menuContext = useContext( MenuContext ); return ( - ); } ); diff --git a/packages/components/src/menu/stories/index.story.tsx b/packages/components/src/menu/stories/index.story.tsx index 90d15ca2ea6c18..f9684cf980b1eb 100644 --- a/packages/components/src/menu/stories/index.story.tsx +++ b/packages/components/src/menu/stories/index.story.tsx @@ -14,35 +14,35 @@ import { useState, useMemo, useContext } from '@wordpress/element'; * Internal dependencies */ import { useCx } from '../../utils'; -import DropdownMenuV2 from '..'; +import { Menu } from '..'; import Icon from '../../icon'; import Button from '../../button'; import Modal from '../../modal'; import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; import { ContextSystemProvider } from '../../context'; -const meta: Meta< typeof DropdownMenuV2 > = { - title: 'Components (Experimental)/DropdownMenu V2', - component: DropdownMenuV2, +const meta: Meta< typeof Menu > = { + title: 'Components (Experimental)/Menu', + component: Menu, subcomponents: { // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - Item: DropdownMenuV2.Item, + Item: Menu.Item, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - CheckboxItem: DropdownMenuV2.CheckboxItem, + CheckboxItem: Menu.CheckboxItem, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - Group: DropdownMenuV2.Group, + Group: Menu.Group, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - GroupLabel: DropdownMenuV2.GroupLabel, + GroupLabel: Menu.GroupLabel, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - Separator: DropdownMenuV2.Separator, + Separator: Menu.Separator, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - Context: DropdownMenuV2.Context, + Context: Menu.Context, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - RadioItem: DropdownMenuV2.RadioItem, + RadioItem: Menu.RadioItem, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - ItemLabel: DropdownMenuV2.ItemLabel, + ItemLabel: Menu.ItemLabel, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - ItemHelpText: DropdownMenuV2.ItemHelpText, + ItemHelpText: Menu.ItemHelpText, }, argTypes: { children: { control: { type: null } }, @@ -60,52 +60,46 @@ const meta: Meta< typeof DropdownMenuV2 > = { }; export default meta; -export const Default: StoryFn< typeof DropdownMenuV2 > = ( props ) => ( - - - Label - - - Label - Help text - - - Label - +export const Default: StoryFn< typeof Menu > = ( props ) => ( + + + Label + + + Label + Help text + + + Label + The menu item help text is automatically truncated when there are more than two lines of text - - - - Label - + + + + Label + This item doesn't close the menu on click - - - Disabled item - - - Group label - } - > - With prefix - - With suffix - + + Disabled item + + + Group label + }> + With prefix + + With suffix + } suffix="⌥⌘T" > - - Disabled with prefix and suffix - - - And help text - - - - + Disabled with prefix and suffix + And help text + + + ); Default.args = { trigger: ( @@ -115,56 +109,46 @@ Default.args = { ), }; -export const WithSubmenu: StoryFn< typeof DropdownMenuV2 > = ( props ) => ( - - Level 1 item - = ( props ) => ( + + Level 1 item + - + + Submenu trigger item with a long label - - + + } > - - - Level 2 item - - - - - Level 2 item - - - + Level 2 item + + + Level 2 item + + - - Submenu trigger - - + + Submenu trigger + } > - - - Level 3 item - - - - - Level 3 item - - - - - + + Level 3 item + + + Level 3 item + + + + ); WithSubmenu.args = { ...Default.args, }; -export const WithCheckboxes: StoryFn< typeof DropdownMenuV2 > = ( props ) => { +export const WithCheckboxes: StoryFn< typeof Menu > = ( props ) => { const [ isAChecked, setAChecked ] = useState( false ); const [ isBChecked, setBChecked ] = useState( true ); const [ multipleCheckboxesValue, setMultipleCheckboxesValue ] = useState< @@ -172,7 +156,7 @@ export const WithCheckboxes: StoryFn< typeof DropdownMenuV2 > = ( props ) => { >( [ 'b' ] ); const onMultipleCheckboxesCheckedChange: React.ComponentProps< - typeof DropdownMenuV2.CheckboxItem + typeof Menu.CheckboxItem >[ 'onChange' ] = ( e ) => { setMultipleCheckboxesValue( ( prevValues ) => { if ( prevValues.includes( e.target.value ) ) { @@ -183,202 +167,148 @@ export const WithCheckboxes: StoryFn< typeof DropdownMenuV2 > = ( props ) => { }; return ( - - - + + + Single selection, uncontrolled - - + - - Checkbox item A - - - Initially unchecked - - - Checkbox item A + Initially unchecked + + - - Checkbox item B - - - Initially checked - - - - - - - Single selection, controlled - - Checkbox item B + Initially checked + + + + + Single selection, controlled + setAChecked( e.target.checked ) } > - - Checkbox item A - - - Initially unchecked - - - Checkbox item A + Initially unchecked + + setBChecked( e.target.checked ) } > - - Checkbox item B - - - Initially checked - - - - - - + Checkbox item B + Initially checked + + + + + Multiple selection, uncontrolled - - + - - Checkbox item A - - - Initially unchecked - - - Checkbox item A + Initially unchecked + + - - Checkbox item B - - - Initially checked - - - - - - + Checkbox item B + Initially checked + + + + + Multiple selection, controlled - - + - - Checkbox item A - - - Initially unchecked - - - Checkbox item A + Initially unchecked + + - - Checkbox item B - - - Initially checked - - - - + Checkbox item B + Initially checked + + + ); }; WithCheckboxes.args = { ...Default.args, }; -export const WithRadios: StoryFn< typeof DropdownMenuV2 > = ( props ) => { +export const WithRadios: StoryFn< typeof Menu > = ( props ) => { const [ radioValue, setRadioValue ] = useState( 'two' ); const onRadioChange: React.ComponentProps< - typeof DropdownMenuV2.RadioItem + typeof Menu.RadioItem >[ 'onChange' ] = ( e ) => setRadioValue( e.target.value ); return ( - - - - Uncontrolled - - - - Radio item 1 - - - Initially unchecked - - - + + Uncontrolled + + Radio item 1 + Initially unchecked + + - - Radio item 2 - - - Initially checked - - - - - - - Controlled - - Radio item 2 + Initially checked + + + + + Controlled + - - Radio item 1 - - - Initially unchecked - - - Radio item 1 + Initially unchecked + + - - Radio item 2 - - - Initially checked - - - - + Radio item 2 + Initially checked + + + ); }; WithRadios.args = { @@ -392,7 +322,7 @@ const modalOnTopOfDropdown = css` `; // For more examples with `Modal`, check https://ariakit.org/examples/menu-wordpress-modal -export const WithModals: StoryFn< typeof DropdownMenuV2 > = ( props ) => { +export const WithModals: StoryFn< typeof Menu > = ( props ) => { const [ isOuterModalOpen, setOuterModalOpen ] = useState( false ); const [ isInnerModalOpen, setInnerModalOpen ] = useState( false ); @@ -401,23 +331,19 @@ export const WithModals: StoryFn< typeof DropdownMenuV2 > = ( props ) => { return ( <> - - + setOuterModalOpen( true ) } hideOnClick={ false } > - - Open outer modal - - - Open outer modal + + setInnerModalOpen( true ) } hideOnClick={ false } > - - Open inner modal - - + Open inner modal + { isInnerModalOpen && ( setInnerModalOpen( false ) } @@ -429,7 +355,7 @@ export const WithModals: StoryFn< typeof DropdownMenuV2 > = ( props ) => { ) } - + { isOuterModalOpen && ( setOuterModalOpen( false ) } @@ -451,19 +377,16 @@ WithModals.args = { const ExampleSlotFill = createSlotFill( 'Example' ); const Slot = () => { - const dropdownMenuContext = useContext( DropdownMenuV2.Context ); + const menuContext = useContext( Menu.Context ); // Forwarding the content of the slot so that it can be used by the fill const fillProps = useMemo( () => ( { forwardedContext: [ - [ - DropdownMenuV2.Context.Provider, - { value: dropdownMenuContext }, - ], + [ Menu.Context.Provider, { value: menuContext } ], ], } ), - [ dropdownMenuContext ] + [ menuContext ] ); return ( @@ -499,37 +422,31 @@ const Fill = ( { children }: { children: React.ReactNode } ) => { ); }; -export const WithSlotFill: StoryFn< typeof DropdownMenuV2 > = ( props ) => { +export const WithSlotFill: StoryFn< typeof Menu > = ( props ) => { return ( - - - Item - + + + Item + - + - - - Item from fill - - - + Item from fill + + - - Submenu from fill - - + + Submenu from fill + } > - - - Submenu item from fill - - - + + Submenu item from fill + + ); @@ -539,48 +456,40 @@ WithSlotFill.args = { }; const toolbarVariantContextValue = { - DropdownMenuV2: { + Menu: { variant: 'toolbar', }, }; -export const ToolbarVariant: StoryFn< typeof DropdownMenuV2 > = ( props ) => ( +export const ToolbarVariant: StoryFn< typeof Menu > = ( props ) => ( // TODO: add toolbar - - - - Level 1 item - - - - - Level 1 item - - - - + + Level 1 item + + + Level 1 item + + + - - Submenu trigger - - + + Submenu trigger + } > - - - Level 2 item - - - - + + Level 2 item + + + ); ToolbarVariant.args = { ...Default.args, }; -export const InsideModal: StoryFn< typeof DropdownMenuV2 > = ( props ) => { +export const InsideModal: StoryFn< typeof Menu > = ( props ) => { const [ isModalOpen, setModalOpen ] = useState( false ); return ( <> @@ -593,34 +502,28 @@ export const InsideModal: StoryFn< typeof DropdownMenuV2 > = ( props ) => { { isModalOpen && ( setModalOpen( false ) }> - - - - Level 1 item - - - - - Level 1 item - - - - + + Level 1 item + + + Level 1 item + + + - + + Submenu trigger - - + + } > - - - Level 2 item - - - - + + Level 2 item + + + diff --git a/packages/components/src/menu/styles.ts b/packages/components/src/menu/styles.ts index b25613e9feb52b..3312c8cb2de161 100644 --- a/packages/components/src/menu/styles.ts +++ b/packages/components/src/menu/styles.ts @@ -12,7 +12,7 @@ import { COLORS, font, rtl, CONFIG } from '../utils'; import { space } from '../utils/space'; import Icon from '../icon'; import { Truncate } from '../truncate'; -import type { DropdownMenuContext } from './types'; +import type { MenuContext } from './types'; const ANIMATION_PARAMS = { SCALE_AMOUNT_OUTER: 0.82, @@ -43,7 +43,7 @@ const TOOLBAR_VARIANT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ TOOLBAR_VAR const GRID_TEMPLATE_COLS = 'minmax( 0, max-content ) 1fr'; export const MenuPopoverOuterWrapper = styled.div< - Pick< DropdownMenuContext, 'variant' > + Pick< MenuContext, 'variant' > >` position: relative; @@ -229,15 +229,15 @@ const baseItem = css` } `; -export const DropdownMenuItem = styled( Ariakit.MenuItem )` +export const MenuItem = styled( Ariakit.MenuItem )` ${ baseItem }; `; -export const DropdownMenuCheckboxItem = styled( Ariakit.MenuItemCheckbox )` +export const MenuCheckboxItem = styled( Ariakit.MenuItemCheckbox )` ${ baseItem }; `; -export const DropdownMenuRadioItem = styled( Ariakit.MenuItemRadio )` +export const MenuRadioItem = styled( Ariakit.MenuItemRadio )` ${ baseItem }; `; @@ -249,14 +249,14 @@ export const ItemPrefixWrapper = styled.span` * Even when the item is not checked, occupy the same screen space to avoid * the space collapside when no items are checked. */ - ${ DropdownMenuCheckboxItem } > &, - ${ DropdownMenuRadioItem } > & { + ${ MenuCheckboxItem } > &, + ${ MenuRadioItem } > & { /* Same width as the check icons */ min-width: ${ space( 6 ) }; } - ${ DropdownMenuCheckboxItem } > &, - ${ DropdownMenuRadioItem } > &, + ${ MenuCheckboxItem } > &, + ${ MenuRadioItem } > &, &:not( :empty ) { margin-inline-end: ${ space( 2 ) }; } @@ -278,7 +278,7 @@ export const ItemPrefixWrapper = styled.span` } `; -export const DropdownMenuItemContentWrapper = styled.div` +export const MenuItemContentWrapper = styled.div` /* * Always occupy the second column, since the first column * is taken by the prefix wrapper (when displayed). @@ -293,7 +293,7 @@ export const DropdownMenuItemContentWrapper = styled.div` pointer-events: none; `; -export const DropdownMenuItemChildrenWrapper = styled.div` +export const MenuItemChildrenWrapper = styled.div` flex: 1; display: inline-flex; @@ -324,12 +324,12 @@ export const ItemSuffixWrapper = styled.span` } `; -export const DropdownMenuGroup = styled( Ariakit.MenuGroup )` +export const MenuGroup = styled( Ariakit.MenuGroup )` /* Ignore this element when calculating the layout. Useful for subgrid */ display: contents; `; -export const DropdownMenuGroupLabel = styled( Ariakit.MenuGroupLabel )` +export const MenuGroupLabel = styled( Ariakit.MenuGroupLabel )` /* Occupy the width of all grid columns (ie. full width) */ grid-column: 1 / -1; @@ -338,8 +338,8 @@ export const DropdownMenuGroupLabel = styled( Ariakit.MenuGroupLabel )` padding-inline: ${ ITEM_PADDING_INLINE }; `; -export const DropdownMenuSeparator = styled( Ariakit.MenuSeparator )< - Pick< DropdownMenuContext, 'variant' > +export const MenuSeparator = styled( Ariakit.MenuSeparator )< + Pick< MenuContext, 'variant' > >` /* Occupy the width of all grid columns (ie. full width) */ grid-column: 1 / -1; @@ -370,13 +370,13 @@ export const SubmenuChevronIcon = styled( Icon )` ) }; `; -export const DropdownMenuItemLabel = styled( Truncate )` +export const MenuItemLabel = styled( Truncate )` font-size: ${ font( 'default.fontSize' ) }; line-height: 20px; color: inherit; `; -export const DropdownMenuItemHelpText = styled( Truncate )` +export const MenuItemHelpText = styled( Truncate )` font-size: ${ font( 'helpText.fontSize' ) }; line-height: 16px; color: ${ LIGHTER_TEXT_COLOR }; diff --git a/packages/components/src/menu/test/index.tsx b/packages/components/src/menu/test/index.tsx index cb674f27edaacf..668311a4c1e686 100644 --- a/packages/components/src/menu/test/index.tsx +++ b/packages/components/src/menu/test/index.tsx @@ -12,34 +12,24 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import { DropdownMenuV2 } from '..'; +import { Menu } from '..'; const delay = ( delayInMs: number ) => { return new Promise( ( resolve ) => setTimeout( resolve, delayInMs ) ); }; -describe( 'DropdownMenu', () => { +describe( 'Menu', () => { // See https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/ it( 'should follow the WAI-ARIA spec', async () => { render( - Open dropdown }> - Dropdown menu item - - - Dropdown submenu - - } - > - - Dropdown submenu item 1 - - - Dropdown submenu item 2 - - - + Open dropdown }> + Dropdown menu item + + Dropdown submenu }> + Dropdown submenu item 1 + Dropdown submenu item 2 + + ); const toggleButton = screen.getByRole( 'button', { @@ -94,36 +84,32 @@ describe( 'DropdownMenu', () => { describe( 'pointer and keyboard interactions', () => { it( 'should open and focus the menu when clicking the trigger', async () => { render( - Open dropdown }> - - Dropdown menu item - - + Open dropdown }> + Dropdown menu item + ); const toggleButton = screen.getByRole( 'button', { name: 'Open dropdown', } ); - // DropdownMenu closed + // Menu closed expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument(); // Click to open the menu await click( toggleButton ); - // DropdownMenu open, focus is on the menu wrapper + // Menu open, focus is on the menu wrapper expect( screen.getByRole( 'menu' ) ).toHaveFocus(); } ); it( 'should open and focus the first item when pressing the arrow down key on the trigger', async () => { render( - Open dropdown }> - - First item - - Second item - Third item - + Open dropdown }> + First item + Second item + Third item + ); const toggleButton = screen.getByRole( 'button', { @@ -135,12 +121,12 @@ describe( 'DropdownMenu', () => { expect( toggleButton ).toHaveFocus(); - // DropdownMenu closed + // Menu closed expect( screen.queryByRole( 'menuitem' ) ).not.toBeInTheDocument(); await press.ArrowDown(); - // DropdownMenu open, focus is on the first focusable item + // Menu open, focus is on the first focusable item // (disabled items are still focusable and accessible) expect( screen.getByRole( 'menuitem', { name: 'First item' } ) @@ -149,13 +135,11 @@ describe( 'DropdownMenu', () => { it( 'should open and focus the first item when pressing the space key on the trigger', async () => { render( - Open dropdown }> - - First item - - Second item - Third item - + Open dropdown }> + First item + Second item + Third item + ); const toggleButton = screen.getByRole( 'button', { @@ -167,12 +151,12 @@ describe( 'DropdownMenu', () => { expect( toggleButton ).toHaveFocus(); - // DropdownMenu closed + // Menu closed expect( screen.queryByRole( 'menuitem' ) ).not.toBeInTheDocument(); await press.Space(); - // DropdownMenu open, focus is on the first focusable item + // Menu open, focus is on the first focusable item // (disabled items are still focusable and accessible expect( screen.getByRole( 'menuitem', { name: 'First item' } ) @@ -181,11 +165,9 @@ describe( 'DropdownMenu', () => { it( 'should close when pressing the escape key', async () => { render( - Open dropdown }> - - Dropdown menu item - - + Open dropdown }> + Dropdown menu item + ); const trigger = screen.getByRole( 'button', { @@ -212,14 +194,9 @@ describe( 'DropdownMenu', () => { it( 'should close when clicking outside of the content', async () => { render( - Open dropdown } - > - - Dropdown menu item - - + Open dropdown }> + Dropdown menu item + ); expect( screen.getByRole( 'menu' ) ).toBeInTheDocument(); @@ -232,14 +209,9 @@ describe( 'DropdownMenu', () => { it( 'should close when clicking on a menu item', async () => { render( - Open dropdown } - > - - Dropdown menu item - - + Open dropdown }> + Dropdown menu item + ); expect( screen.getByRole( 'menu' ) ).toBeInTheDocument(); @@ -252,14 +224,11 @@ describe( 'DropdownMenu', () => { it( 'should not close when clicking on a menu item when the `hideOnClick` prop is set to `false`', async () => { render( - Open dropdown } - > - + Open dropdown }> + Dropdown menu item - - + + ); expect( screen.getByRole( 'menu' ) ).toBeVisible(); @@ -272,14 +241,9 @@ describe( 'DropdownMenu', () => { it( 'should not close when clicking on a disabled menu item', async () => { render( - Open dropdown } - > - - Dropdown menu item - - + Open dropdown }> + Dropdown menu item + ); expect( screen.getByRole( 'menu' ) ).toBeInTheDocument(); @@ -292,34 +256,15 @@ describe( 'DropdownMenu', () => { it( 'should reveal submenu content when hovering over the submenu trigger', async () => { render( - Open dropdown } - > - - Dropdown menu item 1 - - - Dropdown menu item 2 - - - Dropdown submenu - - } - > - - Dropdown submenu item 1 - - - Dropdown submenu item 2 - - - - Dropdown menu item 3 - - + Open dropdown }> + Dropdown menu item 1 + Dropdown menu item 2 + Dropdown submenu }> + Dropdown submenu item 1 + Dropdown submenu item 2 + + Dropdown menu item 3 + ); // Before hover, submenu items are not rendered @@ -343,34 +288,15 @@ describe( 'DropdownMenu', () => { it( 'should navigate menu items and subitems using the arrow, spacebar and enter keys', async () => { render( - Open dropdown } - > - - Dropdown menu item 1 - - - Dropdown menu item 2 - - - Dropdown submenu - - } - > - - Dropdown submenu item 1 - - - Dropdown submenu item 2 - - - - Dropdown menu item 3 - - + Open dropdown }> + Dropdown menu item 1 + Dropdown menu item 2 + Dropdown submenu }> + Dropdown submenu item 1 + Dropdown submenu item 2 + + Dropdown menu item 3 + ); // The menu is focused automatically when `defaultOpen` is set. @@ -473,32 +399,32 @@ describe( 'DropdownMenu', () => { const ControlledRadioGroup = () => { const [ radioValue, setRadioValue ] = useState( 'two' ); const onRadioChange: React.ComponentProps< - typeof DropdownMenuV2.RadioItem + typeof Menu.RadioItem >[ 'onChange' ] = ( e ) => { onRadioValueChangeSpy( e.target.value ); setRadioValue( e.target.value ); }; return ( - Open dropdown }> - - Open dropdown }> + + Radio item one - - + Radio item two - - - + + + ); }; @@ -556,9 +482,9 @@ describe( 'DropdownMenu', () => { it( 'should check radio items and keep the menu open when clicking (uncontrolled)', async () => { const onRadioValueChangeSpy = jest.fn(); render( - Open dropdown }> - - Open dropdown }> + + @@ -566,8 +492,8 @@ describe( 'DropdownMenu', () => { } > Radio item one - - + { } > Radio item two - - - + + + ); // Open dropdown @@ -640,8 +566,8 @@ describe( 'DropdownMenu', () => { useState< boolean >(); return ( - Open dropdown }> - Open dropdown }> + { } } > Checkbox item one - + - { } } > Checkbox item two - - + + ); }; @@ -763,8 +689,8 @@ describe( 'DropdownMenu', () => { const onCheckboxValueChangeSpy = jest.fn(); render( - Open dropdown }> - Open dropdown }> + { @@ -776,9 +702,9 @@ describe( 'DropdownMenu', () => { } } > Checkbox item one - + - { } } > Checkbox item two - - + + ); // Open dropdown @@ -881,11 +807,9 @@ describe( 'DropdownMenu', () => { it( 'should be modal by default', async () => { render( <> - Open dropdown }> - - Dropdown menu item - - + Open dropdown }> + Dropdown menu item + ); @@ -897,7 +821,7 @@ describe( 'DropdownMenu', () => { } ) ); - // DropdownMenu open, focus is on the menu wrapper + // Menu open, focus is on the menu wrapper expect( screen.getByRole( 'menu' ) ).toHaveFocus(); expect( @@ -910,14 +834,12 @@ describe( 'DropdownMenu', () => { it( 'should not be modal when the `modal` prop is set to `false`', async () => { render( <> - Open dropdown } modal={ false } > - - Dropdown menu item - - + Dropdown menu item + ); @@ -929,17 +851,17 @@ describe( 'DropdownMenu', () => { } ) ); - // DropdownMenu open, focus is on the menu wrapper + // Menu open, focus is on the menu wrapper expect( screen.getByRole( 'menu' ) ).toHaveFocus(); - // DropdownMenu is not modal, therefore the outer button is part of the + // Menu is not modal, therefore the outer button is part of the // accessibility tree and can be found. const outerButton = screen.getByRole( 'button', { name: 'Button outside of dropdown', } ); // The outer button can be focused by pressing tab. Doing so will cause - // the DropdownMenu to close. + // the Menu to close. await press.Tab(); expect( outerButton ).toBeInTheDocument(); expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument(); @@ -949,11 +871,11 @@ describe( 'DropdownMenu', () => { describe( 'items prefix and suffix', () => { it( 'should display a prefix on regular items', async () => { render( - Open dropdown }> - Item prefix }> + Open dropdown }> + Item prefix }> Dropdown menu item - - + + ); // Click to open the menu @@ -973,11 +895,11 @@ describe( 'DropdownMenu', () => { it( 'should display a suffix on regular items', async () => { render( - Open dropdown }> - Item suffix }> + Open dropdown }> + Item suffix }> Dropdown menu item - - + + ); // Click to open the menu @@ -997,15 +919,15 @@ describe( 'DropdownMenu', () => { it( 'should display a suffix on radio items', async () => { render( - Open dropdown }> - Open dropdown }> + Radio item one - - + + ); // Click to open the menu @@ -1025,15 +947,15 @@ describe( 'DropdownMenu', () => { it( 'should display a suffix on checkbox items', async () => { render( - Open dropdown }> - Open dropdown }> + Checkbox item one - - + + ); // Click to open the menu @@ -1055,10 +977,10 @@ describe( 'DropdownMenu', () => { describe( 'typeahead', () => { it( 'should highlight matching item', async () => { render( - Open dropdown }> - One - Two - + Open dropdown }> + One + Two + ); // Click to open the menu @@ -1088,10 +1010,10 @@ describe( 'DropdownMenu', () => { it( 'should keep previous focus when no matches are found', async () => { render( - Open dropdown }> - One - Two - + Open dropdown }> + One + Two + ); // Click to open the menu diff --git a/packages/components/src/menu/types.ts b/packages/components/src/menu/types.ts index 795cd9ac76ff58..10c68a540765b7 100644 --- a/packages/components/src/menu/types.ts +++ b/packages/components/src/menu/types.ts @@ -4,9 +4,9 @@ import type * as Ariakit from '@ariakit/react'; import type { Placement } from '@floating-ui/react-dom'; -export interface DropdownMenuContext { +export interface MenuContext { /** - * The ariakit store shared across all DropdownMenu subcomponents. + * The ariakit store shared across all Menu subcomponents. */ store: Ariakit.MenuStore; /** @@ -15,7 +15,7 @@ export interface DropdownMenuContext { variant?: 'toolbar'; } -export interface DropdownMenuProps { +export interface MenuProps { /** * The trigger button. */ @@ -80,21 +80,21 @@ export interface DropdownMenuProps { ) => boolean ); } -export interface DropdownMenuGroupProps { +export interface MenuGroupProps { /** * The contents of the dropdown menu group. */ children: React.ReactNode; } -export interface DropdownMenuGroupLabelProps { +export interface MenuGroupLabelProps { /** * The contents of the dropdown menu group. */ children: React.ReactNode; } -export interface DropdownMenuItemProps { +export interface MenuItemProps { /** * The contents of the menu item. */ @@ -119,8 +119,8 @@ export interface DropdownMenuItemProps { disabled?: boolean; } -export interface DropdownMenuCheckboxItemProps - extends Omit< DropdownMenuItemProps, 'prefix' | 'hideOnClick' > { +export interface MenuCheckboxItemProps + extends Omit< MenuItemProps, 'prefix' | 'hideOnClick' > { /** * Whether to hide the dropdown menu when the item is clicked. * @@ -151,8 +151,8 @@ export interface DropdownMenuCheckboxItemProps onChange?: ( event: React.ChangeEvent< HTMLInputElement > ) => void; } -export interface DropdownMenuRadioItemProps - extends Omit< DropdownMenuItemProps, 'prefix' | 'hideOnClick' > { +export interface MenuRadioItemProps + extends Omit< MenuItemProps, 'prefix' | 'hideOnClick' > { /** * Whether to hide the dropdown menu when the item is clicked. * @@ -182,4 +182,4 @@ export interface DropdownMenuRadioItemProps onChange?: ( event: React.ChangeEvent< HTMLInputElement > ) => void; } -export interface DropdownMenuSeparatorProps {} +export interface MenuSeparatorProps {} diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index 6d28765c2f685e..f5d58283bb09fe 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -3,7 +3,7 @@ */ import { positionToPlacement as __experimentalPopoverLegacyPositionToPlacement } from './popover/utils'; import { createPrivateSlotFill } from './slot-fill'; -import { DropdownMenuV2 } from './menu'; +import { Menu } from './menu'; import { ComponentsContext } from './context/context-system-provider'; import Theme from './theme'; import { Tabs } from './tabs'; @@ -17,6 +17,6 @@ lock( privateApis, { ComponentsContext, Tabs, Theme, - DropdownMenuV2, + DropdownMenuV2: Menu, kebabCase, } ); From ce98c097886d81ed7a2dad06d907b95f404eb6b2 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 21 Oct 2024 18:10:02 +0200 Subject: [PATCH 2/7] Usage: DropdownMenuV2 => Menu --- .../block-editor/src/hooks/block-bindings.js | 28 ++++----- packages/components/src/private-apis.ts | 2 +- .../dataviews-filters/add-filter.tsx | 14 ++--- .../dataviews-item-actions/index.tsx | 16 ++--- .../dataviews-view-config/index.tsx | 14 ++--- .../src/dataviews-layouts/list/index.tsx | 6 +- .../table/column-header-menu.tsx | 60 +++++++++---------- .../global-styles/font-sizes/font-size.js | 26 ++++---- .../global-styles/font-sizes/font-sizes.js | 16 +++-- .../global-styles/shadows-edit-panel.js | 14 ++--- .../src/components/post-actions/index.js | 19 +++--- 11 files changed, 99 insertions(+), 116 deletions(-) diff --git a/packages/block-editor/src/hooks/block-bindings.js b/packages/block-editor/src/hooks/block-bindings.js index 694c9c1c0bb3cc..615804a311c0fb 100644 --- a/packages/block-editor/src/hooks/block-bindings.js +++ b/packages/block-editor/src/hooks/block-bindings.js @@ -34,7 +34,7 @@ import { useBlockEditContext } from '../components/block-edit'; import { useBlockBindingsUtils } from '../utils/block-bindings'; import { store as blockEditorStore } from '../store'; -const { DropdownMenuV2 } = unlock( componentsPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); const EMPTY_OBJECT = {}; @@ -70,18 +70,18 @@ function BlockBindingsPanelDropdown( { fieldsList, attribute, binding } ) { <> { Object.entries( fieldsList ).map( ( [ name, fields ], i ) => ( - + { Object.keys( fieldsList ).length > 1 && ( - + { registeredSources[ name ].label } - + ) } { Object.entries( fields ) .filter( ( [ , args ] ) => args?.type === attributeType ) .map( ( [ key, args ] ) => ( - updateBlockBindings( { @@ -95,17 +95,17 @@ function BlockBindingsPanelDropdown( { fieldsList, attribute, binding } ) { value={ key } checked={ key === currentKey } > - + { args?.label } - - + + { args?.value } - - + + ) ) } - + { i !== Object.keys( fieldsList ).length - 1 && ( - + ) } ) ) } @@ -175,7 +175,7 @@ function EditableBlockBindingsPanelItems( { } ); } } > - - + ); } ) } diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index f5d58283bb09fe..bea16b719a463d 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -17,6 +17,6 @@ lock( privateApis, { ComponentsContext, Tabs, Theme, - DropdownMenuV2: Menu, + Menu, kebabCase, } ); diff --git a/packages/dataviews/src/components/dataviews-filters/add-filter.tsx b/packages/dataviews/src/components/dataviews-filters/add-filter.tsx index c8b6b5fda38fcc..0fc4044c2d3f6b 100644 --- a/packages/dataviews/src/components/dataviews-filters/add-filter.tsx +++ b/packages/dataviews/src/components/dataviews-filters/add-filter.tsx @@ -19,7 +19,7 @@ import { forwardRef } from '@wordpress/element'; import { unlock } from '../../lock-unlock'; import type { NormalizedFilter, View } from '../../types'; -const { DropdownMenuV2 } = unlock( componentsPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); interface AddFilterProps { filters: NormalizedFilter[]; @@ -39,10 +39,10 @@ export function AddFilterDropdownMenu( { } ) { const inactiveFilters = filters.filter( ( filter ) => ! filter.isVisible ); return ( - + { inactiveFilters.map( ( filter ) => { return ( - { setOpenedFilter( filter.field ); @@ -60,13 +60,11 @@ export function AddFilterDropdownMenu( { } ); } } > - - { filter.name } - - + { filter.name } + ); } ) } - + ); } diff --git a/packages/dataviews/src/components/dataviews-item-actions/index.tsx b/packages/dataviews/src/components/dataviews-item-actions/index.tsx index e33cb0fe56d0ed..3d610ca83ec637 100644 --- a/packages/dataviews/src/components/dataviews-item-actions/index.tsx +++ b/packages/dataviews/src/components/dataviews-item-actions/index.tsx @@ -23,7 +23,7 @@ import { useRegistry } from '@wordpress/data'; import { unlock } from '../../lock-unlock'; import type { Action, ActionModal as ActionModalType } from '../../types'; -const { DropdownMenuV2, kebabCase } = unlock( componentsPrivateApis ); +const { Menu, kebabCase } = unlock( componentsPrivateApis ); export interface ActionTriggerProps< Item > { action: Action< Item >; @@ -85,12 +85,12 @@ function DropdownMenuItemTrigger< Item >( { const label = typeof action.label === 'string' ? action.label : action.label( items ); return ( - - { label } - + { label } + ); } @@ -152,7 +152,7 @@ export function ActionsDropdownMenuGroup< Item >( { }: ActionsDropdownMenuGroupProps< Item > ) { const registry = useRegistry(); return ( - + { actions.map( ( action ) => { if ( 'RenderModal' in action ) { return ( @@ -175,7 +175,7 @@ export function ActionsDropdownMenuGroup< Item >( { /> ); } ) } - + ); } @@ -245,7 +245,7 @@ function CompactItemActions< Item >( { actions, }: CompactItemActionsProps< Item > ) { return ( - ( { placement="bottom-end" > - + ); } diff --git a/packages/dataviews/src/components/dataviews-view-config/index.tsx b/packages/dataviews/src/components/dataviews-view-config/index.tsx index 9824f9c8e8c6f6..c8b26c51275891 100644 --- a/packages/dataviews/src/components/dataviews-view-config/index.tsx +++ b/packages/dataviews/src/components/dataviews-view-config/index.tsx @@ -51,7 +51,7 @@ import DataViewsContext from '../dataviews-context'; import { unlock } from '../../lock-unlock'; import DensityPicker from '../../dataviews-layouts/grid/density-picker'; -const { DropdownMenuV2 } = unlock( componentsPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); interface ViewTypeMenuProps { defaultLayouts?: SupportedLayouts; @@ -69,7 +69,7 @@ function ViewTypeMenu( { } const activeView = VIEW_LAYOUTS.find( ( v ) => view.type === v.type ); return ( - - - { config.label } - - + { config.label } + ); } ) } - + ); } diff --git a/packages/dataviews/src/dataviews-layouts/list/index.tsx b/packages/dataviews/src/dataviews-layouts/list/index.tsx index e737b18f5b02a9..1de7660b6bc056 100644 --- a/packages/dataviews/src/dataviews-layouts/list/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/list/index.tsx @@ -49,7 +49,7 @@ interface ListViewItemProps< Item > { onDropdownTriggerKeyDown: React.KeyboardEventHandler< HTMLButtonElement >; } -const { DropdownMenuV2: DropdownMenu } = unlock( componentsPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); function generateItemWrapperCompositeId( idPrefix: string ) { return `${ idPrefix }-item-wrapper`; @@ -195,7 +195,7 @@ function ListItem< Item >( { /> ) }
- ( { actions={ eligibleActions } item={ item } /> - +
); diff --git a/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx b/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx index aff211fb613dcf..470eb5cf35a75c 100644 --- a/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx +++ b/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx @@ -29,7 +29,7 @@ import type { } from '../../types'; import { getVisibleFieldIds } from '../index'; -const { DropdownMenuV2 } = unlock( componentsPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); interface HeaderMenuProps< Item > { fieldId: string; @@ -45,7 +45,7 @@ function WithDropDownMenuSeparators( { children }: { children: ReactNode } ) { .filter( Boolean ) .map( ( child, i ) => ( - { i > 0 && } + { i > 0 && } { child } ) ); @@ -101,7 +101,7 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item >( } return ( - ( > { isSortable && ( - + { SORTING_DIRECTIONS.map( ( direction: SortDirection ) => { const isChecked = @@ -133,7 +133,7 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item >( const value = `${ fieldId }-${ direction }`; return ( - ( } ); } } > - + { sortLabels[ direction ] } - - + + ); } ) } - + ) } { canAddFilter && ( - - + } onClick={ () => { setOpenedFilter( fieldId ); @@ -182,14 +182,14 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item >( } ); } } > - + { __( 'Add filter' ) } - - - + + + ) } - - + } disabled={ index < 1 } onClick={ () => { @@ -207,11 +207,9 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item >( } ); } } > - - { __( 'Move left' ) } - - - { __( 'Move left' ) } + + } disabled={ index >= visibleFieldIds.length - 1 } onClick={ () => { @@ -227,12 +225,10 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item >( } ); } } > - - { __( 'Move right' ) } - - + { __( 'Move right' ) } + { isHidable && field && ( - } onClick={ () => { onHide( field ); @@ -244,14 +240,14 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item >( } ); } } > - + { __( 'Hide column' ) } - - + + ) } - + - + ); } ); diff --git a/packages/edit-site/src/components/global-styles/font-sizes/font-size.js b/packages/edit-site/src/components/global-styles/font-sizes/font-size.js index 43418f9caf5eb6..25dcc69185cae6 100644 --- a/packages/edit-site/src/components/global-styles/font-sizes/font-size.js +++ b/packages/edit-site/src/components/global-styles/font-sizes/font-size.js @@ -27,7 +27,7 @@ import ConfirmDeleteFontSizeDialog from './confirm-delete-font-size-dialog'; import RenameFontSizeDialog from './rename-font-size-dialog'; import SizeControl from '../size-control'; -const { DropdownMenuV2 } = unlock( componentsPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); const { useGlobalSetting } = unlock( blockEditorPrivateApis ); function FontSize() { @@ -166,7 +166,7 @@ function FontSize() { marginBottom={ 0 } paddingX={ 4 } > - } > - - + + { __( 'Rename' ) } - - - - + + + + { __( 'Delete' ) } - - - + + + ) } diff --git a/packages/edit-site/src/components/global-styles/font-sizes/font-sizes.js b/packages/edit-site/src/components/global-styles/font-sizes/font-sizes.js index e09dda1a82fe81..4bda7a7b3266b5 100644 --- a/packages/edit-site/src/components/global-styles/font-sizes/font-sizes.js +++ b/packages/edit-site/src/components/global-styles/font-sizes/font-sizes.js @@ -27,7 +27,7 @@ import { useState } from '@wordpress/element'; * Internal dependencies */ import { unlock } from '../../../lock-unlock'; -const { DropdownMenuV2 } = unlock( componentsPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); const { useGlobalSetting } = unlock( blockEditorPrivateApis ); import Subtitle from '../subtitle'; import { NavigationButtonAsItem } from '../navigation-button'; @@ -81,7 +81,7 @@ function FontSizeGroup( { /> ) } { !! handleResetFontSizes && ( - } > - - + + { origin === 'custom' ? __( 'Remove font size presets' ) : __( 'Reset font size presets' ) } - - - + + + ) } diff --git a/packages/edit-site/src/components/global-styles/shadows-edit-panel.js b/packages/edit-site/src/components/global-styles/shadows-edit-panel.js index 7321e927fbbae3..127480ee5af497 100644 --- a/packages/edit-site/src/components/global-styles/shadows-edit-panel.js +++ b/packages/edit-site/src/components/global-styles/shadows-edit-panel.js @@ -51,7 +51,7 @@ import { } from './shadow-utils'; const { useGlobalSetting } = unlock( blockEditorPrivateApis ); -const { DropdownMenuV2 } = unlock( componentsPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); const customShadowMenuItems = [ { @@ -163,7 +163,7 @@ export default function ShadowsEditPanel() { - ( - onMenuClick( item.action ) } disabled={ @@ -185,12 +185,12 @@ export default function ShadowsEditPanel() { baseSelectedShadow.shadow } > - + { item.label } - - + + ) ) } - + diff --git a/packages/editor/src/components/post-actions/index.js b/packages/editor/src/components/post-actions/index.js index 8a3850e8f547c1..9f39b1f3305aeb 100644 --- a/packages/editor/src/components/post-actions/index.js +++ b/packages/editor/src/components/post-actions/index.js @@ -18,7 +18,7 @@ import { store as coreStore } from '@wordpress/core-data'; import { unlock } from '../../lock-unlock'; import { usePostActions } from './actions'; -const { DropdownMenuV2, kebabCase } = unlock( componentsPrivateApis ); +const { Menu, kebabCase } = unlock( componentsPrivateApis ); export default function PostActions( { postType, postId, onActionPerformed } ) { const [ isActionsMenuOpen, setIsActionsMenuOpen ] = useState( false ); @@ -54,7 +54,7 @@ export default function PostActions( { postType, postId, onActionPerformed } ) { }, [ allActions, itemWithPermissions ] ); return ( - - + ); } @@ -93,12 +93,9 @@ function DropdownMenuItemTrigger( { action, onClick, items } ) { const label = typeof action.label === 'string' ? action.label : action.label( items ); return ( - - { label } - + + { label } + ); } @@ -145,7 +142,7 @@ function ActionWithModal( { action, item, ActionTrigger, onClose } ) { // With an added onClose prop. function ActionsDropdownMenuGroup( { actions, item, onClose } ) { return ( - + { actions.map( ( action ) => { if ( action.RenderModal ) { return ( @@ -167,6 +164,6 @@ function ActionsDropdownMenuGroup( { actions, item, onClose } ) { /> ); } ) } - + ); } From 48177d809589f8d220a0c291a08c29f2519710a6 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 21 Oct 2024 18:25:06 +0200 Subject: [PATCH 3/7] CHANGELOG --- packages/components/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index a9f1286bdc4167..1b75c498c47425 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -25,6 +25,7 @@ - `Tabs`: update indicator more reactively ([#66207](https://github.com/WordPress/gutenberg/pull/66207)). - `Tabs` and `TabPanel`: Fix arrow key navigation in RTL ([#66201](https://github.com/WordPress/gutenberg/pull/66201)). - `Tabs`: override tablist's tabindex only when necessary ([#66209](https://github.com/WordPress/gutenberg/pull/66209)). +- `DropdownMenuV2`: rename to `Menu` ([#66289](https://github.com/WordPress/gutenberg/pull/66289)). ### Internal From 7990d8780fc1ed386bc8e8db402800e87d9977b6 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 28 Oct 2024 11:36:38 +0100 Subject: [PATCH 4/7] Add toolbar variant context variable for Menu --- packages/components/src/toolbar/toolbar/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/components/src/toolbar/toolbar/index.tsx b/packages/components/src/toolbar/toolbar/index.tsx index ba2e8062aed85d..6b7f4843560fe7 100644 --- a/packages/components/src/toolbar/toolbar/index.tsx +++ b/packages/components/src/toolbar/toolbar/index.tsx @@ -40,6 +40,9 @@ function UnforwardedToolbar( Dropdown: { variant: 'toolbar', }, + Menu: { + variant: 'toolbar', + }, }; }, [ isVariantDefined ] ); From d1b24df8c18dba39770df41338d0a2d0ed43e825 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 28 Oct 2024 16:44:06 +0100 Subject: [PATCH 5/7] Remove unnecessary "dropdown" from test file --- packages/components/src/menu/test/index.tsx | 102 ++++++++++---------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/packages/components/src/menu/test/index.tsx b/packages/components/src/menu/test/index.tsx index 668311a4c1e686..60276cdb2379a0 100644 --- a/packages/components/src/menu/test/index.tsx +++ b/packages/components/src/menu/test/index.tsx @@ -23,11 +23,11 @@ describe( 'Menu', () => { it( 'should follow the WAI-ARIA spec', async () => { render( Open dropdown }> - Dropdown menu item + Menu item - Dropdown submenu }> - Dropdown submenu item 1 - Dropdown submenu item 2 + Submenu trigger item }> + Submenu item 1 + Submenu item 2 ); @@ -58,7 +58,7 @@ describe( 'Menu', () => { expect( screen.getAllByRole( 'menuitem' ) ).toHaveLength( 2 ); const submenuTrigger = screen.getByRole( 'menuitem', { - name: 'Dropdown submenu', + name: 'Submenu trigger item', } ); expect( submenuTrigger ).toHaveAttribute( 'aria-haspopup', 'menu' ); expect( submenuTrigger ).toHaveAttribute( 'aria-expanded', 'false' ); @@ -85,7 +85,7 @@ describe( 'Menu', () => { it( 'should open and focus the menu when clicking the trigger', async () => { render( Open dropdown }> - Dropdown menu item + Menu item ); @@ -166,7 +166,7 @@ describe( 'Menu', () => { it( 'should close when pressing the escape key', async () => { render( Open dropdown }> - Dropdown menu item + Menu item ); @@ -195,7 +195,7 @@ describe( 'Menu', () => { it( 'should close when clicking outside of the content', async () => { render( Open dropdown }> - Dropdown menu item + Menu item ); @@ -210,7 +210,7 @@ describe( 'Menu', () => { it( 'should close when clicking on a menu item', async () => { render( Open dropdown }> - Dropdown menu item + Menu item ); @@ -225,9 +225,7 @@ describe( 'Menu', () => { it( 'should not close when clicking on a menu item when the `hideOnClick` prop is set to `false`', async () => { render( Open dropdown }> - - Dropdown menu item - + Menu item ); @@ -242,7 +240,7 @@ describe( 'Menu', () => { it( 'should not close when clicking on a disabled menu item', async () => { render( Open dropdown }> - Dropdown menu item + Menu item ); @@ -257,45 +255,49 @@ describe( 'Menu', () => { it( 'should reveal submenu content when hovering over the submenu trigger', async () => { render( Open dropdown }> - Dropdown menu item 1 - Dropdown menu item 2 - Dropdown submenu }> - Dropdown submenu item 1 - Dropdown submenu item 2 + Menu item 1 + Menu item 2 + Submenu trigger item } + > + Submenu item 1 + Submenu item 2 - Dropdown menu item 3 + Menu item 3 ); // Before hover, submenu items are not rendered expect( screen.queryByRole( 'menuitem', { - name: 'Dropdown submenu item 1', + name: 'Submenu item 1', } ) ).not.toBeInTheDocument(); await hover( - screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } ) + screen.getByRole( 'menuitem', { name: 'Submenu trigger item' } ) ); // After hover, submenu items are rendered // Reason for `findByRole`: due to the animation, we've got to wait // a short amount of time for the submenu to appear await screen.findByRole( 'menuitem', { - name: 'Dropdown submenu item 1', + name: 'Submenu item 1', } ); } ); it( 'should navigate menu items and subitems using the arrow, spacebar and enter keys', async () => { render( Open dropdown }> - Dropdown menu item 1 - Dropdown menu item 2 - Dropdown submenu }> - Dropdown submenu item 1 - Dropdown submenu item 2 + Menu item 1 + Menu item 2 + Submenu trigger item } + > + Submenu item 1 + Submenu item 2 - Dropdown menu item 3 + Menu item 3 ); @@ -308,58 +310,58 @@ describe( 'Menu', () => { // The selection wraps around from last to first and viceversa await press.ArrowDown(); expect( - screen.getByRole( 'menuitem', { name: 'Dropdown menu item 1' } ) + screen.getByRole( 'menuitem', { name: 'Menu item 1' } ) ).toHaveFocus(); await press.ArrowDown(); expect( - screen.getByRole( 'menuitem', { name: 'Dropdown menu item 2' } ) + screen.getByRole( 'menuitem', { name: 'Menu item 2' } ) ).toHaveFocus(); await press.ArrowDown(); expect( - screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } ) + screen.getByRole( 'menuitem', { name: 'Submenu trigger item' } ) ).toHaveFocus(); await press.ArrowDown(); expect( - screen.getByRole( 'menuitem', { name: 'Dropdown menu item 3' } ) + screen.getByRole( 'menuitem', { name: 'Menu item 3' } ) ).toHaveFocus(); await press.ArrowDown(); expect( - screen.getByRole( 'menuitem', { name: 'Dropdown menu item 1' } ) + screen.getByRole( 'menuitem', { name: 'Menu item 1' } ) ).toHaveFocus(); await press.ArrowUp(); expect( - screen.getByRole( 'menuitem', { name: 'Dropdown menu item 3' } ) + screen.getByRole( 'menuitem', { name: 'Menu item 3' } ) ).toHaveFocus(); await press.ArrowUp(); expect( - screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } ) + screen.getByRole( 'menuitem', { name: 'Submenu trigger item' } ) ).toHaveFocus(); // Arrow right/left can be used to enter/leave submenus await press.ArrowRight(); expect( screen.getByRole( 'menuitem', { - name: 'Dropdown submenu item 1', + name: 'Submenu item 1', } ) ).toHaveFocus(); await press.ArrowDown(); expect( screen.getByRole( 'menuitem', { - name: 'Dropdown submenu item 2', + name: 'Submenu item 2', } ) ).toHaveFocus(); await press.ArrowLeft(); expect( screen.getByRole( 'menuitem', { - name: 'Dropdown submenu', + name: 'Submenu trigger item', } ) ).toHaveFocus(); @@ -367,28 +369,28 @@ describe( 'Menu', () => { await press.Enter(); expect( screen.getByRole( 'menuitem', { - name: 'Dropdown submenu item 1', + name: 'Submenu item 1', } ) ).toHaveFocus(); await press.ArrowLeft(); expect( screen.getByRole( 'menuitem', { - name: 'Dropdown submenu', + name: 'Submenu trigger item', } ) ).toHaveFocus(); await press.Space(); expect( screen.getByRole( 'menuitem', { - name: 'Dropdown submenu item 1', + name: 'Submenu item 1', } ) ).toHaveFocus(); await press.ArrowLeft(); expect( screen.getByRole( 'menuitem', { - name: 'Dropdown submenu', + name: 'Submenu trigger item', } ) ).toHaveFocus(); } ); @@ -808,7 +810,7 @@ describe( 'Menu', () => { render( <> Open dropdown }> - Dropdown menu item + Menu item @@ -838,7 +840,7 @@ describe( 'Menu', () => { trigger={ } modal={ false } > - Dropdown menu item + Menu item @@ -872,9 +874,7 @@ describe( 'Menu', () => { it( 'should display a prefix on regular items', async () => { render( Open dropdown }> - Item prefix }> - Dropdown menu item - + Item prefix }>Menu item ); @@ -888,7 +888,7 @@ describe( 'Menu', () => { // The contents of the prefix are rendered before the item's children expect( screen.getByRole( 'menuitem', { - name: 'Item prefix Dropdown menu item', + name: 'Item prefix Menu item', } ) ).toBeInTheDocument(); } ); @@ -896,9 +896,7 @@ describe( 'Menu', () => { it( 'should display a suffix on regular items', async () => { render( Open dropdown }> - Item suffix }> - Dropdown menu item - + Item suffix }>Menu item ); @@ -912,7 +910,7 @@ describe( 'Menu', () => { // The contents of the suffix are rendered after the item's children expect( screen.getByRole( 'menuitem', { - name: 'Dropdown menu item Item suffix', + name: 'Menu item Item suffix', } ) ).toBeInTheDocument(); } ); From 47f94c7ada62e71627cf34959ba16510c2df2e8d Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 28 Oct 2024 16:54:01 +0100 Subject: [PATCH 6/7] Reword README and JSDocs to remove "dropdown" --- packages/components/src/menu/README.md | 26 +++++++++---------- packages/components/src/menu/index.tsx | 4 +-- .../src/menu/stories/index.story.tsx | 4 +-- packages/components/src/menu/types.ts | 25 +++++++++--------- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/packages/components/src/menu/README.md b/packages/components/src/menu/README.md index b9fb782cd24436..6732610c0c6cae 100644 --- a/packages/components/src/menu/README.md +++ b/packages/components/src/menu/README.md @@ -4,7 +4,7 @@ This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. -`Menu` displays a menu to the user (such as a set of actions or functions) triggered by a button. +`Menu` displays a menu to the user (such as a set of actions or functions). The menu is rendered in a popover (this pattern is also known as a "Dropdown menu"), which is triggered by a button. ## Design guidelines @@ -55,45 +55,45 @@ The component accepts the following props: ##### `trigger`: `React.ReactNode` -The trigger button +The button triggering the menu popover. - Required: yes ##### `children`: `React.ReactNode` -The contents of the dropdown +The contents of the menu (ie. one or more menu items). - Required: yes ##### `defaultOpen`: `boolean` -The open state of the dropdown menu when it is initially rendered. Use when not wanting to control its open state. +The open state of the menu popover when it is initially rendered. Use when not wanting to control its open state. - Required: no - Default: `false` ##### `open`: `boolean` -The controlled open state of the dropdown menu. Must be used in conjunction with `onOpenChange`. +The controlled open state of the menu popover. Must be used in conjunction with `onOpenChange`. - Required: no ##### `onOpenChange`: `(open: boolean) => void` -Event handler called when the open state of the dropdown menu changes. +Event handler called when the open state of the menu popover changes. - Required: no ##### `modal`: `boolean` -The modality of the dropdown menu. When set to true, interaction with outside elements will be disabled and only menu content will be visible to screen readers. +The modality of the menu popover. When set to true, interaction with outside elements will be disabled and only menu content will be visible to screen readers. - Required: no - Default: `true` ##### `placement`: ``'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'` -The placement of the dropdown menu popover. +The placement of the menu popover. - Required: no - Default: `'bottom-start'` for root-level menus, `'right-start'` for nested menus @@ -140,7 +140,7 @@ The contents of the item's suffix. ##### `hideOnClick`: `boolean` -Whether to hide the dropdown menu when the menu item is clicked. +Whether to hide the menu popover when the menu item is clicked. - Required: no - Default: `true` @@ -174,7 +174,7 @@ The contents of the item's suffix. ##### `hideOnClick`: `boolean` -Whether to hide the dropdown menu when the menu item is clicked. +Whether to hide the menu popover when the menu item is clicked. - Required: no - Default: `false` @@ -240,7 +240,7 @@ The contents of the item's suffix. ##### `hideOnClick`: `boolean` -Whether to hide the dropdown menu when the menu item is clicked. +Whether to hide the menu popover when the menu item is clicked. - Required: no - Default: `false` @@ -321,7 +321,7 @@ The component accepts the following props: ##### `children`: `React.ReactNode` -The contents of the group. +The contents of the menu group (ie. an optional menu group label and one or more menu items). - Required: yes @@ -335,7 +335,7 @@ The component accepts the following props: ##### `children`: `React.ReactNode` -The contents of the group label. +The contents of the menu group label. - Required: yes diff --git a/packages/components/src/menu/index.tsx b/packages/components/src/menu/index.tsx index ee5d9a6f8c9df4..6f6e89b0a1c72b 100644 --- a/packages/components/src/menu/index.tsx +++ b/packages/components/src/menu/index.tsx @@ -69,8 +69,8 @@ const UnconnectedMenu = ( const computedDirection = isRTL() ? 'rtl' : 'ltr'; // If an explicit value for the `placement` prop is not passed, - // apply a default placement of `bottom-start` for the root dropdown, - // and of `right-start` for nested dropdowns. + // apply a default placement of `bottom-start` for the root menu popover, + // and of `right-start` for nested menu popovers. let computedPlacement = props.placement ?? ( parentContext?.store ? 'right-start' : 'bottom-start' ); diff --git a/packages/components/src/menu/stories/index.story.tsx b/packages/components/src/menu/stories/index.story.tsx index f9684cf980b1eb..ecb4cc3c7593f8 100644 --- a/packages/components/src/menu/stories/index.story.tsx +++ b/packages/components/src/menu/stories/index.story.tsx @@ -315,7 +315,7 @@ WithRadios.args = { ...Default.args, }; -const modalOnTopOfDropdown = css` +const modalOnTopOfMenuPopover = css` && { z-index: 1000000; } @@ -327,7 +327,7 @@ export const WithModals: StoryFn< typeof Menu > = ( props ) => { const [ isInnerModalOpen, setInnerModalOpen ] = useState( false ); const cx = useCx(); - const modalOverlayClassName = cx( modalOnTopOfDropdown ); + const modalOverlayClassName = cx( modalOnTopOfMenuPopover ); return ( <> diff --git a/packages/components/src/menu/types.ts b/packages/components/src/menu/types.ts index 10c68a540765b7..7b58cef241743e 100644 --- a/packages/components/src/menu/types.ts +++ b/packages/components/src/menu/types.ts @@ -17,31 +17,31 @@ export interface MenuContext { export interface MenuProps { /** - * The trigger button. + * The button triggering the menu popover. */ trigger: React.ReactElement; /** - * The contents of the dropdown. + * The contents of the menu (ie. one or more menu items). */ children?: React.ReactNode; /** - * The open state of the dropdown menu when it is initially rendered. Use when + * The open state of the menu popover when it is initially rendered. Use when * not wanting to control its open state. * * @default false */ defaultOpen?: boolean; /** - * The controlled open state of the dropdown menu. Must be used in conjunction + * The controlled open state of the menu popover. Must be used in conjunction * with `onOpenChange`. */ open?: boolean; /** - * Event handler called when the open state of the dropdown menu changes. + * Event handler called when the open state of the menu popover changes. */ onOpenChange?: ( open: boolean ) => void; /** - * The modality of the dropdown menu. When set to true, interaction with + * The modality of the menu popover. When set to true, interaction with * outside elements will be disabled and only menu content will be visible to * screen readers. * @@ -49,7 +49,7 @@ export interface MenuProps { */ modal?: boolean; /** - * The placement of the dropdown menu popover. + * The placement of the menu popover. * * @default 'bottom-start' for root-level menus, 'right-start' for nested menus */ @@ -82,14 +82,15 @@ export interface MenuProps { export interface MenuGroupProps { /** - * The contents of the dropdown menu group. + * The contents of the menu group (ie. an optional menu group label and one + * or more menu items). */ children: React.ReactNode; } export interface MenuGroupLabelProps { /** - * The contents of the dropdown menu group. + * The contents of the menu group label. */ children: React.ReactNode; } @@ -108,7 +109,7 @@ export interface MenuItemProps { */ suffix?: React.ReactNode; /** - * Whether to hide the parent menu when the item is clicked. + * Whether to hide the menu popover when the menu item is clicked. * * @default true */ @@ -122,7 +123,7 @@ export interface MenuItemProps { export interface MenuCheckboxItemProps extends Omit< MenuItemProps, 'prefix' | 'hideOnClick' > { /** - * Whether to hide the dropdown menu when the item is clicked. + * Whether to hide the menu popover when the menu item is clicked. * * @default false */ @@ -154,7 +155,7 @@ export interface MenuCheckboxItemProps export interface MenuRadioItemProps extends Omit< MenuItemProps, 'prefix' | 'hideOnClick' > { /** - * Whether to hide the dropdown menu when the item is clicked. + * Whether to hide the menu popover when the menu item is clicked. * * @default false */ From 4ff28dfb6d90296a239acc1ee58e27144519780a Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 28 Oct 2024 16:59:39 +0100 Subject: [PATCH 7/7] Rename "DropdownMenu" to "Menu" in dataviews --- .../components/dataviews-filters/add-filter.tsx | 4 ++-- .../src/components/dataviews-filters/index.tsx | 4 ++-- .../components/dataviews-item-actions/index.tsx | 14 +++++++------- .../dataviews/src/dataviews-layouts/list/index.tsx | 4 ++-- .../dataviews-layouts/table/column-header-menu.tsx | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/dataviews/src/components/dataviews-filters/add-filter.tsx b/packages/dataviews/src/components/dataviews-filters/add-filter.tsx index 0fc4044c2d3f6b..94aebb71ea5874 100644 --- a/packages/dataviews/src/components/dataviews-filters/add-filter.tsx +++ b/packages/dataviews/src/components/dataviews-filters/add-filter.tsx @@ -28,7 +28,7 @@ interface AddFilterProps { setOpenedFilter: ( filter: string | null ) => void; } -export function AddFilterDropdownMenu( { +export function AddFilterMenu( { filters, view, onChangeView, @@ -77,7 +77,7 @@ function AddFilter( } const inactiveFilters = filters.filter( ( filter ) => ! filter.isVisible ); return ( - extends ActionModalProps< Item > { isBusy?: boolean; } -interface ActionsDropdownMenuGroupProps< Item > { +interface ActionsMenuGroupProps< Item > { actions: Action< Item >[]; item: Item; } @@ -77,7 +77,7 @@ function ButtonTrigger< Item >( { ); } -function DropdownMenuItemTrigger< Item >( { +function MenuItemTrigger< Item >( { action, onClick, items, @@ -146,10 +146,10 @@ export function ActionWithModal< Item >( { ); } -export function ActionsDropdownMenuGroup< Item >( { +export function ActionsMenuGroup< Item >( { actions, item, -}: ActionsDropdownMenuGroupProps< Item > ) { +}: ActionsMenuGroupProps< Item > ) { const registry = useRegistry(); return ( @@ -160,12 +160,12 @@ export function ActionsDropdownMenuGroup< Item >( { key={ action.id } action={ action } items={ [ item ] } - ActionTrigger={ DropdownMenuItemTrigger } + ActionTrigger={ MenuItemTrigger } /> ); } return ( - { @@ -258,7 +258,7 @@ function CompactItemActions< Item >( { } placement="bottom-end" > - + ); } diff --git a/packages/dataviews/src/dataviews-layouts/list/index.tsx b/packages/dataviews/src/dataviews-layouts/list/index.tsx index 1de7660b6bc056..82f9b5ea4d4fc5 100644 --- a/packages/dataviews/src/dataviews-layouts/list/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/list/index.tsx @@ -32,7 +32,7 @@ import { useRegistry } from '@wordpress/data'; */ import { unlock } from '../../lock-unlock'; import { - ActionsDropdownMenuGroup, + ActionsMenuGroup, ActionModal, } from '../../components/dataviews-item-actions'; import type { Action, NormalizedField, ViewListProps } from '../../types'; @@ -215,7 +215,7 @@ function ListItem< Item >( { } placement="bottom-end" > - diff --git a/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx b/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx index 470eb5cf35a75c..7071e54620f369 100644 --- a/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx +++ b/packages/dataviews/src/dataviews-layouts/table/column-header-menu.tsx @@ -40,7 +40,7 @@ interface HeaderMenuProps< Item > { setOpenedFilter: ( fieldId: string ) => void; } -function WithDropDownMenuSeparators( { children }: { children: ReactNode } ) { +function WithMenuSeparators( { children }: { children: ReactNode } ) { return Children.toArray( children ) .filter( Boolean ) .map( ( child, i ) => ( @@ -120,7 +120,7 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item >( } style={ { minWidth: '240px' } } > - + { isSortable && ( { SORTING_DIRECTIONS.map( @@ -246,7 +246,7 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item >( ) } - + ); } );