Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).
Expand Down
7 changes: 7 additions & 0 deletions packages/components/src/menu-item/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
16 changes: 11 additions & 5 deletions packages/components/src/menu-item/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function MenuItem( props, ref ) {
shortcut,
isSelected,
role = 'menuitem',
suffix,
...buttonProps
} = props;

Expand Down Expand Up @@ -63,11 +64,16 @@ export function MenuItem( props, ref ) {
{ ...buttonProps }
>
<span className="components-menu-item__item">{ children }</span>
<Shortcut
className="components-menu-item__shortcut"
shortcut={ shortcut }
/>
{ icon && iconPosition === 'right' && <Icon icon={ icon } /> }
{ ! suffix && (
<Shortcut
className="components-menu-item__shortcut"
shortcut={ shortcut }
/>
) }
{ ! suffix && icon && iconPosition === 'right' && (
<Icon icon={ icon } />
) }
{ suffix }
</Button>
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/menu-item/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
36 changes: 36 additions & 0 deletions packages/components/src/menu-item/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<MenuItem
icon={ <span>Icon</span> }
iconPosition="right"
role="menuitemcheckbox"
shortcut="Shortcut"
suffix="Suffix"
>
My item
</MenuItem>
);

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(
<MenuItem
icon={ <span>Icon</span> }
iconPosition="left"
role="menuitemcheckbox"
shortcut="Shortcut"
suffix="Suffix"
>
My item
</MenuItem>
);

expect( screen.getByText( 'Icon' ) ).toBeInTheDocument();
expect( screen.getByText( 'Suffix' ) ).toBeInTheDocument();
expect( screen.queryByText( 'Shortcut' ) ).not.toBeInTheDocument();
} );
} );
27 changes: 27 additions & 0 deletions packages/components/src/tools-panel/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -79,6 +81,31 @@ export const _default = () => {
onChange={ ( next ) => setMinHeight( next ) }
/>
</ToolsPanelItem>
<ToolsPanelItem
hasValue={ () => !! scale }
label="Scale"
onDeselect={ () => setScale( undefined ) }
>
<ToggleGroupControl
label="Scale"
value={ scale }
onChange={ ( next ) => setScale( next ) }
isBlock
>
<ToggleGroupControlOption
value="cover"
label="Cover"
/>
<ToggleGroupControlOption
value="contain"
label="Contain"
/>
<ToggleGroupControlOption
value="fill"
label="Fill"
/>
</ToggleGroupControl>
</ToolsPanelItem>
</ToolsPanel>
</Panel>
</PanelWrapperView>
Expand Down
29 changes: 28 additions & 1 deletion packages/components/src/tools-panel/styles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* External dependencies
*/
import styled from '@emotion/styled';
import { css } from '@emotion/react';

/**
Expand All @@ -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 = {
Expand Down Expand Up @@ -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;
}
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -20,28 +20,32 @@ 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 ) => {
if ( ! items.length ) {
return null;
}

const resetSuffix = <ResetLabel aria-hidden>{ __( 'Reset' ) }</ResetLabel>;

return (
<MenuGroup>
<MenuGroup label={ __( 'Defaults' ) }>
{ items.map( ( [ label, hasValue ] ) => {
if ( hasValue ) {
return (
<MenuItem
key={ label }
className={ itemClassName }
role="menuitem"
icon={ reset }
label={ sprintf(
// translators: %s: The name of the control being reset e.g. "Padding".
__( 'Reset %s' ),
Expand All @@ -58,6 +62,7 @@ const DefaultControlsGroup = ( {
'assertive'
);
} }
suffix={ resetSuffix }
>
{ label }
</MenuItem>
Expand All @@ -67,8 +72,8 @@ const DefaultControlsGroup = ( {
return (
<MenuItem
key={ label }
className={ itemClassName }
role="menuitemcheckbox"
icon={ check }
isSelected
aria-disabled
>
Expand All @@ -89,7 +94,7 @@ const OptionalControlsGroup = ( {
}

return (
<MenuGroup>
<MenuGroup label={ __( 'Tools' ) }>
{ items.map( ( [ label, isSelected ] ) => {
const itemLabel = isSelected
? sprintf(
Expand Down Expand Up @@ -147,6 +152,7 @@ const ToolsPanelHeader = (
) => {
const {
areAllOptionalControlsHidden,
defaultControlsItemClassName,
dropdownMenuClassName,
hasMenuItems,
headingClassName,
Expand Down Expand Up @@ -197,6 +203,7 @@ const ToolsPanelHeader = (
<DefaultControlsGroup
items={ defaultItems }
toggleItem={ toggleItem }
itemClassName={ defaultControlsItemClassName }
/>
<OptionalControlsGroup
items={ optionalItems }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,17 @@ export function useToolsPanelHeader(
return cx( styles.ToolsPanelHeading );
}, [ cx ] );

const defaultControlsItemClassName = useMemo( () => {
return cx( styles.DefaultControlsItem );
}, [ cx ] );

const { menuItems, hasMenuItems, areAllOptionalControlsHidden } =
useToolsPanelContext();

return {
...otherProps,
areAllOptionalControlsHidden,
defaultControlsItemClassName,
dropdownMenuClassName,
hasMenuItems,
headingClassName,
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/tools-panel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export type ToolsPanelContext = {
};

export type ToolsPanelControlsGroupProps = {
itemClassName?: string;
items: [ string, boolean ][];
toggleItem: ( label: string ) => void;
};
Expand Down