diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 92763e010d775..1bacc0b9aa5f2 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -29,7 +29,7 @@ function gutenberg_register_layout_support( $block_type ) { * Generates the CSS corresponding to the provided layout. * * @param string $selector CSS selector. - * @param array $layout Layout object. + * @param array $layout Layout object. The one that is passed has already checked the existance of default block layout. * @param boolean $has_block_gap_support Whether the theme has support for the block gap. * * @return string CSS style. @@ -68,6 +68,13 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support $style .= "$selector > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }"; } } elseif ( 'flex' === $layout_type ) { + $justify_content_options = array( + 'left' => 'flex-start', + 'right' => 'flex-end', + 'center' => 'center', + 'space-between' => 'space-between', + ); + $style = "$selector {"; $style .= 'display: flex;'; if ( $has_block_gap_support ) { @@ -77,6 +84,14 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support } $style .= 'flex-wrap: wrap;'; $style .= 'align-items: center;'; + /** + * Add this style only if is not empty for backwards compatibility, + * since we intend to convert blocks that had flex layout implemented + * by custom css. + */ + if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) { + $style .= "justify-content: {$justify_content_options[ $layout['justifyContent'] ]};"; + } $style .= '}'; $style .= "$selector > * { margin: 0; }"; diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index 796de8c479a2f..a1f17c525c0c6 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -40,23 +40,32 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { return getSettings().supportsLayout; }, [] ); - if ( ! themeSupportsLayout ) { - return null; - } - + const layoutBlockSupport = getBlockSupport( + blockName, + layoutBlockSupportKey, + {} + ); const { - allowSwitching: canBlockSwitchLayout, + allowSwitching, allowEditing = true, allowInheriting = true, default: defaultBlockLayout, - } = getBlockSupport( blockName, layoutBlockSupportKey ) || {}; + } = layoutBlockSupport; if ( ! allowEditing ) { return null; } - const usedLayout = layout ? layout : defaultBlockLayout || {}; + const usedLayout = layout || defaultBlockLayout || {}; const { inherit = false, type = 'default' } = usedLayout; + /** + * `themeSupportsLayout` is only relevant to the `default/flow` + * layout and it should not be taken into account when other + * `layout` types are used. + */ + if ( type === 'default' && ! themeSupportsLayout ) { + return null; + } const layoutType = getLayoutType( type ); const onChangeType = ( newType ) => @@ -65,33 +74,45 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { setAttributes( { layout: newLayout } ); return ( - - - { allowInheriting && !! defaultThemeLayout && ( - - setAttributes( { layout: { inherit: ! inherit } } ) - } - /> - ) } - - { ! inherit && canBlockSwitchLayout && ( - - ) } - - { ! inherit && layoutType && ( - - ) } - - + <> + + + { allowInheriting && !! defaultThemeLayout && ( + + setAttributes( { + layout: { inherit: ! inherit }, + } ) + } + /> + ) } + + { ! inherit && allowSwitching && ( + + ) } + + { ! inherit && layoutType && ( + + ) } + + + { ! inherit && layoutType && ( + + ) } + ); } diff --git a/packages/block-editor/src/layouts/flex.js b/packages/block-editor/src/layouts/flex.js index 268674ee3e503..2c1b9f69be4a5 100644 --- a/packages/block-editor/src/layouts/flex.js +++ b/packages/block-editor/src/layouts/flex.js @@ -1,27 +1,63 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; +import { + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, +} from '@wordpress/components'; /** * Internal dependencies */ import { appendSelectors } from './utils'; import useSetting from '../components/use-setting'; +import { BlockControls, JustifyContentControl } from '../components'; + +const justifyContentMap = { + left: 'flex-start', + right: 'flex-end', + center: 'center', + 'space-between': 'space-between', +}; export default { name: 'flex', - label: __( 'Flex' ), - - edit() { - return null; + inspectorControls: function FlexLayoutInspectorControls( { + layout = {}, + onChange, + } ) { + return ( + + ); }, - - save: function FlexLayoutStyle( { selector } ) { + toolBarControls: function FlexLayoutToolbarControls( { + layout = {}, + onChange, + layoutBlockSupport, + } ) { + if ( layoutBlockSupport?.allowSwitching ) { + return null; + } + return ( + + + + ); + }, + save: function FlexLayoutStyle( { selector, layout } ) { const blockGapSupport = useSetting( 'spacing.blockGap' ); const hasBlockGapStylesSupport = blockGapSupport !== null; - + const justifyContent = + justifyContentMap[ layout.justifyContent ] || 'flex-start'; return ( ); }, - getOrientation() { return 'horizontal'; }, - getAlignments() { return []; }, }; + +function FlexLayoutJustifyContentControl( { + layout, + onChange, + isToolbar = false, +} ) { + const { justifyContent = 'left' } = layout; + if ( isToolbar ) { + return ( + { + onChange( { + ...layout, + justifyContent: value, + } ); + } } + popoverProps={ { + position: 'bottom right', + isAlternate: true, + } } + /> + ); + } + return ( + { + onChange( { + ...layout, + justifyContent: value, + } ); + } } + isBlock + > + + + + + + ); +} diff --git a/packages/block-editor/src/layouts/flow.js b/packages/block-editor/src/layouts/flow.js index 3e0fc374ee548..97741a6a93017 100644 --- a/packages/block-editor/src/layouts/flow.js +++ b/packages/block-editor/src/layouts/flow.js @@ -17,10 +17,11 @@ import { appendSelectors } from './utils'; export default { name: 'default', - label: __( 'Flow' ), - - edit: function LayoutDefaultEdit( { layout, onChange } ) { + inspectorControls: function DefaultLayoutInspectorControls( { + layout, + onChange, + } ) { const { wideSize, contentSize } = layout; const units = useCustomUnits( { availableUnits: useSetting( 'spacing.units' ) || [ @@ -101,7 +102,9 @@ export default { ); }, - + toolBarControls: function DefaultLayoutToolbarControls() { + return null; + }, save: function DefaultLayoutStyle( { selector, layout = {} } ) { const { contentSize, wideSize } = layout; const blockGapSupport = useSetting( 'spacing.blockGap' ); @@ -115,11 +118,11 @@ export default { margin-left: auto !important; margin-right: auto !important; } - + ${ appendSelectors( selector, '> [data-align="wide"]' ) } { max-width: ${ wideSize ?? contentSize }; } - + ${ appendSelectors( selector, '> [data-align="full"]' ) } { max-width: none; } @@ -131,7 +134,7 @@ export default { float: left; margin-right: 2em; } - + ${ appendSelectors( selector, '> [data-align="right"]' ) } { float: right; margin-left: 2em; @@ -150,11 +153,9 @@ export default { return ; }, - getOrientation() { return 'vertical'; }, - getAlignments( layout ) { if ( layout.alignments !== undefined ) { return layout.alignments; diff --git a/packages/block-library/src/social-links/block.json b/packages/block-library/src/social-links/block.json index 73ac6142c7042..b9c8eddbdc4d6 100644 --- a/packages/block-library/src/social-links/block.json +++ b/packages/block-library/src/social-links/block.json @@ -41,7 +41,14 @@ "supports": { "align": [ "left", "center", "right" ], "anchor": true, - "__experimentalExposeControlsToChildren": true + "__experimentalExposeControlsToChildren": true, + "__experimentalLayout": { + "allowSwitching": false, + "allowInheriting": false, + "default": { + "type": "flex" + } + } }, "styles": [ { "name": "default", "label": "Default", "isDefault": true }, diff --git a/packages/block-library/src/social-links/deprecated.js b/packages/block-library/src/social-links/deprecated.js index e8615282201f5..8f1ae2c42a68e 100644 --- a/packages/block-library/src/social-links/deprecated.js +++ b/packages/block-library/src/social-links/deprecated.js @@ -8,8 +8,101 @@ import classNames from 'classnames'; */ import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; +/** + * The specific handling by `className` below is needed because `itemsJustification` + * was introduced in https://github.com/WordPress/gutenberg/pull/28980/files and wasn't + * declared in block.json. + * + * @param {Object} attributes Block's attributes. + */ +const migrateWithLayout = ( attributes ) => { + if ( !! attributes.layout ) { + return attributes; + } + const { className } = attributes; + // Matches classes with `items-justified-` prefix. + const prefix = `items-justified-`; + const justifiedItemsRegex = new RegExp( `\\b${ prefix }[^ ]*[ ]?\\b`, 'g' ); + const newAttributes = { + ...attributes, + className: className?.replace( justifiedItemsRegex, '' ).trim(), + }; + /** + * Add `layout` prop only if `justifyContent` is defined, for backwards + * compatibility. In other cases the block's default layout will be used. + * Also noting that due to the missing attribute, it's possible for a block + * to have more than one of `justified` classes. + */ + const justifyContent = className + ?.match( justifiedItemsRegex )?.[ 0 ] + ?.trim(); + if ( justifyContent ) { + Object.assign( newAttributes, { + layout: { + type: 'flex', + justifyContent: justifyContent.slice( prefix.length ), + }, + } ); + } + return newAttributes; +}; + // Social Links block deprecations. const deprecated = [ + // Implement `flex` layout. + { + attributes: { + iconColor: { + type: 'string', + }, + customIconColor: { + type: 'string', + }, + iconColorValue: { + type: 'string', + }, + iconBackgroundColor: { + type: 'string', + }, + customIconBackgroundColor: { + type: 'string', + }, + iconBackgroundColorValue: { + type: 'string', + }, + openInNewTab: { + type: 'boolean', + default: false, + }, + size: { + type: 'string', + }, + }, + isEligible: ( { layout } ) => ! layout, + migrate: migrateWithLayout, + save( props ) { + const { + attributes: { + iconBackgroundColorValue, + iconColorValue, + itemsJustification, + size, + }, + } = props; + + const className = classNames( size, { + 'has-icon-color': iconColorValue, + 'has-icon-background-color': iconBackgroundColorValue, + [ `items-justified-${ itemsJustification }` ]: itemsJustification, + } ); + + return ( +
    + +
+ ); + }, + }, // V1. Remove CSS variable use for colors. { attributes: { @@ -46,6 +139,7 @@ const deprecated = [ align: [ 'left', 'center', 'right' ], anchor: true, }, + migrate: migrateWithLayout, save: ( props ) => { const { attributes: { diff --git a/packages/block-library/src/social-links/edit.js b/packages/block-library/src/social-links/edit.js index cd4a10b171adb..3b3214be223ff 100644 --- a/packages/block-library/src/social-links/edit.js +++ b/packages/block-library/src/social-links/edit.js @@ -6,15 +6,13 @@ import classNames from 'classnames'; /** * WordPress dependencies */ - +import { getBlockSupport } from '@wordpress/blocks'; import { Fragment, useEffect } from '@wordpress/element'; - import { BlockControls, __experimentalUseInnerBlocksProps as useInnerBlocksProps, useBlockProps, InspectorControls, - JustifyContentControl, ContrastChecker, PanelColorSettings, withColors, @@ -38,8 +36,17 @@ const sizeOptions = [ { name: __( 'Huge' ), value: 'has-huge-icon-size' }, ]; +const getDefaultBlockLayout = ( blockTypeOrName ) => { + const layoutBlockSupportConfig = getBlockSupport( + blockTypeOrName, + '__experimentalLayout' + ); + return layoutBlockSupportConfig?.default; +}; + export function SocialLinksEdit( props ) { const { + name, attributes, iconBackgroundColor, iconColor, @@ -52,10 +59,11 @@ export function SocialLinksEdit( props ) { const { iconBackgroundColorValue, iconColorValue, - itemsJustification, openInNewTab, size, + layout, } = attributes; + const usedLayout = layout || getDefaultBlockLayout( name ); // Remove icon background color if logos only style selected. const logosOnly = @@ -93,16 +101,15 @@ export function SocialLinksEdit( props ) { 'has-icon-color': iconColor.color || iconColorValue, 'has-icon-background-color': iconBackgroundColor.color || iconBackgroundColorValue, - [ `items-justified-${ itemsJustification }` ]: itemsJustification, } ); const blockProps = useBlockProps( { className } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { allowedBlocks: ALLOWED_BLOCKS, - orientation: 'horizontal', placeholder: isSelected ? SelectedSocialPlaceholder : SocialPlaceholder, templateLock: false, __experimentalAppenderTagName: 'li', + __experimentalLayout: usedLayout, } ); const POPOVER_PROPS = { @@ -111,24 +118,6 @@ export function SocialLinksEdit( props ) { return ( - - - setAttributes( { itemsJustification: value } ) - } - popoverProps={ { - position: 'bottom right', - isAlternate: true, - } } - /> - @@ -16,13 +14,7 @@ box-shadow: none; } - // Vertically balance the margin of each icon. .wp-social-link { - // This needs specificity to override some themes. - &.wp-social-link.wp-social-link { - margin: 4px 8px 4px 0; - } - // By setting the font size, we can scale icons and paddings consistently based on that. // This also allows themes to override this, if need be. a { diff --git a/test/integration/fixtures/blocks/core__social-links__deprecated-1.html b/test/integration/fixtures/blocks/core__social-links__deprecated-1.html new file mode 100644 index 0000000000000..b18602d8e8c14 --- /dev/null +++ b/test/integration/fixtures/blocks/core__social-links__deprecated-1.html @@ -0,0 +1,7 @@ + + + diff --git a/test/integration/fixtures/blocks/core__social-links__deprecated-1.json b/test/integration/fixtures/blocks/core__social-links__deprecated-1.json new file mode 100644 index 0000000000000..e1c9d8dd21021 --- /dev/null +++ b/test/integration/fixtures/blocks/core__social-links__deprecated-1.json @@ -0,0 +1,55 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/social-links", + "isValid": true, + "attributes": { + "customIconColor": "#ffffff", + "iconColorValue": "#ffffff", + "customIconBackgroundColor": "#3962e3", + "iconBackgroundColorValue": "#3962e3", + "openInNewTab": false, + "className": "", + "layout": { + "type": "flex", + "justifyContent": "space-between" + } + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/social-link", + "isValid": true, + "attributes": { + "url": "https://wordpress.org", + "service": "wordpress" + }, + "innerBlocks": [], + "originalContent": "" + }, + { + "clientId": "_clientId_1", + "name": "core/social-link", + "isValid": true, + "attributes": { + "url": "#", + "service": "chain" + }, + "innerBlocks": [], + "originalContent": "" + }, + { + "clientId": "_clientId_2", + "name": "core/social-link", + "isValid": true, + "attributes": { + "url": "#", + "service": "mail" + }, + "innerBlocks": [], + "originalContent": "" + } + ], + "originalContent": "" + } +] diff --git a/test/integration/fixtures/blocks/core__social-links__deprecated-1.parsed.json b/test/integration/fixtures/blocks/core__social-links__deprecated-1.parsed.json new file mode 100644 index 0000000000000..9746fed44ab7e --- /dev/null +++ b/test/integration/fixtures/blocks/core__social-links__deprecated-1.parsed.json @@ -0,0 +1,54 @@ +[ + { + "blockName": "core/social-links", + "attrs": { + "customIconColor": "#ffffff", + "iconColorValue": "#ffffff", + "customIconBackgroundColor": "#3962e3", + "iconBackgroundColorValue": "#3962e3", + "className": "has-icon-color" + }, + "innerBlocks": [ + { + "blockName": "core/social-link", + "attrs": { + "url": "https://wordpress.org", + "service": "wordpress" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": "core/social-link", + "attrs": { + "url": "#", + "service": "chain" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": "core/social-link", + "attrs": { + "url": "#", + "service": "mail" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } + ], + "innerHTML": "\n\n", + "innerContent": [ + "\n\n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__social-links__deprecated-1.serialized.html b/test/integration/fixtures/blocks/core__social-links__deprecated-1.serialized.html new file mode 100644 index 0000000000000..b2fa19fb12064 --- /dev/null +++ b/test/integration/fixtures/blocks/core__social-links__deprecated-1.serialized.html @@ -0,0 +1,7 @@ + + +