Skip to content

Commit

Permalink
Limit component.js files to presentation
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronrobertshaw committed Aug 5, 2021
1 parent ed518fd commit f9411d1
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 175 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,25 @@ import { __ } from '@wordpress/i18n';
import DropdownMenu from '../../dropdown-menu';
import MenuGroup from '../../menu-group';
import MenuItem from '../../menu-item';
import { useToolsPanelContext } from '../context';
import { useToolsPanelHeader } from './hook';
import { MENU_STATES } from '../utils';
import { contextConnect } from '../../ui/context';

const ToolsPanelHeader = ( props, forwardedRef ) => {
const {
hasMenuItems,
header,
menuItems,
menuLabel,
resetAll,
header,
toggleItem,
...headerProps
} = useToolsPanelHeader( props );

const { menuItems } = useToolsPanelContext();

if ( ! header ) {
return null;
}

const menuItemEntries = Object.entries( menuItems );
const hasMenuItems = !! menuItemEntries.length;

return (
<h2 { ...headerProps } ref={ forwardedRef }>
{ header }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useMemo } from '@wordpress/element';
* Internal dependencies
*/
import * as styles from '../styles';
import { useToolsPanelContext } from '../context';
import { useContextSystem } from '../../ui/context';
import { useCx } from '../../utils/hooks/use-cx';

Expand All @@ -17,13 +18,17 @@ export function useToolsPanelHeader( props ) {
);

const cx = useCx();

const classes = useMemo( () => {
return cx( styles.ToolsPanelHeader, className );
}, [ className ] );

const { menuItems } = useToolsPanelContext();
const hasMenuItems = !! Object.entries( menuItems ).length;

return {
...otherProps,
hasMenuItems,
menuItems,
className: classes,
};
}
69 changes: 4 additions & 65 deletions packages/components/src/tools-panel/tools-panel-item/component.js
Original file line number Diff line number Diff line change
@@ -1,79 +1,18 @@
/**
* WordPress dependencies
*/
import { usePrevious } from '@wordpress/compose';
import { useEffect } from '@wordpress/element';

/**
* Internal dependencies
*/
import { useToolsPanelContext } from '../context';
import { useToolsPanelItem } from './hook';
import { MENU_STATES } from '../utils';
import { View } from '../../view';
import { contextConnect } from '../../ui/context';

