Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add spacing presets UI
  • Loading branch information
juanfra committed Oct 13, 2025
commit 26b1032325a395f1281d32eed8de4d19f437bde5
65 changes: 65 additions & 0 deletions lib/block-supports/spacing.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,71 @@
return $attributes;
}

/**
* Returns a spacing size value based on a given spacing size preset.
*
* @since TBD
*
* @param array $preset {
* Required. spacingSizes preset value as seen in theme.json.
*
* @type string $name Name of the spacing size preset.
* @type string $slug Kebab-case unique identifier for the spacing size preset.
* @type string|int|float $size CSS spacing size value, including units where applicable.
* @type array $fluid Fluid spacing settings when fluid spacing is enabled.
* }
* @param bool|array $settings Optional Theme JSON settings array that overrides any global theme settings.
* Default is `array()`.
*
* @return string|null Spacing size value or `null` if a size is not passed in $preset.
*/
function gutenberg_get_spacing_size_value( $preset, $settings = array() ) {

Check warning on line 94 in lib/block-supports/spacing.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Unused function parameter $settings.
if ( ! isset( $preset['size'] ) ) {
return null;
}

// Treat existing functional CSS size as authoritative; return as-is.
$size = isset( $preset['size'] ) ? (string) $preset['size'] : '';
if (
false !== strpos( $size, 'clamp(' ) ||
false !== strpos( $size, 'min(' ) ||
false !== strpos( $size, 'max(' ) ||
false !== strpos( $size, 'calc(' ) ||
false !== strpos( $size, 'var(' )
) {
return $size;
}

// Fluid settings from the preset (can be false|true|array).
$fluid_spacing_settings = $preset['fluid'] ?? null;

// Explicitly disabled or empty size → return static size.
if ( false === $fluid_spacing_settings || empty( $size ) ) {
return $size;
}

// If fluid is boolean true (no explicit values), return static size for spacing.
// @todo: We need to create a fluid calculation for spacing (or use the same one as typography)
if ( true === $fluid_spacing_settings ) {
return $size;
}

// Build clamp() only when explicit min/preferred/max are provided via fluid object.
if ( is_array( $fluid_spacing_settings ) && ! empty( $fluid_spacing_settings ) ) {
$min = isset( $fluid_spacing_settings['min'] ) ? trim( (string) $fluid_spacing_settings['min'] ) : '';

Check warning on line 127 in lib/block-supports/spacing.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Equals sign not aligned with surrounding assignments; expected 7 spaces but found 8 spaces
$preferred = isset( $fluid_spacing_settings['preferred'] ) ? trim( (string) $fluid_spacing_settings['preferred'] ) : trim( (string) $size );

Check warning on line 128 in lib/block-supports/spacing.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Equals sign not aligned with surrounding assignments; expected 1 space but found 2 spaces
$max = isset( $fluid_spacing_settings['max'] ) ? trim( (string) $fluid_spacing_settings['max'] ) : '';

Check warning on line 129 in lib/block-supports/spacing.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Equals sign not aligned with surrounding assignments; expected 7 spaces but found 8 spaces

// All three are required to form a valid clamp().
if ( $min !== '' && $preferred !== '' && $max !== '' ) {

Check failure on line 132 in lib/block-supports/spacing.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Use Yoda Condition checks, you must.

Check failure on line 132 in lib/block-supports/spacing.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Use Yoda Condition checks, you must.

Check failure on line 132 in lib/block-supports/spacing.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Use Yoda Condition checks, you must.
return sprintf( 'clamp(%s, %s, %s)', $min, $preferred, $max );
}
}

// Fallback to static size.
return $size;
}

