diff --git a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
index 3d30e7664ae887..abf30378e091d4 100644
--- a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
+++ b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
@@ -80,7 +80,7 @@ exports[`ColorPaletteControl matches the snapshot 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
diff --git a/packages/components/src/color-palette/test/__snapshots__/index.js.snap b/packages/components/src/color-palette/test/__snapshots__/index.js.snap
index 7bf8892b02e943..c75cd0d1cd0138 100644
--- a/packages/components/src/color-palette/test/__snapshots__/index.js.snap
+++ b/packages/components/src/color-palette/test/__snapshots__/index.js.snap
@@ -365,7 +365,7 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
}
@@ -388,10 +388,10 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
diff --git a/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js b/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js
index 328f9496c4c0b9..fa2318a9fd7282 100644
--- a/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js
+++ b/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js
@@ -7,7 +7,7 @@ import { Text, TouchableOpacity, View } from 'react-native';
* WordPress dependencies
*/
import { withPreferredColorScheme } from '@wordpress/compose';
-import { Icon, minus, plus } from '@wordpress/icons';
+import { Icon, plus, reset } from '@wordpress/icons';
/**
* Internal dependencies
@@ -46,7 +46,7 @@ function Stepper( {
onPressOut={ onPressOut }
style={ [ buttonStyle, isMinValue ? { opacity: 0.4 } : null ] }
>
-
+
{
hasValue={ () => !! width }
label="Width"
onDeselect={ () => setWidth( undefined ) }
+ isShownByDefault={ true }
>
{
hasValue={ () => !! height }
label="Height"
onDeselect={ () => setHeight( undefined ) }
+ isShownByDefault={ true }
>
{
expect( control ).toBeInTheDocument();
} );
- it( 'should prevent panel item rendering when toggled off via menu item', async () => {
+ it( 'should prevent optional panel item rendering when toggled off via menu item', async () => {
renderPanel();
await selectMenuItem( controlProps.label );
const control = screen.queryByText( 'Example control' );
@@ -265,7 +265,7 @@ describe( 'ToolsPanel', () => {
expect( control ).not.toBeInTheDocument();
} );
- it( 'should prevent shown by default item rendering when toggled off via menu item', async () => {
+ it( 'should continue to render shown by default item after it is toggled off via menu item', async () => {
render(
{
await selectMenuItem( controlProps.label );
const resetControl = screen.queryByText( 'Default control' );
- expect( resetControl ).not.toBeInTheDocument();
+ expect( resetControl ).toBeInTheDocument();
+ } );
+
+ it( 'should render appropriate menu groups', async () => {
+ const { container } = render(
+
+
+ Default control
+
+
+ Optional control
+
+
+ );
+ openDropdownMenu();
+
+ const menuGroups = container.querySelectorAll(
+ '.components-menu-group'
+ );
+
+ // Groups should be: default controls, optional controls & reset all.
+ expect( menuGroups.length ).toEqual( 3 );
} );
} );
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 09331069378493..2a1e82e2338f71 100644
--- a/packages/components/src/tools-panel/tools-panel-header/component.js
+++ b/packages/components/src/tools-panel/tools-panel-header/component.js
@@ -1,8 +1,8 @@
/**
* WordPress dependencies
*/
-import { check, moreVertical } from '@wordpress/icons';
-import { __ } from '@wordpress/i18n';
+import { check, reset, moreVertical } from '@wordpress/icons';
+import { __, sprintf } from '@wordpress/i18n';
/**
* Internal dependencies
@@ -13,8 +13,87 @@ import MenuItem from '../../menu-item';
import { useToolsPanelHeader } from './hook';
import { contextConnect } from '../../ui/context';
+const DefaultControlsGroup = ( { items, onClose, toggleItem } ) => {
+ if ( ! items.length ) {
+ return null;
+ }
+
+ return (
+
+ { items.map( ( [ label, hasValue ] ) => {
+ const icon = hasValue ? reset : check;
+ const itemLabel = hasValue
+ ? sprintf(
+ // translators: %s: The name of the control being reset e.g. "Padding".
+ __( 'Reset %s' ),
+ label
+ )
+ : undefined;
+
+ return (
+
+ );
+ } ) }
+
+ );
+};
+
+const OptionalControlsGroup = ( { items, onClose, toggleItem } ) => {
+ if ( ! items.length ) {
+ return null;
+ }
+
+ return (
+
+ { items.map( ( [ label, isSelected ] ) => {
+ const itemLabel = isSelected
+ ? sprintf(
+ // translators: %s: The name of the control being hidden and reset e.g. "Padding".
+ __( 'Hide and reset %s' ),
+ label
+ )
+ : sprintf(
+ // translators: %s: The name of the control to display e.g. "Padding".
+ __( 'Show %s' ),
+ label
+ );
+
+ return (
+
+ );
+ } ) }
+
+ );
+};
+
const ToolsPanelHeader = ( props, forwardedRef ) => {
const {
+ dropdownMenuClassName,
hasMenuItems,
label: labelText,
menuItems,
@@ -27,35 +106,33 @@ const ToolsPanelHeader = ( props, forwardedRef ) => {
return null;
}
+ const defaultItems = Object.entries( menuItems?.default || {} );
+ const optionalItems = Object.entries( menuItems?.optional || {} );
+
return (
{ labelText }
{ hasMenuItems && (
-
+
{ ( { onClose } ) => (
<>
-
- { Object.entries( menuItems ).map(
- ( [ label, isSelected ] ) => {
- return (
-
- );
- }
- ) }
-
+
+