From 5494ff5e528fa7ebe2cb53bf33cf914de3405e60 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 17 Apr 2020 14:03:33 +0100 Subject: [PATCH] Move the Block Patterns UI to the inserter and redesign the inserter (#20951) --- docs/designers-developers/glossary.md | 4 +- lib/client-assets.php | 2 +- packages/base-styles/_variables.scss | 1 + .../downloadable-block-list-item/style.scss | 2 +- .../src/components/block-list/style.scss | 9 +- .../src/components/block-patterns/style.scss | 40 --- .../src/components/block-preview/auto.js | 1 - .../src/components/block-preview/style.scss | 15 + .../src/components/block-styles/style.scss | 4 +- packages/block-editor/src/components/index.js | 2 +- .../components/inserter-list-item/style.scss | 9 +- .../src/components/inserter/block-list.js | 306 +++++------------- .../index.js => inserter/block-patterns.js} | 47 +-- .../src/components/inserter/library.js | 47 +++ .../src/components/inserter/menu.js | 173 +++++++++- .../src/components/inserter/panel.js | 22 ++ .../src/components/inserter/preview-panel.js | 5 +- .../src/components/inserter/search-form.js | 12 +- .../src/components/inserter/style.scss | 205 +++++++----- .../components/inserter/test/block-list.js | 135 ++++---- .../use-async-list.js | 0 packages/block-editor/src/style.scss | 1 - packages/block-library/src/image/editor.scss | 4 - packages/components/src/animate/style.scss | 4 + packages/components/src/button/style.scss | 17 + packages/components/src/tab-panel/index.js | 1 - packages/components/src/tab-panel/style.scss | 41 ++- packages/components/src/toolbar/style.scss | 6 +- packages/e2e-test-utils/README.md | 9 +- .../src/get-all-block-inserter-item-titles.js | 2 +- .../src/get-available-block-transforms.js | 2 +- packages/e2e-test-utils/src/index.js | 6 +- packages/e2e-test-utils/src/insert-block.js | 9 +- .../src/open-all-block-inserter-categories.js | 12 - .../src/open-global-block-inserter.js | 28 +- .../e2e-test-utils/src/search-for-block.js | 3 + .../e2e-test-utils/src/transform-block-to.js | 5 +- .../specs/editor/blocks/columns.test.js | 4 +- .../specs/editor/blocks/image.test.js | 2 + .../specs/editor/blocks/table.test.js | 4 + .../specs/editor/plugins/block-icons.test.js | 6 +- .../inner-blocks-allowed-blocks.test.js | 12 +- .../inner-blocks-render-appender.test.js | 9 +- .../specs/editor/various/a11y.test.js | 2 +- .../editor/various/adding-blocks.test.js | 24 +- .../block-hierarchy-navigation.test.js | 3 + .../editor/various/block-switcher.test.js | 2 +- .../specs/editor/various/editor-modes.test.js | 2 +- .../various/keyboard-navigable-blocks.test.js | 5 +- .../specs/editor/various/rich-text.test.js | 2 + .../specs/editor/various/writing-flow.test.js | 8 +- .../components/header/header-toolbar/index.js | 35 +- .../header/header-toolbar/style.scss | 8 + .../edit-post/src/components/header/index.js | 7 +- .../src/components/header/style.scss | 24 -- .../edit-post/src/components/layout/index.js | 61 +++- .../src/components/layout/style.scss | 25 ++ .../src/components/visual-editor/style.scss | 6 - .../src/plugins/block-patterns/index.js | 42 --- packages/edit-post/src/plugins/index.js | 7 - packages/edit-post/src/store/reducer.js | 7 - .../editor/src/components/provider/index.js | 1 + .../components/interface-skeleton/index.js | 23 +- .../components/interface-skeleton/style.scss | 16 +- 64 files changed, 836 insertions(+), 702 deletions(-) delete mode 100644 packages/block-editor/src/components/block-patterns/style.scss rename packages/block-editor/src/components/{block-patterns/index.js => inserter/block-patterns.js} (53%) create mode 100644 packages/block-editor/src/components/inserter/library.js create mode 100644 packages/block-editor/src/components/inserter/panel.js rename packages/block-editor/src/components/{block-patterns => inserter}/use-async-list.js (100%) delete mode 100644 packages/e2e-test-utils/src/open-all-block-inserter-categories.js delete mode 100644 packages/edit-post/src/plugins/block-patterns/index.js diff --git a/docs/designers-developers/glossary.md b/docs/designers-developers/glossary.md index 97c512dbc26244..5a53c51bf2f5fb 100644 --- a/docs/designers-developers/glossary.md +++ b/docs/designers-developers/glossary.md @@ -19,8 +19,8 @@
Block name
A unique identifier for a block type, consisting of a plugin-specific namespace and a short label describing the block's intent. e.g. core/image
-
Block patterns
-
Block patterns are predefined layouts of blocks that can be inserted as starter content that are meant to be changed by the user every time. Once inserted, they exist as a local save and are not global.
+
Patterns
+
Patterns are predefined layouts of blocks that can be inserted as starter content that are meant to be changed by the user every time. Once inserted, they exist as a local save and are not global.
Block type
In contrast with the blocks composing a particular post, a block type describes the blueprint by which any block of that type should behave. So while there may be many images within a post, each behaves consistent with a unified image block type definition.
diff --git a/lib/client-assets.php b/lib/client-assets.php index 609627dc1e3ed0..32b92c7bdbde68 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -636,7 +636,7 @@ function gutenberg_load_block_pattern( $name ) { } /** - * Extends block editor settings to include a list of default block patterns. + * Extends block editor settings to include a list of default patterns. * * @param array $settings Default editor settings. * diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss index 1d33712cc44e65..8935761075ed9a 100644 --- a/packages/base-styles/_variables.scss +++ b/packages/base-styles/_variables.scss @@ -38,6 +38,7 @@ $grid-unit-60: 6 * $grid-unit; // 48px * Dimensions. */ +$icon-size: 24px; $button-size: 36px; $button-size-small: 24px; $panel-padding: 16px; diff --git a/packages/block-directory/src/components/downloadable-block-list-item/style.scss b/packages/block-directory/src/components/downloadable-block-list-item/style.scss index b32b58356c4fd4..84521724a1da0c 100644 --- a/packages/block-directory/src/components/downloadable-block-list-item/style.scss +++ b/packages/block-directory/src/components/downloadable-block-list-item/style.scss @@ -10,7 +10,7 @@ justify-content: center; background: transparent; word-break: break-word; - border-radius: $radius-round-rectangle; + border-radius: $radius-block-ui; border: $border-width solid $light-gray-500; transition: all 0.05s ease-in-out; @include reduce-motion("transition"); diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index c6a7caeee341b2..f7dc606b69cb15 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -1,3 +1,8 @@ +.block-editor-block-list__block { + margin-left: auto; + margin-right: auto; +} + .block-editor-block-list__layout .block-editor-block-list__block { // Needs specificity to override inherited styles. // While block is being dragged, dim the slot dragged from, and hide some UI. &.is-dragging { @@ -608,6 +613,8 @@ // The purpose of this padding is to ensure that on small viewports, there is // room for the block border that sits 14px ($block-padding) offset from the // block footprint. +// These paddings and margins are removed from the BlockPreview component's style +// Any change need to be reflected there. .block-editor-block-list__layout.is-root-container { padding-left: $block-padding; padding-right: $block-padding; @@ -616,9 +623,7 @@ padding-left: $block-side-ui-width; padding-right: $block-side-ui-width; } -} -.block-editor-block-list__layout.is-root-container { // Full-wide. (to account for the padddings added above) // The first two rules account for the alignment wrapper div for the image block. > div:not(.block-editor-block-list__block) > .block-editor-block-list__block[data-align="full"], diff --git a/packages/block-editor/src/components/block-patterns/style.scss b/packages/block-editor/src/components/block-patterns/style.scss deleted file mode 100644 index 3d668004212c98..00000000000000 --- a/packages/block-editor/src/components/block-patterns/style.scss +++ /dev/null @@ -1,40 +0,0 @@ -.block-editor-patterns { - background: $light-gray-200; - padding: $grid-unit-20; -} - -.block-editor-patterns__item { - background: $white; - border-radius: $radius-block-ui; - - &.is-placeholder .block-editor-patterns__item-preview { - min-height: 100px; - } -} - -.block-editor-patterns__item { - border-radius: $radius-block-ui; - cursor: pointer; - margin-bottom: $grid-unit-20; - border: 1px solid $light-gray-500; - transition: all 0.05s ease-in-out; - position: relative; - - &:hover { - background: $white; - box-shadow: 0 0 0 1px $white, 0 0 0 3px $dark-gray-500; - } - - &:focus { - box-shadow: inset 0 0 0 1px $white, 0 0 0 $border-width-focus $theme-color; - - // Windows High Contrast mode will show this outline, but not the box-shadow. - outline: 2px solid transparent; - } -} - -.block-editor-patterns__item-title { - text-align: center; - padding: 10px 0; - padding: $grid-unit-20; -} diff --git a/packages/block-editor/src/components/block-preview/auto.js b/packages/block-editor/src/components/block-preview/auto.js index d819680b24053e..b02b362390e46a 100644 --- a/packages/block-editor/src/components/block-preview/auto.js +++ b/packages/block-editor/src/components/block-preview/auto.js @@ -31,7 +31,6 @@ function AutoBlockPreview( { viewportWidth, __experimentalPadding } ) { aria-hidden style={ { height: contentHeight * scale + 2 * __experimentalPadding, - padding: __experimentalPadding, } } > { containerResizeListener } diff --git a/packages/block-editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss index c6ef74f6c28b60..1d0df3962fd741 100644 --- a/packages/block-editor/src/components/block-preview/style.scss +++ b/packages/block-editor/src/components/block-preview/style.scss @@ -38,4 +38,19 @@ .block-list-appender { display: none; } + + // Reset default editor padding + .block-editor-block-list__layout.is-root-container { + padding-left: 0; + padding-right: 0; + + > div:not(.block-editor-block-list__block) > .block-editor-block-list__block[data-align="full"], + > div:not(.block-editor-block-list__block) > .block-editor-block-list__block.alignfull, + > .block-editor-block-list__block[data-align="full"], + > .block-editor-block-list__block.alignfull { + margin-left: 0; + margin-right: 0; + } + } } + diff --git a/packages/block-editor/src/components/block-styles/style.scss b/packages/block-editor/src/components/block-styles/style.scss index 0be7aab8113c90..ab720c9495d992 100644 --- a/packages/block-editor/src/components/block-styles/style.scss +++ b/packages/block-editor/src/components/block-styles/style.scss @@ -10,7 +10,7 @@ flex-shrink: 0; cursor: pointer; overflow: hidden; - border-radius: $radius-round-rectangle; + border-radius: $radius-block-ui; padding: $grid-unit-05 * 1.5; display: flex; flex-direction: column; @@ -42,7 +42,7 @@ outline: $border-width solid transparent; // Shown in Windows High Contrast mode. padding: 0; border: $border-width solid rgba($dark-gray-primary, 0.2); - border-radius: $radius-round-rectangle; + border-radius: $radius-block-ui; display: flex; overflow: hidden; background: $white; diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index ae163f11df919f..7b2414c3daf02b 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -15,7 +15,6 @@ export { default as BlockFormatControls } from './block-format-controls'; export { default as BlockIcon } from './block-icon'; export { default as BlockNavigationDropdown } from './block-navigation/dropdown'; export { default as __experimentalBlockNavigationList } from './block-navigation/list'; -export { default as __experimentalBlockPatterns } from './block-patterns'; export { default as __experimentalBlockVariationPicker } from './block-variation-picker'; export { default as BlockVerticalAlignmentToolbar } from './block-vertical-alignment-toolbar'; export { default as ButtonBlockerAppender } from './button-block-appender'; @@ -74,6 +73,7 @@ export { default as CopyHandler } from './copy-handler'; export { default as DefaultBlockAppender } from './default-block-appender'; export { default as __unstableEditorStyles } from './editor-styles'; export { default as Inserter } from './inserter'; +export { default as __experimentalLibrary } from './inserter/library'; export { default as BlockEditorKeyboardShortcuts } from './keyboard-shortcuts'; export { default as MultiSelectScrollIntoView } from './multi-select-scroll-into-view'; export { default as NavigableToolbar } from './navigable-toolbar'; diff --git a/packages/block-editor/src/components/inserter-list-item/style.scss b/packages/block-editor/src/components/inserter-list-item/style.scss index 00c9eae4ed38fe..745caa0ed1ff7a 100644 --- a/packages/block-editor/src/components/inserter-list-item/style.scss +++ b/packages/block-editor/src/components/inserter-list-item/style.scss @@ -2,7 +2,7 @@ display: block; width: 33.33%; padding: 0; - margin: 0 0 12px; + margin: 0; } .components-button.block-editor-block-types-list__item { @@ -11,13 +11,13 @@ width: 100%; font-size: $default-font-size; color: $dark-gray-700; - padding: 0 4px; + padding: $grid-unit-10; align-items: stretch; justify-content: center; cursor: pointer; background: transparent; word-break: break-word; - border-radius: $radius-round-rectangle; + border-radius: $radius-block-ui; border: $border-width solid transparent; transition: all 0.05s ease-in-out; @include reduce-motion("transition"); @@ -48,7 +48,7 @@ .block-editor-block-types-list__item-icon { padding: 12px 20px; - border-radius: $radius-round-rectangle; + border-radius: $radius-block-ui; color: $dark-gray-primary; transition: all 0.05s ease-in-out; @include reduce-motion("transition"); @@ -66,4 +66,5 @@ .block-editor-block-types-list__item-title { padding: 4px 2px 8px; + font-size: 12px; } diff --git a/packages/block-editor/src/components/inserter/block-list.js b/packages/block-editor/src/components/inserter/block-list.js index acb1a68c55ee2a..9bffeef1adfca5 100644 --- a/packages/block-editor/src/components/inserter/block-list.js +++ b/packages/block-editor/src/components/inserter/block-list.js @@ -3,7 +3,6 @@ */ import { map, - pick, includes, filter, findIndex, @@ -11,22 +10,20 @@ import { sortBy, groupBy, isEmpty, - without, } from 'lodash'; -import scrollIntoView from 'dom-scroll-into-view'; /** * WordPress dependencies */ import { __, _x, _n, sprintf } from '@wordpress/i18n'; -import { PanelBody, withSpokenMessages } from '@wordpress/components'; +import { withSpokenMessages } from '@wordpress/components'; import { addQueryArgs } from '@wordpress/url'; import { controlsRepeat } from '@wordpress/icons'; import { speak } from '@wordpress/a11y'; -import { createBlock, isUnmodifiedDefaultBlock } from '@wordpress/blocks'; -import { useMemo, useEffect, useState, useRef } from '@wordpress/element'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { compose, withSafeTimeout } from '@wordpress/compose'; +import { createBlock } from '@wordpress/blocks'; +import { useMemo, useEffect } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; /** * Internal dependencies @@ -35,6 +32,7 @@ import BlockTypesList from '../block-types-list'; import ChildBlocks from './child-blocks'; import __experimentalInserterMenuExtension from '../inserter-menu-extension'; import { searchItems } from './search-items'; +import InserterPanel from './panel'; // Copied over from the Columns block. It seems like it should become part of public API. const createBlocksFromInnerBlocksTemplate = ( innerBlocksTemplate ) => { @@ -54,76 +52,42 @@ const getBlockNamespace = ( item ) => item.name.split( '/' )[ 0 ]; const MAX_SUGGESTED_ITEMS = 9; function InserterBlockList( { - clientId, - isAppender, rootClientId, - onSelect, + onInsert, onHover, __experimentalSelectBlockOnInsert: selectBlockOnInsert, filterValue, debouncedSpeak, - setTimeout: safeSetTimeout, } ) { const { categories, collections, items, rootChildBlocks, - getSelectedBlock, - destinationRootClientId, - getBlockIndex, - getBlockSelectionEnd, - getBlockOrder, fetchReusableBlocks, } = useSelect( ( select ) => { - const { - getInserterItems, - getBlockName, - getBlockRootClientId, - getBlockSelectionEnd: _getBlockSelectionEnd, - getSettings, - } = select( 'core/block-editor' ); + const { getInserterItems, getBlockName, getSettings } = select( + 'core/block-editor' + ); const { getCategories, getCollections, getChildBlockNames, } = select( 'core/blocks' ); - - let destRootClientId = rootClientId; - if ( ! destRootClientId && ! clientId && ! isAppender ) { - const end = _getBlockSelectionEnd(); - if ( end ) { - destRootClientId = getBlockRootClientId( end ) || undefined; - } - } - const destinationRootBlockName = getBlockName( destRootClientId ); - + const rootBlockName = getBlockName( rootClientId ); const { __experimentalFetchReusableBlocks } = getSettings(); return { categories: getCategories(), collections: getCollections(), - rootChildBlocks: getChildBlockNames( destinationRootBlockName ), - items: getInserterItems( destRootClientId ), - destinationRootClientId: destRootClientId, + rootChildBlocks: getChildBlockNames( rootBlockName ), + items: getInserterItems( rootClientId ), fetchReusableBlocks: __experimentalFetchReusableBlocks, - ...pick( select( 'core/block-editor' ), [ - 'getSelectedBlock', - 'getBlockIndex', - 'getBlockSelectionEnd', - 'getBlockOrder', - ] ), }; }, - [ clientId, isAppender, rootClientId ] + [ rootClientId ] ); - const { - replaceBlocks, - insertBlock, - showInsertionPoint, - hideInsertionPoint, - } = useDispatch( 'core/block-editor' ); // Fetch resuable blocks on mount useEffect( () => { @@ -132,68 +96,21 @@ function InserterBlockList( { } }, [] ); - // To avoid duplication, getInsertionIndex is extracted and used in two event handlers - // This breaks the withDispatch not containing any logic rule. - // Since it's a function only called when the event handlers are called, - // it's fine to extract it. - // eslint-disable-next-line no-restricted-syntax - function getInsertionIndex() { - // If the clientId is defined, we insert at the position of the block. - if ( clientId ) { - return getBlockIndex( clientId, destinationRootClientId ); - } - - // If there a selected block, we insert after the selected block. - const end = getBlockSelectionEnd(); - if ( ! isAppender && end ) { - return getBlockIndex( end, destinationRootClientId ) + 1; - } - - // Otherwise, we insert at the end of the current rootClientId - return getBlockOrder( destinationRootClientId ).length; - } - - const onHoverItem = ( item ) => { - onHover( item ); - if ( item ) { - const index = getInsertionIndex(); - showInsertionPoint( destinationRootClientId, index ); - } else { - hideInsertionPoint(); - } - }; - const onSelectItem = ( item ) => { const { name, title, initialAttributes, innerBlocks } = item; - const selectedBlock = getSelectedBlock(); const insertedBlock = createBlock( name, initialAttributes, createBlocksFromInnerBlocksTemplate( innerBlocks ) ); - if ( - ! isAppender && - selectedBlock && - isUnmodifiedDefaultBlock( selectedBlock ) - ) { - replaceBlocks( selectedBlock.clientId, insertedBlock ); - } else { - insertBlock( - insertedBlock, - getInsertionIndex(), - destinationRootClientId, - selectBlockOnInsert - ); - if ( ! selectBlockOnInsert ) { - // translators: %s: the name of the block that has been added - const message = sprintf( __( '%s block added' ), title ); - speak( message ); - } - } + onInsert( insertedBlock ); - onSelect(); - return insertedBlock; + if ( ! selectBlockOnInsert ) { + // translators: %s: the name of the block that has been added + const message = sprintf( __( '%s block added' ), title ); + speak( message ); + } }; const filteredItems = useMemo( () => { @@ -248,61 +165,6 @@ function InserterBlockList( { return result; }, [ filteredItems, collections ] ); - const inserterResults = useRef(); - const panels = useRef( [] ); - const bindPanel = ( name ) => ( ref ) => { - panels.current[ name ] = ref; - }; - const [ openPanels, setOpenPanels ] = useState( [ 'suggested' ] ); - - const onTogglePanel = ( panel ) => { - return () => { - const isOpened = openPanels.indexOf( panel ) !== -1; - if ( isOpened ) { - setOpenPanels( without( openPanels, panel ) ); - } else { - setOpenPanels( [ ...openPanels, panel ] ); - - safeSetTimeout( () => { - // We need a generic way to access the panel's container - scrollIntoView( - panels.current[ panel ], - inserterResults.current, - { - alignWithTop: true, - } - ); - } ); - } - }; - }; - - // Update the open panels on search - useEffect( () => { - if ( ! filterValue ) { - setOpenPanels( [ 'suggested' ] ); - return; - } - let newOpenPanels = []; - if ( reusableItems.length > 0 ) { - newOpenPanels.push( 'reusable' ); - } - if ( filteredItems.length > 0 ) { - newOpenPanels = newOpenPanels.concat( - Object.keys( itemsPerCategory ), - Object.keys( itemsPerCollection ) - ); - } - - setOpenPanels( newOpenPanels ); - }, [ - filterValue, - filteredItems, - reusableItems, - itemsPerCategory, - itemsPerCollection, - ] ); - // Announce search results on change useEffect( () => { const resultCount = Object.keys( itemsPerCategory ).reduce( @@ -322,100 +184,81 @@ function InserterBlockList( { debouncedSpeak( resultsFoundMessage ); }, [ itemsPerCategory, debouncedSpeak ] ); - const isPanelOpen = ( panel ) => openPanels.indexOf( panel ) !== -1; - const hasItems = ! isEmpty( filteredItems ); + const hasChildItems = childItems.length > 0; return ( -
+
- { !! suggestedItems.length && ! filterValue && ( - + { ! hasChildItems && !! suggestedItems.length && ! filterValue && ( + - + ) } - { map( categories, ( category ) => { - const categoryItems = itemsPerCategory[ category.slug ]; - if ( ! categoryItems || ! categoryItems.length ) { - return null; - } - return ( - - - - ); - } ) } + { ! hasChildItems && + map( categories, ( category ) => { + const categoryItems = itemsPerCategory[ category.slug ]; + if ( ! categoryItems || ! categoryItems.length ) { + return null; + } + return ( + + + + ); + } ) } - { map( collections, ( collection, namespace ) => { - const collectionItems = itemsPerCollection[ namespace ]; - if ( ! collectionItems || ! collectionItems.length ) { - return null; - } + { ! hasChildItems && + map( collections, ( collection, namespace ) => { + const collectionItems = itemsPerCollection[ namespace ]; + if ( ! collectionItems || ! collectionItems.length ) { + return null; + } - return ( - - - - ); - } ) } + return ( + + + + ); + } ) } - { !! reusableItems.length && ( - { __( 'Manage all reusable blocks' ) } - + ) } <__experimentalInserterMenuExtension.Slot fillProps={ { onSelect: onSelectItem, - onHover: onHoverItem, + onHover, filterValue, hasItems, } } @@ -454,7 +297,4 @@ function InserterBlockList( { ); } -export default compose( - withSpokenMessages, - withSafeTimeout -)( InserterBlockList ); +export default compose( withSpokenMessages )( InserterBlockList ); diff --git a/packages/block-editor/src/components/block-patterns/index.js b/packages/block-editor/src/components/inserter/block-patterns.js similarity index 53% rename from packages/block-editor/src/components/block-patterns/index.js rename to packages/block-editor/src/components/inserter/block-patterns.js index 729065ff06d04f..56288df4be228a 100644 --- a/packages/block-editor/src/components/block-patterns/index.js +++ b/packages/block-editor/src/components/inserter/block-patterns.js @@ -8,23 +8,24 @@ import { map } from 'lodash'; */ import { useMemo, useCallback } from '@wordpress/element'; import { parse, cloneBlock } from '@wordpress/blocks'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useDispatch } from '@wordpress/data'; import { ENTER, SPACE } from '@wordpress/keycodes'; -import { __, sprintf } from '@wordpress/i18n'; +import { __, sprintf, _x } from '@wordpress/i18n'; /** * Internal dependencies */ import BlockPreview from '../block-preview'; import useAsyncList from './use-async-list'; +import InserterPanel from './panel'; function BlockPattern( { pattern, onClick } ) { - const { title, content } = pattern; + const { content } = pattern; const blocks = useMemo( () => parse( content ), [ content ] ); return (
onClick( pattern, blocks ) } onKeyDown={ ( event ) => { @@ -33,41 +34,24 @@ function BlockPattern( { pattern, onClick } ) { } } } tabIndex={ 0 } + aria-label={ pattern.title } > -
- -
-
{ title }
+
); } -function BlockPatternPlaceholder( { pattern } ) { - const { title } = pattern; - +function BlockPatternPlaceholder() { return ( -
-
-
{ title }
-
+
); } -function BlockPatterns( { patterns } ) { +function BlockPatterns( { patterns, onInsert } ) { const currentShownPatterns = useAsyncList( patterns ); - const getBlockInsertionPoint = useSelect( ( select ) => { - return select( 'core/block-editor' ).getBlockInsertionPoint; - } ); - const { insertBlocks } = useDispatch( 'core/block-editor' ); const { createSuccessNotice } = useDispatch( 'core/notices' ); const onClickPattern = useCallback( ( pattern, blocks ) => { - const { index, rootClientId } = getBlockInsertionPoint(); - insertBlocks( - map( blocks, ( block ) => cloneBlock( block ) ), - index, - rootClientId, - false - ); + onInsert( map( blocks, ( block ) => cloneBlock( block ) ) ); createSuccessNotice( sprintf( /* translators: %s: block pattern title. */ @@ -81,7 +65,7 @@ function BlockPatterns( { patterns } ) { }, [] ); return ( -
+ { patterns.map( ( pattern, index ) => currentShownPatterns[ index ] === pattern ? ( ) : ( - + ) ) } -
+ ); } diff --git a/packages/block-editor/src/components/inserter/library.js b/packages/block-editor/src/components/inserter/library.js new file mode 100644 index 00000000000000..a5e7094f54d4a7 --- /dev/null +++ b/packages/block-editor/src/components/inserter/library.js @@ -0,0 +1,47 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import InserterMenu from './menu'; + +function InserterLibrary( { + rootClientId, + clientId, + isAppender, + showInserterHelpPanel, + __experimentalSelectBlockOnInsert: selectBlockOnInsert, + onSelect = noop, +} ) { + const { destinationRootClientId } = useSelect( ( select ) => { + const { getBlockRootClientId } = select( 'core/block-editor' ); + + rootClientId = + rootClientId || getBlockRootClientId( clientId ) || undefined; + + return { + rootClientId, + }; + } ); + + return ( + + ); +} + +export default InserterLibrary; diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 5698b278e211dd..6e446717f37517 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -1,13 +1,17 @@ /** * External dependencies */ -import { includes } from 'lodash'; +import { includes, pick } from 'lodash'; /** * WordPress dependencies */ import { useState } from '@wordpress/element'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; +import { TabPanel } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { isUnmodifiedDefaultBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -16,6 +20,7 @@ import Tips from './tips'; import InserterSearchForm from './search-form'; import InserterPreviewPanel from './preview-panel'; import InserterBlockList from './block-list'; +import BlockPatterns from './block-patterns'; const stopKeyPropagation = ( event ) => event.stopPropagation(); @@ -29,6 +34,49 @@ function InserterMenu( { } ) { const [ filterValue, setFilterValue ] = useState( '' ); const [ hoveredItem, setHoveredItem ] = useState( null ); + const { + destinationRootClientId, + patterns, + getSelectedBlock, + getBlockIndex, + getBlockSelectionEnd, + getBlockOrder, + } = useSelect( ( select ) => { + const { + getSettings, + getBlockRootClientId, + getBlockSelectionEnd: _getBlockSelectionEnd, + } = select( 'core/block-editor' ); + + let destRootClientId = rootClientId; + if ( ! destRootClientId && ! clientId && ! isAppender ) { + const end = _getBlockSelectionEnd(); + if ( end ) { + destRootClientId = getBlockRootClientId( end ) || undefined; + } + } + return { + patterns: getSettings().__experimentalBlockPatterns, + destinationRootClientId: destRootClientId, + ...pick( select( 'core/block-editor' ), [ + 'getSelectedBlock', + 'getBlockIndex', + 'getBlockSelectionEnd', + 'getBlockOrder', + ] ), + }; + }, [] ); + const { + replaceBlocks, + insertBlocks, + showInsertionPoint, + hideInsertionPoint, + } = useDispatch( 'core/block-editor' ); + const hasPatterns = + ! destinationRootClientId && + !! patterns && + !! patterns.length && + ! filterValue; const onKeyDown = ( event ) => { if ( includes( @@ -41,6 +89,86 @@ function InserterMenu( { } }; + // To avoid duplication, getInsertionIndex is extracted and used in two event handlers + // This breaks the withDispatch not containing any logic rule. + // Since it's a function only called when the event handlers are called, + // it's fine to extract it. + // eslint-disable-next-line no-restricted-syntax + function getInsertionIndex() { + // If the clientId is defined, we insert at the position of the block. + if ( clientId ) { + return getBlockIndex( clientId, destinationRootClientId ); + } + + // If there a selected block, we insert after the selected block. + const end = getBlockSelectionEnd(); + if ( ! isAppender && end ) { + return getBlockIndex( end, destinationRootClientId ) + 1; + } + + // Otherwise, we insert at the end of the current rootClientId + return getBlockOrder( destinationRootClientId ).length; + } + + const onInsertBlocks = ( blocks ) => { + const selectedBlock = getSelectedBlock(); + if ( + ! isAppender && + selectedBlock && + isUnmodifiedDefaultBlock( selectedBlock ) + ) { + replaceBlocks( selectedBlock.clientId, blocks ); + } else { + insertBlocks( + blocks, + getInsertionIndex(), + destinationRootClientId, + __experimentalSelectBlockOnInsert + ); + } + + onSelect(); + }; + + const onHover = ( item ) => { + setHoveredItem( item ); + if ( item ) { + const index = getInsertionIndex(); + showInsertionPoint( destinationRootClientId, index ); + } else { + hideInsertionPoint(); + } + }; + + const blocksTab = ( + <> +
+
+ +
+
+ { showInserterHelpPanel && ( +
+ +
+ ) } + + ); + + const patternsTab = ( +
+ +
+ ); + // Disable reason (no-autofocus): The inserter menu is a modal display, not one which // is always visible, and one which already incurs this behavior of autoFocus via // Popover's focusOnMount. @@ -55,25 +183,36 @@ function InserterMenu( { >
- - { showInserterHelpPanel && ( -
- -
+ { hasPatterns && ( + + { ( tab ) => { + if ( tab.name === 'blocks' ) { + return blocksTab; + } + return patternsTab; + } } + ) } + { ! hasPatterns && blocksTab }
{ showInserterHelpPanel && hoveredItem && ( - +
+ +
) }
); diff --git a/packages/block-editor/src/components/inserter/panel.js b/packages/block-editor/src/components/inserter/panel.js new file mode 100644 index 00000000000000..aa53395d55dd65 --- /dev/null +++ b/packages/block-editor/src/components/inserter/panel.js @@ -0,0 +1,22 @@ +/** + * WordPress dependencies + */ +import { Icon } from '@wordpress/components'; + +function InserterPanel( { title, icon, children } ) { + return ( + <> +
+ + { title } + + +
+
+ { children } +
+ + ); +} + +export default InserterPanel; diff --git a/packages/block-editor/src/components/inserter/preview-panel.js b/packages/block-editor/src/components/inserter/preview-panel.js index 06f989a2a9bb06..763edbbd506377 100644 --- a/packages/block-editor/src/components/inserter/preview-panel.js +++ b/packages/block-editor/src/components/inserter/preview-panel.js @@ -18,12 +18,12 @@ import BlockPreview from '../block-preview'; function InserterPreviewPanel( { item } ) { const hoveredItemBlockType = getBlockType( item.name ); return ( -
- { ! isReusableBlock( item ) && } +
{ isReusableBlock( item ) || hoveredItemBlockType.example ? (
) }
+ { ! isReusableBlock( item ) && }
); } diff --git a/packages/block-editor/src/components/inserter/search-form.js b/packages/block-editor/src/components/inserter/search-form.js index ca326e003d7090..6c1f140a98ac7f 100644 --- a/packages/block-editor/src/components/inserter/search-form.js +++ b/packages/block-editor/src/components/inserter/search-form.js @@ -4,6 +4,7 @@ import { useInstanceId } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { VisuallyHidden } from '@wordpress/components'; +import { Icon, search } from '@wordpress/icons'; function InserterSearchForm( { onChange } ) { const instanceId = useInstanceId( InserterSearchForm ); @@ -13,7 +14,7 @@ function InserterSearchForm( { onChange } ) { // Popover's focusOnMount. /* eslint-disable jsx-a11y/no-autofocus */ return ( - <> +
onChange( event.target.value ) } + autoComplete="off" /> - + +
); /* eslint-enable jsx-a11y/no-autofocus */ } diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 7adfb07c597f64..000f3c2cf99bd4 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -1,6 +1,6 @@ $block-inserter-preview-height: 350px; +$block-inserter-width: 350px; $block-inserter-tabs-height: 44px; -$block-inserter-search-height: 38px; .block-editor-inserter { display: inline-block; @@ -37,6 +37,8 @@ $block-inserter-search-height: 38px; .block-editor-inserter__menu { height: 100%; + position: relative; + overflow: visible; } .block-editor-inserter__main-area { @@ -45,8 +47,7 @@ $block-inserter-search-height: 38px; flex-direction: column; height: 100%; @include break-medium { - width: 400px; - position: relative; + width: $block-inserter-width; } } @@ -62,43 +63,98 @@ $block-inserter-search-height: 38px; z-index: z-index(".components-popover.block-editor-inserter__popover"); } -.components-popover input[type="search"].block-editor-inserter__search { - display: block; - margin: $grid-unit-20; - padding: 11px $grid-unit-20; - position: relative; +.block-editor-inserter__search { + padding: $grid-unit-20; z-index: 1; - border-radius: $radius-round-rectangle; flex-shrink: 0; + position: relative; - /* Fonts smaller than 16px causes mobile safari to zoom. */ - font-size: $mobile-text-min-font-size; - @include break-small { - font-size: $default-font-size; - } + input[type="search"].block-editor-inserter__search-input { + display: block; + padding: $grid-unit-20 $grid-unit-60 $grid-unit-20 $grid-unit-20; + border-radius: $radius-block-ui; + background: $light-gray-200; + border: none; + width: 100%; + height: $grid-unit-60; + + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: $mobile-text-min-font-size; + @include break-small { + font-size: $default-font-size; + } - &:focus { - @include input-style__focus(); + &:focus { + background: $white; + box-shadow: 0 0 0 $border-width-focus $theme-color; + } } } -.block-editor-inserter__results { +.block-editor-inserter__search-icon { + position: absolute; + top: $grid-unit-20 + ($grid-unit-60 - $icon-size) / 2; + right: $grid-unit-20 + ($grid-unit-60 - $icon-size) / 2; +} + +.block-editor-inserter__tabs { + display: flex; flex-grow: 1; - overflow: auto; - position: relative; - z-index: 1; // Necessary for the stacked card below parent blocks to show up. - padding: 0 $grid-unit-20 $grid-unit-20 $grid-unit-20; + flex-direction: column; + margin-top: -$grid-unit-10; - &:focus { - outline: $border-width dotted $dark-gray-500; + .components-tab-panel__tabs { + border-bottom: $border-width solid $light-gray-500; + + .components-tab-panel__tabs-item { + flex-grow: 1; + margin-bottom: -$border-width; + } } - // Don't show the top border on the first panel, let the Search border be the border. - [role="presentation"] + .components-panel__body { - border-top: none; + .components-tab-panel__tab-content { + display: flex; + flex-grow: 1; + flex-direction: column; + position: relative; } } +.block-editor-inserter__panel-header { + display: inline-flex; + align-items: center; + padding: $grid-unit-20 $grid-unit-20 0; +} + +.block-editor-inserter__panel-content { + padding: 0 $grid-unit-20; +} + +.block-editor-inserter__panel-title { + color: $theme-color; + text-transform: uppercase; + font-size: 11px; + font-weight: 500; + margin-right: $grid-unit-10; +} + +.block-editor-inserter__block-list { + flex-grow: 1; + position: relative; +} + +// This extra div is needed because +// flex grow and overflow auto doesn't work well together. +.block-editor-inserter__scrollable { + overflow: auto; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + overflow-y: scroll; +} + .block-editor-inserter__popover .block-editor-block-types-list { margin: 0 -8px; } @@ -109,7 +165,8 @@ $block-inserter-search-height: 38px; } .block-editor-inserter__manage-reusable-blocks { - margin: $grid-unit-20 0 0 $grid-unit-20; + display: inline-block; + margin: $grid-unit-20; } .block-editor-inserter__no-results { @@ -135,95 +192,73 @@ $block-inserter-search-height: 38px; } } -.block-editor-inserter__menu-help-panel { +.block-editor-inserter__preview-container { display: none; - border: $border-width solid $light-gray-secondary; width: 300px; - margin-right: 20px; - padding: 20px; background: $white; - box-shadow: $shadow-popover; border-radius: $radius-block-ui; + border: $border-width solid $light-gray-500; + position: absolute; + top: $grid-unit-20; + left: calc(100% + #{$grid-unit-20}); @include break-medium { - position: absolute; - top: -$border-width; - left: calc(100% + #{$grid-unit-15}); display: block; } .block-editor-block-card { - padding-bottom: 20px; - margin-bottom: 20px; - border-bottom: $border-width solid $light-gray-500; - @include edit-post__fade-in-animation(); + padding: $grid-unit-20; } -} -.block-editor-inserter__menu-help-panel-no-block { - display: flex; - height: 100%; - flex-direction: column; - @include edit-post__fade-in-animation(); - - .block-editor-inserter__menu-help-panel-no-block-text { - flex-grow: 1; - - h4 { - font-size: $big-font-size; - } - } - - .components-notice { - margin: 0; - } - - h4 { - margin-top: 0; + .block-editor-block-card__title { + font-size: $default-font-size; } } -.block-editor-inserter__menu-help-panel-hover-area { - flex-grow: 1; - margin-top: 20px; - padding: 20px; - border: 1px dotted $light-gray-500; - display: flex; - align-items: center; - text-align: center; -} - -.block-editor-inserter__menu-help-panel-title { - font-size: $big-font-size; - font-weight: 600; - margin-bottom: 20px; -} - .block-editor-inserter__preview-content { - border: $border-width solid $light-gray-500; - border-radius: $radius-round-rectangle; min-height: $grid-unit-60 * 3; + background: $light-gray-200; display: grid; flex-grow: 1; align-items: center; - - .block-editor-block-preview__container { - padding: 10px; - } } .block-editor-inserter__preview-content-missing { flex: 1; display: flex; justify-content: center; - color: $dark-gray-400; - border: $border-width solid $light-gray-500; - border-radius: $radius-round-rectangle; align-items: center; min-height: $grid-unit-60 * 3; + color: $dark-gray-400; + background: $light-gray-200; } .block-editor-inserter__tips { + border-top: $border-width solid $light-gray-500; padding: $grid-unit-20; flex-shrink: 0; } + +.block-editor-inserter__patterns-item { + border-radius: $radius-block-ui; + cursor: pointer; + margin-top: $grid-unit-20; + transition: all 0.05s ease-in-out; + position: relative; + border: $border-width solid transparent; + + &:hover { + border: $border-width solid $theme-color; + } + + &:focus { + box-shadow: inset 0 0 0 1px $white, 0 0 0 $border-width-focus $theme-color; + + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; + } + + &.is-placeholder { + min-height: 100px; + } +} diff --git a/packages/block-editor/src/components/inserter/test/block-list.js b/packages/block-editor/src/components/inserter/test/block-list.js index 54e35f17462a51..65b70a70902100 100644 --- a/packages/block-editor/src/components/inserter/test/block-list.js +++ b/packages/block-editor/src/components/inserter/test/block-list.js @@ -65,23 +65,6 @@ const assertNoResultsMessageNotToBePresent = ( element ) => { expect( noResultsMessage ).toBe( null ); }; -const assertOpenedPanels = ( element, expectedOpen = 0 ) => { - expect( - element.querySelectorAll( '.components-panel__body.is-opened ' ) - ).toHaveLength( expectedOpen ); -}; - -const getTabButtonWithContent = ( element, content ) => { - let foundButton; - const buttons = element.querySelectorAll( '.components-button' ); - buttons.forEach( ( button ) => { - if ( button.textContent === content ) { - foundButton = button; - } - } ); - return foundButton; -}; - describe( 'InserterMenu', () => { beforeEach( () => { useSelect.mockImplementation( () => ( { @@ -91,14 +74,6 @@ describe( 'InserterMenu', () => { } ) ); } ); - it( 'should show the suggested tab by default', () => { - const element = initializeMenuDefaultStateAndReturnElement(); - const activeCategory = element.querySelector( - '.components-panel__body.is-opened > .components-panel__body-title' - ); - expect( activeCategory.textContent ).toBe( 'Most used' ); - } ); - it( 'should show nothing if there are no items', () => { const noItems = []; useSelect.mockImplementation( () => ( { @@ -118,7 +93,10 @@ describe( 'InserterMenu', () => { it( 'should show only high utility items in the suggested tab', () => { const element = initializeMenuDefaultStateAndReturnElement(); - const visibleBlocks = element.querySelectorAll( + const firstPanel = element.querySelector( + '.block-editor-inserter__panel-content' + ); + const visibleBlocks = firstPanel.querySelectorAll( '.block-editor-block-types-list__item-title' ); expect( visibleBlocks ).toHaveLength( 3 ); @@ -129,71 +107,70 @@ describe( 'InserterMenu', () => { it( 'should show items from the embed category in the embed tab', () => { const element = initializeAllClosedMenuStateAndReturnElement(); - const embedTab = getTabButtonWithContent( element, 'Embeds' ); - - TestUtils.Simulate.click( embedTab ); - - assertOpenedPanels( element, 1 ); - - const visibleBlocks = element.querySelectorAll( + const embedTabContent = element.querySelectorAll( + '.block-editor-inserter__panel-content' + )[ 4 ]; + const embedTabTitle = element.querySelectorAll( + '.block-editor-inserter__panel-title' + )[ 4 ]; + const blocks = embedTabContent.querySelectorAll( '.block-editor-block-types-list__item-title' ); - expect( visibleBlocks ).toHaveLength( 2 ); - expect( visibleBlocks[ 0 ].textContent ).toBe( 'YouTube' ); - expect( visibleBlocks[ 1 ].textContent ).toBe( 'A Text Embed' ); + expect( embedTabTitle.textContent ).toBe( 'Embeds' ); + expect( blocks ).toHaveLength( 2 ); + expect( blocks[ 0 ].textContent ).toBe( 'YouTube' ); + expect( blocks[ 1 ].textContent ).toBe( 'A Text Embed' ); assertNoResultsMessageNotToBePresent( element ); } ); it( 'should show reusable items in the reusable tab', () => { const element = initializeAllClosedMenuStateAndReturnElement(); - const reusableTab = getTabButtonWithContent( element, 'Reusable' ); - - TestUtils.Simulate.click( reusableTab ); - - assertOpenedPanels( element, 1 ); - - const visibleBlocks = element.querySelectorAll( + const reusableTabContent = element.querySelectorAll( + '.block-editor-inserter__panel-content' + )[ 6 ]; + const reusableTabTitle = element.querySelectorAll( + '.block-editor-inserter__panel-title' + )[ 6 ]; + const blocks = reusableTabContent.querySelectorAll( '.block-editor-block-types-list__item-title' ); - expect( visibleBlocks ).toHaveLength( 1 ); - expect( visibleBlocks[ 0 ].textContent ).toBe( 'My reusable block' ); + expect( reusableTabTitle.textContent ).toBe( 'Reusable' ); + expect( blocks ).toHaveLength( 1 ); + expect( blocks[ 0 ].textContent ).toBe( 'My reusable block' ); assertNoResultsMessageNotToBePresent( element ); } ); it( 'should show the common category blocks', () => { const element = initializeAllClosedMenuStateAndReturnElement(); - const commonBlocksTab = getTabButtonWithContent( - element, - 'Common blocks' - ); - - TestUtils.Simulate.click( commonBlocksTab ); - - assertOpenedPanels( element, 1 ); - - const visibleBlocks = element.querySelectorAll( + const commonTabContent = element.querySelectorAll( + '.block-editor-inserter__panel-content' + )[ 1 ]; + const commonTabTitle = element.querySelectorAll( + '.block-editor-inserter__panel-title' + )[ 1 ]; + const blocks = commonTabContent.querySelectorAll( '.block-editor-block-types-list__item-title' ); - expect( visibleBlocks ).toHaveLength( 3 ); - expect( visibleBlocks[ 0 ].textContent ).toBe( 'Text' ); - expect( visibleBlocks[ 1 ].textContent ).toBe( 'Advanced Text' ); - expect( visibleBlocks[ 2 ].textContent ).toBe( 'Some Other Block' ); + expect( commonTabTitle.textContent ).toBe( 'Common blocks' ); + expect( blocks ).toHaveLength( 3 ); + expect( blocks[ 0 ].textContent ).toBe( 'Text' ); + expect( blocks[ 1 ].textContent ).toBe( 'Advanced Text' ); + expect( blocks[ 2 ].textContent ).toBe( 'Some Other Block' ); assertNoResultsMessageNotToBePresent( element ); } ); it( 'should disable items with `isDisabled`', () => { - const element = initializeMenuDefaultStateAndReturnElement(); - const layoutTab = getTabButtonWithContent( element, 'Layout elements' ); - - TestUtils.Simulate.click( layoutTab ); - - const disabledBlocks = element.querySelectorAll( + const element = initializeAllClosedMenuStateAndReturnElement(); + const layoutTabContent = element.querySelectorAll( + '.block-editor-inserter__panel-content' + )[ 2 ]; + const disabledBlocks = layoutTabContent.querySelectorAll( '.block-editor-block-types-list__item[disabled], .block-editor-block-types-list__item[aria-disabled="true"]' ); @@ -206,24 +183,22 @@ describe( 'InserterMenu', () => { filterValue: 'text', } ); - assertOpenedPanels( element, 3 ); - const matchingCategories = element.querySelectorAll( - '.components-panel__body-toggle' + '.block-editor-inserter__panel-title' ); expect( matchingCategories ).toHaveLength( 3 ); expect( matchingCategories[ 0 ].textContent ).toBe( 'Common blocks' ); expect( matchingCategories[ 1 ].textContent ).toBe( 'Embeds' ); - const visibleBlocks = element.querySelectorAll( + const blocks = element.querySelectorAll( '.block-editor-block-types-list__item-title' ); - expect( visibleBlocks ).toHaveLength( 5 ); - expect( visibleBlocks[ 0 ].textContent ).toBe( 'Text' ); - expect( visibleBlocks[ 1 ].textContent ).toBe( 'Advanced Text' ); - expect( visibleBlocks[ 2 ].textContent ).toBe( 'A Text Embed' ); + expect( blocks ).toHaveLength( 5 ); + expect( blocks[ 0 ].textContent ).toBe( 'Text' ); + expect( blocks[ 1 ].textContent ).toBe( 'Advanced Text' ); + expect( blocks[ 2 ].textContent ).toBe( 'A Text Embed' ); assertNoResultsMessageNotToBePresent( element ); } ); @@ -233,24 +208,22 @@ describe( 'InserterMenu', () => { filterValue: ' text', } ); - assertOpenedPanels( element, 3 ); - const matchingCategories = element.querySelectorAll( - '.components-panel__body-toggle' + '.block-editor-inserter__panel-title' ); expect( matchingCategories ).toHaveLength( 3 ); expect( matchingCategories[ 0 ].textContent ).toBe( 'Common blocks' ); expect( matchingCategories[ 1 ].textContent ).toBe( 'Embeds' ); - const visibleBlocks = element.querySelectorAll( + const blocks = element.querySelectorAll( '.block-editor-block-types-list__item-title' ); - expect( visibleBlocks ).toHaveLength( 5 ); - expect( visibleBlocks[ 0 ].textContent ).toBe( 'Text' ); - expect( visibleBlocks[ 1 ].textContent ).toBe( 'Advanced Text' ); - expect( visibleBlocks[ 2 ].textContent ).toBe( 'A Text Embed' ); + expect( blocks ).toHaveLength( 5 ); + expect( blocks[ 0 ].textContent ).toBe( 'Text' ); + expect( blocks[ 1 ].textContent ).toBe( 'Advanced Text' ); + expect( blocks[ 2 ].textContent ).toBe( 'A Text Embed' ); assertNoResultsMessageNotToBePresent( element ); } ); diff --git a/packages/block-editor/src/components/block-patterns/use-async-list.js b/packages/block-editor/src/components/inserter/use-async-list.js similarity index 100% rename from packages/block-editor/src/components/block-patterns/use-async-list.js rename to packages/block-editor/src/components/inserter/use-async-list.js diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 1a426760ff9c71..ba14cf1e346cbd 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -16,7 +16,6 @@ @import "./components/block-mobile-toolbar/style.scss"; @import "./components/block-mover/style.scss"; @import "./components/block-navigation/style.scss"; -@import "./components/block-patterns/style.scss"; @import "./components/block-preview/style.scss"; @import "./components/block-settings-menu/style.scss"; @import "./components/block-styles/style.scss"; diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index e23efffc0eb4da..6fc8a48bc6b481 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -1,10 +1,6 @@ .wp-block-image { position: relative; - // This resets the intrinsic margin on the figure in non-floated, wide, and full-wide alignments. - margin-left: 0; - margin-right: 0; - &.is-transient img { opacity: 0.3; } diff --git a/packages/components/src/animate/style.scss b/packages/components/src/animate/style.scss index b6932156b244ac..1d64423e42f1f0 100644 --- a/packages/components/src/animate/style.scss +++ b/packages/components/src/animate/style.scss @@ -36,6 +36,10 @@ &.is-from-left { transform: translateX(+100%); } + + &.is-from-right { + transform: translateX(-100%); + } } @keyframes components-animate__slide-in-animation { diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 1ad73b8d4f142c..559d207972bb91 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -258,6 +258,23 @@ } } + // Toggled style. + &.is-pressed { + color: $white; + background: $dark-gray-primary; + + &:focus:not(:disabled) { + box-shadow: inset 0 0 0 1px $white, 0 0 0 $border-width-focus $theme-color; + + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; + } + + &:hover:not(:disabled) { + background: $dark-gray-primary; + } + } + svg { fill: currentColor; outline: none; diff --git a/packages/components/src/tab-panel/index.js b/packages/components/src/tab-panel/index.js index 1940ef4de9c019..682ac3fcfe1029 100644 --- a/packages/components/src/tab-panel/index.js +++ b/packages/components/src/tab-panel/index.js @@ -103,7 +103,6 @@ class TabPanel extends Component { role="tabpanel" id={ selectedId + '-view' } className="components-tab-panel__tab-content" - tabIndex="0" > { this.props.children( selectedTab ) }
diff --git a/packages/components/src/tab-panel/style.scss b/packages/components/src/tab-panel/style.scss index b4238983bb9b24..fbc051f407e45c 100644 --- a/packages/components/src/tab-panel/style.scss +++ b/packages/components/src/tab-panel/style.scss @@ -6,21 +6,37 @@ .components-tab-panel__tabs-item { background: transparent; border: none; - border-radius: 0; box-shadow: none; + border-radius: 0; cursor: pointer; - height: 50px; - padding: 3px 15px; // Use padding to offset the is-active border, this benefits Windows High Contrast mode + height: $grid-unit-60; + padding: 3px $grid-unit-20; // Use padding to offset the is-active border, this benefits Windows High Contrast mode margin-left: 0; - font-weight: 400; + font-weight: 500; transition: box-shadow 0.1s linear; + box-sizing: border-box; + + // This pseudo-element "duplicates" the tab label and sets the text to bold. + // This ensures that the tab doesn't change width when selected. + // See: https://github.com/WordPress/gutenberg/pull/9793 + &::after { + content: attr(data-label); + display: block; + height: 0; + overflow: hidden; + speak: none; + visibility: hidden; + } + + &:focus:not(:disabled) { + box-shadow: inset 0 $border-width-focus $theme-color; + } + - &:focus:enabled, &.is-active { - box-shadow: inset 0 -3px theme(outlines); - font-weight: 600; + // The transparent shadow ensures no jumpiness when focus animates on an active tab. + box-shadow: inset 0 0 0 $border-width-focus transparent, inset 0 0 -$border-width-tab 0 0 $theme-color; position: relative; - background: transparent; // This border appears in Windows High Contrast mode instead of the box-shadow. &::before { @@ -30,8 +46,15 @@ bottom: 1px; right: 0; left: 0; - border-bottom: 3px solid transparent; + border-bottom: $border-width-tab solid transparent; } } + &:focus { + box-shadow: inset 0 0 0 $border-width-focus $theme-color; + } + + &.is-active:focus { + box-shadow: inset 0 0 0 $border-width-focus $theme-color, inset 0 0 -$border-width-tab 0 0 $theme-color; + } } diff --git a/packages/components/src/toolbar/style.scss b/packages/components/src/toolbar/style.scss index 74afbf16b7c812..fdbcccb3f20294 100644 --- a/packages/components/src/toolbar/style.scss +++ b/packages/components/src/toolbar/style.scss @@ -56,7 +56,11 @@ // Toggled style. &.is-pressed { - color: $white; + background: transparent; + + &:hover { + background: transparent; + } &::before { background: $dark-gray-primary; diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index e8af181a034c1b..1e84e815f74dc3 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -72,6 +72,10 @@ _Parameters_ - _buttonLabel_ `string`: The label to search the button for. +# **closeGlobalBlockInserter** + +Undocumented declaration. + # **createEmbeddingMatcher** Creates a function to determine if a request is embedding a certain URL. @@ -283,7 +287,6 @@ result that appears. _Parameters_ - _searchTerm_ `string`: The text to search the inserter for. -- _panelName_ `string`: The inserter panel to open (if it's closed by default). # **installPlugin** @@ -343,10 +346,6 @@ _Returns_ - `Promise`: Promise that uses `mockCheck` to see if a request should be mocked with `mock`, and optionally transforms the response with `responseObjectTransform`. -# **openAllBlockInserterCategories** - -Opens all block inserter categories. - # **openDocumentSettingsSidebar** Clicks on the button in the header which opens Document Settings sidebar when it is closed. diff --git a/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js b/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js index eb2802e879b559..b9bc3b058a0c1c 100644 --- a/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js +++ b/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js @@ -12,7 +12,7 @@ export async function getAllBlockInserterItemTitles() { const inserterItemTitles = await page.evaluate( () => { return Array.from( document.querySelectorAll( - '.block-editor-inserter__results .block-editor-block-types-list__item-title' + '.block-editor-inserter__block-list .block-editor-block-types-list__item-title' ) ).map( ( inserterItem ) => { return inserterItem.innerText; 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 1b3074f0c501a7..eeadabea18f360 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-types-list .block-editor-block-types-list__list-item button' ); + }, '.block-editor-block-switcher__popover .block-editor-block-types-list .block-editor-block-types-list__list-item button' ); }; diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index d71cacc1cd70c6..422eca76bee4a3 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -32,9 +32,11 @@ export { enableFocusLossObservation, disableFocusLossObservation, } from './observe-focus-loss'; -export { openAllBlockInserterCategories } from './open-all-block-inserter-categories'; export { openDocumentSettingsSidebar } from './open-document-settings-sidebar'; -export { openGlobalBlockInserter } from './open-global-block-inserter'; +export { + openGlobalBlockInserter, + closeGlobalBlockInserter, +} from './open-global-block-inserter'; export { openPublishPanel } from './open-publish-panel'; export { pressKeyTimes } from './press-key-times'; export { pressKeyWithModifier } from './press-key-with-modifier'; diff --git a/packages/e2e-test-utils/src/insert-block.js b/packages/e2e-test-utils/src/insert-block.js index 4190d7c1136ce3..a3a31994165969 100644 --- a/packages/e2e-test-utils/src/insert-block.js +++ b/packages/e2e-test-utils/src/insert-block.js @@ -8,16 +8,9 @@ import { searchForBlock } from './search-for-block'; * result that appears. * * @param {string} searchTerm The text to search the inserter for. - * @param {string} panelName The inserter panel to open (if it's closed by default). */ -export async function insertBlock( searchTerm, panelName = null ) { +export async function insertBlock( searchTerm ) { await searchForBlock( searchTerm ); - if ( panelName ) { - const panelButton = ( - await page.$x( `//button[contains(text(), '${ panelName }')]` ) - )[ 0 ]; - await panelButton.click(); - } const insertButton = ( await page.$x( `//button//span[contains(text(), '${ searchTerm }')]` ) )[ 0 ]; diff --git a/packages/e2e-test-utils/src/open-all-block-inserter-categories.js b/packages/e2e-test-utils/src/open-all-block-inserter-categories.js deleted file mode 100644 index 9b31a0cd36c4ae..00000000000000 --- a/packages/e2e-test-utils/src/open-all-block-inserter-categories.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Opens all block inserter categories. - */ -export async function openAllBlockInserterCategories() { - const notOppenedCategorySelector = - '.block-editor-inserter__results .components-panel__body:not(.is-opened)'; - let categoryPanel = await page.$( notOppenedCategorySelector ); - while ( categoryPanel !== null ) { - await categoryPanel.click(); - categoryPanel = await page.$( notOppenedCategorySelector ); - } -} diff --git a/packages/e2e-test-utils/src/open-global-block-inserter.js b/packages/e2e-test-utils/src/open-global-block-inserter.js index 6819a636d91dd7..2e39325f763b21 100644 --- a/packages/e2e-test-utils/src/open-global-block-inserter.js +++ b/packages/e2e-test-utils/src/open-global-block-inserter.js @@ -2,11 +2,31 @@ * Opens the global block inserter. */ export async function openGlobalBlockInserter() { + if ( ! ( await isGlobalInserterOpen() ) ) { + await toggleGlobalBlockInserter(); + + // Waiting here is necessary because sometimes the inserter takes more time to + // render than Puppeteer takes to complete the 'click' action + await page.waitForSelector( '.block-editor-inserter__menu' ); + } +} + +export async function closeGlobalBlockInserter() { + if ( await isGlobalInserterOpen() ) { + await toggleGlobalBlockInserter(); + } +} + +async function isGlobalInserterOpen() { + return await page.evaluate( () => { + return !! document.querySelector( + '.edit-post-header [aria-label="Add block"].is-pressed, .edit-site-header [aria-label="Add block"].is-pressed' + ); + } ); +} + +async function toggleGlobalBlockInserter() { await page.click( '.edit-post-header [aria-label="Add block"], .edit-site-header [aria-label="Add block"]' ); - - // Waiting here is necessary because sometimes the inserter takes more time to - // render than Puppeteer takes to complete the 'click' action - await page.waitForSelector( '.block-editor-inserter__menu' ); } diff --git a/packages/e2e-test-utils/src/search-for-block.js b/packages/e2e-test-utils/src/search-for-block.js index bf8b7cd1e74d4c..2763d98f614923 100644 --- a/packages/e2e-test-utils/src/search-for-block.js +++ b/packages/e2e-test-utils/src/search-for-block.js @@ -2,6 +2,7 @@ * Internal dependencies */ import { openGlobalBlockInserter } from './open-global-block-inserter'; +import { pressKeyWithModifier } from './press-key-with-modifier'; /** * Search for block in the global inserter @@ -10,5 +11,7 @@ import { openGlobalBlockInserter } from './open-global-block-inserter'; */ export async function searchForBlock( searchTerm ) { await openGlobalBlockInserter(); + await page.focus( '.block-editor-inserter__search-input' ); + await pressKeyWithModifier( 'primary', 'a' ); await page.keyboard.type( searchTerm ); } diff --git a/packages/e2e-test-utils/src/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js index f3aa8f97b2f523..8de1debe491a83 100644 --- a/packages/e2e-test-utils/src/transform-block-to.js +++ b/packages/e2e-test-utils/src/transform-block-to.js @@ -12,9 +12,10 @@ export async function transformBlockTo( name ) { await switcherToggle.click(); // Find the block button option within the switcher popover. - const switcher = await page.$( '.block-editor-block-switcher__container' ); const insertButton = ( - await switcher.$x( `//button[.='${ name }']` ) + await page.$x( + `//*[contains(@class, "block-editor-block-switcher__popover")]//button[.='${ name }']` + ) )[ 0 ]; // Clicks may fail if the button is out of view. Assure it is before click. diff --git a/packages/e2e-tests/specs/editor/blocks/columns.test.js b/packages/e2e-tests/specs/editor/blocks/columns.test.js index 6255f6c5c5ac8d..294c2de3be44f7 100644 --- a/packages/e2e-tests/specs/editor/blocks/columns.test.js +++ b/packages/e2e-tests/specs/editor/blocks/columns.test.js @@ -5,8 +5,8 @@ import { createNewPost, getAllBlockInserterItemTitles, insertBlock, - openAllBlockInserterCategories, openGlobalBlockInserter, + closeGlobalBlockInserter, } from '@wordpress/e2e-test-utils'; describe( 'Columns', () => { @@ -16,6 +16,7 @@ describe( 'Columns', () => { it( 'restricts all blocks inside the columns block', async () => { await insertBlock( 'Columns' ); + await closeGlobalBlockInserter(); await page.click( '[aria-label="Two columns; equal split"]' ); await page.click( '[aria-label="Block navigation"]' ); const columnBlockMenuItem = ( @@ -25,7 +26,6 @@ describe( 'Columns', () => { )[ 0 ]; await columnBlockMenuItem.click(); await openGlobalBlockInserter(); - await openAllBlockInserterCategories(); expect( await getAllBlockInserterItemTitles() ).toHaveLength( 0 ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/blocks/image.test.js b/packages/e2e-tests/specs/editor/blocks/image.test.js index 1f81dc89df39d7..c98ac89daf14af 100644 --- a/packages/e2e-tests/specs/editor/blocks/image.test.js +++ b/packages/e2e-tests/specs/editor/blocks/image.test.js @@ -14,6 +14,7 @@ import { getEditedPostContent, createNewPost, clickButton, + openDocumentSettingsSidebar, } from '@wordpress/e2e-test-utils'; async function upload( selector ) { @@ -61,6 +62,7 @@ describe( 'Image', () => { ); expect( await getEditedPostContent() ).toMatch( regex1 ); + await openDocumentSettingsSidebar(); await page.click( '[aria-label="Image Size"] button' ); const regex2 = new RegExp( diff --git a/packages/e2e-tests/specs/editor/blocks/table.test.js b/packages/e2e-tests/specs/editor/blocks/table.test.js index 680416980067d5..96bb008e613df3 100644 --- a/packages/e2e-tests/specs/editor/blocks/table.test.js +++ b/packages/e2e-tests/specs/editor/blocks/table.test.js @@ -12,6 +12,7 @@ import { createNewPost, getEditedPostContent, insertBlock, + openDocumentSettingsSidebar, } from '@wordpress/e2e-test-utils'; const createButtonLabel = 'Create Table'; @@ -99,6 +100,7 @@ describe( 'Table', () => { it( 'allows header and footer rows to be switched on and off', async () => { await insertBlock( 'Table' ); + await openDocumentSettingsSidebar(); const headerSwitchSelector = "//label[text()='Header section']"; const footerSwitchSelector = "//label[text()='Footer section']"; @@ -144,6 +146,7 @@ describe( 'Table', () => { it( 'allows adding and deleting columns across the table header, body and footer', async () => { await insertBlock( 'Table' ); + await openDocumentSettingsSidebar(); // Create the table. await clickButton( createButtonLabel ); @@ -217,6 +220,7 @@ describe( 'Table', () => { // Testing for regressions of https://github.com/WordPress/gutenberg/issues/14904. it( 'allows cells to be selected when the cell area outside of the RichText is clicked', async () => { await insertBlock( 'Table' ); + await openDocumentSettingsSidebar(); // Create the table. await clickButton( createButtonLabel ); diff --git a/packages/e2e-tests/specs/editor/plugins/block-icons.test.js b/packages/e2e-tests/specs/editor/plugins/block-icons.test.js index 2e5bcf666bb2bc..fd506d98fb3f7a 100644 --- a/packages/e2e-tests/specs/editor/plugins/block-icons.test.js +++ b/packages/e2e-tests/specs/editor/plugins/block-icons.test.js @@ -8,10 +8,11 @@ import { insertBlock, pressKeyWithModifier, searchForBlock, + openDocumentSettingsSidebar, } from '@wordpress/e2e-test-utils'; const INSERTER_BUTTON_SELECTOR = - '.components-popover__content .block-editor-block-types-list__item'; + '.block-editor-inserter__block-list .block-editor-block-types-list__item'; const INSERTER_ICON_WRAPPER_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .block-editor-block-types-list__item-icon`; const INSERTER_ICON_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .block-editor-block-icon`; const INSPECTOR_ICON_SELECTOR = '.edit-post-sidebar .block-editor-block-icon'; @@ -89,6 +90,7 @@ describe( 'Correctly Renders Block Icons on Inserter and Inspector', () => { it( 'Renders correctly the icon on the inspector', async () => { await insertBlock( blockTitle ); + await openDocumentSettingsSidebar(); await selectFirstBlock(); validateIcon( await getInnerHTML( INSPECTOR_ICON_SELECTOR ) ); } ); @@ -127,6 +129,7 @@ describe( 'Correctly Renders Block Icons on Inserter and Inspector', () => { it( 'Renders the icon in the inspector with the correct colors', async () => { await insertBlock( blockTitle ); + await openDocumentSettingsSidebar(); await selectFirstBlock(); validateDashIcon( await getInnerHTML( INSPECTOR_ICON_SELECTOR ) ); expect( @@ -153,6 +156,7 @@ describe( 'Correctly Renders Block Icons on Inserter and Inspector', () => { it( 'Renders correctly the icon on the inspector', async () => { await insertBlock( blockTitle ); + await openDocumentSettingsSidebar(); await selectFirstBlock(); validateSvgIcon( await getInnerHTML( INSPECTOR_ICON_SELECTOR ) ); expect( diff --git a/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js index ec7396b2dc30fc..1e02d4369f1b3b 100644 --- a/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js +++ b/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js @@ -7,8 +7,8 @@ import { deactivatePlugin, getAllBlockInserterItemTitles, insertBlock, - openAllBlockInserterCategories, openGlobalBlockInserter, + closeGlobalBlockInserter, } from '@wordpress/e2e-test-utils'; describe( 'Allowed Blocks Setting on InnerBlocks ', () => { @@ -30,11 +30,11 @@ describe( 'Allowed Blocks Setting on InnerBlocks ', () => { const parentBlockSelector = '[data-type="test/allowed-blocks-unset"]'; const childParagraphSelector = `${ parentBlockSelector } ${ paragraphSelector }`; await insertBlock( 'Allowed Blocks Unset' ); + await closeGlobalBlockInserter(); await page.waitForSelector( childParagraphSelector ); await page.click( childParagraphSelector ); await openGlobalBlockInserter(); - await openAllBlockInserterCategories(); - expect( + await expect( ( await getAllBlockInserterItemTitles() ).length ).toBeGreaterThan( 20 ); } ); @@ -43,10 +43,10 @@ describe( 'Allowed Blocks Setting on InnerBlocks ', () => { const parentBlockSelector = '[data-type="test/allowed-blocks-set"]'; const childParagraphSelector = `${ parentBlockSelector } ${ paragraphSelector }`; await insertBlock( 'Allowed Blocks Set' ); + await closeGlobalBlockInserter(); await page.waitForSelector( childParagraphSelector ); await page.click( childParagraphSelector ); await openGlobalBlockInserter(); - await openAllBlockInserterCategories(); expect( await getAllBlockInserterItemTitles() ).toEqual( [ 'Button', 'Gallery', @@ -58,12 +58,12 @@ describe( 'Allowed Blocks Setting on InnerBlocks ', () => { it( 'correctly applies dynamic allowed blocks restrictions', async () => { await insertBlock( 'Allowed Blocks Dynamic' ); + await closeGlobalBlockInserter(); const parentBlockSelector = '[data-type="test/allowed-blocks-dynamic"]'; const blockAppender = '.block-list-appender button'; const appenderSelector = `${ parentBlockSelector } ${ blockAppender }`; await page.waitForSelector( appenderSelector ); await page.click( appenderSelector ); - await openAllBlockInserterCategories(); expect( await getAllBlockInserterItemTitles() ).toEqual( [ 'Image', 'List', @@ -73,9 +73,9 @@ describe( 'Allowed Blocks Setting on InnerBlocks ', () => { )[ 0 ]; await insertButton.click(); await insertBlock( 'Image' ); + await closeGlobalBlockInserter(); await page.waitForSelector( '.product[data-number-of-children="2"]' ); await page.click( appenderSelector ); - await openAllBlockInserterCategories(); expect( await getAllBlockInserterItemTitles() ).toEqual( [ 'Gallery', 'Video', diff --git a/packages/e2e-tests/specs/editor/plugins/inner-blocks-render-appender.test.js b/packages/e2e-tests/specs/editor/plugins/inner-blocks-render-appender.test.js index ad27838a4a8ed1..137ac319a6800a 100644 --- a/packages/e2e-tests/specs/editor/plugins/inner-blocks-render-appender.test.js +++ b/packages/e2e-tests/specs/editor/plugins/inner-blocks-render-appender.test.js @@ -8,10 +8,10 @@ import { getAllBlockInserterItemTitles, getEditedPostContent, insertBlock, - openAllBlockInserterCategories, + closeGlobalBlockInserter, } from '@wordpress/e2e-test-utils'; -const INSERTER_RESULTS_SELECTOR = '.block-editor-inserter__results'; +const INSERTER_RESULTS_SELECTOR = '.block-editor-inserter__block-list'; const QUOTE_INSERT_BUTTON_SELECTOR = '//button[.="Quote"]'; const APPENDER_SELECTOR = '.my-custom-awesome-appender'; const DYNAMIC_APPENDER_SELECTOR = 'my-dynamic-blocks-appender'; @@ -32,6 +32,7 @@ describe( 'RenderAppender prop of InnerBlocks ', () => { it( 'Users can customize the appender and can still insert blocks using exposed components', async () => { // Insert the InnerBlocks renderAppender block. await insertBlock( 'InnerBlocks renderAppender' ); + await closeGlobalBlockInserter(); // Wait for the custom block appender to appear. await page.waitForSelector( APPENDER_SELECTOR ); // Verify if the custom block appender text is the expected one. @@ -46,8 +47,6 @@ describe( 'RenderAppender prop of InnerBlocks ', () => { await page.click( `${ APPENDER_SELECTOR } .block-editor-button-block-appender` ); - await openAllBlockInserterCategories(); - // Verify if the blocks the custom inserter is rendering are the expected ones. expect( await getAllBlockInserterItemTitles() ).toEqual( [ 'Quote', @@ -69,6 +68,7 @@ describe( 'RenderAppender prop of InnerBlocks ', () => { it( 'Users can dynamically customize the appender', async () => { // Insert the InnerBlocks renderAppender dynamic block. await insertBlock( 'InnerBlocks renderAppender dynamic' ); + await closeGlobalBlockInserter(); // Wait for the custom dynamic block appender to appear. await page.waitForSelector( '.' + DYNAMIC_APPENDER_SELECTOR ); @@ -81,7 +81,6 @@ describe( 'RenderAppender prop of InnerBlocks ', () => { // Open the inserter of our custom block appender and expand all the categories. const blockAppenderButtonSelector = `.${ DYNAMIC_APPENDER_SELECTOR } .block-editor-button-block-appender`; await page.click( blockAppenderButtonSelector ); - await openAllBlockInserterCategories(); // Verify if the blocks the custom inserter is rendering are the expected ones. expect( await getAllBlockInserterItemTitles() ).toEqual( [ diff --git a/packages/e2e-tests/specs/editor/various/a11y.test.js b/packages/e2e-tests/specs/editor/various/a11y.test.js index 0ca44d7fb7114a..a25b5d9760c3ce 100644 --- a/packages/e2e-tests/specs/editor/various/a11y.test.js +++ b/packages/e2e-tests/specs/editor/various/a11y.test.js @@ -23,7 +23,7 @@ describe( 'a11y', () => { ':focus', ( focusedElement ) => { return focusedElement.classList.contains( - 'block-editor-inserter__toggle' + 'edit-post-header-toolbar__inserter-toggle' ); } ); diff --git a/packages/e2e-tests/specs/editor/various/adding-blocks.test.js b/packages/e2e-tests/specs/editor/various/adding-blocks.test.js index 11f78fe278ab8a..65fd287650b462 100644 --- a/packages/e2e-tests/specs/editor/various/adding-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/adding-blocks.test.js @@ -6,8 +6,8 @@ import { insertBlock, getEditedPostContent, pressKeyTimes, - switchEditorModeTo, setBrowserViewport, + closeGlobalBlockInserter, } from '@wordpress/e2e-test-utils'; /** @typedef {import('puppeteer').ElementHandle} ElementHandle */ @@ -92,6 +92,7 @@ describe( 'adding blocks', () => { // Unselect blocks to avoid conflicts with the inbetween inserter await page.click( '.editor-post-title__input' ); + await closeGlobalBlockInserter(); // Using the between inserter const insertionPoint = await page.$( '[data-type="core/quote"]' ); @@ -111,16 +112,14 @@ describe( 'adding blocks', () => { () => document.activeElement && document.activeElement.classList.contains( - 'block-editor-inserter__search' + 'block-editor-inserter__search-input' ) ); await page.keyboard.type( 'para' ); - await pressKeyTimes( 'Tab', 3 ); + await pressKeyTimes( 'Tab', 4 ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'Second paragraph' ); - await switchEditorModeTo( 'Code' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -137,7 +136,7 @@ describe( 'adding blocks', () => { () => document.activeElement.classList ); expect( Object.values( activeElementClassList ) ).toContain( - 'block-editor-inserter__search' + 'block-editor-inserter__search-input' ); // Try using the up arrow key (vertical navigation triggers the issue described in #9583). @@ -148,29 +147,30 @@ describe( 'adding blocks', () => { () => document.activeElement.classList ); expect( Object.values( activeElementClassList ) ).toContain( - 'block-editor-inserter__search' + 'block-editor-inserter__search-input' ); - // Tab to the block search results + // Tab to the block list + await page.keyboard.press( 'Tab' ); await page.keyboard.press( 'Tab' ); - // Expect the search results to be the active element. + // Expect the block list to be the active element. activeElementClassList = await page.evaluate( () => document.activeElement.classList ); expect( Object.values( activeElementClassList ) ).toContain( - 'block-editor-inserter__results' + 'block-editor-block-types-list__item' ); // Try using the up arrow key await page.keyboard.press( 'ArrowUp' ); - // Expect the search results to still be the active element. + // Expect the block list to still be the active element. activeElementClassList = await page.evaluate( () => document.activeElement.classList ); expect( Object.values( activeElementClassList ) ).toContain( - 'block-editor-inserter__results' + 'block-editor-block-types-list__item' ); // Press escape to close the block inserter. diff --git a/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js index 5aab0d85e31b91..63ae6d57ecd1fb 100644 --- a/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js @@ -7,6 +7,7 @@ import { getEditedPostContent, pressKeyTimes, pressKeyWithModifier, + openDocumentSettingsSidebar, } from '@wordpress/e2e-test-utils'; async function openBlockNavigator() { @@ -43,6 +44,7 @@ describe( 'Navigating the block hierarchy', () => { await columnsBlockMenuItem.click(); // Tweak the columns count. + await openDocumentSettingsSidebar(); await page.focus( '.block-editor-block-inspector .components-range-control__number[aria-label="Columns"]' ); @@ -73,6 +75,7 @@ describe( 'Navigating the block hierarchy', () => { it( 'should navigate block hierarchy using only the keyboard', async () => { await insertBlock( 'Columns' ); + await openDocumentSettingsSidebar(); await page.click( '[aria-label="Two columns; equal split"]' ); // Add a paragraph in the first column. diff --git a/packages/e2e-tests/specs/editor/various/block-switcher.test.js b/packages/e2e-tests/specs/editor/various/block-switcher.test.js index 12c40442195160..c330f8680af551 100644 --- a/packages/e2e-tests/specs/editor/various/block-switcher.test.js +++ b/packages/e2e-tests/specs/editor/various/block-switcher.test.js @@ -9,7 +9,7 @@ import { pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; -describe( 'adding blocks', () => { +describe( 'Block Switcher', () => { beforeEach( async () => { await createNewPost(); } ); diff --git a/packages/e2e-tests/specs/editor/various/editor-modes.test.js b/packages/e2e-tests/specs/editor/various/editor-modes.test.js index 15d767cd6cc2c9..6aef17cd69594d 100644 --- a/packages/e2e-tests/specs/editor/various/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor/various/editor-modes.test.js @@ -146,7 +146,7 @@ describe( 'Editing modes (visual/HTML)', () => { // The inserter is disabled const disabledInserter = await page.$( - '.block-editor-inserter > button:disabled, .block-editor-inserter > button[aria-disabled="true"]' + '.edit-post-header-toolbar__inserter-toggle:disabled, .edit-post-header-toolbar__inserter-toggle[aria-disabled="true"]' ); expect( disabledInserter ).not.toBeNull(); } ); diff --git a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js index 8f3854fb8ef4f1..8f7d6818d380d3 100644 --- a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js @@ -22,6 +22,7 @@ const navigateToContentEditorTop = async () => { // Use 'Ctrl+`' to return to the top of the editor await pressKeyWithModifier( 'ctrl', '`' ); await pressKeyWithModifier( 'ctrl', '`' ); + await pressKeyWithModifier( 'ctrl', '`' ); }; const tabThroughParagraphBlock = async ( paragraphText ) => { @@ -38,7 +39,7 @@ const tabThroughParagraphBlock = async ( paragraphText ) => { ).toBe( paragraphText ); await page.keyboard.press( 'Tab' ); - await expect( await getActiveLabel() ).toBe( 'Document' ); + await expect( await getActiveLabel() ).toBe( 'Open document settings' ); }; const tabThroughBlockMoverControl = async () => { @@ -133,7 +134,7 @@ describe( 'Order of block keyboard navigation', () => { ); await page.keyboard.press( 'Tab' ); - await expect( await getActiveLabel() ).toBe( 'Document (selected)' ); + await expect( await getActiveLabel() ).toBe( 'Open document settings' ); } ); it( 'allows tabbing in navigation mode if no block is selected (reverse)', async () => { diff --git a/packages/e2e-tests/specs/editor/various/rich-text.test.js b/packages/e2e-tests/specs/editor/various/rich-text.test.js index cd3fa85e62fdcf..e29475984b7f88 100644 --- a/packages/e2e-tests/specs/editor/various/rich-text.test.js +++ b/packages/e2e-tests/specs/editor/various/rich-text.test.js @@ -7,6 +7,7 @@ import { insertBlock, clickBlockAppender, pressKeyWithModifier, + openDocumentSettingsSidebar, } from '@wordpress/e2e-test-utils'; describe( 'RichText', () => { @@ -22,6 +23,7 @@ describe( 'RichText', () => { // // See: https://github.com/WordPress/gutenberg/issues/3091 await insertBlock( 'Heading' ); + await openDocumentSettingsSidebar(); await page.click( '[aria-label="Heading 3"]' ); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/editor/various/writing-flow.test.js b/packages/e2e-tests/specs/editor/various/writing-flow.test.js index d06176cc7de460..7a379731f8789a 100644 --- a/packages/e2e-tests/specs/editor/various/writing-flow.test.js +++ b/packages/e2e-tests/specs/editor/various/writing-flow.test.js @@ -39,7 +39,9 @@ describe( 'Writing Flow', () => { await page.keyboard.press( 'Enter' ); await page.click( ':focus [aria-label="Two columns; equal split"]' ); await page.click( ':focus .block-editor-button-block-appender' ); - await page.waitForSelector( ':focus.block-editor-inserter__search' ); + await page.waitForSelector( + ':focus.block-editor-inserter__search-input' + ); await page.keyboard.type( 'Paragraph' ); await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. @@ -50,7 +52,9 @@ describe( 'Writing Flow', () => { // is a temporary solution. await page.focus( '.wp-block[data-type="core/column"]:nth-child(2)' ); await page.click( ':focus .block-editor-button-block-appender' ); - await page.waitForSelector( ':focus.block-editor-inserter__search' ); + await page.waitForSelector( + ':focus.block-editor-inserter__search-input' + ); await page.keyboard.type( 'Paragraph' ); await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index 080f9e2c258257..4e386d22cc4f70 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -3,9 +3,8 @@ */ import { useViewportMatch } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { - Inserter, BlockToolbar, NavigableToolbar, BlockNavigationDropdown, @@ -16,13 +15,14 @@ import { EditorHistoryRedo, EditorHistoryUndo, } from '@wordpress/editor'; +import { Button } from '@wordpress/components'; +import { plus } from '@wordpress/icons'; -const inserterToggleProps = { isPrimary: true }; - -function HeaderToolbar() { +function HeaderToolbar( { onToggleInserter, isInserterOpen } ) { const { hasFixedToolbar, - showInserter, + isInserterEnabled, + isInserterVisible, isTextModeEnabled, previewDeviceType, } = useSelect( @@ -31,9 +31,10 @@ function HeaderToolbar() { 'fixedToolbar' ), // This setting (richEditingEnabled) should not live in the block editor's setting. - showInserter: + isInserterEnabled: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isInserterVisible: select( 'core/block-editor' ).hasInserterItems(), isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', previewDeviceType: select( @@ -58,12 +59,20 @@ function HeaderToolbar() { className="edit-post-header-toolbar" aria-label={ toolbarAriaLabel } > - + { isInserterVisible && ( +
+
+ { + if ( isMobileViewport ) { + setIsInserterOpen( false ); + } + } } + /> +
+
+ ) + } sidebar={ ( ! isMobileViewport || sidebarIsOpened ) && ( <> diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index cdab72c4bed4c3..340284a8613b15 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -110,3 +110,28 @@ background-color: $light-gray-700; } +.edit-post-layout__inserter-panel { + height: 100%; + display: flex; + flex-direction: column; +} + +.edit-post-layout__inserter-panel-header { + padding-top: $grid-unit-10; + padding-right: $grid-unit-10; + display: flex; + justify-content: flex-end; + + @include break-medium() { + display: none; + } +} + +.edit-post-layout__inserter-panel-content { + // Leave space for the close button + height: calc(100% - #{$button-size} - #{$grid-unit-10}); + + @include break-medium() { + height: 100%; + } +} diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss index acdf055d22faa1..0093354ef12e00 100644 --- a/packages/edit-post/src/components/visual-editor/style.scss +++ b/packages/edit-post/src/components/visual-editor/style.scss @@ -36,12 +36,6 @@ height: 0; } -// The base width of blocks -.edit-post-visual-editor .block-editor-block-list__block { - margin-left: auto; - margin-right: auto; -} - // Ideally this wrapper div is not needed but if we waant to match the positionning of blocks // .block-editor-block-list__layout and block-editor-block-list__block // We need to have two DOM elements. diff --git a/packages/edit-post/src/plugins/block-patterns/index.js b/packages/edit-post/src/plugins/block-patterns/index.js deleted file mode 100644 index e9018bbfd3ed91..00000000000000 --- a/packages/edit-post/src/plugins/block-patterns/index.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * WordPress dependencies - */ -import { layout } from '@wordpress/icons'; -import { __ } from '@wordpress/i18n'; -import { __experimentalBlockPatterns as BlockPatternsList } from '@wordpress/block-editor'; -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import PluginSidebar from '../../components/sidebar/plugin-sidebar'; -import PluginSidebarMoreMenuItem from '../../components/header/plugin-sidebar-more-menu-item'; - -function BlockPatterns() { - const { __experimentalBlockPatterns: blockPatterns = [] } = useSelect( - ( select ) => { - return select( 'core/editor' ).getEditorSettings(); - }, - [] - ); - - return ( - <> - - - - - { __( 'Block Patterns' ) } - - - ); -} - -export default BlockPatterns; diff --git a/packages/edit-post/src/plugins/index.js b/packages/edit-post/src/plugins/index.js index 5b0395de59eca8..cbdc9b2bb7ace7 100644 --- a/packages/edit-post/src/plugins/index.js +++ b/packages/edit-post/src/plugins/index.js @@ -14,13 +14,6 @@ import ManageBlocksMenuItem from './manage-blocks-menu-item'; import KeyboardShortcutsHelpMenuItem from './keyboard-shortcuts-help-menu-item'; import ToolsMoreMenuGroup from '../components/header/tools-more-menu-group'; import WelcomeGuideMenuItem from './welcome-guide-menu-item'; -import BlockPatterns from './block-patterns'; - -registerPlugin( 'edit-post-block-patterns', { - render() { - return ; - }, -} ); registerPlugin( 'edit-post', { render() { diff --git a/packages/edit-post/src/store/reducer.js b/packages/edit-post/src/store/reducer.js index de17f27eedde01..6f77bb46e0937b 100644 --- a/packages/edit-post/src/store/reducer.js +++ b/packages/edit-post/src/store/reducer.js @@ -13,13 +13,6 @@ import { combineReducers } from '@wordpress/data'; */ import { PREFERENCES_DEFAULTS } from './defaults'; -/** - * The default active general sidebar: The "Document" tab. - * - * @type {string} - */ -export const DEFAULT_ACTIVE_GENERAL_SIDEBAR = 'edit-post/document'; - /** * Higher-order reducer creator which provides the given initial state for the * original reducer. diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 74c7b2f67cf72d..d57ceeede790a7 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -125,6 +125,7 @@ class EditorProvider extends Component { '__experimentalGlobalStylesUserEntityId', '__experimentalGlobalStylesBase', '__experimentalDisableCustomLineHeight', + '__experimentalBlockPatterns', 'gradients', ] ), mediaUpload: hasUploadPermissions ? mediaUpload : undefined, diff --git a/packages/interface/src/components/interface-skeleton/index.js b/packages/interface/src/components/interface-skeleton/index.js index d1065602373608..7ba335b9dffa6e 100644 --- a/packages/interface/src/components/interface-skeleton/index.js +++ b/packages/interface/src/components/interface-skeleton/index.js @@ -28,6 +28,7 @@ function InterfaceSkeleton( { footer, header, sidebar, + leftSidebar, content, actions, labels, @@ -36,10 +37,17 @@ function InterfaceSkeleton( { useHTMLClass( 'interface-interface-skeleton__html-container' ); const defaultLabels = { + /* translators: accessibility text for the top bar landmark region. */ header: __( 'Header' ), + /* translators: accessibility text for the content landmark region. */ body: __( 'Content' ), + /* translators: accessibility text for the left sidebar landmark region. */ + leftSidebar: __( 'Left sidebar' ), + /* translators: accessibility text for the settings landmark region. */ sidebar: __( 'Settings' ), + /* translators: accessibility text for the publish landmark region. */ actions: __( 'Publish' ), + /* translators: accessibility text for the footer landmark region. */ footer: __( 'Footer' ), }; @@ -56,7 +64,6 @@ function InterfaceSkeleton( {
@@ -64,10 +71,19 @@ function InterfaceSkeleton( {
) }
+ { !! leftSidebar && ( +
+ { leftSidebar } +
+ ) }
@@ -77,7 +93,6 @@ function InterfaceSkeleton( {
@@ -88,7 +103,6 @@ function InterfaceSkeleton( {
@@ -100,7 +114,6 @@ function InterfaceSkeleton( {
diff --git a/packages/interface/src/components/interface-skeleton/style.scss b/packages/interface/src/components/interface-skeleton/style.scss index 39b7b85493f20d..9e3ee7884c7880 100644 --- a/packages/interface/src/components/interface-skeleton/style.scss +++ b/packages/interface/src/components/interface-skeleton/style.scss @@ -73,6 +73,7 @@ html.interface-interface-skeleton__html-container { } +.interface-interface-skeleton__left-sidebar, .interface-interface-skeleton__sidebar { display: block; width: auto; // Keep the sidebar width flexible. @@ -88,13 +89,24 @@ html.interface-interface-skeleton__html-container { // On Mobile the header is fixed to keep HTML as scrollable. @include break-medium() { - overflow: auto; - border-left: $border-width solid $light-gray-500; position: relative !important; z-index: z-index(".interface-interface-skeleton__sidebar {greater than small}"); } } +.interface-interface-skeleton__sidebar { + @include break-medium() { + overflow: auto; + border-left: $border-width solid $light-gray-500; + } +} + +.interface-interface-skeleton__left-sidebar { + @include break-medium() { + border-right: $border-width solid $light-gray-500; + } +} + .interface-interface-skeleton__header { flex-shrink: 0; height: auto; // Keep the height flexible.