// Register the block support.
WP_Block_Supports::get_instance()->register(
'spacing',
Expand Down
2 changes: 1 addition & 1 deletion lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ class WP_Theme_JSON_Gutenberg {
'path' => array( 'spacing', 'spacingSizes' ),
'prevent_override' => array( 'spacing', 'defaultSpacingSizes' ),
'use_default_names' => true,
'value_key' => 'size',
'value_func' => 'gutenberg_get_spacing_size_value',
'css_vars' => '--wp--preset--spacing--$slug',
'classes' => array(),
'properties' => array( 'padding', 'margin' ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const translationMap = {
'settings.typography': __( 'Typography' ),
'settings.shadow': __( 'Shadow' ),
'settings.layout': __( 'Layout' ),
'settings.spacing': __( 'Spacing' ),
'styles.color': __( 'Colors' ),
'styles.spacing': __( 'Spacing' ),
'styles.background': __( 'Background' ),
Expand Down
12 changes: 11 additions & 1 deletion packages/edit-site/src/components/global-styles/screen-layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
*/
import DimensionsPanel from './dimensions-panel';
import ScreenHeader from './header';
import SpacingsCount from './spacing/spacings-count';
import { hasAvailableSpacingSizes } from './spacing/utils';
import { unlock } from '../../lock-unlock';

const { useHasDimensionsPanel, useGlobalSetting, useSettingsForBlockElement } =
Expand All @@ -18,11 +20,19 @@ function ScreenLayout() {
const [ rawSettings ] = useGlobalSetting( '' );
const settings = useSettingsForBlockElement( rawSettings );
const hasDimensionsPanel = useHasDimensionsPanel( settings );
const hasSpacingSizes = hasAvailableSpacingSizes( settings );

return (
<>
<ScreenHeader title={ __( 'Layout' ) } />
{ hasDimensionsPanel && <DimensionsPanel /> }
<>
{ hasDimensionsPanel && <DimensionsPanel /> }
{ hasSpacingSizes && (
<div className="edit-site-global-styles-screen">
<SpacingsCount />
</div>
) }
</>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,47 @@ function SizeControl( {
...props
} ) {
const { baseControlProps } = useBaseControlProps( props );
const { value, onChange, fallbackValue, disabled, label } = props;
const { value, onChange, fallbackValue, disabled, label, max } = props;

const units = useCustomUnits( {
availableUnits: DEFAULT_UNITS,
} );

const maxRelativeValue = 10;
const maxNonRelativeValue = 100;

// Helper function to check if a unit is relative
const isUnitRelative = ( unit ) =>
!! unit && [ 'em', 'rem', 'vw', 'vh' ].includes( unit );

const [ valueQuantity, valueUnit = 'px' ] =
parseQuantityAndUnitFromRawValue( value, units );

const isValueUnitRelative =
!! valueUnit && [ 'em', 'rem', 'vw', 'vh' ].includes( valueUnit );
const isValueUnitRelative = isUnitRelative( valueUnit );

// Determine the max value for the range control
const getMaxValue = () => {
// For relative units, always use 10
if ( isValueUnitRelative ) {
return maxRelativeValue;
}
// For non-relative units, use custom max from props or default 500
return max !== undefined ? max : maxNonRelativeValue;
};

// Receives the new value from the UnitControl component as a string containing the value and unit.
const handleUnitControlChange = ( newValue ) => {
onChange( newValue );
const [ newQuantity, newUnit = 'px' ] =
parseQuantityAndUnitFromRawValue( newValue, units );

const isNewUnitRelative = isUnitRelative( newUnit );

// If switching to a relative unit and the value exceeds the max, clamp it
if ( isNewUnitRelative && newQuantity > maxRelativeValue ) {
onChange( maxRelativeValue + newUnit );
} else {
onChange( newValue );
}
};

// Receives the new value from the RangeControl component as a number.
Expand Down Expand Up @@ -75,7 +101,7 @@ function SizeControl( {
withInputField={ false }
onChange={ handleRangeControlChange }
min={ 0 }
max={ isValueUnitRelative ? 10 : 100 }
max={ getMaxValue() }
step={ isValueUnitRelative ? 0.1 : 1 }
disabled={ disabled }
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components';

function ConfirmDeleteSpacingDialog( {
spacingSize,
isOpen,
toggleOpen,
handleRemoveSpacingSize,
} ) {
const handleConfirm = () => {
handleRemoveSpacingSize();
toggleOpen();
};

const handleCancel = () => {
toggleOpen();
};

return (
<ConfirmDialog
isOpen={ isOpen }
cancelButtonText={ __( 'Cancel' ) }
confirmButtonText={ __( 'Delete' ) }
onCancel={ handleCancel }
onConfirm={ handleConfirm }
size="medium"
>
{ spacingSize &&
sprintf(
/* translators: %s: Name of the spacing size preset. */
__(
'Are you sure you want to delete "%s" spacing size preset?'
),
spacingSize.name
) }
</ConfirmDialog>
);
}

export default ConfirmDeleteSpacingDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components';

function ConfirmResetSpacingsDialog( {
text,
confirmButtonText,
isOpen,
toggleOpen,
onConfirm,
} ) {
const handleConfirm = () => {
onConfirm();
toggleOpen();
};

const handleCancel = () => {
toggleOpen();
};

if ( ! isOpen ) {
return null;
}

return (
<ConfirmDialog
isOpen={ isOpen }
cancelButtonText={ __( 'Cancel' ) }
confirmButtonText={ confirmButtonText }
onCancel={ handleCancel }
onConfirm={ handleConfirm }
size="medium"
>
{ text }
</ConfirmDialog>
);
}

export default ConfirmResetSpacingsDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import {
Button,
Modal,
__experimentalInputControl as InputControl,
__experimentalHStack as HStack,
__experimentalVStack as VStack,
} from '@wordpress/components';
import { useState } from '@wordpress/element';

function RenameSpacingDialog( { spacingSize, toggleOpen, handleRename } ) {
const [ editedName, setEditedName ] = useState( spacingSize.name );

const handleConfirm = () => {
if ( editedName !== spacingSize.name ) {
handleRename( editedName );
}
toggleOpen();
};

const handleCancel = () => {
toggleOpen();
};

return (
<Modal
title={ __( 'Rename spacing size' ) }
onRequestClose={ toggleOpen }
focusOnMount="firstContentElement"
size="medium"
>
<form
onSubmit={ ( event ) => {
event.preventDefault();
handleConfirm();
} }
>
<VStack spacing={ 3 }>
<InputControl
__next40pxDefaultSize
autoComplete="off"
value={ editedName }
onChange={ setEditedName }
label={ __( 'Name' ) }
placeholder={ __( 'Spacing size preset name' ) }
help={ sprintf(
/* translators: %s: spacing size preset slug. */
__(
'Spacing size slug is %s and cannot be changed.'
),
spacingSize.slug
) }
/>
<HStack justify="right">
<Button
__next40pxDefaultSize
variant="tertiary"
onClick={ handleCancel }
>
{ __( 'Cancel' ) }
</Button>
<Button
__next40pxDefaultSize
variant="primary"
type="submit"
>
{ __( 'Save' ) }
</Button>
</HStack>
</VStack>
</form>
</Modal>
);
}

export default RenameSpacingDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Internal dependencies
*/
import { parseComparisonValue } from './utils';

/**
* Renders a preview of a spacing size with visual representation.
*
* @param {Object} props Component props.
* @param {Object} props.spacingSize The spacing size object.
* @return {Element} The spacing preview component.
*/
function SpacingPreview( { spacingSize } ) {
// Handle fluid spacing values
const spacingValue = spacingSize?.size || '1rem';

// Parse CSS comparison functions (clamp, min, max)
const { displayValue, previewValue } = parseComparisonValue( spacingValue );

const hasFluidObject =
spacingSize?.fluid &&
typeof spacingSize.fluid === 'object' &&
spacingSize.fluid.min &&
spacingSize.fluid.preferred &&
spacingSize.fluid.max;

let boxSize = previewValue;
let label = displayValue;
if ( hasFluidObject ) {
const { min, preferred, max } = spacingSize.fluid;
const preferredValue = preferred || spacingSize.size || '1rem';
boxSize = preferredValue;
label = `${ min } → ${ max }`;
}

return (
<div className="edit-site-spacing-preview">
<div
className="edit-site-spacing-preview__box"
style={ { width: boxSize, height: boxSize } }
/>
<div className="edit-site-spacing-preview__value">{ label }</div>
</div>
);
}

export default SpacingPreview;
Loading
Loading