diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2206e37b3e9534..9e9c452ff5828a 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -10,6 +10,11 @@ - The `LinkedButton` to unlink sides in `BoxControl`, `BorderBoxControl` and `BorderRadiusControl` have changed from a rectangular primary button to an icon-only button, with a sentence case tooltip, and default-size icon for better legibility. The `Button` component has been fixed so when `isSmall` and `icon` props are set, and no text is present, the button shape is square rather than rectangular. +### New Features + +- `MenuItem`: Add suffix prop for injecting non-icon and non-shortcut content to menu items ([#44260](https://github.com/WordPress/gutenberg/pull/44260)). +- `ToolsPanel`: Add subheadings to ellipsis menu and reset text to default control menu items ([#44260](https://github.com/WordPress/gutenberg/pull/44260)). + ### Internal - `NavigationMenu` updated to ignore `react/exhaustive-deps` eslint rule ([#44090](https://github.com/WordPress/gutenberg/pull/44090)). diff --git a/packages/components/src/menu-item/README.md b/packages/components/src/menu-item/README.md index c9af4f057a2260..68affcd63b8bfd 100644 --- a/packages/components/src/menu-item/README.md +++ b/packages/components/src/menu-item/README.md @@ -79,3 +79,10 @@ If shortcut is a string, it is expecting the display text. If shortcut is an obj - Default: `'menuitem'` [Aria Spec](https://www.w3.org/TR/wai-aria-1.1/#aria-checked). If you need to have selectable menu items use menuitemradio for single select, and menuitemcheckbox for multiselect. + +### `suffix` + +- Type: `WPElement` +- Required: No + +Allows for markup other than icons or shortcuts to be added to the menu item. diff --git a/packages/components/src/menu-item/index.js b/packages/components/src/menu-item/index.js index 7440b3b38ef351..907d54fb680541 100644 --- a/packages/components/src/menu-item/index.js +++ b/packages/components/src/menu-item/index.js @@ -26,6 +26,7 @@ export function MenuItem( props, ref ) { shortcut, isSelected, role = 'menuitem', + suffix, ...buttonProps } = props; @@ -63,11 +64,16 @@ export function MenuItem( props, ref ) { { ...buttonProps } > { children } - - { icon && iconPosition === 'right' && } + { ! suffix && ( + + ) } + { ! suffix && icon && iconPosition === 'right' && ( + + ) } + { suffix } ); } diff --git a/packages/components/src/menu-item/style.scss b/packages/components/src/menu-item/style.scss index 18c2666025a5db..d0d11643d7c96e 100644 --- a/packages/components/src/menu-item/style.scss +++ b/packages/components/src/menu-item/style.scss @@ -8,6 +8,7 @@ // Ensure unchecked items have clearance for consistency // with checked items containing an icon or shortcut. padding-right: $grid-unit-60; + box-sizing: initial; } } diff --git a/packages/components/src/menu-item/test/index.js b/packages/components/src/menu-item/test/index.js index aa815dd7e4bb7e..fd074759ee3828 100644 --- a/packages/components/src/menu-item/test/index.js +++ b/packages/components/src/menu-item/test/index.js @@ -107,4 +107,40 @@ describe( 'MenuItem', () => { expect( checkboxMenuItem ).toBeChecked(); expect( checkboxMenuItem ).toHaveAttribute( 'aria-checked', 'true' ); } ); + + it( 'should not render shortcut or right icon if suffix provided', () => { + render( + Icon } + iconPosition="right" + role="menuitemcheckbox" + shortcut="Shortcut" + suffix="Suffix" + > + My item + + ); + + expect( screen.getByText( 'Suffix' ) ).toBeInTheDocument(); + expect( screen.queryByText( 'Shortcut' ) ).not.toBeInTheDocument(); + expect( screen.queryByText( 'Icon' ) ).not.toBeInTheDocument(); + } ); + + it( 'should render left icon despite suffix being provided', () => { + render( + Icon } + iconPosition="left" + role="menuitemcheckbox" + shortcut="Shortcut" + suffix="Suffix" + > + My item + + ); + + expect( screen.getByText( 'Icon' ) ).toBeInTheDocument(); + expect( screen.getByText( 'Suffix' ) ).toBeInTheDocument(); + expect( screen.queryByText( 'Shortcut' ) ).not.toBeInTheDocument(); + } ); } ); diff --git a/packages/components/src/tools-panel/stories/index.js b/packages/components/src/tools-panel/stories/index.js index 803a01eb341d35..b9d317a0fb71f2 100644 --- a/packages/components/src/tools-panel/stories/index.js +++ b/packages/components/src/tools-panel/stories/index.js @@ -29,11 +29,13 @@ export const _default = () => { const [ height, setHeight ] = useState(); const [ minHeight, setMinHeight ] = useState(); const [ width, setWidth ] = useState(); + const [ scale, setScale ] = useState(); const resetAll = () => { setHeight( undefined ); setWidth( undefined ); setMinHeight( undefined ); + setScale( undefined ); }; return ( @@ -79,6 +81,31 @@ export const _default = () => { onChange={ ( next ) => setMinHeight( next ) } /> + !! scale } + label="Scale" + onDeselect={ () => setScale( undefined ) } + > + setScale( next ) } + isBlock + > + + + + + diff --git a/packages/components/src/tools-panel/styles.ts b/packages/components/src/tools-panel/styles.ts index 7fe2fc0a0a1a2c..f76bf47ec18c5b 100644 --- a/packages/components/src/tools-panel/styles.ts +++ b/packages/components/src/tools-panel/styles.ts @@ -1,6 +1,7 @@ /** * External dependencies */ +import styled from '@emotion/styled'; import { css } from '@emotion/react'; /** @@ -12,7 +13,7 @@ import { Wrapper as BaseControlWrapper, } from '../base-control/styles/base-control-styles'; import { LabelWrapper } from '../input-control/styles/input-control-styles'; -import { COLORS, CONFIG } from '../utils'; +import { COLORS, CONFIG, rtl } from '../utils'; import { space } from '../ui/utils/space'; const toolsPanelGrid = { @@ -145,3 +146,29 @@ export const ToolsPanelItemPlaceholder = css` export const DropdownMenu = css` min-width: 200px; `; + +export const ResetLabel = styled.span` + color: var( --wp-admin-theme-color-darker-10 ); + font-size: 11px; + font-weight: 500; + line-height: 1.4; + ${ rtl( { marginLeft: space( 3 ) } ) } + text-transform: uppercase; +`; + +export const DefaultControlsItem = css` + color: ${ COLORS.gray[ 900 ] }; + + &&[aria-disabled='true'] { + color: ${ COLORS.gray[ 700 ] }; + opacity: 1; + + &:hover { + color: ${ COLORS.gray[ 700 ] }; + } + + ${ ResetLabel } { + opacity: 0.3; + } + } +`; diff --git a/packages/components/src/tools-panel/tools-panel-header/component.tsx b/packages/components/src/tools-panel/tools-panel-header/component.tsx index 62a40a966dcb7d..57416d4d0c311d 100644 --- a/packages/components/src/tools-panel/tools-panel-header/component.tsx +++ b/packages/components/src/tools-panel/tools-panel-header/component.tsx @@ -7,7 +7,7 @@ import type { ForwardedRef } from 'react'; * WordPress dependencies */ import { speak } from '@wordpress/a11y'; -import { check, reset, moreVertical, plus } from '@wordpress/icons'; +import { check, moreVertical, plus } from '@wordpress/icons'; import { __, _x, sprintf } from '@wordpress/i18n'; /** @@ -20,12 +20,14 @@ import { HStack } from '../../h-stack'; import { Heading } from '../../heading'; import { useToolsPanelHeader } from './hook'; import { contextConnect, WordPressComponentProps } from '../../ui/context'; +import { ResetLabel } from '../styles'; import type { ToolsPanelControlsGroupProps, ToolsPanelHeaderProps, } from '../types'; const DefaultControlsGroup = ( { + itemClassName, items, toggleItem, }: ToolsPanelControlsGroupProps ) => { @@ -33,15 +35,17 @@ const DefaultControlsGroup = ( { return null; } + const resetSuffix = { __( 'Reset' ) }; + return ( - + { items.map( ( [ label, hasValue ] ) => { if ( hasValue ) { return ( { label } @@ -67,8 +72,8 @@ const DefaultControlsGroup = ( { return ( @@ -89,7 +94,7 @@ const OptionalControlsGroup = ( { } return ( - + { items.map( ( [ label, isSelected ] ) => { const itemLabel = isSelected ? sprintf( @@ -147,6 +152,7 @@ const ToolsPanelHeader = ( ) => { const { areAllOptionalControlsHidden, + defaultControlsItemClassName, dropdownMenuClassName, hasMenuItems, headingClassName, @@ -197,6 +203,7 @@ const ToolsPanelHeader = ( { + return cx( styles.DefaultControlsItem ); + }, [ cx ] ); + const { menuItems, hasMenuItems, areAllOptionalControlsHidden } = useToolsPanelContext(); return { ...otherProps, areAllOptionalControlsHidden, + defaultControlsItemClassName, dropdownMenuClassName, hasMenuItems, headingClassName, diff --git a/packages/components/src/tools-panel/types.ts b/packages/components/src/tools-panel/types.ts index 3290b9fe497fc7..b95a50a4aa9cbc 100644 --- a/packages/components/src/tools-panel/types.ts +++ b/packages/components/src/tools-panel/types.ts @@ -147,6 +147,7 @@ export type ToolsPanelContext = { }; export type ToolsPanelControlsGroupProps = { + itemClassName?: string; items: [ string, boolean ][]; toggleItem: ( label: string ) => void; };