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

Add initial block support for width #26045

Closed
wants to merge 14 commits into from
47 changes: 47 additions & 0 deletions lib/block-supports/dimensions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* Width block support flag.
*
* @package gutenberg
*/

/**
* Registers the style and width block attributes for block types that
* support it.
stacimc marked this conversation as resolved.
Show resolved Hide resolved
*
* @param WP_Block_Type $block_type Block Type.
*/
function gutenberg_register_dimensions_support( $block_type ) {
// Determine if width supported.
$has_width_support = gutenberg_has_dimensions_support( $block_type, 'width' );

// Setup attributes and styles within that if needed.
if ( ! $block_type->attributes ) {
$block_type->attributes = array();
}

if ( $has_width_support && ! array_key_exists( 'style', $block_type->attributes ) ) {
$block_type->attributes['style'] = array(
'type' => 'object',
);
}
}

/**
* Checks whether the current block type supports the experimental feature
* requested.
*
* @param WP_Block_Type $block_type Block type to check for support.
* @param string $feature Name of the feature to check support for.
* @param mixed $default Fallback value for feature support, defaults to false.
*
* @return boolean Whether or not the feature is supported.
*/
function gutenberg_has_dimensions_support( $block_type, $feature, $default = false ) {
$block_support = false;
if ( property_exists( $block_type, 'supports' ) ) {
$block_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalDimensions' ), $default );
}

return true === $block_support || ( is_array( $block_support ) && gutenberg_experimental_get( $block_support, array( $feature ), false ) );
}
24 changes: 24 additions & 0 deletions lib/experimental-default-theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,30 @@
"spacing": {
"customPadding": false,
"units": [ "px", "em", "rem", "vh", "vw" ]
},
"dimensions": {
"width": [
{
"name": "25%",
"slug": "25",
"value": "25%"
},
{
"name": "50%",
"slug": "50",
"value": "50%"
},
{
"name": "75%",
"slug": "75",
"value": "75%"
},
{
"name": "100%",
"slug": "100",
"value": "100%"
}
]
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions lib/global-styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ function gutenberg_experimental_global_styles_get_style_property() {
'color' => array( 'color', 'text' ),
'fontSize' => array( 'typography', 'fontSize' ),
'lineHeight' => array( 'typography', 'lineHeight' ),
'width' => array( 'dimensions', 'width' ),
);
}

Expand All @@ -423,6 +424,7 @@ function gutenberg_experimental_global_styles_get_support_keys() {
'color' => array( 'color' ),
'fontSize' => array( 'fontSize' ),
'lineHeight' => array( 'lineHeight' ),
'width' => array( '__experimentalDimensions', 'width' ),
);
}

Expand All @@ -445,6 +447,10 @@ function gutenberg_experimental_global_styles_get_presets_structure() {
'path' => array( 'typography', 'fontSizes' ),
'key' => 'size',
),
'width' => array(
'path' => array( 'dimensions', 'width' ),
'key' => 'value',
),
);
}

Expand Down Expand Up @@ -819,6 +825,7 @@ function gutenberg_experimental_global_styles_normalize_schema( $tree ) {
'custom' => array(),
'typography' => array(),
'spacing' => array(),
'dimensions' => array(),
),
);

