diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 51813220e58240..307f46ca97ff80 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -302,7 +302,7 @@ Gather blocks in a layout container. ([Source](https://github.com/WordPress/gute - **Name:** core/group - **Category:** design -- **Supports:** align (full, wide), anchor, ariaLabel, color (background, gradients, heading, link, text), dimensions (minHeight), layout (allowSizingOnChildren), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align (full, wide), anchor, ariaLabel, color (background, gradients, heading, link, text), dimensions (minHeight), layout (allowSizingOnChildren, allowSwitching), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** allowedBlocks, tagName, templateLock ## Heading diff --git a/packages/block-editor/src/components/block-edit/index.js b/packages/block-editor/src/components/block-edit/index.js index 6faefbc6261929..d5dfcc55d0c0d2 100644 --- a/packages/block-editor/src/components/block-edit/index.js +++ b/packages/block-editor/src/components/block-edit/index.js @@ -26,17 +26,23 @@ export default function BlockEdit( props ) { isSelected, clientId, attributes = {}, + setAttributes, __unstableLayoutClassNames, } = props; + const { layout = null } = attributes; const layoutSupport = hasBlockSupport( name, 'layout', false ) || hasBlockSupport( name, '__experimentalLayout', false ); + const updateLayoutType = ( newLayout ) => { + setAttributes( { layout: { ...layout, type: newLayout } } ); + }; const context = { name, isSelected, clientId, layout: layoutSupport ? layout : null, + updateLayoutType, __unstableLayoutClassNames, }; return ( diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index a2acb3c7b53be6..225f8cb3b416bc 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -91,6 +91,7 @@ function BlockListBlock( { onInsertBlocksAfter, onMerge, toggleSelection, + updateLayoutType, } ) { const { themeSupportsLayout, @@ -138,6 +139,7 @@ function BlockListBlock( { __unstableParentLayout={ Object.keys( parentLayout ).length ? parentLayout : undefined } + updateParentLayoutType={ updateLayoutType } /> ); diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 04b767d9568b78..3fb66e0e74f2cc 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -140,6 +140,7 @@ function Items( { renderAppender, __experimentalAppenderTagName, layout = defaultLayout, + updateLayoutType, } ) { const { order, selectedBlocks, visibleBlocks } = useSelect( ( select ) => { @@ -172,6 +173,7 @@ function Items( { ) ) } diff --git a/packages/block-editor/src/components/child-layout-control/index.js b/packages/block-editor/src/components/child-layout-control/index.js index b7679234ae5939..e55aed1f9bf4e6 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -26,17 +26,18 @@ function helpText( selfStretch, parentLayout ) { /** * Form to edit the child layout value. * - * @param {Object} props Props. - * @param {Object} props.value The child layout value. - * @param {Function} props.onChange Function to update the child layout value. - * @param {Object} props.parentLayout The parent layout value. - * + * @param {Object} props Props. + * @param {Object} props.value The child layout value. + * @param {Function} props.onChange Function to update the child layout value. + * @param {Object} props.parentLayout The parent layout value. + * @param {Function} props.updateParentLayoutType Function to update the parent layout type. * @return {WPElement} child layout edit element. */ export default function ChildLayoutControl( { value: childLayout = {}, onChange, parentLayout, + updateParentLayoutType, } ) { const { selfStretch, flexSize } = childLayout; @@ -59,6 +60,9 @@ export default function ChildLayoutControl( { help={ helpText( selfStretch, parentLayout ) } onChange={ ( value ) => { const newFlexSize = value !== 'fixed' ? null : flexSize; + if ( value === 'fill' ) { + updateParentLayoutType( 'flex' ); + } onChange( { ...childLayout, selfStretch: value, 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 b5eb6175c8c5e4..38659f0f529250 100644 --- a/packages/block-editor/src/components/global-styles/dimensions-panel.js +++ b/packages/block-editor/src/components/global-styles/dimensions-panel.js @@ -85,7 +85,7 @@ function useHasChildLayout( settings ) { } = settings?.parentLayout ?? {}; const support = - ( defaultParentLayoutType === 'flex' || parentLayoutType === 'flex' ) && + ( defaultParentLayoutType || parentLayoutType ) && allowSizingOnChildren; return !! settings?.layout && support; @@ -206,6 +206,7 @@ export default function DimensionsPanel( { // Special case because the layout controls are not part of the dimensions panel // in global styles but not in block inspector. includeLayoutControls = false, + updateParentLayoutType, } ) { const { dimensions, spacing } = settings; @@ -633,6 +634,7 @@ export default function DimensionsPanel( { value={ childLayout } onChange={ setChildLayout } parentLayout={ settings?.parentLayout } + updateParentLayoutType={ updateParentLayoutType } /> ) } diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index 9e0e4f19cfc7ea..370f8a702f299d 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -60,6 +60,7 @@ function UncontrolledInnerBlocks( props ) { orientation, placeholder, layout, + updateLayoutType, } = props; useNestedSettingsUpdate( @@ -125,6 +126,7 @@ function UncontrolledInnerBlocks( props ) { layout={ memoedLayout } wrapperRef={ wrapperRef } placeholder={ placeholder } + updateLayoutType={ updateLayoutType } /> ); @@ -175,6 +177,7 @@ export function useInnerBlocksProps( props = {}, options = {} ) { clientId, layout = null, __unstableLayoutClassNames: layoutClassNames = '', + updateLayoutType, } = useBlockEditContext(); const isSmallScreen = useViewportMatch( 'medium', '<' ); const { __experimentalCaptureToolbars, hasOverlay } = useSelect( @@ -222,6 +225,7 @@ export function useInnerBlocksProps( props = {}, options = {} ) { const innerBlocksProps = { __experimentalCaptureToolbars, layout, + updateLayoutType, ...options, }; const InnerBlocks = diff --git a/packages/block-editor/src/hooks/dimensions.js b/packages/block-editor/src/hooks/dimensions.js index 084763f0c21b16..f5fce760f70b6a 100644 --- a/packages/block-editor/src/hooks/dimensions.js +++ b/packages/block-editor/src/hooks/dimensions.js @@ -72,6 +72,7 @@ export function DimensionsPanel( props ) { attributes, setAttributes, __unstableParentLayout, + updateParentLayoutType, } = props; const settings = useBlockSettings( name, __unstableParentLayout ); const isEnabled = useHasDimensionsPanel( settings ); @@ -110,6 +111,7 @@ export function DimensionsPanel( props ) { onChange={ onChange } defaultControls={ defaultControls } onVisualize={ setVisualizedProperty } + updateParentLayoutType={ updateParentLayoutType } /> { !! settings?.spacing?.padding && ( { const { getSettings } = select( blockEditorStore ); return { @@ -149,34 +174,25 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { {} ); const { - allowSwitching, + allowSwitching = false, allowEditing = true, - allowInheriting = true, - default: defaultBlockLayout, + // allowInheriting = true, + default: defaultBlockLayout = { type: 'default' }, } = layoutBlockSupport; if ( ! allowEditing ) { return null; } - // Only show the inherit toggle if it's supported, - // a default theme layout is set (e.g. one that provides `contentSize` and/or `wideSize` values), - // and either the default / flow or the constrained layout type is in use, as the toggle switches from one to the other. - const showInheritToggle = !! ( - allowInheriting && - !! defaultThemeLayout && - ( ! layout?.type || - layout?.type === 'default' || - layout?.type === 'constrained' || - layout?.inherit ) - ); - const usedLayout = layout || defaultBlockLayout || {}; const { inherit = false, type = 'default', contentSize = null, + orientation = 'horizontal', + flexWrap = 'nowrap', } = usedLayout; + const { type: defaultBlockLayoutType } = defaultBlockLayout; /** * `themeSupportsLayout` is only relevant to the `default/flow` or * `constrained` layouts and it should not be taken into account when other @@ -192,74 +208,473 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { const constrainedType = getLayoutType( 'constrained' ); const displayControlsForLegacyLayouts = ! usedLayout.type && ( contentSize || inherit ); - const hasContentSizeOrLegacySettings = !! inherit || !! contentSize; - const onChangeType = ( newType ) => - setAttributes( { layout: { type: newType } } ); + const innerWidthOptions = [ + { + value: 'theme', + icon: ( + + + + + + + + + ), + label: __( 'Fixed' ), + }, + { + value: 'fill', + icon: ( + + + + + + + ), + label: __( 'Fill' ), + }, + ]; + + if ( allowSwitching || defaultBlockLayoutType === 'flex' ) { + innerWidthOptions.unshift( { + value: 'fit', + icon: ( + + + + + + + ), + label: __( 'Fit' ), + } ); + } + const horizontalAlignmentOptions = [ + { + value: 'left', + icon: justifyLeft, + label: __( 'Left' ), + }, + { + value: 'center', + icon: justifyCenter, + label: __( 'Middle' ), + }, + { + value: 'right', + icon: justifyRight, + label: __( 'Right' ), + }, + ]; + + if ( type === 'flex' ) { + horizontalAlignmentOptions.push( { + value: 'space-between', + icon: justifySpaceBetween, + label: __( 'Space Between' ), + } ); + } + + const verticalAlignmentOptions = [ + { + value: 'top', + icon: alignTop, + label: __( 'Top' ), + }, + { + value: 'center', + icon: alignCenter, + label: __( 'Middle' ), + }, + { + value: 'bottom', + icon: alignBottom, + label: __( 'Bottom' ), + }, + ]; + + if ( orientation === 'horizontal' ) { + verticalAlignmentOptions.push( { + value: 'stretch', + icon: alignStretch, + label: __( 'Stretch' ), + } ); + } else { + verticalAlignmentOptions.push( { + value: 'space-between', + icon: spaceBetween, + label: __( 'Space Between' ), + } ); + } + + const onChangeType = ( newType ) => { + if ( newType === 'stack' ) { + const { type: previousLayoutType } = usedLayout; + if ( previousLayoutType === 'flex' ) { + setAttributes( { + layout: { + ...usedLayout, + type: 'flex', + orientation: 'vertical', + }, + } ); + } else { + setAttributes( { layout: { type: 'default' } } ); + } + } else { + setAttributes( { + layout: { + ...usedLayout, + type: newType, + orientation: 'horizontal', + }, + } ); + } + }; + + const onChangeInnerWidth = ( key ) => { + if ( key === 'theme' ) { + setAttributes( { + layout: { ...usedLayout, type: 'constrained' }, + } ); + } else if ( key === 'fit' ) { + setAttributes( { + layout: { + ...usedLayout, + type: 'flex', + orientation: 'vertical', + }, + } ); + } else { + setAttributes( { + layout: { ...usedLayout, type: 'default' }, + } ); + } + }; + const onChangeLayout = ( newLayout ) => setAttributes( { layout: newLayout } ); + const onChangeGap = ( newGap ) => { + setAttributes( { + style: { + ...style, + spacing: { + ...style?.spacing, + blockGap: `${ newGap }px`, + }, + }, + } ); + }; + + const onChangeWrap = ( newWrap ) => { + setAttributes( { + layout: { + ...usedLayout, + flexWrap: newWrap, + }, + } ); + }; + + const defaultHorizontalAlign = type === 'constrained' ? 'center' : 'left'; + + let defaultContentWidthValue = 'fill'; + if ( defaultBlockLayoutType === 'constrained' ) { + defaultContentWidthValue = 'theme'; + } else if ( defaultBlockLayoutType === 'flex' ) { + defaultContentWidthValue = 'fit'; + } + + let usedContentWidthValue = 'fill'; + if ( type === 'constrained' ) { + usedContentWidthValue = 'theme'; + } else if ( type === 'flex' ) { + usedContentWidthValue = 'fit'; + } + return ( <> - { showInheritToggle && ( - <> - + { ( allowSwitching || + defaultBlockLayoutType === 'flex' ) && ( + - setAttributes( { - layout: { - type: - layoutType?.name === - 'constrained' || - hasContentSizeOrLegacySettings - ? 'default' - : 'constrained', - }, - } ) - } - help={ - layoutType?.name === 'constrained' || - hasContentSizeOrLegacySettings - ? __( - 'Nested blocks use content width with options for full and wide widths.' - ) - : __( - 'Nested blocks will fill the width of this container. Toggle to constrain.' - ) + style={ { marginBottom: 0, marginTop: 0 } } + size={ '__unstable-large' } + label={ __( 'Layout direction' ) } + value={ + type === 'default' || + type === 'constrained' || + ( type === 'flex' && + orientation === 'vertical' ) + ? 'stack' + : type } + onChange={ onChangeType } + isBlock={ true } + className="components-toggle-group-control__full-width" + > + + + + + { allowSwitching && ( + + ) } + + ) } + { type === 'grid' && ( + - - ) } - - { ! inherit && allowSwitching && ( - - ) } - - { layoutType && layoutType.name !== 'default' && ( - - ) } - { constrainedType && displayControlsForLegacyLayouts && ( - + { innerWidthOptions.map( ( option ) => ( + + ) ) } + + ) } + + { type === 'flex' && ( + + { + onChangeLayout( { + ...usedLayout, + verticalAlignment: selectedItem, + } ); + } } + isBlock={ true } + className="components-toggle-group-control__full-width" + > + { verticalAlignmentOptions.map( + ( option ) => ( + + ) + ) } + + + ) } + + { ( type === 'flex' || + type === 'constrained' ) && ( + { + onChangeLayout( { + ...usedLayout, + justifyContent: selectedItem, + } ); + } } + className="components-toggle-group-control__full-width" + > + { horizontalAlignmentOptions.map( + ( { value, icon, label } ) => ( + + ) + ) } + + ) } + + + + { type === 'flex' && orientation === 'horizontal' && ( + + + + + ) } + { constrainedType && + displayControlsForLegacyLayouts && ( + + ) } + - ) } + { ! inherit && blockEditingMode === 'default' && layoutType && ( @@ -273,24 +688,6 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) { ); } -function LayoutTypeSwitcher( { type, onChange } ) { - return ( - - { getLayoutTypes().map( ( { name, label } ) => { - return ( - - ); - } ) } - - ); -} - /** * Filters registered block settings, extending attributes to include `layout`. * diff --git a/packages/block-editor/src/hooks/layout.scss b/packages/block-editor/src/hooks/layout.scss index 83a044e3cdca75..37843fd23166c4 100644 --- a/packages/block-editor/src/hooks/layout.scss +++ b/packages/block-editor/src/hooks/layout.scss @@ -39,3 +39,21 @@ .block-editor-hooks__toggle-control.block-editor-hooks__toggle-control { margin-bottom: $grid-unit-20; } + + +// Temporary + +.components-toggle-group-control__full-width { + .components-toggle-group-control-option-base { + width: 100%; + button { + width: 100%; + } + } +} + +.components-wrapper-vstack { + > div { + margin-bottom: 0 !important; + } +} diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json index 135aedf03589d0..11b3296fc7b168 100644 --- a/packages/block-library/src/group/block.json +++ b/packages/block-library/src/group/block.json @@ -77,7 +77,8 @@ } }, "layout": { - "allowSizingOnChildren": true + "allowSizingOnChildren": true, + "allowSwitching": true } }, "editorStyle": "wp-block-group-editor", diff --git a/packages/block-library/src/group/variations.js b/packages/block-library/src/group/variations.js index 3f5dcc0a45a9e4..543f70d6c6690e 100644 --- a/packages/block-library/src/group/variations.js +++ b/packages/block-library/src/group/variations.js @@ -11,7 +11,7 @@ const variations = [ description: __( 'Gather blocks in a container.' ), attributes: { layout: { type: 'constrained' } }, isDefault: true, - scope: [ 'block', 'inserter', 'transform' ], + scope: [ 'block', 'inserter' ], isActive: ( blockAttributes ) => ! blockAttributes.layout || ! blockAttributes.layout?.type || @@ -24,7 +24,7 @@ const variations = [ title: _x( 'Row', 'single horizontal line' ), description: __( 'Arrange blocks horizontally.' ), attributes: { layout: { type: 'flex', flexWrap: 'nowrap' } }, - scope: [ 'block', 'inserter', 'transform' ], + scope: [ 'block', 'inserter' ], isActive: ( blockAttributes ) => blockAttributes.layout?.type === 'flex' && ( ! blockAttributes.layout?.orientation || @@ -36,7 +36,7 @@ const variations = [ title: __( 'Stack' ), description: __( 'Arrange blocks vertically.' ), attributes: { layout: { type: 'flex', orientation: 'vertical' } }, - scope: [ 'block', 'inserter', 'transform' ], + scope: [ 'block', 'inserter' ], isActive: ( blockAttributes ) => blockAttributes.layout?.type === 'flex' && blockAttributes.layout?.orientation === 'vertical', @@ -50,7 +50,7 @@ if ( window?.__experimentalEnableGroupGridVariation ) { title: __( 'Grid' ), description: __( 'Arrange blocks in a grid.' ), attributes: { layout: { type: 'grid' } }, - scope: [ 'block', 'inserter', 'transform' ], + scope: [ 'block', 'inserter' ], isActive: ( blockAttributes ) => blockAttributes.layout?.type === 'grid', icon: grid,