From 912d7dff5e76dc239cf1d470723defd3fe8f48cf Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 23 Dec 2024 11:43:45 +0100 Subject: [PATCH 1/5] Add best practices page --- .../src/menu/stories/best-practices.mdx | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 packages/components/src/menu/stories/best-practices.mdx diff --git a/packages/components/src/menu/stories/best-practices.mdx b/packages/components/src/menu/stories/best-practices.mdx new file mode 100644 index 0000000000000..148e9c187c600 --- /dev/null +++ b/packages/components/src/menu/stories/best-practices.mdx @@ -0,0 +1,38 @@ +import { Meta } from '@storybook/blocks'; + +import * as MenuStories from './index.story'; + + + +# Menu + +## Usage + +### When to use a `Menu` + +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. + +`Menu` 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. +The elements, which should include one instance of the `Menu.TriggerButton` +component and one instance of the `Menu.Popover` component. -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. + - Type: `ReactNode` + - Required: No -**Do** -Use a `Menu` to display a list of actions after the user interacts with an icon. +### `defaultOpen` -**Don’t** use a `Menu` for important actions that should always be visible. Use a `Toolbar` instead. +Whether the menu popover and its contents should be visible by default. -**Don’t** -Don’t use a `Menu` for frequently used actions. Use a `Toolbar` instead. +Note: this prop will be overridden by the `open` prop if it is +provided (meaning the component will be used in "controlled" mode). -#### Behavior + - Type: `boolean` + - Required: No + - Default: `false` -Generally, the parent button should indicate that interacting with it will show a `Menu`. +### `open` -The parent button should retain the same visual styling regardless of whether the `Menu` is displayed or not. +Whether the menu popover and its contents should be visible. +Should be used in conjunction with `onOpenChange` in order to control +the open state of the menu popover. -#### Placement +Note: this prop will set the component in "controlled" mode, and it will +override the `defaultOpen` prop. -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. + - Type: `boolean` + - Required: No -## Development guidelines +### `onOpenChange` -This component is still highly experimental, and it's not normally accessible to consumers of the `@wordpress/components` package. +A callback that gets called when the `open` state changes. -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. + - Type: `(open: boolean) => void` + - Required: No -### `Menu` +### `placement` -The root component, used to specify the menu's trigger and its contents. +The placement of the menu popover. + + - Type: `"top" | "bottom" | "left" | "right" | "top-start" | "bottom-start" | "left-start" | "right-start" | "top-end" | "bottom-end" | ...` + - Required: No + - Default: `'bottom-start' for root-level menus, 'right-start' for submenus` + +## Subcomponents + +### Menu.TriggerButton #### Props -The component accepts the following props: +##### `accessibleWhenDisabled` -##### `trigger`: `React.ReactNode` +Indicates whether the element should be focusable even when it is +`disabled`. -The button triggering the menu popover. +This is important when discoverability is a concern. For example: -- Required: yes +> A toolbar in an editor contains a set of special smart paste functions +that are disabled when the clipboard is empty or when the function is not +applicable to the current content of the clipboard. It could be helpful to +keep the disabled buttons focusable if the ability to discover their +functionality is primarily via their presence on the toolbar. -##### `children`: `React.ReactNode` +Learn more on [Focusability of disabled +controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols). -The contents of the menu (ie. one or more menu items). + - Type: `boolean` + - Required: No -- Required: yes +##### `children` -##### `defaultOpen`: `boolean` +The contents of the menu trigger button. -The open state of the menu popover when it is initially rendered. Use when not wanting to control its open state. + - Type: `ReactNode` + - Required: No -- Required: no -- Default: `false` +##### `disabled` -##### `open`: `boolean` +Determines if the element is disabled. This sets the `aria-disabled` +attribute accordingly, enabling support for all elements, including those +that don't support the native `disabled` attribute. -The controlled open state of the menu popover. Must be used in conjunction with `onOpenChange`. +This feature can be combined with the `accessibleWhenDisabled` prop to +make disabled elements still accessible via keyboard. -- Required: no + - Type: `boolean` + - Required: No + - Default: `false` -##### `onOpenChange`: `(open: boolean) => void` +##### `render` -Event handler called when the open state of the menu popover changes. +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. -- Required: no + - Type: `ReactElement> | RenderProp & { ref?: Ref; }>` + - Required: No -##### `modal`: `boolean` +### Menu.Popover -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. +#### Props -- Required: no -- Default: `true` +##### `children` -##### `placement`: ``'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'` +The contents of the menu popover, which should include instances of the +`Menu.Item`, `Menu.CheckboxItem`, `Menu.RadioItem`, `Menu.Group`, and +`Menu.Separator` components. -The placement of the menu popover. + - Type: `ReactNode` + - Required: No + +##### `gutter` + +The distance between the popover and the anchor element. + + - Type: `number` + - Required: No + - Default: `8 for root-level menus, 16 for nested menus` + +##### `hideOnEscape` + +Determines if the menu popover will hide when the user presses the +Escape key. + +This prop can be either a boolean or a function that accepts an event as an +argument and returns a boolean. The event object represents the keydown +event that initiated the hide action, which could be either a native +keyboard event or a React synthetic event. -- Required: no -- Default: `'bottom-start'` for root-level menus, `'right-start'` for nested menus + - Type: `BooleanOrCallback>` + - Required: No + - Default: ``( event ) => { event.preventDefault(); return true; }`` -##### `gutter`: `number` +##### `modal` -The distance in pixels from the trigger. +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: `8` for root-level menus, `16` for nested menus +Determines whether the menu popover is modal. Modal dialogs have distinct +states and behaviors: +- The `portal` and `preventBodyScroll` props are set to `true`. They can + still be manually set to `false`. +- When the dialog is open, element tree outside it will be inert. -##### `shift`: `number` + - Type: `boolean` + - Required: No + - Default: `true` -The skidding of the popover along the anchor element. Can be set to negative values to make the popover shift to the opposite side. +##### `shift` -- Required: no -- Default: `0` for root-level menus, `-8` for nested menus +The skidding of the popover along the anchor element. Can be set to +negative values to make the popover shift to the opposite side. -### `Menu.Item` + - Type: `number` + - Required: No + - Default: `0 for root-level menus, -8 for nested menus` -Used to render a menu item. +### Menu.Item #### Props -The component accepts the following props: +##### `children` -##### `children`: `React.ReactNode` +The contents of the menu item, which could include one instance of the +`Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` +component. -The contents of the item + - Type: `ReactNode` + - Required: Yes -- Required: yes +##### `disabled` -##### `prefix`: `React.ReactNode` +Determines if the element is disabled. This sets the `aria-disabled` +attribute accordingly, enabling support for all elements, including those +that don't support the native `disabled` attribute. -The contents of the item's prefix. + - Type: `boolean` + - Required: No + - Default: `false` -- Required: no +##### `hideOnClick` -##### `suffix`: `React.ReactNode` +Determines if the menu should hide when this item is clicked. -The contents of the item's suffix. +**Note**: This behavior isn't triggered if this menu item is rendered as a +link and modifier keys are used to either open the link in a new tab or +download it. -- Required: no + - Type: `BooleanOrCallback>` + - Required: No + - Default: `true` -##### `hideOnClick`: `boolean` +##### `prefix` -Whether to hide the menu popover when the menu item is clicked. +The contents of the menu item's prefix, such as an icon. -- Required: no -- Default: `true` + - Type: `ReactNode` + - Required: No -##### `disabled`: `boolean` +##### `render` -Determines if the element is disabled. +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. -- Required: no -- Default: `false` + - Type: `ReactElement> | RenderProp & { ref?: Ref; }>` + - Required: No -### `Menu.CheckboxItem` +##### `suffix` -Used to render a checkbox item. +The contents of the menu item's suffix, such as a keyboard shortcut. + + - Type: `ReactNode` + - Required: No + +### Menu.RadioItem #### Props -The component accepts the following props: +##### `children` -##### `children`: `React.ReactNode` +The contents of the menu item, which could include one instance of the +`Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` +component. -The contents of the item + - Type: `ReactNode` + - Required: Yes -- Required: yes +##### `checked` -##### `suffix`: `React.ReactNode` +The controlled checked state of the radio menu item. -The contents of the item's suffix. +Note: this prop will override the `defaultChecked` prop. -- Required: no + - Type: `boolean` + - Required: No -##### `hideOnClick`: `boolean` +##### `disabled` -Whether to hide the menu popover when the menu item is clicked. +Determines if the element is disabled. This sets the `aria-disabled` +attribute accordingly, enabling support for all elements, including those +that don't support the native `disabled` attribute. -- Required: no -- Default: `false` + - Type: `boolean` + - Required: No + - Default: `false` -##### `disabled`: `boolean` +##### `defaultChecked` -Determines if the element is disabled. +The checked state of the radio menu item when it is initially rendered. +Use when not wanting to control its checked state. -- Required: no -- Default: `false` +Note: this prop will be overriden by the `checked` prop, if it is defined. -##### `name`: `string` + - Type: `boolean` + - Required: No -The checkbox item's name. +##### `hideOnClick` -- Required: yes +Determines if the menu should hide when this item is clicked. -##### `value`: `string` +**Note**: This behavior isn't triggered if this menu item is rendered as a +link and modifier keys are used to either open the link in a new tab or +download it. -The checkbox item's value, useful when using multiple checkbox items -associated to the same `name`. + - Type: `BooleanOrCallback>` + - Required: No + - Default: `false` -- Required: no +##### `name` -##### `checked`: `boolean` +The radio item's name. -The checkbox item's value, useful when using multiple checkbox items -associated to the same `name`. + - Type: `string` + - Required: Yes + +##### `onChange` -- Required: no +A function that is called when the checkbox's checked state changes. -##### `defaultChecked`: `boolean` + - Type: `BivariantCallback<(event: ChangeEvent) => void>` + - Required: No -The checked state of the checkbox menu item when it is initially rendered. Use when not wanting to control its checked state. +##### `render` -- Required: no +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. -##### `onChange`: `( event: React.ChangeEvent< HTMLInputElement > ) => void;` + - Type: `ReactElement> | RenderProp & { ref?: Ref; }>` + - Required: No -Event handler called when the checked state of the checkbox menu item changes. +##### `suffix` -- Required: no +The contents of the menu item's suffix, such as a keyboard shortcut. -### `Menu.RadioItem` + - Type: `ReactNode` + - Required: No -Used to render a radio item. +##### `value` + +The radio item's value. + + - Type: `string | number` + - Required: Yes + +### Menu.CheckboxItem #### Props -The component accepts the following props: +##### `children` -##### `children`: `React.ReactNode` +The contents of the menu item, which could include one instance of the +`Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` +component. -The contents of the item + - Type: `ReactNode` + - Required: Yes -- Required: yes +##### `checked` -##### `suffix`: `React.ReactNode` +The controlled checked state of the checkbox menu item. -The contents of the item's suffix. +Note: this prop will override the `defaultChecked` prop. -- Required: no + - Type: `boolean` + - Required: No -##### `hideOnClick`: `boolean` +##### `disabled` -Whether to hide the menu popover when the menu item is clicked. +Determines if the element is disabled. This sets the `aria-disabled` +attribute accordingly, enabling support for all elements, including those +that don't support the native `disabled` attribute. -- Required: no -- Default: `false` + - Type: `boolean` + - Required: No + - Default: `false` -##### `disabled`: `boolean` +##### `defaultChecked` -Determines if the element is disabled. +The checked state of the checkbox menu item when it is initially rendered. +Use when not wanting to control its checked state. -- Required: no -- Default: `false` +Note: this prop will be overriden by the `checked` prop, if it is defined. -##### `name`: `string` + - Type: `boolean` + - Required: No -The radio item's name. +##### `hideOnClick` -- Required: yes +Determines if the menu should hide when this item is clicked. -##### `value`: `string | number` +**Note**: This behavior isn't triggered if this menu item is rendered as a +link and modifier keys are used to either open the link in a new tab or +download it. -The radio item's value. + - Type: `BooleanOrCallback>` + - Required: No + - Default: `false` -- Required: yes +##### `name` -##### `checked`: `boolean` +The checkbox menu item's name. -The checkbox item's value, useful when using multiple checkbox items -associated to the same `name`. + - Type: `string` + - Required: Yes -- Required: no +##### `onChange` -##### `defaultChecked`: `boolean` +A function that is called when the checkbox's checked state changes. -The checked state of the radio menu item when it is initially rendered. Use when not wanting to control its checked state. + - Type: `ChangeEventHandler` + - Required: No -- Required: no +##### `render` -##### `onChange`: `( event: React.ChangeEvent< HTMLInputElement > ) => void;` +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. -Event handler called when the checked radio menu item changes. + - Type: `ReactElement> | RenderProp & { ref?: Ref; }>` + - Required: No -- Required: no +##### `suffix` -### `Menu.ItemLabel` +The contents of the menu item's suffix, such as a keyboard shortcut. -Used to render the menu item's label. + - Type: `ReactNode` + - Required: No -#### Props +##### `value` + +The checkbox item's value, useful when using multiple checkbox menu items +associated to the same `name`. + + - Type: `string | number | readonly string[]` + - Required: No -The component accepts the following props: +### Menu.ItemLabel -##### `children`: `React.ReactNode` +#### Props -The label contents. +##### `as` -- Required: yes +The HTML element or React component to render the component as. -### `Menu.ItemHelpText` + - Type: `"symbol" | "object" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | ...` + - Required: No -Used to render the menu item's help text. +### Menu.ItemHelpText #### Props -The component accepts the following props: +##### `as` -##### `children`: `React.ReactNode` +The HTML element or React component to render the component as. -The help text contents. + - Type: `"symbol" | "object" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | ...` + - Required: No -- Required: yes +### Menu.Group -### `Menu.Group` +#### Props + +##### `children` + +The contents of the menu group, which should include one instance of the +`Menu.GroupLabel` component and one or more instances of `Menu.Item`, +`Menu.CheckboxItem`, and `Menu.RadioItem`. -Used to group menu items. + - Type: `ReactNode` + - Required: Yes + +### Menu.GroupLabel #### Props -The component accepts the following props: +##### `children` -##### `children`: `React.ReactNode` +The contents of the menu group label, which should provide an accessible +label for the menu group. -The contents of the menu group (ie. an optional menu group label and one or more menu items). + - Type: `ReactNode` + - Required: Yes -- Required: yes +### Menu.Separator -### `Menu.GroupLabel` +#### Props -Used to render a group label. The label text should be kept as short as possible. +### Menu.SubmenuTriggerItem #### Props -The component accepts the following props: +##### `children` + +The contents of the menu item, which could include one instance of the +`Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` +component. + + - Type: `ReactNode` + - Required: Yes + +##### `disabled` + +Determines if the element is disabled. This sets the `aria-disabled` +attribute accordingly, enabling support for all elements, including those +that don't support the native `disabled` attribute. + + - Type: `boolean` + - Required: No + - Default: `false` + +##### `hideOnClick` + +Determines if the menu should hide when this item is clicked. + +**Note**: This behavior isn't triggered if this menu item is rendered as a +link and modifier keys are used to either open the link in a new tab or +download it. + + - Type: `BooleanOrCallback>` + - Required: No + - Default: `true` + +##### `prefix` + +The contents of the menu item's prefix, such as an icon. + + - Type: `ReactNode` + - Required: No + +##### `render` -##### `children`: `React.ReactNode` +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. -The contents of the menu group label. + - Type: `ReactElement> | RenderProp & { ref?: Ref; }>` + - Required: No -- Required: yes +##### `suffix` -### `Menu.Separator` +The contents of the menu item's suffix, such as a keyboard shortcut. -Used to render a visual separator. + - Type: `ReactNode` + - Required: No diff --git a/packages/components/src/menu/docs-manifest.json b/packages/components/src/menu/docs-manifest.json new file mode 100644 index 0000000000000..50e92189a3999 --- /dev/null +++ b/packages/components/src/menu/docs-manifest.json @@ -0,0 +1,62 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "Menu", + "filePath": "./index.tsx", + "subcomponents": [ + { + "displayName": "MenuTriggerButton", + "preferredDisplayName": "Menu.TriggerButton", + "filePath": "./trigger-button.tsx" + }, + { + "displayName": "MenuPopover", + "preferredDisplayName": "Menu.Popover", + "filePath": "./popover.tsx" + }, + { + "displayName": "MenuItem", + "preferredDisplayName": "Menu.Item", + "filePath": "./item.tsx" + }, + { + "displayName": "MenuRadioItem", + "preferredDisplayName": "Menu.RadioItem", + "filePath": "./radio-item.tsx" + }, + { + "displayName": "MenuCheckboxItem", + "preferredDisplayName": "Menu.CheckboxItem", + "filePath": "./checkbox-item.tsx" + }, + { + "displayName": "MenuItemLabel", + "preferredDisplayName": "Menu.ItemLabel", + "filePath": "./item-label.tsx" + }, + { + "displayName": "MenuItemHelpText", + "preferredDisplayName": "Menu.ItemHelpText", + "filePath": "./item-help-text.tsx" + }, + { + "displayName": "MenuGroup", + "preferredDisplayName": "Menu.Group", + "filePath": "./group.tsx" + }, + { + "displayName": "MenuGroupLabel", + "preferredDisplayName": "Menu.GroupLabel", + "filePath": "./group-label.tsx" + }, + { + "displayName": "MenuSeparator", + "preferredDisplayName": "Menu.Separator", + "filePath": "./separator.tsx" + }, + { + "displayName": "MenuSubmenuTriggerItem", + "preferredDisplayName": "Menu.SubmenuTriggerItem", + "filePath": "./submenu-trigger-item.tsx" + } + ] +} From 6edb5da357ca7a9597d084d90fdb7141941fa212 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 23 Dec 2024 15:18:02 +0100 Subject: [PATCH 3/5] Remove "experimental" from Storybook id + redirect --- packages/components/src/menu/stories/index.story.tsx | 2 +- storybook/manager-head.html | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/components/src/menu/stories/index.story.tsx b/packages/components/src/menu/stories/index.story.tsx index 37ebb6f905dc8..666d607d57b01 100644 --- a/packages/components/src/menu/stories/index.story.tsx +++ b/packages/components/src/menu/stories/index.story.tsx @@ -23,7 +23,7 @@ import { ContextSystemProvider } from '../../context'; import type { MenuProps } from '../types'; const meta: Meta< typeof Menu > = { - id: 'components-experimental-menu', + id: 'components-menu', title: 'Components (Experimental)/Actions/Menu', component: Menu, subcomponents: { diff --git a/storybook/manager-head.html b/storybook/manager-head.html index d3f156a6eb788..a4f6941e98111 100644 --- a/storybook/manager-head.html +++ b/storybook/manager-head.html @@ -7,6 +7,7 @@ 'boxcontrol', 'customselectcontrol-v2', 'dimensioncontrol', + 'menu', 'navigation', 'navigator', 'progressbar', From 78909c6aaf4ea35ccff72243d0a0e59b715a3616 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 23 Dec 2024 16:47:52 +0100 Subject: [PATCH 4/5] Add JS Docs --- packages/components/src/menu/README.md | 9 +++ packages/components/src/menu/index.tsx | 82 ++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/packages/components/src/menu/README.md b/packages/components/src/menu/README.md index 575037db2b1ab..a0fe52d060b2a 100644 --- a/packages/components/src/menu/README.md +++ b/packages/components/src/menu/README.md @@ -4,6 +4,15 @@