// This wraps controls to be conditionally displayed within a tools panel. It
// prevents props being applied to HTML elements that would make them invalid.
const ToolsPanelItem = ( props, forwardedRef ) => {
const {
children,
hasValue,
isShownByDefault,
label,
onDeselect = () => undefined,
onSelect = () => undefined,
...toolsPanelItemProps
} = useToolsPanelItem( props );

const {
checkMenuItem,
menuItems,
registerPanelItem,
} = useToolsPanelContext();

const isValueSet = hasValue();

useEffect( () => {
registerPanelItem( {
hasValue,
isShownByDefault,
label,
} );
}, [] );

// When the user sets a value on the panel item's control, tell the panel
// to check its corresponding menu item.
useEffect( () => {
if ( isValueSet ) {
checkMenuItem( label );
}
}, [ isValueSet ] );

// Note: `label` is used as a key when building menu item state in
// `ToolsPanel`.
const isMenuItemChecked = menuItems[ label ] === MENU_STATES.CHECKED;
const wasMenuItemChecked = usePrevious( isMenuItemChecked );

useEffect( () => {
// If the panel's menu item is now checked but wasn't previously and
// we don't have a current value, consider the menu item has just been
// selected.
if (
isMenuItemChecked &&
! isValueSet &&
wasMenuItemChecked === false
) {
onSelect();
}

if ( ! isMenuItemChecked && wasMenuItemChecked ) {
onDeselect();
}
}, [ isMenuItemChecked, wasMenuItemChecked, isValueSet ] );
const { children, isShown, ...toolsPanelItemProps } = useToolsPanelItem(
props
);

// Do not show if menu item not selected and not shown by default.
if ( menuItems[ label ] === MENU_STATES.UNCHECKED ) {
if ( ! isShown ) {
return null;
}

Expand Down
71 changes: 65 additions & 6 deletions packages/components/src/tools-panel/tools-panel-item/hook.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,88 @@
/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';
import { usePrevious } from '@wordpress/compose';
import { useEffect, useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import * as styles from '../styles';
import { useToolsPanelContext } from '../context';
import { MENU_STATES } from '../utils';
import { useContextSystem } from '../../ui/context';
import { useCx } from '../../utils/hooks/use-cx';

export function useToolsPanelItem( props ) {
const { className, ...otherProps } = useContextSystem(
props,
'ToolsPanel'
);
const {
className,
hasValue,
isShownByDefault,
label,
onDeselect = () => undefined,
onSelect = () => undefined,
...otherProps
} = useContextSystem( props, 'ToolsPanelItem' );

const cx = useCx();

const classes = useMemo( () => {
return cx( styles.ToolsPanelItem, className );
} );

const {
checkMenuItem,
menuItems,
registerPanelItem,
} = useToolsPanelContext();

// Registering the panel item allows the panel to include it in its
// automatically generated menu and determine its initial checked status.
useEffect( () => {
registerPanelItem( {
hasValue,
isShownByDefault,
label,
} );
}, [] );

const isValueSet = hasValue();

// When the user sets a value on the panel item's control, tell the panel
// to check its corresponding menu item.
useEffect( () => {
if ( isValueSet ) {
checkMenuItem( label );
}
}, [ isValueSet ] );

// Note: `label` is used as a key when building menu item state in
// `ToolsPanel`.
const isShown = menuItems[ label ] !== MENU_STATES.UNCHECKED;
const isMenuItemChecked = menuItems[ label ] === MENU_STATES.CHECKED;
const wasMenuItemChecked = usePrevious( isMenuItemChecked );

// Determine if the panel item's corresponding menu it is being toggled and
// trigger appropriate callback if it is.
useEffect( () => {
// If the panel's menu item is now checked but wasn't previously and
// we don't have a current value, consider the menu item as having just
// been selected.
if (
isMenuItemChecked &&
! isValueSet &&
wasMenuItemChecked === false
) {
onSelect();
}

if ( ! isMenuItemChecked && wasMenuItemChecked ) {
onDeselect();
}
}, [ isMenuItemChecked, wasMenuItemChecked, isValueSet ] );

return {
...otherProps,
isShown,
className: classes,
};
}
98 changes: 5 additions & 93 deletions packages/components/src/tools-panel/tools-panel/component.js
Original file line number Diff line number Diff line change
@@ -1,117 +1,29 @@
/**
* WordPress dependencies
*/
import { useEffect, useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import ToolsPanelHeader from '../tools-panel-header';
import { ToolsPanelContext } from '../context';
import { useToolsPanel } from './hook';
import { MENU_STATES } from '../utils';
import { View } from '../../view';
import { contextConnect } from '../../ui/context';

const ToolsPanel = ( props, forwardedRef ) => {
const {
children,
header,
label: menuLabel,
resetAll,
label,
panelContext,
resetAllItems,
toggleItem,
...toolsPanelProps
} = useToolsPanel( props );

// Allow panel items to register themselves.
const [ panelItems, setPanelItems ] = useState( [] );

const registerPanelItem = ( item ) => {
setPanelItems( ( items ) => [ ...items, item ] );
};

// Manage and share display state of menu items representing child controls.
const [ menuItems, setMenuItems ] = useState( {} );

// Setup menuItems state as panel items register themselves.
useEffect( () => {
const items = {};

panelItems.forEach( ( { hasValue, isShownByDefault, label } ) => {
let menuItemState = hasValue()
? MENU_STATES.CHECKED
: MENU_STATES.UNCHECKED;

// Disable the menu item if its unchecked and a default control.
if ( menuItemState === MENU_STATES.UNCHECKED && isShownByDefault ) {
menuItemState = MENU_STATES.DISABLED;
}

items[ label ] = menuItemState;
} );

setMenuItems( items );
}, [ panelItems ] );

// When a panel item gets a value set, update its menu item.
const checkMenuItem = ( label ) => {
setMenuItems( ( items ) => ( {
...items,
[ label ]: MENU_STATES.CHECKED,
} ) );
};

// Toggles the customized state of the panel item and its display if it
// isn't to be displayed by default. When toggling a panel item its
// onSelect or onDeselect callbacks are called as appropriate.
const toggleItem = ( label ) => {
const wasChecked = menuItems[ label ] === MENU_STATES.CHECKED;
const panelItem = panelItems.find( ( item ) => item.label === label );

let menuItemState = wasChecked
? MENU_STATES.UNCHECKED
: MENU_STATES.CHECKED;

if (
menuItemState === MENU_STATES.UNCHECKED &&
panelItem.isShownByDefault
) {
menuItemState = MENU_STATES.DISABLED;
}

setMenuItems( {
...menuItems,
[ label ]: menuItemState,
} );
};

// Resets display of children and executes resetAll callback if available.
const resetAllItems = () => {
if ( typeof resetAll === 'function' ) {
resetAll();
}

// Turn off all menu items. Default controls will continue to display
// by virtue of their `isShownByDefault` prop however their menu item
// will be disabled to prevent behaviour where toggling has no effect.
const resetMenuItems = {};

panelItems.forEach( ( { label, isShownByDefault } ) => {
resetMenuItems[ label ] = isShownByDefault
? MENU_STATES.DISABLED
: MENU_STATES.UNCHECKED;
} );

setMenuItems( resetMenuItems );
};

const panelContext = { checkMenuItem, menuItems, registerPanelItem };

return (
<View { ...toolsPanelProps } ref={ forwardedRef }>
<ToolsPanelContext.Provider value={ panelContext }>
<ToolsPanelHeader
header={ header }
menuLabel={ menuLabel }
menuLabel={ label }
resetAll={ resetAllItems }
toggleItem={ toggleItem }
/>
Expand Down
Loading

0 comments on commit f9411d1

Please sign in to comment.