Skip to content

Commit

Permalink
ToolsPanel: switch to plus icon when no controls present in panel body (
Browse files Browse the repository at this point in the history
#34107)

* When there are no selected controls within a ToolsPanel, change the icon to a plus symbol. This is to indicate to the user that they can add controls to the panel.

* Moving business logic into the hook

* Ensure that optional controls, when hidden and available, trigger the plus icon in the ToolsPanel header.
Updated stories.

* updating variable name to make it clearer that we want to track if optional items are available at all *and* if so, are they hidden

* This commit adds a variable aria-label to the tools panel header menu button to describe the element and its intended effects.
Added component tests.

* Changing copy from "Show" to "View"
Updating tests

* Renaming `areAllOptionalControlsHidden` variable and adding it as a property to the ToolsPanelContext type

Modified default value of areAllOptionalControlsHidden and updated comments

* Make `areAllOptionalControlsHidden` required on the ToolsPanelContent object.

* Update packages/components/src/tools-panel/test/index.js

Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com>

* Add custom fixtures for optional control props in order to test the panel header icon toggle in isolation from the other tests.

* Update packages/components/src/tools-panel/stories/index.js

Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com>

* Consolidating comment and reinstating `false` as the default value for the state var `areAllOptionalControlsHidden`

Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com>
  • Loading branch information
ramonjd and aaronrobertshaw committed Oct 14, 2021
1 parent 5430baf commit 1961b03
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 15 deletions.
1 change: 1 addition & 0 deletions packages/components/src/tools-panel/context.ts
Expand Up @@ -18,6 +18,7 @@ export const ToolsPanelContext = createContext< ToolsPanelContextType >( {
registerPanelItem: noop,
deregisterPanelItem: noop,
flagItemCustomization: noop,
areAllOptionalControlsHidden: true,
} );

export const useToolsPanelContext = () =>
Expand Down
54 changes: 53 additions & 1 deletion packages/components/src/tools-panel/stories/index.js
Expand Up @@ -35,7 +35,10 @@ export const _default = () => {
return (
<PanelWrapperView>
<Panel>
<ToolsPanel label="Tools Panel" resetAll={ resetAll }>
<ToolsPanel
label="Tools Panel (default example)"
resetAll={ resetAll }
>
<ToolsPanelItem
className="single-column"
hasValue={ () => !! width }
Expand Down Expand Up @@ -66,6 +69,7 @@ export const _default = () => {
hasValue={ () => !! minHeight }
label="Minimum height"
onDeselect={ () => setMinHeight( undefined ) }
isShownByDefault={ true }
>
<UnitControl
label="Minimum height"
Expand All @@ -79,6 +83,54 @@ export const _default = () => {
);
};

export const WithOptionalItemsPlusIcon = () => {
const [ height, setHeight ] = useState();
const [ width, setWidth ] = useState();

const resetAll = () => {
setHeight( undefined );
setWidth( undefined );
};

return (
<PanelWrapperView>
<Panel>
<ToolsPanel
label="Tools Panel (optional items only)"
resetAll={ resetAll }
>
<ToolsPanelItem
className="single-column"
hasValue={ () => !! width }
label="Width"
onDeselect={ () => setWidth( undefined ) }
isShownByDefault={ false }
>
<UnitControl
label="Width"
value={ width }
onChange={ ( next ) => setWidth( next ) }
/>
</ToolsPanelItem>
<ToolsPanelItem
className="single-column"
hasValue={ () => !! height }
label="Height"
onDeselect={ () => setHeight( undefined ) }
isShownByDefault={ false }
>
<UnitControl
label="Height"
value={ height }
onChange={ ( next ) => setHeight( next ) }
/>
</ToolsPanelItem>
</ToolsPanel>
</Panel>
</PanelWrapperView>
);
};

const { Fill: ToolsPanelItems, Slot } = createSlotFill( 'ToolsPanelSlot' );
const panelId = 'unique-tools-panel-id';

Expand Down
56 changes: 53 additions & 3 deletions packages/components/src/tools-panel/test/index.js
Expand Up @@ -139,10 +139,17 @@ const renderPanel = () => {
);
};

// Helper to find the menu button and simulate a user click.
/**
* Helper to find the menu button and simulate a user click.
*
* @return {HTMLElement} The menuButton.
*/
const openDropdownMenu = () => {
const menuButton = screen.getByLabelText( defaultProps.label );
const menuButton = screen.getByRole( 'button', {
name: /view([\w\s]+)options/i,
} );
fireEvent.click( menuButton );
return menuButton;
};

// Opens dropdown then selects the menu item by label before simulating a click.
Expand Down Expand Up @@ -212,7 +219,7 @@ describe( 'ToolsPanel', () => {
it( 'should render panel menu when at least one panel item', () => {
renderPanel();

const menuButton = screen.getByLabelText( defaultProps.label );
const menuButton = openDropdownMenu();
expect( menuButton ).toBeInTheDocument();
} );

Expand Down Expand Up @@ -509,4 +516,47 @@ describe( 'ToolsPanel', () => {
expect( items[ 1 ] ).toHaveTextContent( 'Item 2' );
} );
} );

describe( 'panel header icon toggle', () => {
const optionalControls = {
attributes: { value: false },
hasValue: jest.fn().mockImplementation( () => {
return !! optionalControls.attributes.value;
} ),
label: 'Optional',
onDeselect: jest.fn(),
onSelect: jest.fn(),
isShownByDefault: false,
};

it( 'should render appropriate icons for the dropdown menu', async () => {
render(
<ToolsPanel { ...defaultProps }>
<ToolsPanelItem { ...optionalControls }>
<div>Optional control</div>
</ToolsPanelItem>
</ToolsPanel>
);

// There are unactivated, optional menu items in the Tools Panel dropdown.
const optionsHiddenIcon = screen.getByRole( 'button', {
name: 'View and add options',
} );

expect( optionsHiddenIcon ).toBeInTheDocument();

await selectMenuItem( optionalControls.label );

// There are now NO unactivated, optional menu items in the Tools Panel dropdown.
expect(
screen.queryByRole( 'button', { name: 'View and add options' } )
).not.toBeInTheDocument();

const optionsDisplayedIcon = screen.getByRole( 'button', {
name: 'View options',
} );

expect( optionsDisplayedIcon ).toBeInTheDocument();
} );
} );
} );
Expand Up @@ -7,8 +7,8 @@ import type { Ref } from 'react';
/**
* WordPress dependencies
*/
import { check, reset, moreVertical } from '@wordpress/icons';
import { __, sprintf } from '@wordpress/i18n';
import { check, reset, moreVertical, plus } from '@wordpress/icons';
import { __, _x, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
Expand Down Expand Up @@ -118,6 +118,7 @@ const ToolsPanelHeader = (
const {
dropdownMenuClassName,
hasMenuItems,
areAllOptionalControlsHidden,
label: labelText,
menuItems,
resetAll,
Expand All @@ -131,14 +132,21 @@ const ToolsPanelHeader = (

const defaultItems = Object.entries( menuItems?.default || {} );
const optionalItems = Object.entries( menuItems?.optional || {} );
const dropDownMenuIcon = areAllOptionalControlsHidden ? plus : moreVertical;
const dropDownMenuLabelText = areAllOptionalControlsHidden
? _x(
'View and add options',
'Button label to reveal tool panel options'
)
: _x( 'View options', 'Button label to reveal tool panel options' );

return (
<h2 { ...headerProps } ref={ forwardedRef }>
{ labelText }
{ hasMenuItems && (
<DropdownMenu
icon={ moreVertical }
label={ labelText }
icon={ dropDownMenuIcon }
label={ dropDownMenuLabelText }
menuProps={ { className: dropdownMenuClassName } }
>
{ ( { onClose = noop } ) => (
Expand Down
Expand Up @@ -29,12 +29,17 @@ export function useToolsPanelHeader(
return cx( styles.DropdownMenu );
}, [] );

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

return {
...otherProps,
dropdownMenuClassName,
hasMenuItems,
areAllOptionalControlsHidden,
menuItems,
className: classes,
};
Expand Down
11 changes: 5 additions & 6 deletions packages/components/src/tools-panel/tools-panel/hook.ts
Expand Up @@ -109,17 +109,15 @@ export function useToolsPanel(
} );
};

// Track whether all optional controls are displayed or not.
// If no optional controls are present, then none are hidden and this will
// be `false`.
// Whether all optional menu items are hidden or not must be tracked
// in order to later determine if the panel display is empty and handle
// conditional display of a plus icon to indicate the presence of further
// menu items.
const [
areAllOptionalControlsHidden,
setAreAllOptionalControlsHidden,
] = useState( false );

// We need to track whether any optional menu items are active to later
// determine whether the panel is currently empty and any inner wrapper
// should be hidden.
useEffect( () => {
if ( menuItems.optional ) {
const optionalItems = Object.entries( menuItems.optional );
Expand Down Expand Up @@ -203,6 +201,7 @@ export function useToolsPanel(
registerPanelItem,
deregisterPanelItem,
flagItemCustomization,
areAllOptionalControlsHidden,
hasMenuItems: !! panelItems.length,
isResetting: isResetting.current,
shouldRenderPlaceholderItems,
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/tools-panel/types.ts
Expand Up @@ -127,6 +127,7 @@ export type ToolsPanelContext = {
flagItemCustomization: ( label: string ) => void;
isResetting: boolean;
shouldRenderPlaceholderItems: boolean;
areAllOptionalControlsHidden: boolean;
};

export type ToolsPanelControlsGroupProps = {
Expand Down

0 comments on commit 1961b03

Please sign in to comment.