See the WordPress Storybook for more detailed, interactive documentation.

+Menu is a collection of React components that combine to render +ARIA-compliant [menu](https://www.w3.org/WAI/ARIA/apg/patterns/menu/) and +[menu button](https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/) patterns. + +`Menu` itself is a wrapper component and context provider. +It is responsible for managing the state of the menu and its items, and for +rendering the `Menu.TriggerButton` (or the `Menu.SubmenuTriggerItem`) +component, and the `Menu.Popover` component. + ## Props ### `as` diff --git a/packages/components/src/menu/index.tsx b/packages/components/src/menu/index.tsx index 2e0fc91cfbc34..4a99533c9d07c 100644 --- a/packages/components/src/menu/index.tsx +++ b/packages/components/src/menu/index.tsx @@ -27,6 +27,16 @@ import { MenuTriggerButton } from './trigger-button'; import { MenuSubmenuTriggerItem } from './submenu-trigger-item'; import { MenuPopover } from './popover'; +/** + * Menu is a collection of React components that combine to render + * ARIA-compliant [menu](https://www.w3.org/WAI/ARIA/apg/patterns/menu/) and + * [menu button](https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/) patterns. + * + * `Menu` itself is a wrapper component and context provider. + * It is responsible for managing the state of the menu and its items, and for + * rendering the `Menu.TriggerButton` (or the `Menu.SubmenuTriggerItem`) + * component, and the `Menu.Popover` component. + */ const UnconnectedMenu = ( props: MenuProps ) => { const { children, @@ -90,42 +100,114 @@ const UnconnectedMenu = ( props: MenuProps ) => { ); }; +/** + * Menu is a collection of React components that combine to render + * ARIA-compliant [menu](https://www.w3.org/WAI/ARIA/apg/patterns/menu/) and + * [menu button](https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/) patterns. + * + * `Menu` itself is a wrapper component and context provider. + * It is responsible for managing the state of the menu and its items, and for + * rendering the `Menu.TriggerButton` (or the `Menu.SubmenuTriggerItem`) + * component, and the `Menu.Popover` component. + */ export const Menu = Object.assign( contextConnectWithoutRef( UnconnectedMenu, 'Menu' ), { Context: Object.assign( MenuContext, { displayName: 'Menu.Context', } ), + /** + * Renders a menu item inside the `Menu.Popover` or `Menu.Group` components. + * + * It can optionally contain one instance of the `Menu.ItemLabel` component + * and one instance of the `Menu.ItemHelpText` component. + */ Item: Object.assign( MenuItem, { displayName: 'Menu.Item', } ), + /** + * Renders a radio menu item inside the `Menu.Popover` or `Menu.Group` + * components. + * + * It can optionally contain one instance of the `Menu.ItemLabel` component + * and one instance of the `Menu.ItemHelpText` component. + */ RadioItem: Object.assign( MenuRadioItem, { displayName: 'Menu.RadioItem', } ), + /** + * Renders a checkbox menu item inside the `Menu.Popover` or `Menu.Group` + * components. + * + * It can optionally contain one instance of the `Menu.ItemLabel` component + * and one instance of the `Menu.ItemHelpText` component. + */ CheckboxItem: Object.assign( MenuCheckboxItem, { displayName: 'Menu.CheckboxItem', } ), + /** + * Renders a group for menu items. + * + * It should contain one instance of `Menu.GroupLabel` and one or more + * instances of `Menu.Item`, `Menu.RadioItem`, or `Menu.CheckboxItem`. + */ Group: Object.assign( MenuGroup, { displayName: 'Menu.Group', } ), + /** + * Renders a label in a menu group. + * + * This component should be wrapped with `Menu.Group` so the + * `aria-labelledby` is correctly set on the group element. + */ GroupLabel: Object.assign( MenuGroupLabel, { displayName: 'Menu.GroupLabel', } ), + /** + * Renders a divider between menu items or menu groups. + */ Separator: Object.assign( MenuSeparator, { displayName: 'Menu.Separator', } ), + /** + * Renders a menu item's label text. It should be wrapped with `Menu.Item`, + * `Menu.RadioItem`, or `Menu.CheckboxItem`. + */ ItemLabel: Object.assign( MenuItemLabel, { displayName: 'Menu.ItemLabel', } ), + /** + * Renders a menu item's help text. It should be wrapped with `Menu.Item`, + * `Menu.RadioItem`, or `Menu.CheckboxItem`. + */ ItemHelpText: Object.assign( MenuItemHelpText, { displayName: 'Menu.ItemHelpText', } ), + /** + * Renders a dropdown menu element that's controlled by a sibling + * `Menu.TriggerButton` component. It renders a popover and automatically + * focuses on items when the menu is shown. + * + * The only valid children of `Menu.Popover` are `Menu.Item`, + * `Menu.RadioItem`, `Menu.CheckboxItem`, `Menu.Group`, `Menu.Separator`, + * and `Menu` (for nested dropdown menus). + */ Popover: Object.assign( MenuPopover, { displayName: 'Menu.Popover', } ), + /** + * Renders a menu button that toggles the visibility of a sibling + * `Menu.Popover` component when clicked or when using arrow keys. + */ TriggerButton: Object.assign( MenuTriggerButton, { displayName: 'Menu.TriggerButton', } ), + /** + * Renders a menu item that toggles the visibility of a sibling + * `Menu.Popover` component when clicked or when using arrow keys. + * + * This component is used to create a nested dropdown menu. + */ SubmenuTriggerItem: Object.assign( MenuSubmenuTriggerItem, { displayName: 'Menu.SubmenuTriggerItem', } ), From 364338ab1c494989e9c7b606078114bc9599417e Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Mon, 23 Dec 2024 17:08:46 +0100 Subject: [PATCH 5/5] Rename subcomponents to get their JSDocs picked up by README generator --- packages/components/src/menu/README.md | 51 ++++++++++++++++ .../components/src/menu/checkbox-item.tsx | 24 ++++---- packages/components/src/menu/context.tsx | 6 +- .../components/src/menu/docs-manifest.json | 22 +++---- packages/components/src/menu/group-label.tsx | 14 ++--- packages/components/src/menu/group.tsx | 18 +++--- packages/components/src/menu/index.tsx | 60 +++++++++---------- .../components/src/menu/item-help-text.tsx | 12 ++-- packages/components/src/menu/item-label.tsx | 12 ++-- packages/components/src/menu/item.tsx | 24 ++++---- packages/components/src/menu/popover.tsx | 18 +++--- packages/components/src/menu/radio-item.tsx | 24 ++++---- packages/components/src/menu/separator.tsx | 14 ++--- .../src/menu/stories/index.story.tsx | 14 ++--- packages/components/src/menu/styles.ts | 48 +++++++-------- .../src/menu/submenu-trigger-item.tsx | 18 +++--- .../components/src/menu/trigger-button.tsx | 12 ++-- packages/components/src/menu/types.ts | 20 +++---- 18 files changed, 225 insertions(+), 186 deletions(-) diff --git a/packages/components/src/menu/README.md b/packages/components/src/menu/README.md index a0fe52d060b2a..b34e21696459d 100644 --- a/packages/components/src/menu/README.md +++ b/packages/components/src/menu/README.md @@ -72,6 +72,9 @@ The placement of the menu popover. ### Menu.TriggerButton +Renders a menu button that toggles the visibility of a sibling +`Menu.Popover` component when clicked or when using arrow keys. + #### Props ##### `accessibleWhenDisabled` @@ -125,6 +128,14 @@ merged. ### Menu.Popover +Renders a dropdown menu element that's controlled by a sibling +`Menu.TriggerButton` component. It renders a popover and automatically +focuses on items when the menu is shown. + +The only valid children of `Menu.Popover` are `Menu.Item`, +`Menu.RadioItem`, `Menu.CheckboxItem`, `Menu.Group`, `Menu.Separator`, +and `Menu` (for nested dropdown menus). + #### Props ##### `children` @@ -185,6 +196,11 @@ negative values to make the popover shift to the opposite side. ### Menu.Item +Renders a menu item inside the `Menu.Popover` or `Menu.Group` components. + +It can optionally contain one instance of the `Menu.ItemLabel` component +and one instance of the `Menu.ItemHelpText` component. + #### Props ##### `children` @@ -244,6 +260,12 @@ The contents of the menu item's suffix, such as a keyboard shortcut. ### Menu.RadioItem +Renders a radio menu item inside the `Menu.Popover` or `Menu.Group` +components. + +It can optionally contain one instance of the `Menu.ItemLabel` component +and one instance of the `Menu.ItemHelpText` component. + #### Props ##### `children` @@ -336,6 +358,12 @@ The radio item's value. ### Menu.CheckboxItem +Renders a checkbox menu item inside the `Menu.Popover` or `Menu.Group` +components. + +It can optionally contain one instance of the `Menu.ItemLabel` component +and one instance of the `Menu.ItemHelpText` component. + #### Props ##### `children` @@ -429,6 +457,9 @@ associated to the same `name`. ### Menu.ItemLabel +Renders a menu item's label text. It should be wrapped with `Menu.Item`, +`Menu.RadioItem`, or `Menu.CheckboxItem`. + #### Props ##### `as` @@ -440,6 +471,9 @@ The HTML element or React component to render the component as. ### Menu.ItemHelpText +Renders a menu item's help text. It should be wrapped with `Menu.Item`, +`Menu.RadioItem`, or `Menu.CheckboxItem`. + #### Props ##### `as` @@ -451,6 +485,11 @@ The HTML element or React component to render the component as. ### Menu.Group +Renders a group for menu items. + +It should contain one instance of `Menu.GroupLabel` and one or more +instances of `Menu.Item`, `Menu.RadioItem`, or `Menu.CheckboxItem`. + #### Props ##### `children` @@ -464,6 +503,11 @@ The contents of the menu group, which should include one instance of the ### Menu.GroupLabel +Renders a label in a menu group. + +This component should be wrapped with `Menu.Group` so the +`aria-labelledby` is correctly set on the group element. + #### Props ##### `children` @@ -476,10 +520,17 @@ label for the menu group. ### Menu.Separator +Renders a divider between menu items or menu groups. + #### Props ### Menu.SubmenuTriggerItem +Renders a menu item that toggles the visibility of a sibling +`Menu.Popover` component when clicked or when using arrow keys. + +This component is used to create a nested dropdown menu. + #### Props ##### `children` diff --git a/packages/components/src/menu/checkbox-item.tsx b/packages/components/src/menu/checkbox-item.tsx index 69339387c3add..a3ae4d7708598 100644 --- a/packages/components/src/menu/checkbox-item.tsx +++ b/packages/components/src/menu/checkbox-item.tsx @@ -13,18 +13,18 @@ import { Icon, check } from '@wordpress/icons'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { MenuContext } from './context'; -import type { MenuCheckboxItemProps } from './types'; +import { Context } from './context'; +import type { CheckboxItemProps } from './types'; import * as Styled from './styles'; -export const MenuCheckboxItem = forwardRef< +export const CheckboxItem = forwardRef< HTMLDivElement, - WordPressComponentProps< MenuCheckboxItemProps, 'div', false > ->( function MenuCheckboxItem( + WordPressComponentProps< CheckboxItemProps, 'div', false > +>( function CheckboxItem( { suffix, children, disabled = false, hideOnClick = false, ...props }, ref ) { - const menuContext = useContext( MenuContext ); + const menuContext = useContext( Context ); if ( ! menuContext?.store ) { throw new Error( @@ -33,7 +33,7 @@ export const MenuCheckboxItem = forwardRef< } return ( - - - + + { children } - + { suffix && ( { suffix } ) } - - + + ); } ); diff --git a/packages/components/src/menu/context.tsx b/packages/components/src/menu/context.tsx index 1205015c57cbe..fa38f2c75aea6 100644 --- a/packages/components/src/menu/context.tsx +++ b/packages/components/src/menu/context.tsx @@ -6,8 +6,6 @@ import { createContext } from '@wordpress/element'; /** * Internal dependencies */ -import type { MenuContext as MenuContextType } from './types'; +import type { ContextProps } from './types'; -export const MenuContext = createContext< MenuContextType | undefined >( - undefined -); +export const Context = createContext< ContextProps | undefined >( undefined ); diff --git a/packages/components/src/menu/docs-manifest.json b/packages/components/src/menu/docs-manifest.json index 50e92189a3999..c47fd97e8e09f 100644 --- a/packages/components/src/menu/docs-manifest.json +++ b/packages/components/src/menu/docs-manifest.json @@ -4,57 +4,57 @@ "filePath": "./index.tsx", "subcomponents": [ { - "displayName": "MenuTriggerButton", + "displayName": "TriggerButton", "preferredDisplayName": "Menu.TriggerButton", "filePath": "./trigger-button.tsx" }, { - "displayName": "MenuPopover", + "displayName": "Popover", "preferredDisplayName": "Menu.Popover", "filePath": "./popover.tsx" }, { - "displayName": "MenuItem", + "displayName": "Item", "preferredDisplayName": "Menu.Item", "filePath": "./item.tsx" }, { - "displayName": "MenuRadioItem", + "displayName": "RadioItem", "preferredDisplayName": "Menu.RadioItem", "filePath": "./radio-item.tsx" }, { - "displayName": "MenuCheckboxItem", + "displayName": "CheckboxItem", "preferredDisplayName": "Menu.CheckboxItem", "filePath": "./checkbox-item.tsx" }, { - "displayName": "MenuItemLabel", + "displayName": "ItemLabel", "preferredDisplayName": "Menu.ItemLabel", "filePath": "./item-label.tsx" }, { - "displayName": "MenuItemHelpText", + "displayName": "ItemHelpText", "preferredDisplayName": "Menu.ItemHelpText", "filePath": "./item-help-text.tsx" }, { - "displayName": "MenuGroup", + "displayName": "Group", "preferredDisplayName": "Menu.Group", "filePath": "./group.tsx" }, { - "displayName": "MenuGroupLabel", + "displayName": "GroupLabel", "preferredDisplayName": "Menu.GroupLabel", "filePath": "./group-label.tsx" }, { - "displayName": "MenuSeparator", + "displayName": "Separator", "preferredDisplayName": "Menu.Separator", "filePath": "./separator.tsx" }, { - "displayName": "MenuSubmenuTriggerItem", + "displayName": "SubmenuTriggerItem", "preferredDisplayName": "Menu.SubmenuTriggerItem", "filePath": "./submenu-trigger-item.tsx" } diff --git a/packages/components/src/menu/group-label.tsx b/packages/components/src/menu/group-label.tsx index 5bf081880cb1d..ce6ecb06900d0 100644 --- a/packages/components/src/menu/group-label.tsx +++ b/packages/components/src/menu/group-label.tsx @@ -7,16 +7,16 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { MenuContext } from './context'; +import { Context } from './context'; import { Text } from '../text'; -import type { MenuGroupLabelProps } from './types'; +import type { GroupLabelProps } from './types'; import * as Styled from './styles'; -export const MenuGroupLabel = forwardRef< +export const GroupLabel = forwardRef< HTMLDivElement, - WordPressComponentProps< MenuGroupLabelProps, 'div', false > ->( function MenuGroup( props, ref ) { - const menuContext = useContext( MenuContext ); + WordPressComponentProps< GroupLabelProps, 'div', false > +>( function Group( props, ref ) { + const menuContext = useContext( Context ); if ( ! menuContext?.store ) { throw new Error( @@ -25,7 +25,7 @@ export const MenuGroupLabel = forwardRef< } return ( - ->( function MenuGroup( props, ref ) { - const menuContext = useContext( MenuContext ); + WordPressComponentProps< GroupProps, 'div', false > +>( function Group( props, ref ) { + const menuContext = useContext( Context ); if ( ! menuContext?.store ) { throw new Error( @@ -24,10 +24,6 @@ export const MenuGroup = forwardRef< } return ( - + ); } ); diff --git a/packages/components/src/menu/index.tsx b/packages/components/src/menu/index.tsx index 4a99533c9d07c..0af9468a198bc 100644 --- a/packages/components/src/menu/index.tsx +++ b/packages/components/src/menu/index.tsx @@ -13,19 +13,19 @@ import { isRTL as isRTLFn } from '@wordpress/i18n'; * Internal dependencies */ import { useContextSystem, contextConnectWithoutRef } from '../context'; -import type { MenuContext as MenuContextType, MenuProps } from './types'; -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'; -import { MenuTriggerButton } from './trigger-button'; -import { MenuSubmenuTriggerItem } from './submenu-trigger-item'; -import { MenuPopover } from './popover'; +import type { ContextProps, Props } from './types'; +import { Context } from './context'; +import { Item } from './item'; +import { CheckboxItem } from './checkbox-item'; +import { RadioItem } from './radio-item'; +import { Group } from './group'; +import { GroupLabel } from './group-label'; +import { Separator } from './separator'; +import { ItemLabel } from './item-label'; +import { ItemHelpText } from './item-help-text'; +import { TriggerButton } from './trigger-button'; +import { SubmenuTriggerItem } from './submenu-trigger-item'; +import { Popover } from './popover'; /** * Menu is a collection of React components that combine to render @@ -37,7 +37,7 @@ import { MenuPopover } from './popover'; * rendering the `Menu.TriggerButton` (or the `Menu.SubmenuTriggerItem`) * component, and the `Menu.Popover` component. */ -const UnconnectedMenu = ( props: MenuProps ) => { +const UnconnectedMenu = ( props: Props ) => { const { children, defaultOpen = false, @@ -49,10 +49,10 @@ const UnconnectedMenu = ( props: MenuProps ) => { variant, } = useContextSystem< // @ts-expect-error TODO: missing 'className' in MenuProps - typeof props & Pick< MenuContextType, 'variant' > + typeof props & Pick< ContextProps, 'variant' > >( props, 'Menu' ); - const parentContext = useContext( MenuContext ); + const parentContext = useContext( Context ); const rtl = isRTLFn(); @@ -94,9 +94,7 @@ const UnconnectedMenu = ( props: MenuProps ) => { ); return ( - - { children } - + { children } ); }; @@ -113,7 +111,7 @@ const UnconnectedMenu = ( props: MenuProps ) => { export const Menu = Object.assign( contextConnectWithoutRef( UnconnectedMenu, 'Menu' ), { - Context: Object.assign( MenuContext, { + Context: Object.assign( Context, { displayName: 'Menu.Context', } ), /** @@ -122,7 +120,7 @@ export const Menu = Object.assign( * It can optionally contain one instance of the `Menu.ItemLabel` component * and one instance of the `Menu.ItemHelpText` component. */ - Item: Object.assign( MenuItem, { + Item: Object.assign( Item, { displayName: 'Menu.Item', } ), /** @@ -132,7 +130,7 @@ export const Menu = Object.assign( * It can optionally contain one instance of the `Menu.ItemLabel` component * and one instance of the `Menu.ItemHelpText` component. */ - RadioItem: Object.assign( MenuRadioItem, { + RadioItem: Object.assign( RadioItem, { displayName: 'Menu.RadioItem', } ), /** @@ -142,7 +140,7 @@ export const Menu = Object.assign( * It can optionally contain one instance of the `Menu.ItemLabel` component * and one instance of the `Menu.ItemHelpText` component. */ - CheckboxItem: Object.assign( MenuCheckboxItem, { + CheckboxItem: Object.assign( CheckboxItem, { displayName: 'Menu.CheckboxItem', } ), /** @@ -151,7 +149,7 @@ export const Menu = Object.assign( * It should contain one instance of `Menu.GroupLabel` and one or more * instances of `Menu.Item`, `Menu.RadioItem`, or `Menu.CheckboxItem`. */ - Group: Object.assign( MenuGroup, { + Group: Object.assign( Group, { displayName: 'Menu.Group', } ), /** @@ -160,27 +158,27 @@ export const Menu = Object.assign( * This component should be wrapped with `Menu.Group` so the * `aria-labelledby` is correctly set on the group element. */ - GroupLabel: Object.assign( MenuGroupLabel, { + GroupLabel: Object.assign( GroupLabel, { displayName: 'Menu.GroupLabel', } ), /** * Renders a divider between menu items or menu groups. */ - Separator: Object.assign( MenuSeparator, { + Separator: Object.assign( Separator, { displayName: 'Menu.Separator', } ), /** * Renders a menu item's label text. It should be wrapped with `Menu.Item`, * `Menu.RadioItem`, or `Menu.CheckboxItem`. */ - ItemLabel: Object.assign( MenuItemLabel, { + ItemLabel: Object.assign( ItemLabel, { displayName: 'Menu.ItemLabel', } ), /** * Renders a menu item's help text. It should be wrapped with `Menu.Item`, * `Menu.RadioItem`, or `Menu.CheckboxItem`. */ - ItemHelpText: Object.assign( MenuItemHelpText, { + ItemHelpText: Object.assign( ItemHelpText, { displayName: 'Menu.ItemHelpText', } ), /** @@ -192,14 +190,14 @@ export const Menu = Object.assign( * `Menu.RadioItem`, `Menu.CheckboxItem`, `Menu.Group`, `Menu.Separator`, * and `Menu` (for nested dropdown menus). */ - Popover: Object.assign( MenuPopover, { + Popover: Object.assign( Popover, { displayName: 'Menu.Popover', } ), /** * Renders a menu button that toggles the visibility of a sibling * `Menu.Popover` component when clicked or when using arrow keys. */ - TriggerButton: Object.assign( MenuTriggerButton, { + TriggerButton: Object.assign( TriggerButton, { displayName: 'Menu.TriggerButton', } ), /** @@ -208,7 +206,7 @@ export const Menu = Object.assign( * * This component is used to create a nested dropdown menu. */ - SubmenuTriggerItem: Object.assign( MenuSubmenuTriggerItem, { + SubmenuTriggerItem: Object.assign( SubmenuTriggerItem, { displayName: 'Menu.SubmenuTriggerItem', } ), } diff --git a/packages/components/src/menu/item-help-text.tsx b/packages/components/src/menu/item-help-text.tsx index 13d14c294125b..e47c54d702342 100644 --- a/packages/components/src/menu/item-help-text.tsx +++ b/packages/components/src/menu/item-help-text.tsx @@ -7,14 +7,14 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { MenuContext } from './context'; +import { Context } from './context'; import * as Styled from './styles'; -export const MenuItemHelpText = forwardRef< +export const ItemHelpText = forwardRef< HTMLSpanElement, WordPressComponentProps< { children: React.ReactNode }, 'span', true > ->( function MenuItemHelpText( props, ref ) { - const menuContext = useContext( MenuContext ); +>( function ItemHelpText( props, ref ) { + const menuContext = useContext( Context ); if ( ! menuContext?.store ) { throw new Error( @@ -22,7 +22,5 @@ export const MenuItemHelpText = forwardRef< ); } - return ( - - ); + return ; } ); diff --git a/packages/components/src/menu/item-label.tsx b/packages/components/src/menu/item-label.tsx index 4f5f80e547861..3a3367f4b481f 100644 --- a/packages/components/src/menu/item-label.tsx +++ b/packages/components/src/menu/item-label.tsx @@ -7,14 +7,14 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { MenuContext } from './context'; +import { Context } from './context'; import * as Styled from './styles'; -export const MenuItemLabel = forwardRef< +export const ItemLabel = forwardRef< HTMLSpanElement, WordPressComponentProps< { children: React.ReactNode }, 'span', true > ->( function MenuItemLabel( props, ref ) { - const menuContext = useContext( MenuContext ); +>( function ItemLabel( props, ref ) { + const menuContext = useContext( Context ); if ( ! menuContext?.store ) { throw new Error( @@ -22,7 +22,5 @@ export const MenuItemLabel = forwardRef< ); } - return ( - - ); + return ; } ); diff --git a/packages/components/src/menu/item.tsx b/packages/components/src/menu/item.tsx index a716cbcc89654..560d20c30436c 100644 --- a/packages/components/src/menu/item.tsx +++ b/packages/components/src/menu/item.tsx @@ -7,14 +7,14 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import type { MenuItemProps } from './types'; +import type { ItemProps } from './types'; import * as Styled from './styles'; -import { MenuContext } from './context'; +import { Context } from './context'; -export const MenuItem = forwardRef< +export const Item = forwardRef< HTMLDivElement, - WordPressComponentProps< MenuItemProps, 'div', false > ->( function MenuItem( + WordPressComponentProps< ItemProps, 'div', false > +>( function Item( { prefix, suffix, @@ -26,7 +26,7 @@ export const MenuItem = forwardRef< }, ref ) { - const menuContext = useContext( MenuContext ); + const menuContext = useContext( Context ); if ( ! menuContext?.store ) { throw new Error( @@ -41,7 +41,7 @@ export const MenuItem = forwardRef< const computedStore = store ?? menuContext.store; return ( - { prefix } - - + + { children } - + { suffix && ( { suffix } ) } - - + + ); } ); diff --git a/packages/components/src/menu/popover.tsx b/packages/components/src/menu/popover.tsx index 19972a31027ce..6a3ad9eb683b5 100644 --- a/packages/components/src/menu/popover.tsx +++ b/packages/components/src/menu/popover.tsx @@ -17,18 +17,18 @@ import { * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import type { MenuPopoverProps } from './types'; +import type { PopoverProps } from './types'; import * as Styled from './styles'; -import { MenuContext } from './context'; +import { Context } from './context'; -export const MenuPopover = forwardRef< +export const Popover = forwardRef< HTMLDivElement, - WordPressComponentProps< MenuPopoverProps, 'div', false > ->( function MenuPopover( + WordPressComponentProps< PopoverProps, 'div', false > +>( function Popover( { gutter, children, shift, modal = true, ...otherProps }, ref ) { - const menuContext = useContext( MenuContext ); + const menuContext = useContext( Context ); // Extract the side from the applied placement — useful for animations. // Using `currentPlacement` instead of `placement` to make sure that we @@ -92,9 +92,9 @@ export const MenuPopover = forwardRef< // container scales with a different factor than its contents. // The {...renderProps} are passed to the inner wrapper, so that the // menu element is the direct parent of the menu item elements. - - - + + + ) } > { children } diff --git a/packages/components/src/menu/radio-item.tsx b/packages/components/src/menu/radio-item.tsx index 28b3199d7d36b..1da6d573c2685 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 { MenuContext } from './context'; -import type { MenuRadioItemProps } from './types'; +import { Context } from './context'; +import type { RadioItemProps } from './types'; import * as Styled from './styles'; import { SVG, Circle } from '@wordpress/primitives'; @@ -24,14 +24,14 @@ const radioCheck = ( ); -export const MenuRadioItem = forwardRef< +export const RadioItem = forwardRef< HTMLDivElement, - WordPressComponentProps< MenuRadioItemProps, 'div', false > ->( function MenuRadioItem( + WordPressComponentProps< RadioItemProps, 'div', false > +>( function RadioItem( { suffix, children, disabled = false, hideOnClick = false, ...props }, ref ) { - const menuContext = useContext( MenuContext ); + const menuContext = useContext( Context ); if ( ! menuContext?.store ) { throw new Error( @@ -40,7 +40,7 @@ export const MenuRadioItem = forwardRef< } return ( - - - + + { children } - + { suffix && ( { suffix } ) } - - + + ); } ); diff --git a/packages/components/src/menu/separator.tsx b/packages/components/src/menu/separator.tsx index 57cff572c287a..bdf79c8bb472a 100644 --- a/packages/components/src/menu/separator.tsx +++ b/packages/components/src/menu/separator.tsx @@ -7,15 +7,15 @@ import { forwardRef, useContext } from '@wordpress/element'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import { MenuContext } from './context'; -import type { MenuSeparatorProps } from './types'; +import { Context } from './context'; +import type { SeparatorProps } from './types'; import * as Styled from './styles'; -export const MenuSeparator = forwardRef< +export const Separator = forwardRef< HTMLHRElement, - WordPressComponentProps< MenuSeparatorProps, 'hr', false > ->( function MenuSeparator( props, ref ) { - const menuContext = useContext( MenuContext ); + WordPressComponentProps< SeparatorProps, 'hr', false > +>( function Separator( props, ref ) { + const menuContext = useContext( Context ); if ( ! menuContext?.store ) { throw new Error( @@ -24,7 +24,7 @@ export const MenuSeparator = forwardRef< } return ( - = { id: 'components-menu', @@ -183,7 +183,7 @@ export const WithSubmenu: StoryObj< typeof Menu > = { }; export const WithCheckboxes: StoryObj< typeof Menu > = { - render: function WithCheckboxes( props: MenuProps ) { + render: function WithCheckboxes( props: Props ) { const [ isAChecked, setAChecked ] = useState( false ); const [ isBChecked, setBChecked ] = useState( true ); const [ multipleCheckboxesValue, setMultipleCheckboxesValue ] = @@ -333,7 +333,7 @@ export const WithCheckboxes: StoryObj< typeof Menu > = { }; export const WithRadios: StoryObj< typeof Menu > = { - render: function WithRadios( props: MenuProps ) { + render: function WithRadios( props: Props ) { const [ radioValue, setRadioValue ] = useState( 'two' ); const onRadioChange: React.ComponentProps< typeof Menu.RadioItem @@ -411,7 +411,7 @@ const modalOnTopOfMenuPopover = css` `; export const WithModals: StoryObj< typeof Menu > = { - render: function WithModals( props: MenuProps ) { + render: function WithModals( props: Props ) { const [ isOuterModalOpen, setOuterModalOpen ] = useState( false ); const [ isInnerModalOpen, setInnerModalOpen ] = useState( false ); @@ -527,7 +527,7 @@ const Fill = ( { children }: { children: React.ReactNode } ) => { }; export const WithSlotFill: StoryObj< typeof Menu > = { - render: ( props: MenuProps ) => { + render: ( props: Props ) => { return ( @@ -579,7 +579,7 @@ const toolbarVariantContextValue = { }; export const ToolbarVariant: StoryObj< typeof Menu > = { - render: ( props: MenuProps ) => ( + render: ( props: Props ) => ( // TODO: add toolbar @@ -619,7 +619,7 @@ export const ToolbarVariant: StoryObj< typeof Menu > = { }; export const InsideModal: StoryObj< typeof Menu > = { - render: function InsideModal( props: MenuProps ) { + render: function InsideModal( props: Props ) { const [ isModalOpen, setModalOpen ] = useState( false ); return ( <> diff --git a/packages/components/src/menu/styles.ts b/packages/components/src/menu/styles.ts index cda5c7321f38b..1235d6ae7ec1b 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 { MenuContext } from './types'; +import type { ContextProps } from './types'; const ANIMATION_PARAMS = { SCALE_AMOUNT_OUTER: 0.82, @@ -42,8 +42,8 @@ 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< MenuContext, 'variant' > +export const PopoverOuterWrapper = styled.div< + Pick< ContextProps, 'variant' > >` position: relative; @@ -95,7 +95,7 @@ export const MenuPopoverOuterWrapper = styled.div< } `; -export const MenuPopoverInnerWrapper = styled.div` +export const PopoverInnerWrapper = styled.div` position: relative; /* Same as popover component */ /* TODO: is there a way to read the sass variable? */ @@ -219,7 +219,7 @@ const baseItem = css` } /* When the item is the trigger of an open submenu */ - ${ MenuPopoverInnerWrapper }:not(:focus) &:not(:focus)[aria-expanded="true"] { + ${ PopoverInnerWrapper }:not(:focus) &:not(:focus)[aria-expanded="true"] { background-color: ${ LIGHT_BACKGROUND_COLOR }; color: ${ COLORS.theme.foreground }; } @@ -229,15 +229,15 @@ const baseItem = css` } `; -export const MenuItem = styled( Ariakit.MenuItem )` +export const Item = styled( Ariakit.MenuItem )` ${ baseItem }; `; -export const MenuCheckboxItem = styled( Ariakit.MenuItemCheckbox )` +export const CheckboxItem = styled( Ariakit.MenuItemCheckbox )` ${ baseItem }; `; -export const MenuRadioItem = styled( Ariakit.MenuItemRadio )` +export const RadioItem = 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. */ - ${ MenuCheckboxItem } > &, - ${ MenuRadioItem } > & { + ${ CheckboxItem } > &, + ${ RadioItem } > & { /* Same width as the check icons */ min-width: ${ space( 6 ) }; } - ${ MenuCheckboxItem } > &, - ${ MenuRadioItem } > &, + ${ CheckboxItem } > &, + ${ RadioItem } > &, &:not( :empty ) { margin-inline-end: ${ space( 2 ) }; } @@ -278,7 +278,7 @@ export const ItemPrefixWrapper = styled.span` } `; -export const MenuItemContentWrapper = styled.div` +export const ItemContentWrapper = 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 MenuItemContentWrapper = styled.div` pointer-events: none; `; -export const MenuItemChildrenWrapper = styled.div` +export const ItemChildrenWrapper = styled.div` flex: 1; display: inline-flex; @@ -317,19 +317,19 @@ export const ItemSuffixWrapper = styled.span` * When the parent menu item is active, except when it's a non-focused/hovered * submenu trigger (in that case, color should not be inherited) */ - [data-active-item]:not( [data-focus-visible] ) *:not(${ MenuPopoverInnerWrapper }) &, + [data-active-item]:not( [data-focus-visible] ) *:not(${ PopoverInnerWrapper }) &, /* When the parent menu item is disabled */ - [aria-disabled='true'] *:not(${ MenuPopoverInnerWrapper }) & { + [aria-disabled='true'] *:not(${ PopoverInnerWrapper }) & { color: inherit; } `; -export const MenuGroup = styled( Ariakit.MenuGroup )` +export const Group = styled( Ariakit.MenuGroup )` /* Ignore this element when calculating the layout. Useful for subgrid */ display: contents; `; -export const MenuGroupLabel = styled( Ariakit.MenuGroupLabel )` +export const GroupLabel = styled( Ariakit.MenuGroupLabel )` /* Occupy the width of all grid columns (ie. full width) */ grid-column: 1 / -1; @@ -338,8 +338,8 @@ export const MenuGroupLabel = styled( Ariakit.MenuGroupLabel )` padding-inline: ${ ITEM_PADDING_INLINE }; `; -export const MenuSeparator = styled( Ariakit.MenuSeparator )< - Pick< MenuContext, 'variant' > +export const Separator = styled( Ariakit.MenuSeparator )< + Pick< ContextProps, 'variant' > >` /* Occupy the width of all grid columns (ie. full width) */ grid-column: 1 / -1; @@ -370,22 +370,22 @@ export const SubmenuChevronIcon = styled( Icon )` ) }; `; -export const MenuItemLabel = styled( Truncate )` +export const ItemLabel = styled( Truncate )` font-size: ${ font( 'default.fontSize' ) }; line-height: 20px; color: inherit; `; -export const MenuItemHelpText = styled( Truncate )` +export const ItemHelpText = styled( Truncate )` font-size: ${ font( 'helpText.fontSize' ) }; line-height: 16px; color: ${ LIGHTER_TEXT_COLOR }; overflow-wrap: anywhere; [data-active-item]:not( [data-focus-visible] ) - *:not( ${ MenuPopoverInnerWrapper } ) + *:not( ${ PopoverInnerWrapper } ) &, - [aria-disabled='true'] *:not( ${ MenuPopoverInnerWrapper } ) & { + [aria-disabled='true'] *:not( ${ PopoverInnerWrapper } ) & { color: inherit; } `; diff --git a/packages/components/src/menu/submenu-trigger-item.tsx b/packages/components/src/menu/submenu-trigger-item.tsx index 23932a14bdaff..9ea24d259af30 100644 --- a/packages/components/src/menu/submenu-trigger-item.tsx +++ b/packages/components/src/menu/submenu-trigger-item.tsx @@ -13,16 +13,16 @@ import { chevronRightSmall } from '@wordpress/icons'; * Internal dependencies */ import type { WordPressComponentProps } from '../context'; -import type { MenuItemProps } from './types'; -import { MenuContext } from './context'; -import { MenuItem } from './item'; +import type { ItemProps } from './types'; +import { Context } from './context'; +import { Item } from './item'; import * as Styled from './styles'; -export const MenuSubmenuTriggerItem = forwardRef< +export const SubmenuTriggerItem = forwardRef< HTMLDivElement, - WordPressComponentProps< MenuItemProps, 'div', false > ->( function MenuSubmenuTriggerItem( { suffix, ...otherProps }, ref ) { - const menuContext = useContext( MenuContext ); + WordPressComponentProps< ItemProps, 'div', false > +>( function SubmenuTriggerItem( { suffix, ...otherProps }, ref ) { + const menuContext = useContext( Context ); if ( ! menuContext?.store.parent ) { throw new Error( @@ -36,10 +36,10 @@ export const MenuSubmenuTriggerItem = forwardRef< accessibleWhenDisabled store={ menuContext.store } render={ - ->( function MenuTriggerButton( { children, disabled = false, ...props }, ref ) { - const menuContext = useContext( MenuContext ); + WordPressComponentProps< TriggerButtonProps, 'button', false > +>( function TriggerButton( { children, disabled = false, ...props }, ref ) { + const menuContext = useContext( Context ); if ( ! menuContext?.store ) { throw new Error( diff --git a/packages/components/src/menu/types.ts b/packages/components/src/menu/types.ts index f9bb0782529d1..4532d97fb13dd 100644 --- a/packages/components/src/menu/types.ts +++ b/packages/components/src/menu/types.ts @@ -3,7 +3,7 @@ */ import type * as Ariakit from '@ariakit/react'; -export interface MenuContext { +export interface ContextProps { /** * The ariakit store shared across all Menu subcomponents. */ @@ -14,7 +14,7 @@ export interface MenuContext { variant?: 'toolbar'; } -export interface MenuProps { +export interface Props { /** * The elements, which should include one instance of the `Menu.TriggerButton` * component and one instance of the `Menu.Popover` component. @@ -50,7 +50,7 @@ export interface MenuProps { placement?: Ariakit.MenuProviderProps[ 'placement' ]; } -export interface MenuPopoverProps { +export interface PopoverProps { /** * The contents of the menu popover, which should include instances of the * `Menu.Item`, `Menu.CheckboxItem`, `Menu.RadioItem`, `Menu.Group`, and @@ -98,7 +98,7 @@ export interface MenuPopoverProps { hideOnEscape?: Ariakit.MenuProps[ 'hideOnEscape' ]; } -export interface MenuTriggerButtonProps { +export interface TriggerButtonProps { /** * The contents of the menu trigger button. */ @@ -139,7 +139,7 @@ export interface MenuTriggerButtonProps { accessibleWhenDisabled?: Ariakit.MenuButtonProps[ 'accessibleWhenDisabled' ]; } -export interface MenuGroupProps { +export interface GroupProps { /** * The contents of the menu group, which should include one instance of the * `Menu.GroupLabel` component and one or more instances of `Menu.Item`, @@ -148,7 +148,7 @@ export interface MenuGroupProps { children: Ariakit.MenuGroupProps[ 'children' ]; } -export interface MenuGroupLabelProps { +export interface GroupLabelProps { /** * The contents of the menu group label, which should provide an accessible * label for the menu group. @@ -156,7 +156,7 @@ export interface MenuGroupLabelProps { children: Ariakit.MenuGroupLabelProps[ 'children' ]; } -export interface MenuItemProps { +export interface ItemProps { /** * The contents of the menu item, which could include one instance of the * `Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` @@ -203,7 +203,7 @@ export interface MenuItemProps { store?: Ariakit.MenuItemProps[ 'store' ]; } -export interface MenuCheckboxItemProps { +export interface CheckboxItemProps { /** * The contents of the menu item, which could include one instance of the * `Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` @@ -267,7 +267,7 @@ export interface MenuCheckboxItemProps { onChange?: Ariakit.MenuItemCheckboxProps[ 'onChange' ]; } -export interface MenuRadioItemProps { +export interface RadioItemProps { /** * The contents of the menu item, which could include one instance of the * `Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` @@ -330,4 +330,4 @@ export interface MenuRadioItemProps { onChange?: Ariakit.MenuItemRadioProps[ 'onChange' ]; } -export interface MenuSeparatorProps {} +export interface SeparatorProps {}