Expand Down
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,4 @@ function gutenberg_is_experiment_enabled( $name ) {
require dirname( __FILE__ ) . '/block-supports/align.php';
require dirname( __FILE__ ) . '/block-supports/typography.php';
require dirname( __FILE__ ) . '/block-supports/custom-classname.php';
require dirname( __FILE__ ) . '/block-supports/dimensions.php';
58 changes: 58 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,58 @@
/**
* WordPress dependencies
*/
import { Button, ButtonGroup } from '@wordpress/components';

/**
* Control to facilitate width selections.
*
* @param {Object} props Component props.
* @param {string} props.value Currently selected text decoration.
stacimc marked this conversation as resolved.
Show resolved Hide resolved
* @param {Array} props.widthOptions Width options available for selection.
* @param {Function} props.onChange Handles change in width selection.
* @return {WPElement} Width control.
*/
export default function WidthControl( {
value: selectedWidth,
widthOptions,
onChange,
} ) {
/**
* Determines the new width as a result of user interaction with
* the control. Then passes this to the supplied onChange handler.
*
* @param {string} newWidth Slug for selected width
*/
const handleChange = ( newWidth ) => {
// Check if we are toggling the width off
const width = selectedWidth === newWidth ? undefined : newWidth;

// Ensure only predefined width options are allowed
const presetWidth = widthOptions.find( ( { slug } ) => slug === width );

// Create string that will be turned into custom CSS property
const customWidthProperty = presetWidth
? `var:preset|width|${ presetWidth.slug }`
: undefined;

// Pass on to the supplied handler.
onChange( customWidthProperty );
};

return (
<ButtonGroup>
{ widthOptions.map( ( widthOption ) => {
return (
<Button
key={ widthOption.slug }
isSmall
isPrimary={ selectedWidth === widthOption.slug }
onClick={ () => handleChange( widthOption.slug ) }
>
{ widthOption.value }
</Button>
);
} ) }
</ButtonGroup>
);
}
111 changes: 111 additions & 0 deletions packages/block-editor/src/hooks/dimensions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* WordPress dependencies
*/
import { hasBlockSupport } from '@wordpress/blocks';
import { PanelBody } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { cleanEmptyObject } from './utils';
import InspectorControls from '../components/inspector-controls';
import useEditorFeature from '../components/use-editor-feature';
import WidthControl from '../components/width-control';

/**
* Key within block settings' supports array indicating support for
* width, e.g. settings found in 'block.json'.
stacimc marked this conversation as resolved.
Show resolved Hide resolved
*/
export const DIMENSIONS_SUPPORT_KEY = '__experimentalDimensions';

export function DimensionsPanel( props ) {
const {
attributes: { style },
setAttributes,
} = props;

const widthOptions = useEditorFeature( 'dimensions.width' );
const isEnabled = useIsWidthEnabled( props );

if ( ! isEnabled ) {
return null;
}

const selectedWidth = getWidthFromAttributeValue(
widthOptions,
style?.dimensions?.width
);

function onChange( newWidth ) {
setAttributes( {
style: cleanEmptyObject( {
...style,
dimensions: {
...style?.dimensions,
width: newWidth,
},
} ),
} );
}

return (
<InspectorControls>
<PanelBody title={ __( 'Width Settings' ) }>
stacimc marked this conversation as resolved.
Show resolved Hide resolved
<WidthControl
value={ selectedWidth }
widthOptions={ widthOptions }
onChange={ onChange }
/>
</PanelBody>
</InspectorControls>
);
}

/**
* Checks if there is block support for width.
*
* @param {string|Object} blockType Block name or Block Type object.
* @return {boolean} Whether there is support.
*/
export function hasWidthSupport( blockName ) {
const support = hasBlockSupport( blockName, DIMENSIONS_SUPPORT_KEY );

// Further dimension properties to be added in future iterations.
// e.g. support && ( support.width || support.height )
return true === support || ( support && support.width );
}

/**
* Checks if width is supported and has not been disabled.
*
* @param {string|Object} blockType Block name or Block Type object.
* @return {boolean} Whether there is support.
*/
export function useIsWidthEnabled( { name: blockName } = {} ) {
const supported = hasWidthSupport( blockName );
const widthOptions = useEditorFeature( 'dimensions.width' );
const hasWidthOptions = !! widthOptions?.length;

return supported && hasWidthOptions;
}

/**
* Extracts the current width selection, if available, from the CSS variable
* set as the 'styles.width' attribute.
*
* @param {Array} widthOptions Available width options as defined in theme.json
* @param {string} value Attribute value in `styles.width`
* @return {string} Actual width value
*/
const getWidthFromAttributeValue = ( widthOptions, value ) => {
const attributeParsed = /var:preset\|width\|(.+)/.exec( value );

if ( attributeParsed && attributeParsed[ 1 ] ) {
return widthOptions.find(
( { slug } ) => slug === attributeParsed[ 1 ]
)?.slug;
}

return value;
};
3 changes: 3 additions & 0 deletions packages/block-editor/src/hooks/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import { COLOR_SUPPORT_KEY, ColorEdit } from './color';
import { TypographyPanel, TYPOGRAPHY_SUPPORT_KEYS } from './typography';
import { SPACING_SUPPORT_KEY, PaddingEdit } from './padding';
import SpacingPanelControl from '../components/spacing-panel-control';
import { DIMENSIONS_SUPPORT_KEY, DimensionsPanel } from './dimensions';

const styleSupportKeys = [
...TYPOGRAPHY_SUPPORT_KEYS,
COLOR_SUPPORT_KEY,
SPACING_SUPPORT_KEY,
DIMENSIONS_SUPPORT_KEY,
];

const hasStyleSupport = ( blockType ) =>
Expand Down Expand Up @@ -156,6 +158,7 @@ export const withBlockControls = createHigherOrderComponent(
return [
<TypographyPanel key="typography" { ...props } />,
<ColorEdit key="colors" { ...props } />,
<DimensionsPanel key="width" { ...props } />,
stacimc marked this conversation as resolved.
Show resolved Hide resolved
<BlockEdit key="edit" { ...props } />,
hasSpacingSupport && (
<SpacingPanelControl key="spacing">
Expand Down
5 changes: 4 additions & 1 deletion packages/block-library/src/button/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
"align": true,
"alignWide": false,
"reusable": false,
"__experimentalSelector": ".wp-block-button > a"
"__experimentalSelector": ".wp-block-button > a",
"__experimentalDimensions": {
"width": true
}
}
}
6 changes: 6 additions & 0 deletions packages/block-library/src/button/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ function ButtonEdit( props ) {
const colorProps = getColorAndStyleProps( attributes, colors, true );
const blockProps = useBlockProps();

// Remove colorProps from style so that they are not applied to the button's
// wrapper
for ( const key of Object.keys( colorProps.style ) ) {
delete blockProps.style[ key ];
}

return (
<>
<ColorEdit { ...props } />
Expand Down
9 changes: 8 additions & 1 deletion packages/block-library/src/button/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,19 @@ export default function save( { attributes } ) {
...colorProps.style,
};

const blockProps = useBlockProps.save();
// Remove colorProps from style so that they are not applied to the button's
// wrapper
for ( const key of Object.keys( colorProps.style ) ) {
delete blockProps.style[ key ];
}

// The use of a `title` attribute here is soft-deprecated, but still applied
// if it had already been assigned, for the sake of backward-compatibility.
// A title will no longer be assigned for new or updated button block links.

return (
<div { ...useBlockProps.save() }>
<div { ...blockProps }>
<RichText.Content
tagName="a"
className={ buttonClasses }
Expand Down
8 changes: 8 additions & 0 deletions packages/block-library/src/button/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,11 @@ $blocks-button__height: 3.1em;
background-color: transparent;
border: 2px solid;
}

// Set the inner button to 100% width when a custom width
// has been set on the parent
div[style*="--wp--preset--width"] {
.wp-block-button__link {
width: 100%;
}
}
7 changes: 5 additions & 2 deletions packages/block-library/src/buttons/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
margin-left: 0;
}

.wp-block[data-align="center"] > .wp-block-buttons {
.wp-block > .wp-block-buttons {
display: flex;
align-items: center;
flex-wrap: wrap;
}

.wp-block[data-align="center"] > .wp-block-buttons {
align-items: center;
justify-content: center;
}

Expand Down
1 change: 1 addition & 0 deletions packages/blocks/src/api/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = {
paddingLeft: [ 'spacing', 'padding', 'left' ],
paddingRight: [ 'spacing', 'padding', 'right' ],
paddingTop: [ 'spacing', 'padding', 'top' ],
width: [ 'dimensions', 'width' ],
};
1 change: 1 addition & 0 deletions packages/edit-site/src/components/editor/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const PRESET_CATEGORIES = {
color: { path: [ 'color', 'palette' ], key: 'color' },
gradient: { path: [ 'color', 'gradients' ], key: 'gradient' },
fontSize: { path: [ 'typography', 'fontSizes' ], key: 'size' },
width: { path: [ 'dimensions', 'width' ], key: 'value' },
};
export const LINK_COLOR = '--wp--style--color--link';
export const LINK_COLOR_DECLARATION = `a { color: var(${ LINK_COLOR }, #00e); }`;
Expand Down