Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Support: Add width block support feature #32502

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 13 additions & 1 deletion lib/block-supports/dimensions.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,19 @@ function gutenberg_apply_dimensions_support( $block_type, $block_attributes ) {
}
}

// Width support to be added in near future.
// Width.

// Width support flag can be true|false|"segmented" cannot use
// `gutenberg_block_has_support` which checked for boolean true or array.
$has_width_support = _wp_array_get( $block_type->supports, array( '__experimentalDimensions', 'width' ), false );

if ( $has_width_support ) {
$width_value = _wp_array_get( $block_attributes, array( 'style', 'dimensions', 'width' ), null );

if ( null !== $width_value ) {
$styles[] = sprintf( 'width: %s;', $width_value );
}
}

return empty( $styles ) ? array() : array( 'style' => implode( ' ', $styles ) );
}
Expand Down
7 changes: 7 additions & 0 deletions lib/compat/wordpress-5.9/class-wp-theme-json-5-9.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ class WP_Theme_JSON_5_9 {
'font-size' => array( 'typography', 'fontSize' ),
'font-style' => array( 'typography', 'fontStyle' ),
'font-weight' => array( 'typography', 'fontWeight' ),
'height' => array( 'dimensions', 'height' ),
'letter-spacing' => array( 'typography', 'letterSpacing' ),
'line-height' => array( 'typography', 'lineHeight' ),
'margin' => array( 'spacing', 'margin' ),
Expand Down Expand Up @@ -238,6 +239,9 @@ class WP_Theme_JSON_5_9 {
'text' => null,
),
'custom' => null,
'dimensions' => array(
'height' => null,
),
'layout' => array(
'contentSize' => null,
'wideSize' => null,
Expand Down Expand Up @@ -274,6 +278,9 @@ class WP_Theme_JSON_5_9 {
'style' => null,
'width' => null,
),
'dimensions' => array(
'height' => null,
),
'color' => array(
'background' => null,
'gradient' => null,
Expand Down
3 changes: 2 additions & 1 deletion lib/compat/wordpress-5.9/theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@
"text": true
},
"dimensions": {
"height": false
"height": false,
"width": false
},
"spacing": {
"blockGap": null,
Expand Down
3 changes: 3 additions & 0 deletions lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class WP_Theme_JSON_Gutenberg extends WP_Theme_JSON_5_9 {
'text-decoration' => array( 'typography', 'textDecoration' ),
'text-transform' => array( 'typography', 'textTransform' ),
'filter' => array( 'filter', 'duotone' ),
'width' => array( 'dimensions', 'width' ),
);

/**
Expand Down Expand Up @@ -85,6 +86,7 @@ class WP_Theme_JSON_Gutenberg extends WP_Theme_JSON_5_9 {
'custom' => null,
'dimensions' => array(
'height' => null,
'width' => null,
),
'layout' => array(
'contentSize' => null,
Expand Down Expand Up @@ -129,6 +131,7 @@ class WP_Theme_JSON_Gutenberg extends WP_Theme_JSON_5_9 {
),
'dimensions' => array(
'height' => null,
'width' => null,
),
'filter' => array(
'duotone' => null,
Expand Down
130 changes: 130 additions & 0 deletions packages/block-editor/src/components/width-control/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* WordPress dependencies
*/
import {
Button,
ButtonGroup,
__experimentalUnitControl as UnitControl,
} from '@wordpress/components';
import { useEffect, useRef, useState } from '@wordpress/element';
import { edit } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';

const DEFAULT_WIDTHS = [ '25%', '50%', '75%', '100%' ];
const DEFAULT_UNIT = '%';
const MIN_WIDTH = 0;

/**
* Determines the CSS unit within the supplied width value.
*
* @param {string} value Value including CSS unit.
* @param {Array} units Available CSS units to validate against.
*
* @return {string} CSS unit extracted from supplied value.
*/
const parseUnit = ( value, units ) => {
let unit = String( value )
.trim()
.match( /[\d.\-\+]*\s*(.*)/ )[ 1 ];

if ( ! unit ) {
return DEFAULT_UNIT;
}

unit = unit.toLowerCase();
unit = units.find( ( item ) => item.value === unit );

return unit?.value || DEFAULT_UNIT;
};

/**
* Width control that will display as either a simple `UnitControl` or a
* segmented control containing preset percentage widths. The segmented version
* contains a toggle to switch to a UnitControl and Slider for explicit control.
*
* @param {Object} props Component props.
* @return {WPElement} Width control.
*/
export default function WidthControl( props ) {
const {
label = __( 'Width' ),
onChange,
units,
value,
isSegmentedControl = false,
min = MIN_WIDTH,
presetWidths = DEFAULT_WIDTHS,
} = props;

const ref = useRef();
const hasCustomValue = value && ! presetWidths.includes( value );
const [ customView, setCustomView ] = useState( hasCustomValue );
const currentUnit = parseUnit( value, units );

// When switching to the custom view, move focus to the UnitControl.
useEffect( () => {
if ( customView && ref.current ) {
ref.current.focus();
}
}, [ customView ] );

// Unless segmented control is desired return a normal UnitControl.
if ( ! isSegmentedControl ) {
return (
<UnitControl
label={ label }
min={ min }
unit={ currentUnit }
{ ...props }
/>
);
}

const toggleCustomView = () => {
setCustomView( ! customView );
};

const handlePresetChange = ( selectedValue ) => {
const newWidth = selectedValue === value ? undefined : selectedValue;
onChange( newWidth );
};

const renderCustomView = () => (
<UnitControl
ref={ ref }
min={ min }
unit={ currentUnit }
{ ...props }
/>
);

const renderPresetView = () => (
<ButtonGroup aria-label={ __( 'Button width' ) }>
{ presetWidths.map( ( width ) => (
<Button
key={ width }
isSmall
variant={ value === width ? 'primary' : undefined }
onClick={ () => handlePresetChange( width ) }
>
{ width }
</Button>
) ) }
</ButtonGroup>
);

return (
<fieldset className="components-width-control is-segmented">
<legend>{ label }</legend>
<div className="components-width-control__wrapper">
{ customView ? renderCustomView() : renderPresetView() }
<Button
icon={ edit }
isSmall
isPressed={ customView }
onClick={ toggleCustomView }
/>
</div>
</fieldset>
);
}
34 changes: 34 additions & 0 deletions packages/block-editor/src/components/width-control/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.components-width-control.is-segmented {
legend {
margin-bottom: $grid-unit-10;
}

.components-width-control__wrapper {
display: flex;
align-items: center;
justify-content: space-between;
}

.components-unit-control-wrapper {
flex: 1;
margin-right: $grid-unit-10;
max-width: 80px;
}

.components-range-control {
flex: 1;
margin-bottom: 0;

.components-base-control__field {
margin-bottom: 0;
height: 30px;
}
}

.components-button.is-small.has-icon:not(.has-text) {
margin-left: $grid-unit-20;
min-width: 30px;
height: 30px;
padding: 0 4px;
}
}
33 changes: 32 additions & 1 deletion packages/block-editor/src/hooks/dimensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ import {
resetPadding,
useIsPaddingDisabled,
} from './padding';
import {
WidthEdit,
hasWidthSupport,
hasWidthValue,
resetWidth,
useIsWidthDisabled,
} from './width';

export const DIMENSIONS_SUPPORT_KEY = '__experimentalDimensions';
export const SPACING_SUPPORT_KEY = 'spacing';
Expand All @@ -55,6 +62,7 @@ export function DimensionsPanel( props ) {
const isPaddingDisabled = useIsPaddingDisabled( props );
const isMarginDisabled = useIsMarginDisabled( props );
const isHeightDisabled = useIsHeightDisabled( props );
const isWidthDisabled = useIsWidthDisabled( props );
const isDisabled = useIsDimensionsDisabled( props );
const isSupported = hasDimensionsSupport( props.name );

Expand Down Expand Up @@ -103,6 +111,21 @@ export function DimensionsPanel( props ) {
<HeightEdit { ...props } />
</ToolsPanelItem>
) }
{ ! isWidthDisabled && (
<ToolsPanelItem
hasValue={ () => hasWidthValue( props ) }
label={ __( 'Width' ) }
onDeselect={ () => resetWidth( props ) }
resetAllFilter={ createResetAllFilter(
'width',
'dimensions'
) }
isShownByDefault={ defaultDimensionsControls?.width }
panelId={ props.clientId }
>
<WidthEdit { ...props } />
</ToolsPanelItem>
) }
{ ! isPaddingDisabled && (
<ToolsPanelItem
hasValue={ () => hasPaddingValue( props ) }
Expand Down Expand Up @@ -167,6 +190,7 @@ export function hasDimensionsSupport( blockName ) {
return (
hasGapSupport( blockName ) ||
hasHeightSupport( blockName ) ||
hasWidthSupport( blockName ) ||
hasPaddingSupport( blockName ) ||
hasMarginSupport( blockName )
);
Expand All @@ -181,10 +205,17 @@ export function hasDimensionsSupport( blockName ) {
const useIsDimensionsDisabled = ( props = {} ) => {
const gapDisabled = useIsGapDisabled( props );
const heightDisabled = useIsHeightDisabled( props );
const widthDisabled = useIsWidthDisabled( props );
const paddingDisabled = useIsPaddingDisabled( props );
const marginDisabled = useIsMarginDisabled( props );

return gapDisabled && heightDisabled && paddingDisabled && marginDisabled;
return (
gapDisabled &&
heightDisabled &&
widthDisabled &&
paddingDisabled &&
marginDisabled
);
};

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/src/hooks/test/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe( 'getInlineStyles', () => {
},
dimensions: {
height: '500px',
width: '100%',
},
spacing: {
blockGap: '1em',
Expand All @@ -44,6 +45,7 @@ describe( 'getInlineStyles', () => {
height: '500px',
marginBottom: '15px',
paddingTop: '10px',
width: '100%',
} );
} );

Expand Down
Loading