From e0b4e47477ce4510c191a9bb63dbd751d6b10dec Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 4 Dec 2024 00:48:03 +0100 Subject: [PATCH 01/13] Global Styles: Introduce border radius presets --- lib/class-wp-theme-json-gutenberg.php | 18 ++- lib/theme-i18n.json | 14 ++ .../all-input-control.js | 67 -------- .../components/border-radius-control/index.js | 122 ++++++++------- .../border-radius-control/input-controls.js | 91 ----------- .../single-input-control.js | 148 ++++++++++++++++++ .../border-radius-control/style.scss | 9 +- .../components/global-styles/border-panel.js | 1 + packages/block-editor/src/hooks/utils.js | 4 + schemas/json/theme.json | 22 +++ test/emptytheme/theme.json | 38 +++++ 11 files changed, 309 insertions(+), 225 deletions(-) delete mode 100644 packages/block-editor/src/components/border-radius-control/all-input-control.js delete mode 100644 packages/block-editor/src/components/border-radius-control/input-controls.js create mode 100644 packages/block-editor/src/components/border-radius-control/single-input-control.js diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 083ce3516b71af..2d9d23c4b3cfdf 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -204,6 +204,15 @@ class WP_Theme_JSON_Gutenberg { 'classes' => array(), 'properties' => array( 'box-shadow' ), ), + array( + 'path' => array( 'border', 'radiusSizes' ), + 'prevent_override' => false, + 'use_default_names' => false, + 'value_key' => 'size', + 'css_vars' => '--wp--preset--border--radius--$slug', + 'classes' => array(), + 'properties' => array( 'border-radius' ), + ), ); /** @@ -386,10 +395,11 @@ class WP_Theme_JSON_Gutenberg { 'backgroundSize' => null, ), 'border' => array( - 'color' => null, - 'radius' => null, - 'style' => null, - 'width' => null, + 'color' => null, + 'radius' => null, + 'style' => null, + 'width' => null, + 'radiusSizes' => null, ), 'color' => array( 'background' => null, diff --git a/lib/theme-i18n.json b/lib/theme-i18n.json index e4d14502132cbe..1da001f7eb842b 100644 --- a/lib/theme-i18n.json +++ b/lib/theme-i18n.json @@ -45,6 +45,13 @@ } ] }, + "border": { + "radiusSizes": [ + { + "name": "Border radius size name" + } + ] + }, "blocks": { "*": { "typography": { @@ -77,6 +84,13 @@ "name": "Space size name" } ] + }, + "border": { + "radiusSizes": [ + { + "name": "Border radius size name" + } + ] } } } diff --git a/packages/block-editor/src/components/border-radius-control/all-input-control.js b/packages/block-editor/src/components/border-radius-control/all-input-control.js deleted file mode 100644 index 14abf3c6c2bc94..00000000000000 --- a/packages/block-editor/src/components/border-radius-control/all-input-control.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * WordPress dependencies - */ -import { __experimentalUnitControl as UnitControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { - getAllValue, - getAllUnit, - hasMixedValues, - hasDefinedValues, -} from './utils'; - -export default function AllInputControl( { - onChange, - selectedUnits, - setSelectedUnits, - values, - ...props -} ) { - let allValue = getAllValue( values ); - - if ( allValue === undefined ) { - // If we don't have any value set the unit to any current selection - // or the most common unit from the individual radii values. - allValue = getAllUnit( selectedUnits ); - } - - const hasValues = hasDefinedValues( values ); - const isMixed = hasValues && hasMixedValues( values ); - const allPlaceholder = isMixed ? __( 'Mixed' ) : null; - - // Filter out CSS-unit-only values to prevent invalid styles. - const handleOnChange = ( next ) => { - const isNumeric = ! isNaN( parseFloat( next ) ); - const nextValue = isNumeric ? next : undefined; - onChange( nextValue ); - }; - - // Store current unit selection for use as fallback for individual - // radii controls. - const handleOnUnitChange = ( unit ) => { - setSelectedUnits( { - topLeft: unit, - topRight: unit, - bottomLeft: unit, - bottomRight: unit, - } ); - }; - - return ( - - ); -} diff --git a/packages/block-editor/src/components/border-radius-control/index.js b/packages/block-editor/src/components/border-radius-control/index.js index cab9b87b3b29c0..c995c126422fd1 100644 --- a/packages/block-editor/src/components/border-radius-control/index.js +++ b/packages/block-editor/src/components/border-radius-control/index.js @@ -3,26 +3,21 @@ */ import { BaseControl, - RangeControl, __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, __experimentalUseCustomUnits as useCustomUnits, + __experimentalVStack as VStack, + __experimentalHStack as HStack, } from '@wordpress/components'; -import { useState } from '@wordpress/element'; +import { useState, useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import AllInputControl from './all-input-control'; -import InputControls from './input-controls'; import LinkedButton from './linked-button'; import { useSettings } from '../use-settings'; -import { - getAllValue, - getAllUnit, - hasDefinedValues, - hasMixedValues, -} from './utils'; +import { hasDefinedValues, hasMixedValues } from './utils'; +import SingleInputControl from './single-input-control'; const DEFAULT_VALUES = { topLeft: undefined, @@ -30,12 +25,33 @@ const DEFAULT_VALUES = { bottomLeft: undefined, bottomRight: undefined, }; -const MIN_BORDER_RADIUS_VALUE = 0; -const MAX_BORDER_RADIUS_VALUES = { - px: 100, - em: 20, - rem: 20, -}; +const RANGE_CONTROL_MAX_SIZE = 8; +const EMPTY_ARRAY = []; +function useBorderRadiusSizes( presets ) { + const defaultSizes = presets?.radiusSizes?.default ?? EMPTY_ARRAY; + const customSizes = presets?.radiusSizes?.custom ?? EMPTY_ARRAY; + const themeSizes = presets?.radiusSizes?.theme ?? EMPTY_ARRAY; + + return useMemo( () => { + const sizes = [ + { name: __( 'None' ), slug: '0', size: 0 }, + ...customSizes, + ...themeSizes, + ...defaultSizes, + ]; + + return sizes.length > RANGE_CONTROL_MAX_SIZE + ? [ + { + name: __( 'Default' ), + slug: 'default', + size: undefined, + }, + ...sizes, + ] + : sizes; + }, [ customSizes, themeSizes, defaultSizes ] ); +} /** * Control to display border radius options. @@ -43,14 +59,15 @@ const MAX_BORDER_RADIUS_VALUES = { * @param {Object} props Component props. * @param {Function} props.onChange Callback to handle onChange. * @param {Object} props.values Border radius values. + * @param {Object} props.presets Border radius presets. * * @return {Element} Custom border radius control. */ -export default function BorderRadiusControl( { onChange, values } ) { +export default function BorderRadiusControl( { onChange, values, presets } ) { const [ isLinked, setIsLinked ] = useState( ! hasDefinedValues( values ) || ! hasMixedValues( values ) ); - + const options = useBorderRadiusSizes( presets ); // Tracking selected units via internal state allows filtering of CSS unit // only values from being saved while maintaining preexisting unit selection // behaviour. Filtering CSS unit only values prevents invalid style values. @@ -72,63 +89,48 @@ export default function BorderRadiusControl( { onChange, values } ) { availableUnits: availableUnits || [ 'px', 'em', 'rem' ], } ); - const unit = getAllUnit( selectedUnits ); - const unitConfig = units && units.find( ( item ) => item.value === unit ); - const step = unitConfig?.step || 1; - - const [ allValue ] = parseQuantityAndUnitFromRawValue( - getAllValue( values ) - ); - const toggleLinked = () => setIsLinked( ! isLinked ); - const handleSliderChange = ( next ) => { - onChange( next !== undefined ? `${ next }${ unit }` : undefined ); - }; - return (
- - { __( 'Radius' ) } - + + + { __( 'Radius' ) } + + +
{ isLinked ? ( <> - - ) : ( - + + { [ + 'topLeft', + 'topRight', + 'bottomLeft', + 'bottomRight', + ].map( ( corner ) => ( + + ) ) } + ) } -
); diff --git a/packages/block-editor/src/components/border-radius-control/input-controls.js b/packages/block-editor/src/components/border-radius-control/input-controls.js deleted file mode 100644 index 4529c00b997ac7..00000000000000 --- a/packages/block-editor/src/components/border-radius-control/input-controls.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * WordPress dependencies - */ -import { - __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, - __experimentalUnitControl as UnitControl, - Tooltip, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -const CORNERS = { - topLeft: __( 'Top left' ), - topRight: __( 'Top right' ), - bottomLeft: __( 'Bottom left' ), - bottomRight: __( 'Bottom right' ), -}; - -export default function BoxInputControls( { - onChange, - selectedUnits, - setSelectedUnits, - values: valuesProp, - ...props -} ) { - const createHandleOnChange = ( corner ) => ( next ) => { - if ( ! onChange ) { - return; - } - - // Filter out CSS-unit-only values to prevent invalid styles. - const isNumeric = ! isNaN( parseFloat( next ) ); - const nextValue = isNumeric ? next : undefined; - - onChange( { - ...values, - [ corner ]: nextValue, - } ); - }; - - const createHandleOnUnitChange = ( side ) => ( next ) => { - const newUnits = { ...selectedUnits }; - newUnits[ side ] = next; - setSelectedUnits( newUnits ); - }; - - // For shorthand style & backwards compatibility, handle flat string value. - const values = - typeof valuesProp !== 'string' - ? valuesProp - : { - topLeft: valuesProp, - topRight: valuesProp, - bottomLeft: valuesProp, - bottomRight: valuesProp, - }; - - // Controls are wrapped in tooltips as visible labels aren't desired here. - // Tooltip rendering also requires the UnitControl to be wrapped. See: - // https://github.com/WordPress/gutenberg/pull/24966#issuecomment-685875026 - return ( -
- { Object.entries( CORNERS ).map( ( [ corner, label ] ) => { - const [ parsedQuantity, parsedUnit ] = - parseQuantityAndUnitFromRawValue( values[ corner ] ); - - const computedUnit = values[ corner ] - ? parsedUnit - : selectedUnits[ corner ] || selectedUnits.flat; - - return ( - -
- -
-
- ); - } ) } -
- ); -} diff --git a/packages/block-editor/src/components/border-radius-control/single-input-control.js b/packages/block-editor/src/components/border-radius-control/single-input-control.js new file mode 100644 index 00000000000000..224bee46feb696 --- /dev/null +++ b/packages/block-editor/src/components/border-radius-control/single-input-control.js @@ -0,0 +1,148 @@ +/** + * WordPress dependencies + */ +import { + __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, + __experimentalUnitControl as UnitControl, + Tooltip, + RangeControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { getAllValue } from './utils'; + +const CORNERS = { + all: __( 'Border radius' ), + topLeft: __( 'Top left' ), + topRight: __( 'Top right' ), + bottomLeft: __( 'Bottom left' ), + bottomRight: __( 'Bottom right' ), +}; +const MIN_BORDER_RADIUS_VALUE = 0; +const MAX_BORDER_RADIUS_VALUES = { + px: 100, + em: 20, + rem: 20, +}; + +export default function SingleInputControl( { + corner, + onChange, + selectedUnits, + setSelectedUnits, + values: valuesProp, + units, +} ) { + const onChangeValue = ( next ) => { + if ( ! onChange ) { + return; + } + + // Filter out CSS-unit-only values to prevent invalid styles. + const isNumeric = ! isNaN( parseFloat( next ) ); + const nextValue = isNumeric ? next : undefined; + if ( corner === 'all' ) { + onChange( { + topLeft: nextValue, + topRight: nextValue, + bottomLeft: nextValue, + bottomRight: nextValue, + } ); + } else { + onChange( { + ...values, + [ corner ]: nextValue, + } ); + } + }; + + const onChangeUnit = ( next ) => { + const newUnits = { ...selectedUnits }; + if ( corner === 'all' ) { + newUnits.topLeft = next; + newUnits.topRight = next; + newUnits.bottomLeft = next; + newUnits.bottomRight = next; + } else { + newUnits[ corner ] = next; + } + setSelectedUnits( newUnits ); + }; + + // For shorthand style & backwards compatibility, handle flat string value. + const values = + typeof valuesProp !== 'string' + ? valuesProp + : { + topLeft: valuesProp, + topRight: valuesProp, + bottomLeft: valuesProp, + bottomRight: valuesProp, + }; + + const value = corner === 'all' ? getAllValue( values ) : values[ corner ]; + const [ parsedQuantity, parsedUnit ] = + parseQuantityAndUnitFromRawValue( value ); + const computedUnit = value + ? parsedUnit + : selectedUnits[ corner ] || selectedUnits.flat; + const unitConfig = + units && units.find( ( item ) => item.value === computedUnit ); + const step = unitConfig?.step || 1; + const handleSliderChange = ( next ) => { + const val = + next !== undefined ? `${ next }${ computedUnit }` : undefined; + if ( corner === 'all' ) { + onChange( { + topLeft: val, + topRight: val, + bottomLeft: val, + bottomRight: val, + } ); + } else { + onChange( { + ...values, + [ corner ]: val, + } ); + } + }; + + // Controls are wrapped in tooltips as visible labels aren't desired here. + // Tooltip rendering also requires the UnitControl to be wrapped. See: + // https://github.com/WordPress/gutenberg/pull/24966#issuecomment-685875026 + return ( +
+ +
+ +
+
+ +
+ ); +} diff --git a/packages/block-editor/src/components/border-radius-control/style.scss b/packages/block-editor/src/components/border-radius-control/style.scss index 18360cf3162ab1..64da74263b0429 100644 --- a/packages/block-editor/src/components/border-radius-control/style.scss +++ b/packages/block-editor/src/components/border-radius-control/style.scss @@ -1,8 +1,13 @@ +.components-border-radius-control__header { + height: $grid-unit-20; + margin-bottom: $grid-unit-15; +} + .components-border-radius-control { margin-bottom: $grid-unit-15; legend { - margin-bottom: $grid-unit-10; + margin-bottom: 0; } .components-border-radius-control__wrapper { @@ -11,7 +16,6 @@ align-items: flex-start; .components-border-radius-control__unit-control { - width: calc((100% - #{$grid-unit-20}) / 2); margin-bottom: 0; margin-right: $grid-unit-20; flex-shrink: 0; @@ -33,7 +37,6 @@ .component-border-radius-control__linked-button { display: flex; justify-content: center; - margin-top: $grid-unit-10; svg { margin-right: 0; diff --git a/packages/block-editor/src/components/global-styles/border-panel.js b/packages/block-editor/src/components/global-styles/border-panel.js index aea8e67f5b5944..a3b0f070248b63 100644 --- a/packages/block-editor/src/components/global-styles/border-panel.js +++ b/packages/block-editor/src/components/global-styles/border-panel.js @@ -277,6 +277,7 @@ export default function BorderPanel( { panelId={ panelId } > { setBorderRadius( newValue || undefined ); diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index ac6e55efe4d3bd..ddf00ee84657dd 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -268,6 +268,7 @@ export function useBlockSettings( name, parentLayout ) { borderRadius, borderStyle, borderWidth, + borderRadiusSizes, customColorsEnabled, customColors, customDuotone, @@ -325,6 +326,7 @@ export function useBlockSettings( name, parentLayout ) { 'border.radius', 'border.style', 'border.width', + 'border.radiusSizes', 'color.custom', 'color.palette.custom', 'color.customDuotone', @@ -423,6 +425,7 @@ export function useBlockSettings( name, parentLayout ) { radius: borderRadius, style: borderStyle, width: borderWidth, + radiusSizes: borderRadiusSizes, }, dimensions: { aspectRatio, @@ -469,6 +472,7 @@ export function useBlockSettings( name, parentLayout ) { borderRadius, borderStyle, borderWidth, + borderRadiusSizes, customColorsEnabled, customColors, customDuotone, diff --git a/schemas/json/theme.json b/schemas/json/theme.json index a1f51ace920259..0d8c77acd95991 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -75,6 +75,28 @@ "description": "Allow users to set custom border widths.", "type": "boolean", "default": false + }, + "radiusSizes": { + "description": "Border radius size presets for the border radius selector.\nGenerates a custom property (`--wp--preset--border--raridus--{slug}`) per preset value.", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "description": "Name of the border radius size preset, translatable.", + "type": "string" + }, + "slug": { + "description": "Unique identifier for the border raidus size preset.", + "type": "string" + }, + "size": { + "description": "CSS border-radius value, including units.", + "type": "string" + } + }, + "additionalProperties": false + } } }, "additionalProperties": false diff --git a/test/emptytheme/theme.json b/test/emptytheme/theme.json index 09f11eeeff8cfb..d14377dc3082bf 100644 --- a/test/emptytheme/theme.json +++ b/test/emptytheme/theme.json @@ -6,6 +6,44 @@ "layout": { "contentSize": "840px", "wideSize": "1100px" + }, + "spacing": { + "spacingSizes": [ + { + "name": "Small", + "slug": "small", + "size": "10px" + }, + { + "name": "Medium", + "slug": "medium", + "size": "20px" + }, + { + "name": "Large", + "slug": "large", + "size": "30px" + } + ] + }, + "border": { + "radiusSizes": [ + { + "name": "Small", + "slug": "small", + "size": "2px" + }, + { + "name": "Medium", + "slug": "medium", + "size": "4px" + }, + { + "name": "Large", + "slug": "large", + "size": "6px" + } + ] } }, "customTemplates": [ From 01db75a05c963cba580a9cfd00c21b6e6bfef6ad Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 4 Dec 2024 00:48:33 +0100 Subject: [PATCH 02/13] Allow picking presets --- .../theme-json-reference/theme-json-living.md | 1 + lib/class-wp-theme-json-gutenberg.php | 2 +- .../components/border-radius-control/index.js | 60 ++--- .../single-input-control.js | 253 +++++++++++++----- .../border-radius-control/style.scss | 26 +- .../components/border-radius-control/utils.js | 103 +++++++ 6 files changed, 336 insertions(+), 109 deletions(-) diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index 95f2e047dd0e31..138e32f76505ee 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -74,6 +74,7 @@ Settings related to borders. | radius | Allow users to set custom border radius. | `boolean` | `false` | | style | Allow users to set custom border styles. | `boolean` | `false` | | width | Allow users to set custom border widths. | `boolean` | `false` | +| radiusSizes | Border radius size presets for the border radius selector. | `[ { name, slug, size } ]` | | --- diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 2d9d23c4b3cfdf..69e33e9459c7e5 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -209,7 +209,7 @@ class WP_Theme_JSON_Gutenberg { 'prevent_override' => false, 'use_default_names' => false, 'value_key' => 'size', - 'css_vars' => '--wp--preset--border--radius--$slug', + 'css_vars' => '--wp--preset--border-radius--$slug', 'classes' => array(), 'properties' => array( 'border-radius' ), ), diff --git a/packages/block-editor/src/components/border-radius-control/index.js b/packages/block-editor/src/components/border-radius-control/index.js index c995c126422fd1..a9a7c44d586e21 100644 --- a/packages/block-editor/src/components/border-radius-control/index.js +++ b/packages/block-editor/src/components/border-radius-control/index.js @@ -28,9 +28,9 @@ const DEFAULT_VALUES = { const RANGE_CONTROL_MAX_SIZE = 8; const EMPTY_ARRAY = []; function useBorderRadiusSizes( presets ) { - const defaultSizes = presets?.radiusSizes?.default ?? EMPTY_ARRAY; - const customSizes = presets?.radiusSizes?.custom ?? EMPTY_ARRAY; - const themeSizes = presets?.radiusSizes?.theme ?? EMPTY_ARRAY; + const defaultSizes = presets?.default ?? EMPTY_ARRAY; + const customSizes = presets?.custom ?? EMPTY_ARRAY; + const themeSizes = presets?.theme ?? EMPTY_ARRAY; return useMemo( () => { const sizes = [ @@ -99,39 +99,39 @@ export default function BorderRadiusControl( { onChange, values, presets } ) { -
- { isLinked ? ( - <> + { isLinked ? ( + <> + + + ) : ( + + { [ + 'topLeft', + 'topRight', + 'bottomLeft', + 'bottomRight', + ].map( ( corner ) => ( - - ) : ( - - { [ - 'topLeft', - 'topRight', - 'bottomLeft', - 'bottomRight', - ].map( ( corner ) => ( - - ) ) } - - ) } -
+ ) ) } + + ) } ); } diff --git a/packages/block-editor/src/components/border-radius-control/single-input-control.js b/packages/block-editor/src/components/border-radius-control/single-input-control.js index 224bee46feb696..c9f1647945d21b 100644 --- a/packages/block-editor/src/components/border-radius-control/single-input-control.js +++ b/packages/block-editor/src/components/border-radius-control/single-input-control.js @@ -4,15 +4,26 @@ import { __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, __experimentalUnitControl as UnitControl, + __experimentalHStack as HStack, Tooltip, RangeControl, + Button, + CustomSelectControl, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +import { settings } from '@wordpress/icons'; /** * Internal dependencies */ -import { getAllValue } from './utils'; +import { + getAllValue, + getCustomValueFromPreset, + getPresetValueFromControlValue, + getSliderValueFromPreset, + isValuePreset, +} from './utils'; const CORNERS = { all: __( 'Border radius' ), @@ -27,6 +38,7 @@ const MAX_BORDER_RADIUS_VALUES = { em: 20, rem: 20, }; +const RANGE_CONTROL_MAX_SIZE = 8; export default function SingleInputControl( { corner, @@ -35,30 +47,35 @@ export default function SingleInputControl( { setSelectedUnits, values: valuesProp, units, + presets, } ) { - const onChangeValue = ( next ) => { - if ( ! onChange ) { - return; - } - - // Filter out CSS-unit-only values to prevent invalid styles. - const isNumeric = ! isNaN( parseFloat( next ) ); - const nextValue = isNumeric ? next : undefined; + const changeCornerValue = ( validatedValue ) => { if ( corner === 'all' ) { onChange( { - topLeft: nextValue, - topRight: nextValue, - bottomLeft: nextValue, - bottomRight: nextValue, + topLeft: validatedValue, + topRight: validatedValue, + bottomLeft: validatedValue, + bottomRight: validatedValue, } ); } else { onChange( { ...values, - [ corner ]: nextValue, + [ corner ]: validatedValue, } ); } }; + const onChangeValue = ( next ) => { + if ( ! onChange ) { + return; + } + + // Filter out CSS-unit-only values to prevent invalid styles. + const isNumeric = ! isNaN( parseFloat( next ) ); + const nextValue = isNumeric ? next : undefined; + changeCornerValue( nextValue ); + }; + const onChangeUnit = ( next ) => { const newUnits = { ...selectedUnits }; if ( corner === 'all' ) { @@ -71,6 +88,11 @@ export default function SingleInputControl( { } setSelectedUnits( newUnits ); }; + const handleSliderChange = ( next ) => { + const val = + next !== undefined ? `${ next }${ computedUnit }` : undefined; + changeCornerValue( val ); + }; // For shorthand style & backwards compatibility, handle flat string value. const values = @@ -84,65 +106,174 @@ export default function SingleInputControl( { }; const value = corner === 'all' ? getAllValue( values ) : values[ corner ]; + const resolvedPresetValue = isValuePreset( value ) + ? getCustomValueFromPreset( value, presets ) + : value; const [ parsedQuantity, parsedUnit ] = - parseQuantityAndUnitFromRawValue( value ); + parseQuantityAndUnitFromRawValue( resolvedPresetValue ); const computedUnit = value ? parsedUnit : selectedUnits[ corner ] || selectedUnits.flat; const unitConfig = units && units.find( ( item ) => item.value === computedUnit ); const step = unitConfig?.step || 1; - const handleSliderChange = ( next ) => { - const val = - next !== undefined ? `${ next }${ computedUnit }` : undefined; - if ( corner === 'all' ) { - onChange( { - topLeft: val, - topRight: val, - bottomLeft: val, - bottomRight: val, - } ); - } else { - onChange( { - ...values, - [ corner ]: val, - } ); - } - }; + const [ showCustomValueControl, setShowCustomValueControl ] = useState( + value !== undefined && ! isValuePreset( value ) + ); + const showRangeControl = presets.length <= RANGE_CONTROL_MAX_SIZE; + const presetIndex = getSliderValueFromPreset( value, presets ); + const rangeTooltip = ( newValue ) => + value === undefined ? undefined : presets[ newValue ]?.name; + const marks = presets + .slice( 1, presets.length - 1 ) + .map( ( _newValue, index ) => ( { + value: index + 1, + label: undefined, + } ) ); + const hasPresets = marks.length > 0; + let options = []; + if ( ! showRangeControl ) { + options = [ + ...presets, + { + name: __( 'Custom' ), + slug: 'custom', + size: resolvedPresetValue, + }, + ].map( ( size, index ) => ( { + key: index, + name: size.name, + } ) ); + } // Controls are wrapped in tooltips as visible labels aren't desired here. // Tooltip rendering also requires the UnitControl to be wrapped. See: // https://github.com/WordPress/gutenberg/pull/24966#issuecomment-685875026 return ( -
- -
- -
-
- + { ! hasPresets || + ( showCustomValueControl && ( +
+ +
+ +
+
+ +
+ ) ) } + { hasPresets && showRangeControl && ! showCustomValueControl && ( + { + changeCornerValue( + getPresetValueFromControlValue( + newSize, + 'range', + presets + ) + ); + } } + /**onMouseDown={ ( event ) => { + // If mouse down is near start of range set initial value to 0, which + // prevents the user have to drag right then left to get 0 setting. + if ( event?.nativeEvent?.offsetX < 35 ) { + setInitialValue(); + } + } }**/ + withInputField={ false } + aria-valuenow={ presetIndex } + aria-valuetext={ presets[ presetIndex ]?.name } + renderTooltipContent={ rangeTooltip } + min={ 0 } + max={ presets.length - 1 } + marks={ marks } + label={ CORNERS[ corner ] } + hideLabelFromVision + __nextHasNoMarginBottom + /*onFocus={ onMouseOver } + onBlur={ onMouseOut }*/ + /> + ) } + + { ! showRangeControl && ! showCustomValueControl && ( + option.key === presetIndex + ) || options[ options.length - 1 ] + } + onChange={ ( selection ) => { + if ( + selection.selectedItem.key === + options.length - 1 + ) { + setShowCustomValueControl( true ); + } else { + changeCornerValue( + getPresetValueFromControlValue( + selection.selectedItem.key, + 'selectList', + presets + ) + ); + } + } } + options={ options } + label={ CORNERS[ corner ] } + hideLabelFromVision + size="__unstable-large" + /*onMouseOver={ onMouseOver } + onMouseOut={ onMouseOut } + onFocus={ onMouseOver } + onBlur={ onMouseOut }*/ + /> + ) } +
+ ); } diff --git a/packages/block-editor/src/components/border-radius-control/style.scss b/packages/block-editor/src/components/border-radius-control/style.scss index 64da74263b0429..ce543d5cbe6f41 100644 --- a/packages/block-editor/src/components/border-radius-control/style.scss +++ b/packages/block-editor/src/components/border-radius-control/style.scss @@ -10,23 +10,6 @@ margin-bottom: 0; } - .components-border-radius-control__wrapper { - display: flex; - justify-content: space-between; - align-items: flex-start; - - .components-border-radius-control__unit-control { - margin-bottom: 0; - margin-right: $grid-unit-20; - flex-shrink: 0; - } - - .components-border-radius-control__range-control { - flex: 1; - margin-right: $grid-unit-15; - } - } - .components-border-radius-control__input-controls-wrapper { display: grid; gap: $grid-unit-20; @@ -43,3 +26,12 @@ } } } + +.components-border-radius-control__custom-select-control, +.components-border-radius-control__range-control { + flex: 1; +} + +.component-border-radius-control__custom-toggle { + flex: 0 0 auto; +} diff --git a/packages/block-editor/src/components/border-radius-control/utils.js b/packages/block-editor/src/components/border-radius-control/utils.js index 0738ef80399eb6..54c153c3041b79 100644 --- a/packages/block-editor/src/components/border-radius-control/utils.js +++ b/packages/block-editor/src/components/border-radius-control/utils.js @@ -117,3 +117,106 @@ export function hasDefinedValues( values ) { return !! filteredValues.length; } + +/** + * Checks is given value is a spacing preset. + * + * @param {string} value Value to check + * + * @return {boolean} Return true if value is string in format var:preset|spacing|. + */ +export function isValuePreset( value ) { + if ( ! value?.includes ) { + return false; + } + return value === '0' || value.includes( 'var:preset|border-radius|' ); +} + +/** + * Returns the slug section of the given preset string. + * + * @param {string} value Value to extract slug from. + * + * @return {string|undefined} The int value of the slug from given preset. + */ +export function getPresetSlug( value ) { + if ( ! value ) { + return; + } + + if ( value === '0' || value === 'default' ) { + return value; + } + + const slug = value.match( /var:preset\|border-radius\|(.+)/ ); + + return slug ? slug[ 1 ] : undefined; +} + +/** + * Converts spacing preset value into a Range component value . + * + * @param {string} presetValue Value to convert to Range value. + * @param {Array} presets Array of current radius preset value objects. + * + * @return {number} The int value for use in Range control. + */ +export function getSliderValueFromPreset( presetValue, presets ) { + if ( presetValue === undefined ) { + return 0; + } + const slug = + parseFloat( presetValue, 10 ) === 0 + ? '0' + : getPresetSlug( presetValue ); + const sliderValue = presets.findIndex( ( size ) => { + return String( size.slug ) === slug; + } ); + + // Returning NaN rather than undefined as undefined makes range control thumb sit in center + return sliderValue !== -1 ? sliderValue : NaN; +} + +/** + * Converts a preset into a custom value. + * + * @param {string} value Value to convert + * @param {Array} presets Array of the current spacing preset objects + * + * @return {string} Mapping of the spacing preset to its equivalent custom value. + */ +export function getCustomValueFromPreset( value, presets ) { + if ( ! isValuePreset( value ) ) { + return value; + } + + const slug = parseFloat( value, 10 ) === 0 ? '0' : getPresetSlug( value ); + const radiusSize = presets.find( ( size ) => String( size.slug ) === slug ); + + return radiusSize?.size; +} + +/** + * Converts a control value into a preset value. + * + * @param {number} controlValue to convert to preset value. + * @param {string} controlType Type of control + * @param {Array} presets Array of current radius preset value objects. + * + * @return {string} The custom value for use in Range control. + */ +export function getPresetValueFromControlValue( + controlValue, + controlType, + presets +) { + const size = parseInt( controlValue, 10 ); + if ( controlType === 'selectList' ) { + if ( size === 0 ) { + return undefined; + } + } else if ( size === 0 ) { + return '0'; + } + return `var:preset|border-radius|${ presets[ controlValue ]?.slug }`; +} From 875dce47fb3449475b9a2f221b1b5125fed30d1d Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 4 Dec 2024 10:53:46 +0100 Subject: [PATCH 03/13] Remove debug code --- test/emptytheme/theme.json | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/test/emptytheme/theme.json b/test/emptytheme/theme.json index d14377dc3082bf..09f11eeeff8cfb 100644 --- a/test/emptytheme/theme.json +++ b/test/emptytheme/theme.json @@ -6,44 +6,6 @@ "layout": { "contentSize": "840px", "wideSize": "1100px" - }, - "spacing": { - "spacingSizes": [ - { - "name": "Small", - "slug": "small", - "size": "10px" - }, - { - "name": "Medium", - "slug": "medium", - "size": "20px" - }, - { - "name": "Large", - "slug": "large", - "size": "30px" - } - ] - }, - "border": { - "radiusSizes": [ - { - "name": "Small", - "slug": "small", - "size": "2px" - }, - { - "name": "Medium", - "slug": "medium", - "size": "4px" - }, - { - "name": "Large", - "slug": "large", - "size": "6px" - } - ] } }, "customTemplates": [ From f3b4abe280ee1bed16fc3ead19be67c3a677a6fd Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 4 Dec 2024 11:26:42 +0100 Subject: [PATCH 04/13] Fix no presets case --- .../single-input-control.js | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/packages/block-editor/src/components/border-radius-control/single-input-control.js b/packages/block-editor/src/components/border-radius-control/single-input-control.js index c9f1647945d21b..7d2c55f4d2d05a 100644 --- a/packages/block-editor/src/components/border-radius-control/single-input-control.js +++ b/packages/block-editor/src/components/border-radius-control/single-input-control.js @@ -151,42 +151,40 @@ export default function SingleInputControl( { // https://github.com/WordPress/gutenberg/pull/24966#issuecomment-685875026 return ( - { ! hasPresets || - ( showCustomValueControl && ( -
- -
- -
-
- -
- ) ) } + { ( ! hasPresets || showCustomValueControl ) && ( +
+ +
+ +
+
+ +
+ ) } { hasPresets && showRangeControl && ! showCustomValueControl && ( ) } -