Skip to content

Commit

Permalink
Add block variations transformation in block switcher (#50139)
Browse files Browse the repository at this point in the history
* Add block variations transformation in block switcher

* move variation transforms to the top of the list

* fix error on multi select

* add e2e test

* Update test/e2e/specs/editor/various/block-switcher-test.spec.js

Co-authored-by: Kai Hao <[email protected]>

* update e2e test

* Update test/e2e/specs/editor/various/block-switcher-test.spec.js

Co-authored-by: Kai Hao <[email protected]>

---------

Co-authored-by: Kai Hao <[email protected]>
  • Loading branch information
ntsekouras and kevin940726 authored May 17, 2023
1 parent 01248b8 commit 8d9752e
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -65,7 +66,9 @@ function useGroupedTransforms( possibleBlockTransformations ) {
const BlockTransformationsMenu = ( {
className,
possibleBlockTransformations,
possibleBlockVariationTransformations,
onSelect,
onSelectVariation,
blocks,
} ) => {
const [ hoveredTransformItemName, setHoveredTransformItemName ] =
Expand Down Expand Up @@ -95,6 +98,15 @@ const BlockTransformationsMenu = ( {
) }
/>
) }
{ !! possibleBlockVariationTransformations?.length && (
<BlockVariationTransformations
transformations={
possibleBlockVariationTransformations
}
blocks={ blocks }
onSelect={ onSelectVariation }
/>
) }
{ priorityTextTransformations.map( ( item ) => (
<BlockTranformationItem
key={ item.name }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* WordPress dependencies
*/
import { MenuItem } from '@wordpress/components';
import {
getBlockMenuDefaultClassName,
cloneBlock,
store as blocksStore,
} from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
import { useState, useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import BlockIcon from '../block-icon';
import PreviewBlockPopover from './preview-block-popover';

const EMPTY_OBJECT = {};

export function useBlockVariationTransforms( { clientIds, blocks } ) {
const { activeBlockVariation, blockVariationTransformations } = useSelect(
( select ) => {
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 && (
<PreviewBlockPopover
blocks={ cloneBlock(
blocks[ 0 ],
transformations.find(
( { name } ) => name === hoveredTransformItemName
).attributes
) }
/>
) }
{ transformations?.map( ( item ) => (
<BlockVariationTranformationItem
key={ item.name }
item={ item }
onSelect={ onSelect }
setHoveredTransformItemName={ setHoveredTransformItemName }
/>
) ) }
</>
);
};

function BlockVariationTranformationItem( {
item,
onSelect,
setHoveredTransformItemName,
} ) {
const { name, icon, title } = item;
return (
<MenuItem
className={ getBlockMenuDefaultClassName( name ) }
onClick={ ( event ) => {
event.preventDefault();
onSelect( name );
} }
onMouseLeave={ () => setHoveredTransformItemName( null ) }
onMouseEnter={ () => setHoveredTransformItemName( name ) }
>
<BlockIcon icon={ icon } showColors />
{ title }
</MenuItem>
);
}

export default BlockVariationTransformations;
43 changes: 38 additions & 5 deletions packages/block-editor/src/components/block-switcher/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
);
Expand Down Expand Up @@ -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,
Expand All @@ -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 );
Expand All @@ -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 (
<ToolbarGroup>
<ToolbarButton
Expand Down Expand Up @@ -160,9 +181,12 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => {
blocks.length
);

const hasBlockOrBlockVariationTransforms =
hasPossibleBlockTransformations ||
hasPossibleBlockVariationTransformations;
const showDropDown =
hasBlockStyles ||
hasPossibleBlockTransformations ||
hasBlockOrBlockVariationTransforms ||
hasPatternTransformation;
return (
<ToolbarGroup>
Expand Down Expand Up @@ -213,17 +237,26 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => {
} }
/>
) }
{ hasPossibleBlockTransformations && (
{ hasBlockOrBlockVariationTransforms && (
<BlockTransformationsMenu
className="block-editor-block-switcher__transforms__menugroup"
possibleBlockTransformations={
possibleBlockTransformations
}
possibleBlockVariationTransformations={
blockVariationTransformations
}
blocks={ blocks }
onSelect={ ( name ) => {
onBlockTransform( name );
onClose();
} }
onSelectVariation={ ( name ) => {
onBlockVariationTransform(
name
);
onClose();
} }
/>
) }
{ hasBlockStyles && (
Expand Down
60 changes: 60 additions & 0 deletions test/e2e/specs/editor/various/block-switcher-test.spec.js
Original file line number Diff line number Diff line change
@@ -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();
} );
} );

0 comments on commit 8d9752e

Please sign in to comment.