diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 75f4d1143895e..154ea248ae651 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -1032,6 +1032,10 @@ _Returns_ A hook used to set the editor mode to zoomed out mode, invoking the hook sets the mode. +_Parameters_ + +- _zoomOut_ `boolean`: If we should enter into zoomOut mode or not + ### Warning _Related_ diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/index.js b/packages/block-editor/src/components/inserter/block-patterns-tab/index.js index 378bd996bf93b..34993fc6db87f 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/index.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/index.js @@ -5,11 +5,10 @@ import { useState } from '@wordpress/element'; import { __, isRTL } from '@wordpress/i18n'; import { useViewportMatch } from '@wordpress/compose'; import { - __experimentalItemGroup as ItemGroup, - __experimentalItem as Item, __experimentalHStack as HStack, FlexBlock, Button, + privateApis as componentsPrivateApis, } from '@wordpress/components'; import { Icon, chevronRight, chevronLeft } from '@wordpress/icons'; @@ -20,41 +19,48 @@ import PatternsExplorerModal from '../block-patterns-explorer'; import MobileTabNavigation from '../mobile-tab-navigation'; import { PatternCategoryPreviews } from './pattern-category-previews'; import { usePatternCategories } from './use-pattern-categories'; +import { unlock } from '../../../lock-unlock'; + +const { Tabs } = unlock( componentsPrivateApis ); function BlockPatternsTab( { onSelectCategory, selectedCategory, onInsert, rootClientId, + children, } ) { const [ showPatternsExplorer, setShowPatternsExplorer ] = useState( false ); const categories = usePatternCategories( rootClientId ); - const initialCategory = selectedCategory || categories[ 0 ]; const isMobile = useViewportMatch( 'medium', '<' ); return ( <> { ! isMobile && (
- + + { categories.map( ( category ) => ( + + { children } + + ) ) } + +
) } { isMobile && ( { ( category ) => ( - +
+ +
) }
) } { showPatternsExplorer && ( setShowPatternsExplorer( false ) } rootClientId={ rootClientId } diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js index 7a5a9eb8d989e..04f25ade4880d 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js @@ -1,16 +1,8 @@ -/** - * WordPress dependencies - */ -import { useRef, useEffect } from '@wordpress/element'; - -import { focus } from '@wordpress/dom'; - /** * Internal dependencies */ import { PatternCategoryPreviews } from './pattern-category-previews'; -import { useZoomOut } from '../../../hooks/use-zoom-out'; export function PatternCategoryPreviewPanel( { rootClientId, @@ -20,34 +12,15 @@ export function PatternCategoryPreviewPanel( { showTitlesAsTooltip, patternFilter, } ) { - const container = useRef(); - - useEffect( () => { - const timeout = setTimeout( () => { - const [ firstTabbable ] = focus.tabbable.find( container.current ); - firstTabbable?.focus(); - } ); - return () => clearTimeout( timeout ); - }, [ category ] ); - - // Move to zoom out mode when this component is mounted - // and back to the previous mode when unmounted. - useZoomOut(); - return ( -
- -
+ ); } diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js index 7b8fd8e76202a..8a4bedbb4a5fc 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js @@ -128,7 +128,7 @@ export function PatternCategoryPreviews( { ); return ( -
+ <> ) } -
+ ); } diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 9e1322f0435fe..f42a7f06019cc 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -33,6 +33,7 @@ import InserterSearchResults from './search-results'; import useInsertionPoint from './hooks/use-insertion-point'; import InserterTabs from './tabs'; import { store as blockEditorStore } from '../../store'; +import { useZoomOut } from '../../hooks/use-zoom-out'; const NOOP = () => {}; function InserterMenu( @@ -125,6 +126,11 @@ function InserterMenu( [ setSelectedPatternCategory, __experimentalOnPatternCategorySelection ] ); + const showPatternPanel = + selectedTab === 'patterns' && + ! delayedFilterValue && + selectedPatternCategory; + const blocksTab = useMemo( () => ( <> @@ -162,13 +168,25 @@ function InserterMenu( onInsert={ onInsertPattern } onSelectCategory={ onClickPatternCategory } selectedCategory={ selectedPatternCategory } - /> + > + { showPatternPanel && ( + + ) } + ), [ destinationRootClientId, onInsertPattern, onClickPatternCategory, selectedPatternCategory, + showPatternPanel, ] ); @@ -205,16 +223,15 @@ function InserterMenu( }, } ) ); - const showPatternPanel = - selectedTab === 'patterns' && - ! delayedFilterValue && - selectedPatternCategory; const showAsTabs = ! delayedFilterValue && ( showPatterns || showMedia ); const showMediaPanel = selectedTab === 'media' && ! delayedFilterValue && selectedMediaCategory; + // When the pattern panel is showing, we want to use zoom out mode + useZoomOut( showPatternPanel ); + const handleSetSelectedTab = ( value ) => { // If no longer on patterns tab remove the category setting. if ( value !== 'patterns' ) { @@ -224,7 +241,11 @@ function InserterMenu( }; return ( -
+
) } - { showPatternPanel && ( - - ) }
); } diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 580e4ec0e21f2..87e9b04fddaf5 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -62,7 +62,7 @@ $block-inserter-tabs-height: 44px; .block-editor-inserter__popover .block-editor-inserter__menu { margin: -$grid-unit-15; - .block-editor-inserter__tabs div[role="tablist"] { + .block-editor-inserter__tablist { top: $grid-unit-10 + $grid-unit-20 + $grid-unit-60 - $grid-unit-15; } @@ -115,7 +115,7 @@ $block-inserter-tabs-height: 44px; flex-direction: column; overflow: hidden; - div[role="tablist"] { + .block-editor-inserter__tablist { border-bottom: $border-width solid $gray-300; button[role="tab"] { @@ -130,7 +130,7 @@ $block-inserter-tabs-height: 44px; } } - div[role="tabpanel"] { + .block-editor-inserter__tabpanel { display: flex; flex-grow: 1; flex-direction: column; @@ -242,60 +242,79 @@ $block-inserter-tabs-height: 44px; .block-editor-inserter__patterns-explore-button.components-button { padding: $grid-unit-20; justify-content: center; - margin-top: $grid-unit-20; width: 100%; } -.block-editor-inserter__patterns-selected-category.block-editor-inserter__patterns-selected-category { - color: var(--wp-admin-theme-color); - position: relative; - - .components-flex-item { - filter: brightness(0.95); - } - - svg { - fill: var(--wp-admin-theme-color); - } - - &::after { - content: ""; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - border-radius: $radius-block-ui; - opacity: 0.04; - background: var(--wp-admin-theme-color); - } -} - .block-editor-inserter__block-patterns-tabs-container { - height: 100%; - nav { - height: 100%; - } + padding: $grid-unit-20; } -.block-editor-inserter__block-patterns-tabs { +.block-editor-inserter__block-patterns-tablist { display: flex; flex-direction: column; - padding: $grid-unit-20; - overflow-y: auto; - height: 100%; + border: none; + margin-bottom: $grid-unit-10; // Push the listitem wrapping the "explore" button to the bottom of the panel. div[role="listitem"]:last-child { margin-top: auto; } - .block-editor-inserter__patterns-category { + .block-editor-inserter__patterns-tab { // Account for the icon on the right so that it's visually balanced. - padding-right: $grid-unit-05; + padding: $grid-unit-10 $grid-unit-05 $grid-unit-10 $grid-unit-15; + text-align: left; + font-weight: inherit; + display: block; + position: relative; + height: auto; + + &[aria-selected="true"] { + color: var(--wp-admin-theme-color); + + .components-flex-item { + filter: brightness(0.95); + } + + svg { + fill: var(--wp-admin-theme-color); + } + + &::after { + content: ""; + display: block; + outline: none; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + border-radius: $radius-block-ui; + opacity: 0.04; + background: var(--wp-admin-theme-color); + height: 100%; + } + } + + &:focus-visible, + &:focus:not(:disabled) { + border-radius: 2px; + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + // Windows high contrast mode. + outline: 2px solid transparent; + outline-offset: 0; + } + + &::before { + display: none; + } + + &::after { + display: none; + } } } -.block-editor-inserter__patterns-category-dialog { +.block-editor-inserter__patterns-category-panel { background: $gray-100; border-left: $border-width solid $gray-200; border-right: $border-width solid $gray-200; @@ -304,35 +323,29 @@ $block-inserter-tabs-height: 44px; left: 0; height: 100%; width: 100%; - - @include break-medium { - left: 100%; - display: block; - width: 300px; - } - - .block-editor-block-patterns-list { - overflow-y: auto; - flex-grow: 1; - height: 100%; - padding: $grid-unit-20 $grid-unit-30; - } -} - -.block-editor-inserter__patterns-category-panel { padding: 0 $grid-unit-20; display: flex; flex-direction: column; - height: 100%; + @include break-medium { padding: 0; + left: 100%; + width: 300px; } + .block-editor-inserter__patterns-category-panel-header { padding: 16px $grid-unit-30; } .block-editor-inserter__patterns-category-no-results { margin-top: $grid-unit-30; } + + .block-editor-block-patterns-list { + overflow-y: auto; + flex-grow: 1; + height: 100%; + padding: $grid-unit-20 $grid-unit-30; + } } .block-editor-inserter__preview-content { @@ -758,13 +771,24 @@ $block-inserter-tabs-height: 44px; } } +// Only relevant in zoom-out-mode +.block-editor-inserter__pattern-panel-placeholder { + display: none; +} + .is-zoom-out { .block-editor-inserter__menu { display: flex; } - .block-editor-inserter__patterns-category-dialog { - position: static; + .show-panel::after { + // Makes space for the inserter flyout panel + @include break-medium { + content: ""; + display: block; + width: 300px; + height: 100%; + } } .block-editor-inserter__media-dialog { diff --git a/packages/block-editor/src/components/inserter/tabs.js b/packages/block-editor/src/components/inserter/tabs.js index 4795c3ce4fdc2..ad9cd4888bd94 100644 --- a/packages/block-editor/src/components/inserter/tabs.js +++ b/packages/block-editor/src/components/inserter/tabs.js @@ -43,9 +43,13 @@ function InserterTabs( { return (
- + { tabs.map( ( tab ) => ( - + { tab.title } ) ) } @@ -55,6 +59,7 @@ function InserterTabs( { key={ tab.name } tabId={ tab.name } focusable={ false } + className="block-editor-inserter__tabpanel" > { tabsContents[ tab.name ] } diff --git a/packages/block-editor/src/hooks/use-zoom-out.js b/packages/block-editor/src/hooks/use-zoom-out.js index 84603c0161dd4..ce20cb5bd7a17 100644 --- a/packages/block-editor/src/hooks/use-zoom-out.js +++ b/packages/block-editor/src/hooks/use-zoom-out.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; /** * Internal dependencies @@ -11,26 +11,36 @@ import { store as blockEditorStore } from '../store'; /** * A hook used to set the editor mode to zoomed out mode, invoking the hook sets the mode. + * + * @param {boolean} zoomOut If we should enter into zoomOut mode or not */ -export function useZoomOut() { +export function useZoomOut( zoomOut = true ) { const { __unstableSetEditorMode } = useDispatch( blockEditorStore ); - const { mode } = useSelect( ( select ) => { - return { - mode: select( blockEditorStore ).__unstableGetEditorMode(), + const { __unstableGetEditorMode } = useSelect( blockEditorStore ); + + const originalEditingMode = useRef( null ); + const mode = __unstableGetEditorMode(); + + useEffect( () => { + // Only set this on mount so we know what to return to when we unmount. + if ( ! originalEditingMode.current ) { + originalEditingMode.current = mode; + } + + return () => { + // We need to use __unstableGetEditorMode() here and not `mode`, as mode may not update on unmount + if ( __unstableGetEditorMode() !== originalEditingMode.current ) { + __unstableSetEditorMode( originalEditingMode.current ); + } }; }, [] ); - // Intentionality left without any dependency. - // This effect should only run when the component is rendered and unmounted. - // The effect opens the zoom-out view if it is not open before when applying a style variation. + // The effect opens the zoom-out view if we want it open and it's not currently in zoom-out mode. useEffect( () => { - if ( mode !== 'zoom-out' ) { + if ( zoomOut && mode !== 'zoom-out' ) { __unstableSetEditorMode( 'zoom-out' ); - return () => { - // Revert to original mode - __unstableSetEditorMode( mode ); - }; + } else if ( ! zoomOut && originalEditingMode.current !== mode ) { + __unstableSetEditorMode( originalEditingMode.current ); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [] ); + }, [ __unstableSetEditorMode, zoomOut, mode ] ); } diff --git a/test/e2e/specs/editor/plugins/pattern-recursion.spec.js b/test/e2e/specs/editor/plugins/pattern-recursion.spec.js index 069f33d671d68..9a8292271be8e 100644 --- a/test/e2e/specs/editor/plugins/pattern-recursion.spec.js +++ b/test/e2e/specs/editor/plugins/pattern-recursion.spec.js @@ -66,7 +66,7 @@ test.describe( 'Preventing Pattern Recursion (server)', () => { // Click the Patterns tab await page.getByRole( 'tab', { name: 'Patterns' } ).click(); // Click the Uncategorized tab - await page.getByRole( 'button', { name: 'Uncategorized' } ).click(); + await page.getByRole( 'tab', { name: 'Uncategorized' } ).click(); // Click the Evil recursive pattern await page.getByRole( 'option', { name: 'Evil recursive' } ).click(); // By simply checking the editor content, we know that the pattern diff --git a/test/e2e/specs/editor/various/patterns.spec.js b/test/e2e/specs/editor/various/patterns.spec.js index 1d0807264413b..4c12847f93372 100644 --- a/test/e2e/specs/editor/various/patterns.spec.js +++ b/test/e2e/specs/editor/various/patterns.spec.js @@ -70,7 +70,7 @@ test.describe( 'Unsynced pattern', () => { } ) .click(); await page - .getByRole( 'button', { + .getByRole( 'tab', { name: newCategory, } ) .click(); @@ -185,7 +185,7 @@ test.describe( 'Synced pattern', () => { } ) .click(); await page - .getByRole( 'button', { + .getByRole( 'tab', { name: newCategory, } ) .click(); diff --git a/test/performance/specs/post-editor.spec.js b/test/performance/specs/post-editor.spec.js index e720396afe5da..c8010c79b1550 100644 --- a/test/performance/specs/post-editor.spec.js +++ b/test/performance/specs/post-editor.spec.js @@ -657,7 +657,7 @@ test.describe( 'Post Editor Performance', () => { const startTime = performance.now(); - await page.getByRole( 'button', { name: 'Test' } ).click(); + await page.getByText( 'Test' ).click(); await Promise.all( testPatterns.map( async ( pattern ) => {