diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js index 75b442085fbb62..151038a7aac7f4 100644 --- a/packages/block-editor/src/components/block-styles/index.js +++ b/packages/block-editor/src/components/block-styles/index.js @@ -82,7 +82,12 @@ const useGenericPreviewBlock = ( block, type ) => [ type.example ? block.name : block, type ] ); -function BlockStyles( { clientId, onSwitch = noop, onHoverClassName = noop } ) { +function BlockStyles( { + clientId, + onSwitch = noop, + onHoverClassName = noop, + itemRole, +} ) { const selector = ( select ) => { const { getBlock } = select( 'core/block-editor' ); const { getBlockStyles } = select( 'core/blocks' ); @@ -141,6 +146,7 @@ function BlockStyles( { clientId, onSwitch = noop, onHoverClassName = noop } ) { onHover={ () => onHoverClassName( styleClassName ) } style={ style } styleClassName={ styleClassName } + itemRole={ itemRole } /> ); } ) } @@ -156,6 +162,7 @@ function BlockStyleItem( { onHover, onSelect, styleClassName, + itemRole, } ) { const previewBlocks = useMemo( () => { return { @@ -182,7 +189,7 @@ function BlockStyleItem( { } } onMouseEnter={ onHover } onMouseLeave={ onBlur } - role="button" + role={ itemRole || 'button' } tabIndex="0" aria-label={ style.label || style.name } > diff --git a/packages/block-editor/src/components/block-styles/style.scss b/packages/block-editor/src/components/block-styles/style.scss index 8ae9248313c87f..ff58284eabf929 100644 --- a/packages/block-editor/src/components/block-styles/style.scss +++ b/packages/block-editor/src/components/block-styles/style.scss @@ -53,5 +53,5 @@ .block-editor-block-styles__item-label { text-align: center; - padding: 4px 2px; + padding: 4px 0; } 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 new file mode 100644 index 00000000000000..d01663a41bd2d1 --- /dev/null +++ b/packages/block-editor/src/components/block-switcher/block-transformations-menu.js @@ -0,0 +1,40 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { MenuGroup, MenuItem } from '@wordpress/components'; +import { getBlockMenuDefaultClassName } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import BlockIcon from '../block-icon'; + +const BlockTransformationsMenu = ( { + className, + possibleBlockTransformations, + onSelect, +} ) => { + return ( + + { possibleBlockTransformations.map( ( item ) => { + const { name, icon, title } = item; + return ( + } + onClick={ ( event ) => { + event.preventDefault(); + onSelect( name ); + } } + > + { title } + + ); + } ) } + + ); +}; + +export default BlockTransformationsMenu; diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 3f2a57fc6665e8..b0005b2e629243 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -1,17 +1,19 @@ /** * External dependencies */ -import { castArray, filter, first, mapKeys, orderBy, uniq, map } from 'lodash'; +import { castArray, filter, mapKeys, orderBy, uniq, map } from 'lodash'; /** * WordPress dependencies */ import { __, _n, sprintf } from '@wordpress/i18n'; import { - Dropdown, + DropdownMenu, ToolbarButton, ToolbarGroup, + __experimentalToolbarItem as ToolbarItem, MenuGroup, + Popover, } from '@wordpress/components'; import { getBlockType, @@ -21,7 +23,6 @@ import { getBlockFromExample, } from '@wordpress/blocks'; import { Component } from '@wordpress/element'; -import { DOWN } from '@wordpress/keycodes'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { layout } from '@wordpress/icons'; @@ -32,12 +33,47 @@ import { layout } from '@wordpress/icons'; import BlockIcon from '../block-icon'; import BlockStyles from '../block-styles'; import BlockPreview from '../block-preview'; -import BlockTypesList from '../block-types-list'; +import BlockTransformationsMenu from './block-transformations-menu'; -const POPOVER_PROPS = { - position: 'bottom right', - isAlternate: true, -}; +function PreviewBlockPopover( { hoveredBlock, hoveredClassName } ) { + const hoveredBlockType = getBlockType( hoveredBlock.name ); + return ( +
+
+ +
+
+ { __( 'Preview' ) } +
+ +
+
+
+
+ ); +} export class BlockSwitcher extends Component { constructor() { @@ -61,13 +97,11 @@ export class BlockSwitcher extends Component { } = this.props; const { hoveredClassName } = this.state; - if ( ! blocks || ! blocks.length ) { + if ( ! Array.isArray( blocks ) || ! blocks.length ) { return null; } - const hoveredBlock = blocks[ 0 ]; - const hoveredBlockType = getBlockType( hoveredBlock.name ); - + const [ hoveredBlock ] = blocks; const itemsByName = mapKeys( inserterItems, ( { name } ) => name ); const possibleBlockTransformations = orderBy( filter( @@ -85,14 +119,16 @@ export class BlockSwitcher extends Component { let icon; if ( isSelectionOfSameType ) { - const sourceBlockName = blocks[ 0 ].name; + const sourceBlockName = hoveredBlock.name; const blockType = getBlockType( sourceBlockName ); icon = blockType.icon; } else { icon = layout; } - if ( ! hasBlockStyles && ! possibleBlockTransformations.length ) { + const hasPossibleBlockTransformations = !! possibleBlockTransformations.length; + + if ( ! hasBlockStyles && ! hasPossibleBlockTransformations ) { return ( { - const openOnArrowDown = ( event ) => { - if ( ! isOpen && event.keyCode === DOWN ) { - event.preventDefault(); - event.stopPropagation(); - onToggle(); - } - }; - const label = - 1 === blocks.length - ? __( 'Change block type or style' ) - : sprintf( - /* translators: %s: number of blocks. */ - _n( - 'Change type of %d block', - 'Change type of %d blocks', - blocks.length - ), - blocks.length - ); + const blockSwitcherLabel = + 1 === blocks.length + ? __( 'Change block type or style' ) + : sprintf( + /* translators: %s: number of blocks. */ + _n( + 'Change type of %d block', + 'Change type of %d blocks', + blocks.length + ), + blocks.length + ); - return ( - - } - /> - - ); - } } - renderContent={ ( { onClose } ) => ( - <> - { ( hasBlockStyles || - possibleBlockTransformations.length !== 0 ) && ( -
- { hasBlockStyles && ( - -
- { __( 'Styles' ) } -
- -
- ) } - { possibleBlockTransformations.length !== 0 && ( - -
- { __( 'Transform to' ) } -
- ( { - id: - destinationBlockType.name, - icon: - destinationBlockType.icon, - title: - destinationBlockType.title, - } ) - ) } - onSelect={ ( item ) => { - onTransform( blocks, item.id ); - onClose(); - } } - /> -
- ) } -
- ) } - { hoveredClassName !== null && ( -
-
- { __( 'Preview' ) } -
- + + { ( toggleProps ) => ( + -
- ) } - - ) } - /> + } + toggleProps={ toggleProps } + menuProps={ { orientation: 'both' } } + > + { ( { onClose } ) => + ( hasBlockStyles || + hasPossibleBlockTransformations ) && ( +
+ { hoveredClassName !== null && ( + + ) } + { hasBlockStyles && ( + + + + ) } + { hasPossibleBlockTransformations && ( + { + onTransform( blocks, name ); + onClose(); + } } + /> + ) } +
+ ) + } + + ) } + +
); } } @@ -237,7 +238,7 @@ export default compose( } = select( 'core/block-editor' ); const { getBlockStyles } = select( 'core/blocks' ); const rootClientId = getBlockRootClientId( - first( castArray( clientIds ) ) + castArray( clientIds )[ 0 ] ); const blocks = getBlocksByClientId( clientIds ); const firstBlock = blocks && blocks.length === 1 ? blocks[ 0 ] : null; diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss index 7e08ab10143329..bb214b10f35776 100644 --- a/packages/block-editor/src/components/block-switcher/style.scss +++ b/packages/block-editor/src/components/block-switcher/style.scss @@ -65,54 +65,31 @@ } } +.components-popover.block-editor-block-switcher__popover .components-popover__content { + min-width: 300px; +} -// We double the max-width for it to fit both the preview & content but we keep the min width the same for the border to work. +// We keep the min width the same for the border to work. .components-popover.block-editor-block-switcher__popover .components-popover__content > div { - min-width: 300px; - max-width: calc(340px * 2); + min-width: auto; display: flex; background: $white; padding: 0; .components-menu-group { - padding: $grid-unit-20 $grid-unit-30; - } - - .components-menu-group + .components-menu-group { - border-color: $light-gray-secondary; + padding: $grid-unit-20; + margin: 0; } +} - .block-editor-block-switcher__container { - min-width: 300px; - max-width: 340px; - width: 50%; - } +.block-editor-block-switcher__popover .components-popover__content { - .block-editor-block-switcher__label { - margin-bottom: $grid-unit-15; - color: $medium-gray-text; - text-transform: uppercase; - font-size: 11px; - font-weight: 500; + .block-editor-block-styles { + margin: 0 -3px; // Remove the panel body padding while keeping it for the title. } - @include break-medium { - position: relative; - - .block-editor-block-switcher__preview { - border-left: $border-width solid $light-gray-500; - margin-top: -$grid-unit-15; - margin-bottom: -$grid-unit-15; - background: $white; - width: 300px; - height: auto; - // Sticky helps us keep the preview at the top when scrolling - position: sticky; - align-self: stretch; - // For sticky to work we need top - top: 0; - padding: 10px; - } + .block-editor-block-styles__item-label { + text-align: left; } // Hide the bottom border on the last panel so it stacks with the popover. @@ -129,12 +106,39 @@ } } -.block-editor-block-switcher__popover .block-editor-block-styles { - margin: 0 -3px; // Remove the panel body padding while keeping it for the title. +.block-editor-block-switcher__popover__preview__parent { + .block-editor-block-switcher__popover__preview__container { + position: absolute; + top: -$grid-unit-15; + left: calc(100% + #{$grid-unit-40}); + } } -.block-editor-block-switcher__popover .block-editor-block-types-list { - margin: $grid-unit-15 0 0 0; +.block-editor-block-switcher__preview__popover { + display: none; + + // Position correctly. Needs specificity. + &.components-popover { + margin-left: $grid-unit-05; + margin-top: $grid-unit-15 - $border-width; + } + + @include break-medium() { + display: block; + } + + .components-popover__content { + box-shadow: none; + border: $border-width solid $dark-gray-primary; + background: $white; + border-radius: $radius-block-ui; + } + + .block-editor-block-switcher__preview { + width: 300px; + height: auto; + padding: $grid-unit-20; + } } .block-editor-block-switcher__preview-title { diff --git a/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap index c8463783bda49e..9cf29a6a7c411e 100644 --- a/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap @@ -26,31 +26,17 @@ exports[`BlockSwitcher should render disabled block switcher with multi block of `; exports[`BlockSwitcher should render enabled block switcher with multi block when transforms exist 1`] = ` - + + + + + `; exports[`BlockSwitcher should render switcher with blocks 1`] = ` - + + + + + `; diff --git a/packages/block-editor/src/components/block-switcher/test/index.js b/packages/block-editor/src/components/block-switcher/test/index.js index 824bf47d882d17..a5879995c271d2 100644 --- a/packages/block-editor/src/components/block-switcher/test/index.js +++ b/packages/block-editor/src/components/block-switcher/test/index.js @@ -1,14 +1,14 @@ /** * External dependencies */ -import { shallow } from 'enzyme'; +import { shallow, mount } from 'enzyme'; /** * WordPress dependencies */ import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; import { DOWN } from '@wordpress/keycodes'; -import { ToolbarButton } from '@wordpress/components'; +import { Button } from '@wordpress/components'; /** * Internal dependencies @@ -154,7 +154,7 @@ describe( 'BlockSwitcher', () => { const onTransformStub = jest.fn(); const getDropdown = () => { - const blockSwitcher = shallow( + const blockSwitcher = mount( { } ); test( 'should simulate a keydown event, which should call onToggle and open transform toggle.', () => { - const toggleClosed = shallow( + const toggleClosed = mount( getDropdown().props().renderToggle( { onToggle: onToggleStub, isOpen: false, } ) ); - const iconButtonClosed = toggleClosed.find( ToolbarButton ); + const iconButtonClosed = toggleClosed.find( Button ); iconButtonClosed.simulate( 'keydown', mockKeyDown ); @@ -195,13 +195,13 @@ describe( 'BlockSwitcher', () => { } ); test( 'should simulate a click event, which should call onToggle.', () => { - const toggleOpen = shallow( + const toggleOpen = mount( getDropdown().props().renderToggle( { onToggle: onToggleStub, isOpen: true, } ) ); - const iconButtonOpen = toggleOpen.find( ToolbarButton ); + const iconButtonOpen = toggleOpen.find( Button ); iconButtonOpen.simulate( 'keydown', mockKeyDown ); @@ -219,8 +219,10 @@ describe( 'BlockSwitcher', () => { .renderContent( { onClose: onCloseStub } ) } ); - const blockList = content.find( 'BlockTypesList' ); - expect( blockList.prop( 'items' ) ).toHaveLength( 1 ); + const blockList = content.find( 'BlockTransformationsMenu' ); + expect( + blockList.prop( 'possibleBlockTransformations' ) + ).toHaveLength( 1 ); } ); } ); } ); diff --git a/packages/dom/src/focusable.js b/packages/dom/src/focusable.js index 12688755cfe445..1022bf827d2923 100644 --- a/packages/dom/src/focusable.js +++ b/packages/dom/src/focusable.js @@ -47,6 +47,21 @@ function isVisible( element ) { ); } +/** + * Returns true if the specified element should be skipped from focusable elements. + * For now it rather specific for `iframes` and if tabindex attribute is set to -1. + * + * @param {Element} element DOM element to test. + * + * @return {boolean} Whether element should be skipped from focusable elements. + */ +function skipFocus( element ) { + return ( + element.nodeName.toLowerCase() === 'iframe' && + element.getAttribute( 'tabindex' ) === '-1' + ); +} + /** * Returns true if the specified area element is a valid focusable element, or * false otherwise. Area is only focusable if within a map where a named map @@ -77,7 +92,7 @@ export function find( context ) { const elements = context.querySelectorAll( SELECTOR ); return Array.from( elements ).filter( ( element ) => { - if ( ! isVisible( element ) ) { + if ( ! isVisible( element ) || skipFocus( element ) ) { return false; } diff --git a/packages/e2e-test-utils/src/get-available-block-transforms.js b/packages/e2e-test-utils/src/get-available-block-transforms.js index eeadabea18f360..6a029210dda117 100644 --- a/packages/e2e-test-utils/src/get-available-block-transforms.js +++ b/packages/e2e-test-utils/src/get-available-block-transforms.js @@ -22,5 +22,5 @@ export const getAvailableBlockTransforms = async () => { return button.textContent; } ); - }, '.block-editor-block-switcher__popover .block-editor-block-types-list .block-editor-block-types-list__list-item button' ); + }, '.block-editor-block-switcher__popover .block-editor-block-switcher__transforms__menugroup button' ); };