Skip to content

Commit

Permalink
Add progressive disclosure panel item component
Browse files Browse the repository at this point in the history
This utilises a new context for the panel's menu items. The menu in the panel title is generated from this and the individual items control their display based of if the item is selected in that menu item context.
  • Loading branch information
aaronrobertshaw committed Jul 8, 2021
1 parent 1fde44b commit 39473a5
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 40 deletions.
27 changes: 16 additions & 11 deletions packages/block-editor/src/hooks/dimensions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/**
* WordPress dependencies
*/
import { __experimentalProgressiveDisclosurePanel as ProgressiveDisclosurePanel } from '@wordpress/components';
import {
__experimentalProgressiveDisclosurePanel as ProgressiveDisclosurePanel,
__experimentalProgressiveDisclosurePanelItem as ProgressiveDisclosurePanelItem,
} from '@wordpress/components';
import { Platform } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { getBlockSupport } from '@wordpress/blocks';
Expand Down Expand Up @@ -73,22 +76,24 @@ export function DimensionsPanel( props ) {
resetAll={ resetAll }
>
{ ! isPaddingDisabled && (
<PaddingEdit
{ ...props }
hasValue={ hasPaddingValue }
<ProgressiveDisclosurePanelItem
hasValue={ () => hasPaddingValue( props ) }
label={ __( 'Padding' ) }
onDeselect={ resetPadding }
onDeselect={ () => resetPadding( props ) }
isShownByDefault={ defaultSpacingControls?.padding }
/>
>
<PaddingEdit { ...props } />
</ProgressiveDisclosurePanelItem>
) }
{ ! isMarginDisabled && (
<MarginEdit
{ ...props }
hasValue={ hasMarginValue }
<ProgressiveDisclosurePanelItem
hasValue={ () => hasMarginValue( props ) }
label={ __( 'Margin' ) }
onDeselect={ resetMargin }
onDeselect={ () => resetMargin( props ) }
isShownByDefault={ defaultSpacingControls?.margin }
/>
>
<MarginEdit { ...props } />
</ProgressiveDisclosurePanelItem>
) }
</ProgressiveDisclosurePanel>
</InspectorControls>
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export { default as PanelRow } from './panel/row';
export { default as Placeholder } from './placeholder';
export { default as Popover } from './popover';
export { default as __experimentalProgressiveDisclosurePanel } from './progressive-disclosure-panel';
export { default as __experimentalProgressiveDisclosurePanelItem } from './progressive-disclosure-panel/item';
export { default as QueryControls } from './query-controls';
export { default as __experimentalRadio } from './radio';
export { default as __experimentalRadioGroup } from './radio-group';
Expand Down
61 changes: 33 additions & 28 deletions packages/components/src/progressive-disclosure-panel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@
* External dependencies
*/
import classnames from 'classnames';
import noop from 'lodash';

/**
* WordPress dependencies
*/
import { useEffect, useMemo, useState } from '@wordpress/element';
import {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from '@wordpress/element';

/**
* Internal dependencies
*/
import ProgressiveDisclosurePanelItem from './item';
import ProgressiveDisclosurePanelTitle from './title';

const PanelContext = createContext( {} );

export const usePanelContext = () => useContext( PanelContext );

const isMenuItem = ( item ) => item.type === ProgressiveDisclosurePanelItem;

const ProgressiveDisclosurePanel = ( props ) => {
const { children, className, label: menuLabel, resetAll, title } = props;
const [ menuItems, setMenuItems ] = useState( {} );
Expand All @@ -22,21 +34,19 @@ const ProgressiveDisclosurePanel = ( props ) => {
// a boolean `false` will be passed as a child if component is excluded.
// This panel is only interested in the children to be displayed.
const filteredChildren = useMemo( () => {
return Array.isArray( children ) ? children.filter( Boolean ) : [];
return Array.isArray( children ) ? children.filter( isMenuItem ) : [];
}, [ children ] );

// Refresh which children should be reflected in the menu and what their
// associated menu item's state is; checked or not.
useEffect( () => {
const items = {};

filteredChildren.forEach( ( child ) => {
filteredChildren.forEach( ( { props: { hasValue, label } } ) => {
// New item is checked if:
// - it currently has a value
// - or it was checked in previous menuItems state.
items[ child.props.label ] =
child.props.hasValue( child.props ) ||
menuItems[ child.props.label ];
items[ label ] = hasValue() || menuItems[ label ];
} );

setMenuItems( items );
Expand All @@ -57,12 +67,14 @@ const ProgressiveDisclosurePanel = ( props ) => {
const toggleChild = ( label ) => {
const wasSelected = menuItems[ label ];
const child = getChildByMenuLabel( label );
const { onDeselect = noop, onSelect = noop } = child.props;
const { onDeselect, onSelect } = child.props;

if ( wasSelected && onDeselect ) {
onDeselect();
}

if ( wasSelected ) {
onDeselect( child.props );
} else {
onSelect( child.props );
if ( ! wasSelected && onSelect ) {
onSelect();
}

setMenuItems( {
Expand Down Expand Up @@ -95,22 +107,15 @@ const ProgressiveDisclosurePanel = ( props ) => {

return (
<div className={ classes }>
<ProgressiveDisclosurePanelTitle
menuLabel={ menuLabel }
title={ title }
menuItems={ menuItems }
toggleChild={ toggleChild }
resetAll={ resetAllChildren }
/>
{ filteredChildren.map( ( child ) => {
// Only display the child if it is toggled on in the menu or is
// set to display by default.
const isShown =
menuItems[ child.props.label ] ||
child.props.isShownByDefault;

return isShown ? child : null;
} ) }
<PanelContext.Provider value={ menuItems }>
<ProgressiveDisclosurePanelTitle
menuLabel={ menuLabel }
title={ title }
toggleChild={ toggleChild }
resetAll={ resetAllChildren }
/>
{ children }
</PanelContext.Provider>
</div>
);
};
Expand Down
23 changes: 23 additions & 0 deletions packages/components/src/progressive-disclosure-panel/item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Internal dependencies
*/
import { usePanelContext } from './';

// This wraps controls to be conditionally displayed within a progressive
// disclosure panel. It helps prevent props being applied to HTML elements that
// would otherwise be invalid.
const ProgressiveDisclosurePanelItem = ( props ) => {
const { children, isShownByDefault, label } = props;
const menuItems = usePanelContext();

// Do not show if menu item not selected and not shown by default.
// If the item has a value that will be reflected in the menu item's
// selected status provided by context.
if ( ! menuItems[ label ] && ! isShownByDefault ) {
return null;
}

return children;
};

export default ProgressiveDisclosurePanelItem;
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { usePanelContext } from './';
import MenuGroup from '../menu-group';
import MenuItem from '../menu-item';
import DropdownMenu from '../dropdown-menu';

const ProgressiveDisclosurePanelTitle = ( props ) => {
const { menuItems = {}, menuLabel, resetAll, title, toggleChild } = props;
const { menuLabel, resetAll, title, toggleChild } = props;
const menuItems = usePanelContext();

if ( ! title ) {
return null;
Expand Down

0 comments on commit 39473a5

Please sign in to comment.