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 78869f7d043e4..0000000000000 --- 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; - padding: $grid-unit-20; -} - -.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-preview { - padding: $grid-unit-20; -} - -.block-editor-patterns__item-title { - text-align: center; - padding: 10px 0; -} diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 3e83be72f19a6..94fc975e108d7 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'; diff --git a/packages/block-editor/src/components/inserter/block-list.js b/packages/block-editor/src/components/inserter/block-list.js index c16ffe5c5ad38..ecfc946172d7a 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, @@ -23,9 +22,9 @@ import { PanelBody, 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 { createBlock } from '@wordpress/blocks'; import { useMemo, useEffect, useState, useRef } from '@wordpress/element'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { compose, withSafeTimeout } from '@wordpress/compose'; /** @@ -54,10 +53,8 @@ const getBlockNamespace = ( item ) => item.name.split( '/' )[ 0 ]; const MAX_SUGGESTED_ITEMS = 9; function InserterBlockList( { - clientId, - isAppender, rootClientId, - onSelect, + onInsert, onHover, __experimentalSelectBlockOnInsert: selectBlockOnInsert, filterValue, @@ -69,61 +66,30 @@ function InserterBlockList( { 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 +98,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( () => { @@ -327,7 +246,6 @@ function InserterBlockList( { return (
{ !! suggestedItems.length && ! filterValue && ( @@ -350,7 +268,7 @@ function InserterBlockList( { ) } @@ -372,7 +290,7 @@ function InserterBlockList( { ); @@ -396,7 +314,7 @@ function InserterBlockList( { ); @@ -414,7 +332,7 @@ function InserterBlockList( { onClick( pattern, blocks ) } onKeyDown={ ( event ) => { @@ -33,28 +33,20 @@ function BlockPattern( { pattern, onClick } ) { } } tabIndex={ 0 } > -
- +
+ +
+
+ { title }
-
{ title }
); } -function BlockPatterns( { patterns } ) { - const getBlockInsertionPoint = useSelect( ( select ) => { - return select( 'core/block-editor' ).getBlockInsertionPoint; - } ); - const { insertBlocks } = useDispatch( 'core/block-editor' ); +function BlockPatterns( { patterns, onInsert } ) { 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. */ @@ -68,10 +60,10 @@ function BlockPatterns( { patterns } ) { }, [] ); return ( -
- { patterns.map( ( pattern, index ) => ( +
+ { patterns.map( ( pattern, patternIndex ) => ( diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 5698b278e211d..4da5338b5fbff 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,22 +183,29 @@ 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/style.scss b/packages/block-editor/src/components/inserter/style.scss index 7adfb07c597f6..7fbf26549c91f 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -1,6 +1,5 @@ $block-inserter-preview-height: 350px; $block-inserter-tabs-height: 44px; -$block-inserter-search-height: 38px; .block-editor-inserter { display: inline-block; @@ -82,9 +81,28 @@ $block-inserter-search-height: 38px; } } -.block-editor-inserter__results { +.block-editor-inserter__tabs { + display: flex; + flex-grow: 1; + flex-direction: column; + margin-top: -$grid-unit-20; + + .components-tab-panel__tabs { + padding: 0 $grid-unit-20; + margin-bottom: -1px; + border-bottom: $border-width solid $light-gray-secondary; + } + + .components-tab-panel__tab-content { + display: flex; + flex-grow: 1; + flex-direction: column; + position: relative; + } +} + +.block-editor-inserter__block-list { 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; @@ -99,6 +117,17 @@ $block-inserter-search-height: 38px; } } +// 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; +} + .block-editor-inserter__popover .block-editor-block-types-list { margin: 0 -8px; } @@ -227,3 +256,37 @@ $block-inserter-search-height: 38px; padding: $grid-unit-20; flex-shrink: 0; } + +.block-editor-inserter__patterns { + padding: $grid-unit-20; +} + +.block-editor-inserter__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-inserter__patterns-item-preview { + padding: $grid-unit-20; +} + +.block-editor-inserter__patterns-item-title { + text-align: center; + padding: 10px 0; +} diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 3f4ae8b3b3136..f64e5506903d9 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -15,7 +15,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/edit-post/src/plugins/block-patterns/index.js b/packages/edit-post/src/plugins/block-patterns/index.js deleted file mode 100644 index e9018bbfd3ed9..0000000000000 --- 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 5b0395de59eca..cbdc9b2bb7ace 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/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 74c7b2f67cf72..d57ceeede790a 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,