From f9411d14a1d3542ec1987460fdef40f8e51f6ef7 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 5 Aug 2021 15:40:37 +1000 Subject: [PATCH] Limit component.js files to presentation --- .../tools-panel-header/component.js | 10 +- .../tools-panel/tools-panel-header/hook.js | 7 +- .../tools-panel/tools-panel-item/component.js | 69 +------------ .../src/tools-panel/tools-panel-item/hook.js | 71 ++++++++++++-- .../src/tools-panel/tools-panel/component.js | 98 +------------------ .../src/tools-panel/tools-panel/hook.js | 93 +++++++++++++++++- 6 files changed, 173 insertions(+), 175 deletions(-) diff --git a/packages/components/src/tools-panel/tools-panel-header/component.js b/packages/components/src/tools-panel/tools-panel-header/component.js index 8751d62dd69e9f..0de574250d714b 100644 --- a/packages/components/src/tools-panel/tools-panel-header/component.js +++ b/packages/components/src/tools-panel/tools-panel-header/component.js @@ -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 (

{ header } diff --git a/packages/components/src/tools-panel/tools-panel-header/hook.js b/packages/components/src/tools-panel/tools-panel-header/hook.js index 8ae082b174608f..a3c2644ba7475e 100644 --- a/packages/components/src/tools-panel/tools-panel-header/hook.js +++ b/packages/components/src/tools-panel/tools-panel-header/hook.js @@ -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'; @@ -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, }; } diff --git a/packages/components/src/tools-panel/tools-panel-item/component.js b/packages/components/src/tools-panel/tools-panel-item/component.js index 9002bb08ecce94..92cca8a626022f 100644 --- a/packages/components/src/tools-panel/tools-panel-item/component.js +++ b/packages/components/src/tools-panel/tools-panel-item/component.js @@ -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; } diff --git a/packages/components/src/tools-panel/tools-panel-item/hook.js b/packages/components/src/tools-panel/tools-panel-item/hook.js index d64d7cedcc7777..c8b1d374e11c03 100644 --- a/packages/components/src/tools-panel/tools-panel-item/hook.js +++ b/packages/components/src/tools-panel/tools-panel-item/hook.js @@ -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, }; } diff --git a/packages/components/src/tools-panel/tools-panel/component.js b/packages/components/src/tools-panel/tools-panel/component.js index 79bc80d22467dd..99ac9baebec1fa 100644 --- a/packages/components/src/tools-panel/tools-panel/component.js +++ b/packages/components/src/tools-panel/tools-panel/component.js @@ -1,15 +1,9 @@ -/** - * 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'; @@ -17,101 +11,19 @@ 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 ( diff --git a/packages/components/src/tools-panel/tools-panel/hook.js b/packages/components/src/tools-panel/tools-panel/hook.js index d8957e9217598b..21dd90682784c3 100644 --- a/packages/components/src/tools-panel/tools-panel/hook.js +++ b/packages/components/src/tools-panel/tools-panel/hook.js @@ -1,29 +1,116 @@ /** * WordPress dependencies */ -import { useMemo } from '@wordpress/element'; +import { useEffect, useMemo, useState } from '@wordpress/element'; /** * Internal dependencies */ import * as styles from '../styles'; +import { MENU_STATES } from '../utils'; import { useContextSystem } from '../../ui/context'; import { useCx } from '../../utils/hooks/use-cx'; export function useToolsPanel( props ) { - const { className, ...otherProps } = useContextSystem( + const { className, resetAll, ...otherProps } = useContextSystem( props, 'ToolsPanel' ); const cx = useCx(); - const classes = useMemo( () => { return cx( styles.ToolsPanel, className ); }, [ className ] ); + // 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 { ...otherProps, + panelContext, + resetAllItems, + toggleItem, className: classes, }; }