From f9792152bf003a4e46f07d4a05da17f806cc930e Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 6 Jul 2022 15:47:43 +1200 Subject: [PATCH 01/89] WIP - some initial structure for adding spacing presets support to the editor UI --- packages/block-editor/src/hooks/index.js | 1 + packages/block-editor/src/hooks/padding.js | 21 +++++--- .../block-editor/src/hooks/spacing-size.js | 51 +++++++++++++++++++ packages/blocks/src/api/constants.js | 1 + 4 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 packages/block-editor/src/hooks/spacing-size.js diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 272e79e78dbdd..0e1c2111defe5 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -15,6 +15,7 @@ import './duotone'; import './font-size'; import './border'; import './layout'; +import './spacing-size'; export { useCustomSides } from './dimensions'; export { getBorderClassesAndStyles, useBorderProps } from './use-border-props'; diff --git a/packages/block-editor/src/hooks/padding.js b/packages/block-editor/src/hooks/padding.js index 02a371db0d6a3..631edd5355c50 100644 --- a/packages/block-editor/src/hooks/padding.js +++ b/packages/block-editor/src/hooks/padding.js @@ -28,6 +28,7 @@ import { } from './dimensions'; import { cleanEmptyObject } from './utils'; import BlockPopover from '../components/block-popover'; +import { SpacingSizeEdit, getSpacingPresetCssVar } from './spacing-size'; /** * Determines if there is padding support. @@ -123,7 +124,12 @@ export function PaddingEdit( props ) { ...style, spacing: { ...style?.spacing, - padding: next, + padding: { + top: next, + right: next, + bottom: next, + left: next, + }, }, }; @@ -135,7 +141,7 @@ export function PaddingEdit( props ) { return Platform.select( { web: ( <> - + /> */ } + ), native: null, @@ -154,10 +161,10 @@ export function PaddingVisualizer( { clientId, attributes } ) { const padding = attributes?.style?.spacing?.padding; const style = useMemo( () => { return { - borderTopWidth: padding?.top ?? 0, - borderRightWidth: padding?.right ?? 0, - borderBottomWidth: padding?.bottom ?? 0, - borderLeftWidth: padding?.left ?? 0, + borderTopWidth: getSpacingPresetCssVar( padding?.top ) ?? 0, + borderRightWidth: getSpacingPresetCssVar( padding?.right ) ?? 0, + borderBottomWidth: getSpacingPresetCssVar( padding?.bottom ) ?? 0, + borderLeftWidth: getSpacingPresetCssVar( padding?.left ) ?? 0, }; }, [ padding ] ); diff --git a/packages/block-editor/src/hooks/spacing-size.js b/packages/block-editor/src/hooks/spacing-size.js new file mode 100644 index 0000000000000..0d822645dd21a --- /dev/null +++ b/packages/block-editor/src/hooks/spacing-size.js @@ -0,0 +1,51 @@ +/** + * WordPress dependencies + */ +import { RangeControl } from '@wordpress/components'; +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import useSetting from '../components/use-setting'; + +export function getSpacingPresetCssVar( presetVar ) { + if ( ! presetVar ) { + return; + } + const slug = /var:preset\|spacing\|(.+)/.exec( presetVar ); + return `var(--wp--preset--spacing--${ slug[ 1 ] })`; +} + +/** + * Inspector control panel containing the spacing size related configuration + * + * @param {Object} props + * + * @return {WPElement} Font size edit element. + */ +export function SpacingSizeEdit( props ) { + const [ valueNow, setValueNow ] = useState( null ); + const spacingSizes = useSetting( 'spacing.spacingSizes' ); + const customTooltipContent = ( value ) => spacingSizes[ value ].name; + return ( + <> + { + setValueNow( newSize ); + props.onChange( + `var:preset|spacing|${ spacingSizes[ newSize ].slug }` + ); + } } + min={ 0 } + max={ spacingSizes.length - 1 } + withInputField={ false } + aria-valuenow={ valueNow } + aria-valuetext={ spacingSizes[ valueNow ]?.name } + renderTooltipContent={ customTooltipContent } + /> + + ); +} diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index 22205777d14e4..bb182d6669676 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -232,4 +232,5 @@ export const __EXPERIMENTAL_PATHS_WITH_MERGE = { 'color.palette': true, 'typography.fontFamilies': true, 'typography.fontSizes': true, + 'spacing.spacingSizes': true, }; From f034f2ff7ef34d3689aceb818d4f781f79ef4fce Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 7 Jul 2022 09:42:54 +1200 Subject: [PATCH 02/89] Fix doc comment --- packages/block-editor/src/hooks/spacing-size.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/spacing-size.js b/packages/block-editor/src/hooks/spacing-size.js index 0d822645dd21a..46a0fc9dfd3c4 100644 --- a/packages/block-editor/src/hooks/spacing-size.js +++ b/packages/block-editor/src/hooks/spacing-size.js @@ -18,7 +18,7 @@ export function getSpacingPresetCssVar( presetVar ) { } /** - * Inspector control panel containing the spacing size related configuration + * Inspector control panel containing the spacing size related configuration. * * @param {Object} props * From b57577f80fab4fb4dbecb7dfbef5dd9be11b1f8d Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 7 Jul 2022 09:51:25 +1200 Subject: [PATCH 03/89] Test --- packages/block-editor/src/hooks/spacing-size.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/spacing-size.js b/packages/block-editor/src/hooks/spacing-size.js index 46a0fc9dfd3c4..0d822645dd21a 100644 --- a/packages/block-editor/src/hooks/spacing-size.js +++ b/packages/block-editor/src/hooks/spacing-size.js @@ -18,7 +18,7 @@ export function getSpacingPresetCssVar( presetVar ) { } /** - * Inspector control panel containing the spacing size related configuration. + * Inspector control panel containing the spacing size related configuration * * @param {Object} props * From 4e8381a58ff650ae40f627cfd5c541ec1686ad15 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 4 Aug 2022 12:57:43 +1200 Subject: [PATCH 04/89] Add initial spacing box control component --- packages/block-editor/src/hooks/padding.js | 40 ++- .../block-editor/src/hooks/spacing-size.js | 41 ++- .../src/spacing-sizes-box-control/README.md | 97 ++++++ .../all-input-control.js | 72 ++++ .../axial-input-controls.js | 119 +++++++ .../src/spacing-sizes-box-control/icon.js | 50 +++ .../src/spacing-sizes-box-control/index.js | 177 ++++++++++ .../input-controls.js | 102 ++++++ .../linked-button.js | 31 ++ .../spacing-range-control.js | 45 +++ .../stories/index.js | 94 ++++++ .../styles/box-control-icon-styles.js | 65 ++++ .../styles/box-control-styles.js | 74 +++++ .../styles/box-control-visualizer-styles.js | 72 ++++ .../spacing-sizes-box-control/test/index.js | 313 ++++++++++++++++++ .../src/spacing-sizes-box-control/utils.js | 203 ++++++++++++ 16 files changed, 1558 insertions(+), 37 deletions(-) create mode 100644 packages/components/src/spacing-sizes-box-control/README.md create mode 100644 packages/components/src/spacing-sizes-box-control/all-input-control.js create mode 100644 packages/components/src/spacing-sizes-box-control/axial-input-controls.js create mode 100644 packages/components/src/spacing-sizes-box-control/icon.js create mode 100644 packages/components/src/spacing-sizes-box-control/index.js create mode 100644 packages/components/src/spacing-sizes-box-control/input-controls.js create mode 100644 packages/components/src/spacing-sizes-box-control/linked-button.js create mode 100644 packages/components/src/spacing-sizes-box-control/spacing-range-control.js create mode 100644 packages/components/src/spacing-sizes-box-control/stories/index.js create mode 100644 packages/components/src/spacing-sizes-box-control/styles/box-control-icon-styles.js create mode 100644 packages/components/src/spacing-sizes-box-control/styles/box-control-styles.js create mode 100644 packages/components/src/spacing-sizes-box-control/styles/box-control-visualizer-styles.js create mode 100644 packages/components/src/spacing-sizes-box-control/test/index.js create mode 100644 packages/components/src/spacing-sizes-box-control/utils.js diff --git a/packages/block-editor/src/hooks/padding.js b/packages/block-editor/src/hooks/padding.js index 631edd5355c50..59d4f8c2c440e 100644 --- a/packages/block-editor/src/hooks/padding.js +++ b/packages/block-editor/src/hooks/padding.js @@ -13,6 +13,7 @@ import { getBlockSupport } from '@wordpress/blocks'; import { __experimentalUseCustomUnits as useCustomUnits, __experimentalBoxControl as BoxControl, + __experimentalSpacingSizesBoxControl as SpacingSizesBoxControl, } from '@wordpress/components'; import isShallowEqual from '@wordpress/is-shallow-equal'; @@ -124,12 +125,7 @@ export function PaddingEdit( props ) { ...style, spacing: { ...style?.spacing, - padding: { - top: next, - right: next, - bottom: next, - left: next, - }, + padding: next, }, }; @@ -141,16 +137,28 @@ export function PaddingEdit( props ) { return Platform.select( { web: ( <> - { /* */ } - + { + + } + { + + } ), native: null, diff --git a/packages/block-editor/src/hooks/spacing-size.js b/packages/block-editor/src/hooks/spacing-size.js index 0d822645dd21a..db05d0b086e48 100644 --- a/packages/block-editor/src/hooks/spacing-size.js +++ b/packages/block-editor/src/hooks/spacing-size.js @@ -1,19 +1,21 @@ /** * WordPress dependencies */ -import { RangeControl } from '@wordpress/components'; -import { useState } from '@wordpress/element'; +import { __experimentalSpacingSizesBoxControl as SpacingSizesBoxControl } from '@wordpress/components'; /** * Internal dependencies */ import useSetting from '../components/use-setting'; -export function getSpacingPresetCssVar( presetVar ) { - if ( ! presetVar ) { +export function getSpacingPresetCssVar( value ) { + if ( ! value ) { return; } - const slug = /var:preset\|spacing\|(.+)/.exec( presetVar ); + const slug = /var:preset\|spacing\|(.+)/.exec( value ); + if ( ! slug ) { + return value; + } return `var(--wp--preset--spacing--${ slug[ 1 ] })`; } @@ -25,26 +27,23 @@ export function getSpacingPresetCssVar( presetVar ) { * @return {WPElement} Font size edit element. */ export function SpacingSizeEdit( props ) { - const [ valueNow, setValueNow ] = useState( null ); const spacingSizes = useSetting( 'spacing.spacingSizes' ); - const customTooltipContent = ( value ) => spacingSizes[ value ].name; + return ( <> - { - setValueNow( newSize ); - props.onChange( - `var:preset|spacing|${ spacingSizes[ newSize ].slug }` - ); - } } - min={ 0 } - max={ spacingSizes.length - 1 } + ); diff --git a/packages/components/src/spacing-sizes-box-control/README.md b/packages/components/src/spacing-sizes-box-control/README.md new file mode 100644 index 0000000000000..8a704482cacd1 --- /dev/null +++ b/packages/components/src/spacing-sizes-box-control/README.md @@ -0,0 +1,97 @@ +# BoxControl + +
+This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. +
+ +BoxControl components let users set values for Top, Right, Bottom, and Left. This can be used as an input control for values like `padding` or `margin`. + +## Usage + +```jsx +import { __experimentalBoxControl as BoxControl } from '@wordpress/components'; +import { useState } from '@wordpress/element'; + +const Example = () => { + const [ values, setValues ] = useState( { + top: '50px', + left: '10%', + right: '10%', + bottom: '50px', + } ); + + return ( + setValues( nextValues ) } + /> + ); +}; +``` + +## Props +### allowReset + +If this property is true, a button to reset the box control is rendered. + +- Type: `Boolean` +- Required: No +- Default: `true` + +### splitOnAxis + +If this property is true, when the box control is unlinked, vertical and horizontal controls can be used instead of updating individual sides. + +- Type: `Boolean` +- Required: No +- Default: `false` + +### inputProps + +Props for the internal [InputControl](../input-control) components. + +- Type: `Object` +- Required: No + +### label + +Heading label for BoxControl. + +- Type: `String` +- Required: No +- Default: `Box Control` + +### onChange + +A callback function when an input value changes. + +- Type: `Function` +- Required: Yes + +### resetValues + +The `top`, `right`, `bottom`, and `left` box dimension values to use when the control is reset. + +- Type: `Object` +- Required: No + +### sides + +Collection of sides to allow control of. If omitted or empty, all sides will be available. + +- Type: `Array` +- Required: No + +### units + +Collection of available units which are compatible with [UnitControl](../unit-control). + +- Type: `Array` +- Required: No + +### values + +The `top`, `right`, `bottom`, and `left` box dimension values. + +- Type: `Object` +- Required: No diff --git a/packages/components/src/spacing-sizes-box-control/all-input-control.js b/packages/components/src/spacing-sizes-box-control/all-input-control.js new file mode 100644 index 0000000000000..860351f9a2ea0 --- /dev/null +++ b/packages/components/src/spacing-sizes-box-control/all-input-control.js @@ -0,0 +1,72 @@ +/** + * Internal dependencies + */ +import SpacingRangeControl from './spacing-range-control'; +import { + ALL_SIDES, + LABELS, + getAllValue, + isValuesMixed, + isValuesDefined, +} from './utils'; + +const noop = () => {}; + +export default function AllInputControl( { + onChange = noop, + onFocus = noop, + values, + sides, + ...props +} ) { + const allValue = getAllValue( values, sides ); + const hasValues = isValuesDefined( values ); + const isMixed = hasValues && isValuesMixed( values, sides ); + const allPlaceholder = isMixed ? LABELS.mixed : null; + + const handleOnFocus = ( event ) => { + onFocus( event, { side: 'all' } ); + }; + + // Applies a value to an object representing top, right, bottom and left + // sides while taking into account any custom side configuration. + const applyValueToSides = ( currentValues, newValue ) => { + const newValues = { ...currentValues }; + + if ( sides?.length ) { + sides.forEach( ( side ) => { + if ( side === 'vertical' ) { + newValues.top = newValue; + newValues.bottom = newValue; + } else if ( side === 'horizontal' ) { + newValues.left = newValue; + newValues.right = newValue; + } else { + newValues[ side ] = newValue; + } + } ); + } else { + ALL_SIDES.forEach( ( side ) => ( newValues[ side ] = newValue ) ); + } + + return newValues; + }; + + const handleOnChange = ( next ) => { + const nextValues = applyValueToSides( values, next ); + + onChange( nextValues ); + }; + + return ( + + ); +} diff --git a/packages/components/src/spacing-sizes-box-control/axial-input-controls.js b/packages/components/src/spacing-sizes-box-control/axial-input-controls.js new file mode 100644 index 0000000000000..20d58326fab8d --- /dev/null +++ b/packages/components/src/spacing-sizes-box-control/axial-input-controls.js @@ -0,0 +1,119 @@ +/** + * Internal dependencies + */ +import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; +import SpacingRangeControl from './spacing-range-control'; +import { LABELS } from './utils'; +import { Layout } from './styles/box-control-styles'; + +const groupedSides = [ 'vertical', 'horizontal' ]; + +export default function AxialInputControls( { + onChange, + onFocus, + onHoverOn, + onHoverOff, + values, + sides, + ...props +} ) { + const createHandleOnFocus = ( side ) => ( event ) => { + if ( ! onFocus ) { + return; + } + onFocus( event, { side } ); + }; + + const createHandleOnHoverOn = ( side ) => () => { + if ( ! onHoverOn ) { + return; + } + if ( side === 'vertical' ) { + onHoverOn( { + top: true, + bottom: true, + } ); + } + if ( side === 'horizontal' ) { + onHoverOn( { + left: true, + right: true, + } ); + } + }; + + const createHandleOnHoverOff = ( side ) => () => { + if ( ! onHoverOff ) { + return; + } + if ( side === 'vertical' ) { + onHoverOff( { + top: false, + bottom: false, + } ); + } + if ( side === 'horizontal' ) { + onHoverOff( { + left: false, + right: false, + } ); + } + }; + + const createHandleOnChange = ( side ) => ( next ) => { + if ( ! onChange ) { + return; + } + const nextValues = { ...values }; + const nextValue = next; + + if ( side === 'vertical' ) { + nextValues.top = nextValue; + nextValues.bottom = nextValue; + } + + if ( side === 'horizontal' ) { + nextValues.left = nextValue; + nextValues.right = nextValue; + } + + onChange( nextValues ); + }; + + // Filter sides if custom configuration provided, maintaining default order. + const filteredSides = sides?.length + ? groupedSides.filter( ( side ) => sides.includes( side ) ) + : groupedSides; + + const first = filteredSides[ 0 ]; + const last = filteredSides[ filteredSides.length - 1 ]; + const only = first === last; + + return ( + + { filteredSides.map( ( side ) => { + const [ parsedQuantity ] = parseQuantityAndUnitFromRawValue( + side === 'vertical' ? values.top : values.left + ); + return ( + + ); + } ) } + + ); +} diff --git a/packages/components/src/spacing-sizes-box-control/icon.js b/packages/components/src/spacing-sizes-box-control/icon.js new file mode 100644 index 0000000000000..2a7db9972c5b1 --- /dev/null +++ b/packages/components/src/spacing-sizes-box-control/icon.js @@ -0,0 +1,50 @@ +/** + * Internal dependencies + */ +import { + Root, + Viewbox, + TopStroke, + RightStroke, + BottomStroke, + LeftStroke, +} from './styles/box-control-icon-styles'; + +const BASE_ICON_SIZE = 24; + +export default function BoxControlIcon( { + size = 24, + side = 'all', + sides, + ...props +} ) { + const isSideDisabled = ( value ) => + sides?.length && ! sides.includes( value ); + + const hasSide = ( value ) => { + if ( isSideDisabled( value ) ) { + return false; + } + + return side === 'all' || side === value; + }; + + const top = hasSide( 'top' ) || hasSide( 'vertical' ); + const right = hasSide( 'right' ) || hasSide( 'horizontal' ); + const bottom = hasSide( 'bottom' ) || hasSide( 'vertical' ); + const left = hasSide( 'left' ) || hasSide( 'horizontal' ); + + // Simulates SVG Icon scaling. + const scale = size / BASE_ICON_SIZE; + + return ( + + + + + + + + + ); +} diff --git a/packages/components/src/spacing-sizes-box-control/index.js b/packages/components/src/spacing-sizes-box-control/index.js new file mode 100644 index 0000000000000..5f4abc7d0839f --- /dev/null +++ b/packages/components/src/spacing-sizes-box-control/index.js @@ -0,0 +1,177 @@ +/** + * WordPress dependencies + */ +import { useInstanceId } from '@wordpress/compose'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import Button from '../button'; +import { FlexItem, FlexBlock } from '../flex'; +import AllInputControl from './all-input-control'; +import InputControls from './input-controls'; +import AxialInputControls from './axial-input-controls'; +import BoxControlIcon from './icon'; +import { Text } from '../text'; +import LinkedButton from './linked-button'; +import { + Root, + Header, + HeaderControlWrapper, +} from './styles/box-control-styles'; +import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; +import { + DEFAULT_VALUES, + getInitialSide, + isValuesMixed, + isValuesDefined, +} from './utils'; +import { useControlledState } from '../utils/hooks'; + +const defaultInputProps = { + min: 0, +}; + +const noop = () => {}; + +function useUniqueId( idProp ) { + const instanceId = useInstanceId( BoxControl, 'inspector-box-control' ); + + return idProp || instanceId; +} +export default function BoxControl( { + id: idProp, + inputProps = defaultInputProps, + onChange = noop, + label = __( 'Box Control' ), + values: valuesProp, + units, + sides, + splitOnAxis = false, + allowReset = true, + resetValues = DEFAULT_VALUES, + spacingSizes, +} ) { + const [ values, setValues ] = useControlledState( valuesProp, { + fallback: DEFAULT_VALUES, + } ); + const inputValues = values || DEFAULT_VALUES; + const hasInitialValue = isValuesDefined( valuesProp ); + const hasOneSide = sides?.length === 1; + + const [ isDirty, setIsDirty ] = useState( hasInitialValue ); + const [ isLinked, setIsLinked ] = useState( + ! hasInitialValue || ! isValuesMixed( inputValues ) || hasOneSide + ); + + const [ side, setSide ] = useState( + getInitialSide( isLinked, splitOnAxis ) + ); + + // Tracking selected units via internal state allows filtering of CSS unit + // only values from being saved while maintaining preexisting unit selection + // behaviour. Filtering CSS only values prevents invalid style values. + const [ selectedUnits, setSelectedUnits ] = useState( { + top: parseQuantityAndUnitFromRawValue( valuesProp?.top )[ 1 ], + right: parseQuantityAndUnitFromRawValue( valuesProp?.right )[ 1 ], + bottom: parseQuantityAndUnitFromRawValue( valuesProp?.bottom )[ 1 ], + left: parseQuantityAndUnitFromRawValue( valuesProp?.left )[ 1 ], + } ); + + const id = useUniqueId( idProp ); + const headingId = `${ id }-heading`; + + const toggleLinked = () => { + setIsLinked( ! isLinked ); + setSide( getInitialSide( ! isLinked, splitOnAxis ) ); + }; + + const handleOnFocus = ( event, { side: nextSide } ) => { + setSide( nextSide ); + }; + + const handleOnChange = ( nextValues ) => { + onChange( nextValues ); + setValues( nextValues ); + setIsDirty( true ); + }; + + const handleOnReset = () => { + onChange( resetValues ); + setValues( resetValues ); + setSelectedUnits( resetValues ); + setIsDirty( false ); + }; + + const inputControlProps = { + ...inputProps, + onChange: handleOnChange, + onFocus: handleOnFocus, + isLinked, + units, + selectedUnits, + setSelectedUnits, + sides, + values: inputValues, + spacingSizes, + }; + + return ( + +
+ + + { label } + + + { allowReset && ( + + + + ) } +
+ + + + + { isLinked && ( + + + + ) } + { ! isLinked && splitOnAxis && ( + + + + ) } + { ! hasOneSide && ( + + + + ) } + + { ! isLinked && ! splitOnAxis && ( + + ) } +
+ ); +} diff --git a/packages/components/src/spacing-sizes-box-control/input-controls.js b/packages/components/src/spacing-sizes-box-control/input-controls.js new file mode 100644 index 0000000000000..72e8e0a5465fe --- /dev/null +++ b/packages/components/src/spacing-sizes-box-control/input-controls.js @@ -0,0 +1,102 @@ +/** + * Internal dependencies + */ +import SpacingRangeControl from './spacing-range-control'; +import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; +import { ALL_SIDES, LABELS } from './utils'; +import { LayoutContainer, Layout } from './styles/box-control-styles'; + +const noop = () => {}; + +export default function BoxInputControls( { + onChange = noop, + onFocus = noop, + values, + selectedUnits, + setSelectedUnits, + sides, + ...props +} ) { + const createHandleOnFocus = ( side ) => ( event ) => { + onFocus( event, { side } ); + }; + + const handleOnChange = ( nextValues ) => { + onChange( nextValues ); + }; + + const createHandleOnChange = ( side ) => ( next ) => { + // const { altKey } = event; + const altKey = null; + const nextValues = { ...values }; + nextValues[ side ] = next; + + /** + * Supports changing pair sides. For example, holding the ALT key + * when changing the TOP will also update BOTTOM. + */ + if ( altKey ) { + switch ( side ) { + case 'top': + nextValues.bottom = next; + break; + case 'bottom': + nextValues.top = next; + break; + case 'left': + nextValues.right = next; + break; + case 'right': + nextValues.left = next; + break; + } + } + + handleOnChange( nextValues ); + }; + + // Filter sides if custom configuration provided, maintaining default order. + const filteredSides = sides?.length + ? ALL_SIDES.filter( ( side ) => sides.includes( side ) ) + : ALL_SIDES; + + const first = filteredSides[ 0 ]; + const last = filteredSides[ filteredSides.length - 1 ]; + const only = first === last && first; + + return ( + + + { filteredSides.map( ( side ) => { + const [ parsedQuantity, parsedUnit ] = + parseQuantityAndUnitFromRawValue( values[ side ] ); + + const computedUnit = values[ side ] + ? parsedUnit + : selectedUnits[ side ]; + + return ( + + ); + } ) } + + + ); +} diff --git a/packages/components/src/spacing-sizes-box-control/linked-button.js b/packages/components/src/spacing-sizes-box-control/linked-button.js new file mode 100644 index 0000000000000..3a24a62f188be --- /dev/null +++ b/packages/components/src/spacing-sizes-box-control/linked-button.js @@ -0,0 +1,31 @@ +/** + * WordPress dependencies + */ +import { link, linkOff } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import Button from '../button'; +import Tooltip from '../tooltip'; + +export default function LinkedButton( { isLinked, ...props } ) { + const label = isLinked ? __( 'Unlink Sides' ) : __( 'Link Sides' ); + + return ( + + +