diff --git a/.eslintrc.js b/.eslintrc.js index 3d4667311fada7..076f0c05823c04 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -298,7 +298,7 @@ module.exports = { 'Button', 'ComboboxControl', 'CustomSelectControl', - 'DimensionControl', + 'FontAppearanceControl', 'FontFamilyControl', 'FontSizePicker', diff --git a/backport-changelog/7.0/10628.md b/backport-changelog/7.0/10628.md new file mode 100644 index 00000000000000..cd026c14e8d27a --- /dev/null +++ b/backport-changelog/7.0/10628.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/10628 + +* https://github.com/WordPress/gutenberg/pull/73811 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 95a599dd97fe27..368b8c8f1ffdc6 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -113,6 +113,7 @@ Settings related to dimensions. | aspectRatios | Allow users to define aspect ratios for some blocks. | `[ { name, slug, ratio } ]` | | | minHeight | Allow users to set custom minimum height. | `boolean` | `false` | | width | Allow users to set custom width. | `boolean` | `false` | +| dimensionSizes | Dimension size presets for dimension block supports. | `[ { name, slug, size } ]` | | --- diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index febaadd441e062..8c9a12e8a2f6d2 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -213,6 +213,15 @@ class WP_Theme_JSON_Gutenberg { 'classes' => array(), 'properties' => array( 'border-radius' ), ), + array( + 'path' => array( 'dimensions', 'dimensionSizes' ), + 'prevent_override' => false, + 'use_default_names' => false, + 'value_key' => 'size', + 'css_vars' => '--wp--preset--dimension--$slug', + 'classes' => array(), + 'properties' => array( 'width', 'min-height' ), + ), ); /** @@ -427,6 +436,7 @@ class WP_Theme_JSON_Gutenberg { 'aspectRatio' => null, 'aspectRatios' => null, 'defaultAspectRatios' => null, + 'dimensionSizes' => null, 'minHeight' => null, 'width' => null, ), diff --git a/lib/theme-i18n.json b/lib/theme-i18n.json index 8ceaee85afbbee..b7f46688e7f91c 100644 --- a/lib/theme-i18n.json +++ b/lib/theme-i18n.json @@ -43,6 +43,11 @@ { "name": "Aspect ratio name" } + ], + "dimensionSizes": [ + { + "name": "Dimension size name" + } ] }, "shadow": { @@ -95,6 +100,11 @@ { "name": "Aspect ratio name" } + ], + "dimensionSizes": [ + { + "name": "Dimension size name" + } ] }, "spacing": { diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index c2ad4c8c80045f..f5785eb227f9cf 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -392,6 +392,25 @@ _Returns_ Undocumented declaration. +### DimensionControl + +DimensionControl renders a linked unit control and range control for adjusting dimensions of a block. + +_Related_ + +- + +_Parameters_ + +- _props_ `Object`: +- _props.label_ `?string`: A label for the control. +- _props.onChange_ `( value: string ) => void`: Called when the dimension value changes. +- _props.value_ `string`: The current dimension value. + +_Returns_ + +- `Component`: The component to be rendered. + ### FontSizePicker _Related_ @@ -600,6 +619,8 @@ _Returns_ ### HeightControl +> **Deprecated** Use DimensionControl instead. + HeightControl renders a linked unit control and range control for adjusting the height of a block. _Related_ diff --git a/packages/block-editor/src/components/dimension-control/README.md b/packages/block-editor/src/components/dimension-control/README.md new file mode 100644 index 00000000000000..042689159b72ac --- /dev/null +++ b/packages/block-editor/src/components/dimension-control/README.md @@ -0,0 +1,106 @@ +# Dimension Control + +The `DimensionControl` component provides a comprehensive control for managing dimensions of a block within the block editor. It supports both preset values from theme settings and custom values with unit controls and sliders. It can be used for controlling various dimension-related block supports like height, width, min-height, etc. + +_Note:_ It is worth noting that dimension options are opt-in features. Themes need to declare support for them before they'll be available, and a convenient way to do that is via opting in to the [appearanceTools](/docs/how-to-guides/themes/global-settings-and-styles.md#opt-in-into-ui-controls) UI controls. + +## Features + +- **Preset Support**: Automatically detects and displays dimension presets from `dimensions.dimensionSizes` theme settings +- **Custom Values**: Allows entering custom dimension values with unit selection +- **Unit Conversion**: Intelligently converts between compatible units (px ↔ em/rem, viewport units) +- **Flexible UI**: Shows either a slider (≤8 presets) or dropdown (>8 presets) for preset selection +- **Toggle Between Modes**: Users can switch between preset and custom value modes + +## Development guidelines + +### Usage + +Renders the markup for dimension control component, to be used in the block inspector. + +```jsx +import { useState } from 'react'; +import { DimensionControl } from '@wordpress/block-editor'; + +const MyDimensionControl = () => ( + const [ value, setValue ] = useState(); + +); +``` + +### Props + +#### `value` + +- **Type:** `String` or `Number` or `Undefined` + +The value of the current dimension. + +#### `onChange` + +- **Type:** `Function` + +A callback function that handles the application of the dimension value. + +#### `label` + +- **Type:** `String` +- **Default:** `'Dimension'` + +A label for the dimension control. This is useful when using the dimension control for different dimension properties. For example, "Height", "Width", "Minimum height", etc. + +## Preset Integration + +The component automatically integrates with the theme's dimension size presets defined in `dimensions.dimensionSizes`. These presets are merged from: + +- **Default presets**: System-defined dimension sizes +- **Theme presets**: Dimension sizes defined by the active theme +- **Custom presets**: User-defined dimension sizes + +### Preset Value Format + +When a preset is selected, the component returns values in the format: +``` +var:preset|dimension|{slug} +``` + +For example: `var:preset|dimension|medium` + +### Theme Configuration + +To provide dimension presets, add them to your theme's `theme.json`: + +```json +{ + "version": 2, + "settings": { + "dimensions": { + "dimensionSizes": [ + { + "name": "Small", + "slug": "small", + "size": "16px" + }, + { + "name": "Medium", + "slug": "medium", + "size": "32px" + }, + { + "name": "Large", + "slug": "large", + "size": "64px" + } + ] + } + } +} +``` + +## Related components + +Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [`BlockEditorProvider`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/provider/README.md) in the components tree. diff --git a/packages/block-editor/src/components/dimension-control/index.js b/packages/block-editor/src/components/dimension-control/index.js new file mode 100644 index 00000000000000..e8480f74ccbd41 --- /dev/null +++ b/packages/block-editor/src/components/dimension-control/index.js @@ -0,0 +1,166 @@ +/** + * WordPress dependencies + */ +import { useMemo, useState } from '@wordpress/element'; +import { + BaseControl, + __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, + __experimentalUseCustomUnits as useCustomUnits, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { useSettings } from '../use-settings'; +import PresetInputControl from '../preset-input-control'; +import { CUSTOM_VALUE_SETTINGS } from '../preset-input-control/constants'; + +const EMPTY_ARRAY = []; + +// Dimension-specific custom value settings - override defaults for larger dimension values +const DIMENSION_CUSTOM_VALUE_SETTINGS = { + ...CUSTOM_VALUE_SETTINGS, + px: { max: 1000, steps: 1 }, + em: { max: 50, steps: 0.1 }, + rem: { max: 50, steps: 0.1 }, +}; + +/** + * Hook to retrieve dimension sizes from theme settings. + * + * @param {Object} presets Dimension presets object containing default, theme, and custom sizes. + * @return {Array} Array of dimension size options. + */ +function useDimensionSizes( presets ) { + const defaultSizes = presets?.default ?? EMPTY_ARRAY; + const customSizes = presets?.custom ?? EMPTY_ARRAY; + const themeSizes = presets?.theme ?? EMPTY_ARRAY; + + return useMemo( () => { + const sizes = [ + { name: __( 'None' ), slug: '0', size: 0 }, + ...customSizes, + ...themeSizes, + ...defaultSizes, + ]; + + return sizes; + }, [ customSizes, themeSizes, defaultSizes ] ); +} + +/** + * DimensionControl renders a linked unit control and range control for adjusting dimensions of a block. + * + * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/dimension-control/README.md + * + * @param {Object} props + * @param {?string} props.label A label for the control. + * @param {( value: string ) => void } props.onChange Called when the dimension value changes. + * @param {string} props.value The current dimension value. + * + * @return {Component} The component to be rendered. + */ +export default function DimensionControl( { + label = __( 'Dimension' ), + onChange, + value, +} ) { + const [ dimensionSizes, availableUnits ] = useSettings( + 'dimensions.dimensionSizes', + 'spacing.units' + ); + + const units = useCustomUnits( { + availableUnits: availableUnits || [ + '%', + 'px', + 'em', + 'rem', + 'vh', + 'vw', + ], + } ); + + const options = useDimensionSizes( dimensionSizes ); + + // Track selected unit for PresetInputControl + const [ selectedUnit, setSelectedUnit ] = useState( () => { + const [ , unit ] = parseQuantityAndUnitFromRawValue( value ); + return unit || units[ 0 ]?.value || 'px'; + } ); + + const handleUnitChange = ( newUnit ) => { + // Attempt to smooth over differences between currentUnit and newUnit. + // This should slightly improve the experience of switching between unit types. + const [ currentValue, currentUnit ] = + parseQuantityAndUnitFromRawValue( value ); + + if ( [ 'em', 'rem' ].includes( newUnit ) && currentUnit === 'px' ) { + // Convert pixel value to an approximate of the new unit, assuming a root size of 16px. + onChange( ( currentValue / 16 ).toFixed( 2 ) + newUnit ); + } else if ( + [ 'em', 'rem' ].includes( currentUnit ) && + newUnit === 'px' + ) { + // Convert to pixel value assuming a root size of 16px. + onChange( Math.round( currentValue * 16 ) + newUnit ); + } else if ( + [ + '%', + 'vw', + 'svw', + 'lvw', + 'dvw', + 'vh', + 'svh', + 'lvh', + 'dvh', + 'vi', + 'svi', + 'lvi', + 'dvi', + 'vb', + 'svb', + 'lvb', + 'dvb', + 'vmin', + 'svmin', + 'lvmin', + 'dvmin', + 'vmax', + 'svmax', + 'lvmax', + 'dvmax', + ].includes( newUnit ) && + currentValue > 100 + ) { + // When converting to `%` or viewport-relative units, cap the new value at 100. + onChange( 100 + newUnit ); + } + + setSelectedUnit( newUnit ); + }; + + return ( +
+ + { label } + + +
+ ); +} diff --git a/packages/block-editor/src/components/dimension-control/stories/index.story.jsx b/packages/block-editor/src/components/dimension-control/stories/index.story.jsx new file mode 100644 index 00000000000000..1dccb511edf0f8 --- /dev/null +++ b/packages/block-editor/src/components/dimension-control/stories/index.story.jsx @@ -0,0 +1,61 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import DimensionControl from '../'; + +export default { + component: DimensionControl, + title: 'BlockEditor/DimensionControl', +}; + +const Template = ( props ) => { + const [ value, setValue ] = useState(); + return ( + + ); +}; + +export const Default = Template.bind( {} ); + +export const WithCustomLabel = Template.bind( {} ); +WithCustomLabel.args = { + label: 'Height', +}; + +export const WithWidthLabel = Template.bind( {} ); +WithWidthLabel.args = { + label: 'Width', +}; + +export const WithMinHeightLabel = Template.bind( {} ); +WithMinHeightLabel.args = { + label: 'Minimum height', +}; + +export const WithPresets = Template.bind( {} ); +WithPresets.args = { + label: 'Height with presets', +}; + +export const WithInitialValue = Template.bind( {} ); +WithInitialValue.args = { + label: 'Height', + value: '24px', +}; + +const TemplateWithPresetValue = ( props ) => { + const [ value, setValue ] = useState( 'var:preset|dimension|medium' ); + return ( + + ); +}; + +export const WithPresetValue = TemplateWithPresetValue.bind( {} ); +WithPresetValue.args = { + label: 'Height (preset value)', +}; diff --git a/packages/block-editor/src/components/dimension-control/style.scss b/packages/block-editor/src/components/dimension-control/style.scss new file mode 100644 index 00000000000000..6af8173decf54b --- /dev/null +++ b/packages/block-editor/src/components/dimension-control/style.scss @@ -0,0 +1,5 @@ +.block-editor-dimension-control { + border: 0; + margin: 0; + padding: 0; +} diff --git a/packages/block-editor/src/components/global-styles/dimensions-panel.js b/packages/block-editor/src/components/global-styles/dimensions-panel.js index ef2aa8804f8aae..612c5f480e022a 100644 --- a/packages/block-editor/src/components/global-styles/dimensions-panel.js +++ b/packages/block-editor/src/components/global-styles/dimensions-panel.js @@ -24,7 +24,7 @@ import { getValueFromVariable } from '@wordpress/global-styles-engine'; */ import { useToolsPanelDropdownMenuProps } from './utils'; import SpacingSizesControl from '../spacing-sizes-control'; -import HeightControl from '../height-control'; +import DimensionControl from '../dimension-control'; import ChildLayoutControl from '../child-layout-control'; import AspectRatioTool from '../dimensions-tool/aspect-ratio-tool'; import { cleanEmptyObject } from '../../hooks/utils'; @@ -700,7 +700,7 @@ export default function DimensionsPanel( { } panelId={ panelId } > - - { + const [ value, setValue ] = useState(); + return ( + + ); +}; +``` + +**Legacy usage (deprecated):** + Renders the markup for height control component, to be used in the block inspector. ```jsx @@ -16,12 +38,14 @@ import { HeightControl } from '@wordpress/block-editor'; const MyLineHeightControl = () => ( const [ value, setValue ] = useState(); - -); + return ( + + ); +}; ``` ### Props @@ -47,4 +71,4 @@ A label for the height control. This is useful when using the height control for ## Related components -Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [`BlockEditorProvider`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/provider/README.md) in the components tree. +Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [`BlockEditorProvider`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/provider/README.md) in the components tree. \ No newline at end of file diff --git a/packages/block-editor/src/components/height-control/index.js b/packages/block-editor/src/components/height-control/index.js index 5d42e217776d6a..1b4a461fb783dd 100644 --- a/packages/block-editor/src/components/height-control/index.js +++ b/packages/block-editor/src/components/height-control/index.js @@ -13,6 +13,7 @@ import { __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -53,6 +54,8 @@ const RANGE_CONTROL_CUSTOM_SETTINGS = { /** * HeightControl renders a linked unit control and range control for adjusting the height of a block. * + * @deprecated Use DimensionControl instead. + * * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/height-control/README.md * * @param {Object} props @@ -67,6 +70,11 @@ export default function HeightControl( { onChange, value, } ) { + deprecated( 'wp.blockEditor.HeightControl', { + since: '7.0', + version: '7.2', + alternative: 'wp.blockEditor.DimensionControl', + } ); const customRangeValue = parseFloat( value ); const [ availableUnits ] = useSettings( 'spacing.units' ); diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index e63404674a8920..b16b5e8307cd8b 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -60,6 +60,7 @@ export { default as __experimentalColorGradientControl } from './colors-gradient export { default as __experimentalColorGradientSettingsDropdown } from './colors-gradients/dropdown'; export { default as __experimentalPanelColorGradientSettings } from './colors-gradients/panel-color-gradient-settings'; export { default as __experimentalUseMultipleOriginColorsAndGradients } from './colors-gradients/use-multiple-origin-colors-and-gradients'; +export { default as DimensionControl } from './dimension-control'; export { default as HeightControl } from './height-control'; export { default as __experimentalImageEditor } from './image-editor'; export { default as __experimentalImageSizeControl } from './image-size-control'; diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index cf56da1203f982..386b050f7cdae5 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -218,6 +218,7 @@ export function usePrivateStyleOverride( { setStyleOverride, deleteStyleOverride, registry, + variation, ] ); } @@ -264,6 +265,7 @@ export function useBlockSettings( name, parentLayout ) { aspectRatio, minHeight, width, + dimensionSizes, layout, borderColor, borderRadius, @@ -323,6 +325,7 @@ export function useBlockSettings( name, parentLayout ) { 'dimensions.aspectRatio', 'dimensions.minHeight', 'dimensions.width', + 'dimensions.dimensionSizes', 'layout', 'border.color', 'border.radius', @@ -433,6 +436,7 @@ export function useBlockSettings( name, parentLayout ) { aspectRatio, minHeight, width, + dimensionSizes, }, layout, parentLayout, @@ -470,6 +474,7 @@ export function useBlockSettings( name, parentLayout ) { aspectRatio, minHeight, width, + dimensionSizes, layout, parentLayout, borderColor, diff --git a/packages/global-styles-engine/src/settings/get-setting.ts b/packages/global-styles-engine/src/settings/get-setting.ts index 23a9a0388bf662..0ff5771067e714 100644 --- a/packages/global-styles-engine/src/settings/get-setting.ts +++ b/packages/global-styles-engine/src/settings/get-setting.ts @@ -37,6 +37,7 @@ const VALID_SETTINGS = [ 'dimensions.aspectRatio', 'dimensions.minHeight', 'dimensions.width', + 'dimensions.dimensionSizes', 'layout.contentSize', 'layout.definitions', 'layout.wideSize', diff --git a/packages/global-styles-engine/src/utils/common.ts b/packages/global-styles-engine/src/utils/common.ts index 73446e1f4600e7..287b1084327093 100644 --- a/packages/global-styles-engine/src/utils/common.ts +++ b/packages/global-styles-engine/src/utils/common.ts @@ -92,6 +92,12 @@ export const PRESET_METADATA = [ cssVarInfix: 'border-radius', classes: [], }, + { + path: [ 'dimensions', 'dimensionSizes' ], + valueKey: 'size', + cssVarInfix: 'dimension', + classes: [], + }, ]; export const STYLE_PATH_TO_CSS_VAR_INFIX: Record< string, string > = { diff --git a/packages/style-engine/src/class-wp-style-engine.php b/packages/style-engine/src/class-wp-style-engine.php index db9e73a2c0641e..b75be5c56961b0 100644 --- a/packages/style-engine/src/class-wp-style-engine.php +++ b/packages/style-engine/src/class-wp-style-engine.php @@ -207,7 +207,7 @@ final class WP_Style_Engine { ), 'path' => array( 'dimensions', 'minHeight' ), 'css_vars' => array( - 'spacing' => '--wp--preset--spacing--$slug', + 'dimension' => '--wp--preset--dimension--$slug', ), ), 'width' => array( @@ -215,6 +215,9 @@ final class WP_Style_Engine { 'default' => 'width', ), 'path' => array( 'dimensions', 'width' ), + 'css_vars' => array( + 'dimension' => '--wp--preset--dimension--$slug', + ), ), ), 'spacing' => array( diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 026313e2379a1b..9f75c589dcb428 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -295,6 +295,28 @@ "description": "Allow users to set custom width.", "type": "boolean", "default": false + }, + "dimensionSizes": { + "description": "Dimension size presets for dimension block supports.\nGenerates a custom property (`--wp--preset--dimension--{slug}`) per preset value.", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "description": "Name of the dimension size preset, translatable.", + "type": "string" + }, + "slug": { + "description": "Unique identifier for the dimension size preset.", + "type": "string" + }, + "size": { + "description": "CSS dimension value, including units.", + "type": "string" + } + }, + "additionalProperties": false + } } }, "additionalProperties": false