diff --git a/packages/block-editor/src/components/block-switcher/block-transformations-menu.js b/packages/block-editor/src/components/block-switcher/block-transformations-menu.js index 6b88fddb1163fa..033201d7facadb 100644 --- a/packages/block-editor/src/components/block-switcher/block-transformations-menu.js +++ b/packages/block-editor/src/components/block-switcher/block-transformations-menu.js @@ -14,6 +14,7 @@ import { useState, useMemo } from '@wordpress/element'; */ import BlockIcon from '../block-icon'; import PreviewBlockPopover from './preview-block-popover'; +import BlockVariationTransformations from './block-variation-transformations'; /** * Helper hook to group transformations to display them in a specific order in the UI. @@ -65,7 +66,9 @@ function useGroupedTransforms( possibleBlockTransformations ) { const BlockTransformationsMenu = ( { className, possibleBlockTransformations, + possibleBlockVariationTransformations, onSelect, + onSelectVariation, blocks, } ) => { const [ hoveredTransformItemName, setHoveredTransformItemName ] = @@ -95,6 +98,15 @@ const BlockTransformationsMenu = ( { ) } /> ) } + { !! possibleBlockVariationTransformations?.length && ( + + ) } { priorityTextTransformations.map( ( item ) => ( { + const { + getBlockRootClientId, + getBlockAttributes, + canRemoveBlocks, + } = select( blockEditorStore ); + const { getActiveBlockVariation, getBlockVariations } = + select( blocksStore ); + const rootClientId = getBlockRootClientId( + Array.isArray( clientIds ) ? clientIds[ 0 ] : clientIds + ); + const canRemove = canRemoveBlocks( clientIds, rootClientId ); + // Only handle single selected blocks for now. + if ( blocks.length !== 1 || ! canRemove ) { + return EMPTY_OBJECT; + } + const [ firstBlock ] = blocks; + return { + blockVariationTransformations: getBlockVariations( + firstBlock.name, + 'transform' + ), + activeBlockVariation: getActiveBlockVariation( + firstBlock.name, + getBlockAttributes( firstBlock.clientId ) + ), + }; + }, + [ clientIds, blocks ] + ); + const transformations = useMemo( () => { + return blockVariationTransformations?.filter( + ( { name } ) => name !== activeBlockVariation?.name + ); + }, [ blockVariationTransformations, activeBlockVariation ] ); + return transformations; +} + +const BlockVariationTransformations = ( { + transformations, + onSelect, + blocks, +} ) => { + const [ hoveredTransformItemName, setHoveredTransformItemName ] = + useState(); + return ( + <> + { hoveredTransformItemName && ( + name === hoveredTransformItemName + ).attributes + ) } + /> + ) } + { transformations?.map( ( item ) => ( + + ) ) } + + ); +}; + +function BlockVariationTranformationItem( { + item, + onSelect, + setHoveredTransformItemName, +} ) { + const { name, icon, title } = item; + return ( + { + event.preventDefault(); + onSelect( name ); + } } + onMouseLeave={ () => setHoveredTransformItemName( null ) } + onMouseEnter={ () => setHoveredTransformItemName( name ) } + > + + { title } + + ); +} + +export default BlockVariationTransformations; diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 696b097757d335..a4c15a9e17062a 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -24,12 +24,14 @@ import { store as blockEditorStore } from '../../store'; import useBlockDisplayInformation from '../use-block-display-information'; import BlockIcon from '../block-icon'; import BlockTransformationsMenu from './block-transformations-menu'; +import { useBlockVariationTransforms } from './block-variation-transformations'; import BlockStylesMenu from './block-styles-menu'; import PatternTransformationsMenu from './pattern-transformations-menu'; import useBlockDisplayTitle from '../block-title/use-block-display-title'; export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => { - const { replaceBlocks, multiSelect } = useDispatch( blockEditorStore ); + const { replaceBlocks, multiSelect, updateBlockAttributes } = + useDispatch( blockEditorStore ); const blockInformation = useBlockDisplayInformation( blocks[ 0 ].clientId ); const { possibleBlockTransformations, @@ -43,9 +45,9 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => { getBlockRootClientId, getBlockTransformItems, __experimentalGetPatternTransformItems, + canRemoveBlocks, } = select( blockEditorStore ); const { getBlockStyles, getBlockType } = select( blocksStore ); - const { canRemoveBlocks } = select( blockEditorStore ); const rootClientId = getBlockRootClientId( Array.isArray( clientIds ) ? clientIds[ 0 ] : clientIds ); @@ -82,6 +84,11 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => { [ clientIds, blocks, blockInformation?.icon ] ); + const blockVariationTransformations = useBlockVariationTransforms( { + clientIds, + blocks, + } ); + const blockTitle = useBlockDisplayTitle( { clientId: Array.isArray( clientIds ) ? clientIds[ 0 ] : clientIds, maximumLength: 35, @@ -105,6 +112,14 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => { selectForMultipleBlocks( newBlocks ); } + function onBlockVariationTransform( name ) { + updateBlockAttributes( blocks[ 0 ].clientId, { + ...blockVariationTransformations.find( + ( { name: variationName } ) => variationName === name + ).attributes, + } ); + } + // Pattern transformation through the `Patterns` API. function onPatternTransform( transformedBlocks ) { replaceBlocks( clientIds, transformedBlocks ); @@ -118,8 +133,14 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => { */ const hasPossibleBlockTransformations = !! possibleBlockTransformations.length && canRemove && ! isTemplate; + const hasPossibleBlockVariationTransformations = + !! blockVariationTransformations?.length; const hasPatternTransformation = !! patterns?.length && canRemove; - if ( ! hasBlockStyles && ! hasPossibleBlockTransformations ) { + if ( + ! hasBlockStyles && + ! hasPossibleBlockTransformations && + ! hasPossibleBlockVariationTransformations + ) { return ( { blocks.length ); + const hasBlockOrBlockVariationTransforms = + hasPossibleBlockTransformations || + hasPossibleBlockVariationTransformations; const showDropDown = hasBlockStyles || - hasPossibleBlockTransformations || + hasBlockOrBlockVariationTransforms || hasPatternTransformation; return ( @@ -213,17 +237,26 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => { } } /> ) } - { hasPossibleBlockTransformations && ( + { hasBlockOrBlockVariationTransforms && ( { onBlockTransform( name ); onClose(); } } + onSelectVariation={ ( name ) => { + onBlockVariationTransform( + name + ); + onClose(); + } } /> ) } { hasBlockStyles && ( diff --git a/test/e2e/specs/editor/various/block-switcher-test.spec.js b/test/e2e/specs/editor/various/block-switcher-test.spec.js new file mode 100644 index 00000000000000..12fd843ed2ed12 --- /dev/null +++ b/test/e2e/specs/editor/various/block-switcher-test.spec.js @@ -0,0 +1,60 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Block Switcher', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + test( 'Block variation transforms', async ( { editor, page } ) => { + // This is the `stack` Group variation. + await editor.insertBlock( { + name: 'core/group', + attributes: { + layout: { + type: 'flex', + orientation: 'vertical', + }, + }, + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { content: '1' }, + }, + ], + } ); + // Transform to `Stack` variation. + await editor.clickBlockToolbarButton( 'Stack' ); + const variations = page + .getByRole( 'menu', { name: 'Stack' } ) + .getByRole( 'group' ); + await expect( + variations.getByRole( 'menuitem', { name: 'Stack' } ) + ).toBeHidden(); + await variations.getByRole( 'menuitem', { name: 'Row' } ).click(); + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/group', + attributes: expect.objectContaining( { + layout: { + type: 'flex', + flexWrap: 'nowrap', + orientation: undefined, + }, + } ), + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { content: '1' }, + }, + ], + }, + ] ); + await editor.clickBlockToolbarButton( 'Row' ); + await expect( + page.locator( 'role=menuitem[name="Stack"i]' ) + ).toBeVisible(); + } ); +} );