Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ToolsPanel: Add subheadings and reset text to tools panel menu #44260

Merged
merged 12 commits into from
Sep 26, 2022
Merged
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 }
ciampo marked this conversation as resolved.
Show resolved Hide resolved
</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;
aaronrobertshaw marked this conversation as resolved.
Show resolved Hide resolved
}
}

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();
aaronrobertshaw marked this conversation as resolved.
Show resolved Hide resolved
} );

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