- { ! isMultiToolbar &&
- ! displayHeaderToolbar &&
- ! isContentLocked &&
{ ( shouldShowVisualToolbar || isMultiToolbar ) &&
! isContentLocked && (
diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss
index 554c5d96d65dbb..fa838928ba7b9e 100644
--- a/packages/block-editor/src/components/block-toolbar/style.scss
+++ b/packages/block-editor/src/components/block-toolbar/style.scss
@@ -56,26 +56,46 @@
}
}
-.block-editor-block-contextual-toolbar.has-parent:not(.is-fixed) {
- margin-left: calc(#{$grid-unit-60} + #{$grid-unit-10});
+// on desktop browsers the fixed toolbar has tweaked borders
+@include break-medium() {
+ .block-editor-block-contextual-toolbar.is-fixed {
+ .block-editor-block-toolbar {
+ .components-toolbar-group,
+ .components-toolbar {
+ border-right: none;
+
+ &::after {
+ content: "";
+ width: $border-width;
+ margin-top: $grid-unit + $grid-unit-05;
+ margin-bottom: $grid-unit + $grid-unit-05;
+ background-color: $gray-300;
+ margin-left: $grid-unit;
+ }
+
+ & .components-toolbar-group.components-toolbar-group {
+ &::after {
+ display: none;
+ }
+ }
+ }
- .show-icon-labels & {
- margin-left: 0;
+ > :last-child,
+ > :last-child .components-toolbar-group,
+ > :last-child .components-toolbar {
+ &::after {
+ display: none;
+ }
+ }
+ }
}
}
-.block-editor-block-parent-selector {
- position: absolute;
- top: -$border-width;
- left: calc(-#{$grid-unit-60} - #{$grid-unit-10} - #{$border-width});
+.block-editor-block-contextual-toolbar.has-parent:not(.is-fixed) {
+ margin-left: calc(#{$grid-unit-60} + #{$grid-unit-10});
.show-icon-labels & {
- position: relative;
- left: auto;
- top: auto;
- margin-top: -$border-width;
- margin-left: -$border-width;
- margin-bottom: -$border-width;
+ margin-left: 0;
}
}
diff --git a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js
index ba31043ada2dd8..c8eee6b48cbd40 100644
--- a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js
+++ b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js
@@ -7,8 +7,16 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
+import { forwardRef, useEffect, useRef, useState } from '@wordpress/element';
import { hasBlockSupport, store as blocksStore } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
+import {
+ ToolbarItem,
+ ToolbarButton,
+ ToolbarGroup,
+} from '@wordpress/components';
+import { levelUp } from '@wordpress/icons';
+import { useViewportMatch } from '@wordpress/compose';
/**
* Internal dependencies
@@ -16,10 +24,57 @@ import { useSelect } from '@wordpress/data';
import NavigableToolbar from '../navigable-toolbar';
import BlockToolbar from '../block-toolbar';
import { store as blockEditorStore } from '../../store';
+import BlockIcon from '../block-icon';
+
+const CollapseFixedToolbarButton = forwardRef( ( { onClick }, ref ) => {
+ return (
+
+ );
+} );
+
+const ExpandFixedToolbarButton = forwardRef( ( { onClick, icon }, ref ) => {
+ return (
+
}
+ onClick={ onClick }
+ ref={ ref }
+ label={ __( 'Show block tools' ) }
+ />
+ );
+} );
function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
- const { blockType, hasParents, showParentSelector } = useSelect(
- ( select ) => {
+ // When the toolbar is fixed it can be collapsed
+ const [ isCollapsed, setIsCollapsed ] = useState( false );
+ const expandFixedToolbarButtonRef = useRef();
+ const collapseFixedToolbarButtonRef = useRef();
+
+ // Don't focus the block toolbar just because it mounts
+ const initialRender = useRef( true );
+ useEffect( () => {
+ if ( initialRender.current ) {
+ initialRender.current = false;
+ return;
+ }
+ if ( isCollapsed && expandFixedToolbarButtonRef.current ) {
+ expandFixedToolbarButtonRef.current.focus();
+ }
+ if ( ! isCollapsed && collapseFixedToolbarButtonRef.current ) {
+ collapseFixedToolbarButtonRef.current.focus();
+ }
+ }, [ isCollapsed ] );
+
+ const { blockType, hasParents, showParentSelector, selectedBlockClientId } =
+ useSelect( ( select ) => {
const {
getBlockName,
getBlockParents,
@@ -28,16 +83,17 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
} = select( blockEditorStore );
const { getBlockType } = select( blocksStore );
const selectedBlockClientIds = getSelectedBlockClientIds();
- const selectedBlockClientId = selectedBlockClientIds[ 0 ];
- const parents = getBlockParents( selectedBlockClientId );
+ const _selectedBlockClientId = selectedBlockClientIds[ 0 ];
+ const parents = getBlockParents( _selectedBlockClientId );
const firstParentClientId = parents[ parents.length - 1 ];
const parentBlockName = getBlockName( firstParentClientId );
const parentBlockType = getBlockType( parentBlockName );
return {
+ selectedBlockClientId: _selectedBlockClientId,
blockType:
- selectedBlockClientId &&
- getBlockType( getBlockName( selectedBlockClientId ) ),
+ _selectedBlockClientId &&
+ getBlockType( getBlockName( _selectedBlockClientId ) ),
hasParents: parents.length,
showParentSelector:
parentBlockType &&
@@ -48,12 +104,16 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
) &&
selectedBlockClientIds.length <= 1 &&
! __unstableGetContentLockingParent(
- selectedBlockClientId
+ _selectedBlockClientId
),
};
- },
- []
- );
+ }, [] );
+
+ useEffect( () => {
+ setIsCollapsed( false );
+ }, [ selectedBlockClientId ] );
+
+ const isLargeViewport = useViewportMatch( 'medium' );
if ( blockType ) {
if ( ! hasBlockSupport( blockType, '__experimentalToolbar', true ) ) {
@@ -65,6 +125,7 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
const classes = classnames( 'block-editor-block-contextual-toolbar', {
'has-parent': hasParents && showParentSelector,
'is-fixed': isFixed,
+ 'is-collapsed': isCollapsed,
} );
return (
@@ -75,7 +136,29 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
aria-label={ __( 'Block tools' ) }
{ ...props }
>
-
+ { isFixed && isLargeViewport && blockType && (
+
+ { isCollapsed ? (
+ setIsCollapsed( false ) }
+ icon={ blockType.icon }
+ ref={ expandFixedToolbarButtonRef }
+ />
+ ) : (
+ setIsCollapsed( true ) }
+ ref={ collapseFixedToolbarButtonRef }
+ />
+ ) }
+
+ ) }
+ { ! isCollapsed &&
}
);
}
diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss
index 099bf14987dcba..9194935c616b27 100644
--- a/packages/block-editor/src/components/block-tools/style.scss
+++ b/packages/block-editor/src/components/block-tools/style.scss
@@ -104,11 +104,10 @@
&.is-fixed {
position: sticky;
top: 0;
- width: 100%;
+ left: 0;
z-index: z-index(".block-editor-block-popover");
- // Fill up when empty
- min-height: $block-toolbar-height;
display: block;
+ width: 100%;
border: none;
border-bottom: $border-width solid $gray-200;
@@ -119,6 +118,226 @@
border-right-color: $gray-200;
}
}
+
+ // on desktop and tablet viewports the toolbar is fixed
+ // on top of interface header
+ @include break-medium() {
+ &.is-fixed {
+
+ // position on top of interface header
+ position: fixed;
+ top: $grid-unit-50 - 2;
+ // leave room for block inserter
+ left: $grid-unit-80 + $grid-unit-40;
+ // Don't fill up when empty
+ min-height: initial;
+ // remove the border
+ border-bottom: none;
+ // has to be flex for collapse button to fit
+ display: flex;
+
+ &.is-collapsed {
+ left: $grid-unit-10 * 32;
+ }
+
+ .is-fullscreen-mode & {
+ top: $grid-unit - 2;
+ // leave room for block inserter
+ left: $grid-unit-80 + $grid-unit-70;
+ &.is-collapsed {
+ left: $grid-unit-10 * 35;
+ }
+ }
+
+ & > .block-editor-block-toolbar__group-collapse-fixed-toolbar {
+ border: none;
+
+ // Add a border as separator in the block toolbar.
+ &::after {
+ content: "";
+ width: $border-width;
+ height: 24px;
+ margin-top: $grid-unit + $grid-unit-05;
+ margin-bottom: $grid-unit + $grid-unit-05;
+ background-color: $gray-300;
+ position: absolute;
+ left: 44px;
+ top: -1px;
+ }
+ }
+
+ & > .block-editor-block-toolbar__group-expand-fixed-toolbar {
+ border: none;
+
+ // Add a border as separator in the block toolbar.
+ &::before {
+ content: "";
+ width: $border-width;
+ margin-top: $grid-unit + $grid-unit-05;
+ margin-bottom: $grid-unit + $grid-unit-05;
+ background-color: $gray-300;
+ position: relative;
+ left: -12px; //the padding of buttons
+ height: 24px;
+ }
+ }
+
+ .show-icon-labels & {
+ left: $grid-unit-80 + $grid-unit-50;
+
+ &.is-collapsed {
+ left: $grid-unit-10 * 56;
+ }
+
+ .is-fullscreen-mode & {
+ left: $grid-unit-80 + $grid-unit-80;
+ }
+
+ .block-editor-block-parent-selector .block-editor-block-parent-selector__button::after {
+ left: 0;
+ }
+
+ .block-editor-block-toolbar__block-controls .block-editor-block-mover {
+ border-left: none;
+ &::before {
+ content: "";
+ width: $border-width;
+ margin-top: $grid-unit + $grid-unit-05;
+ margin-bottom: $grid-unit + $grid-unit-05;
+ background-color: $gray-300;
+ position: relative;
+ }
+ }
+ }
+ }
+
+ &.is-fixed .block-editor-block-parent-selector {
+ .block-editor-block-parent-selector__button {
+ position: relative;
+ top: -1px;
+ border: 0;
+ padding-right: 6px;
+ padding-left: 6px;
+ &::after {
+ content: "\00B7";
+ font-size: 16px;
+ line-height: $grid-unit-40 + $grid-unit-10;
+ position: absolute;
+ left: $grid-unit-40 + $grid-unit-15 + 2px;
+ bottom: $grid-unit-05;
+ }
+ }
+ }
+
+ &:not(.is-fixed) .block-editor-block-parent-selector {
+ position: absolute;
+ top: -$border-width;
+ left: calc(-#{$grid-unit-60} - #{$grid-unit-10} - #{$border-width});
+
+ .show-icon-labels & {
+ position: relative;
+ left: auto;
+ top: auto;
+ margin-top: -$border-width;
+ margin-left: -$border-width;
+ margin-bottom: -$border-width;
+ }
+ }
+ }
+
+ // on tablet vewports the toolbar is fixed
+ // on top of interface header and covers the whole header
+ // except for the inserter on the left
+ @include break-medium() {
+ &.is-fixed {
+
+ left: 28 * $grid-unit;
+ width: calc(100% - #{28 * $grid-unit});
+
+ &.is-collapsed {
+ // when collapsed minimize area
+ width: initial;
+ left: $grid-unit * 48;
+ }
+
+ // collapsed wp admin sidebar when not in full screen mode
+ .auto-fold & {
+ left: $grid-unit-80 + $grid-unit-40;
+ width: calc(100% - #{$grid-unit-80 + $grid-unit-40});
+
+ &.is-collapsed {
+ left: $grid-unit * 32;
+ }
+ }
+
+ .is-fullscreen-mode & {
+ width: calc(100% - #{$grid-unit-80 + $grid-unit-70});
+ left: $grid-unit-80 + $grid-unit-70;
+ &.is-collapsed {
+ left: $grid-unit * 36;
+ // when collapsed minimize area
+ width: initial;
+ }
+ }
+ }
+ }
+
+ // on desktop viewports the toolbar is fixed
+ // on top of interface header and leaves room
+ // for the block inserter the publish button
+ @include break-large() {
+ &.is-fixed {
+
+ .auto-fold & {
+ // Don't fill the whole header, minimize area
+ width: initial;
+
+ // leave room for block inserter and the dashboard navigation
+ left: $grid-unit-80 + $grid-unit-40 + ( $grid-unit-80 * 2 );
+
+ &.is-collapsed {
+ // when collapsed minimize area
+ width: initial;
+ left: $grid-unit * 48;
+ }
+
+ }
+
+ // collapsed wp admin sidebar when not in full screen mode
+ .auto-fold.folded & {
+ width: initial;
+ left: $grid-unit-80 + $grid-unit-40;
+
+ &.is-collapsed {
+ // when collapsed minimize area
+ width: initial;
+ left: $grid-unit * 32;
+ }
+
+ }
+
+ .auto-fold.is-fullscreen-mode & {
+ // Don't fill the whole header, minimize area
+ width: initial;
+ left: $grid-unit-80 + $grid-unit-70;
+
+ &.is-collapsed {
+ // when collapsed minimize area
+ width: initial;
+ left: $grid-unit * 36;
+ }
+ }
+
+ .auto-fold.is-fullscreen-mode .show-icon-labels & {
+ left: $grid-unit-80 * 2;
+ &.is-collapsed {
+ left: $grid-unit * 48;
+ }
+ }
+
+ }
+ }
+
}
/**
diff --git a/packages/e2e-tests/specs/editor/various/navigable-toolbar.test.js b/packages/e2e-tests/specs/editor/various/navigable-toolbar.test.js
index ec93851707849c..8fb2edf4f6768d 100644
--- a/packages/e2e-tests/specs/editor/various/navigable-toolbar.test.js
+++ b/packages/e2e-tests/specs/editor/various/navigable-toolbar.test.js
@@ -11,38 +11,12 @@ async function isInBlockToolbar() {
} );
}
-describe.each( [
- [ 'unified', true ],
- [ 'contextual', false ],
-] )( 'block toolbar (%s: %p)', ( label, isUnifiedToolbar ) => {
+describe( 'Block Toolbar', () => {
beforeEach( async () => {
await createNewPost();
-
- await page.evaluate( ( _isUnifiedToolbar ) => {
- const { select, dispatch } = wp.data;
- const isCurrentlyUnified =
- select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' );
- if ( isCurrentlyUnified !== _isUnifiedToolbar ) {
- dispatch( 'core/edit-post' ).toggleFeature( 'fixedToolbar' );
- }
- }, isUnifiedToolbar );
} );
- it( 'navigates in and out of toolbar by keyboard (Alt+F10, Escape)', async () => {
- // Assumes new post focus starts in title. Create first new
- // block by Enter.
- await page.keyboard.press( 'Enter' );
-
- // [TEMPORARY]: A new paragraph is not technically a block yet
- // until starting to type within it.
- await page.keyboard.type( 'Example' );
-
- // Upward.
- await pressKeyWithModifier( 'alt', 'F10' );
- expect( await isInBlockToolbar() ).toBe( true );
- } );
-
- if ( ! isUnifiedToolbar ) {
+ describe( 'Contextual Toolbar', () => {
it( 'should not scroll page', async () => {
while (
await page.evaluate( () => {
@@ -74,5 +48,57 @@ describe.each( [
expect( scrollTopBefore ).toBe( scrollTopAfter );
} );
- }
+
+ it( 'navigates into the toolbar by keyboard (Alt+F10)', async () => {
+ // Assumes new post focus starts in title. Create first new
+ // block by Enter.
+ await page.keyboard.press( 'Enter' );
+
+ // [TEMPORARY]: A new paragraph is not technically a block yet
+ // until starting to type within it.
+ await page.keyboard.type( 'Example' );
+
+ // Upward.
+ await pressKeyWithModifier( 'alt', 'F10' );
+
+ expect( await isInBlockToolbar() ).toBe( true );
+ } );
+ } );
+
+ describe( 'Unified Toolbar', () => {
+ beforeEach( async () => {
+ // Enable unified toolbar
+ await page.evaluate( () => {
+ const { select, dispatch } = wp.data;
+ const isCurrentlyUnified =
+ select( 'core/edit-post' ).isFeatureActive(
+ 'fixedToolbar'
+ );
+ if ( ! isCurrentlyUnified ) {
+ dispatch( 'core/edit-post' ).toggleFeature(
+ 'fixedToolbar'
+ );
+ }
+ } );
+ } );
+
+ it( 'navigates into the toolbar by keyboard (Alt+F10)', async () => {
+ // Assumes new post focus starts in title. Create first new
+ // block by Enter.
+ await page.keyboard.press( 'Enter' );
+
+ // [TEMPORARY]: A new paragraph is not technically a block yet
+ // until starting to type within it.
+ await page.keyboard.type( 'Example' );
+
+ // Upward.
+ await pressKeyWithModifier( 'alt', 'F10' );
+
+ expect(
+ await page.evaluate( () => {
+ return document.activeElement.getAttribute( 'aria-label' );
+ } )
+ ).toBe( 'Show document tools' );
+ } );
+ } );
} );
diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss
index 229ab58a4e14b0..3fdb90dea8555f 100644
--- a/packages/edit-post/src/components/layout/style.scss
+++ b/packages/edit-post/src/components/layout/style.scss
@@ -99,3 +99,15 @@
.edit-post-layout .entities-saved-states__panel-header {
height: $header-height + $border-width;
}
+
+.edit-post-layout.has-fixed-toolbar {
+ // making the header be lower than the content
+ // so the fixed toolbar can be positioned on top of it
+ // but only on desktop
+ @include break-medium() {
+ .interface-interface-skeleton__header:not(:focus-within) {
+ z-index: 19;
+ }
+ }
+
+}
diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js
index 7ea454da4f150a..609d677c07b3d6 100644
--- a/packages/edit-site/src/components/layout/index.js
+++ b/packages/edit-site/src/components/layout/index.js
@@ -23,6 +23,7 @@ import { useState, useRef } from '@wordpress/element';
import { NavigableRegion } from '@wordpress/interface';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
import { CommandMenu } from '@wordpress/commands';
+import { store as preferencesStore } from '@wordpress/preferences';
/**
* Internal dependencies
@@ -68,8 +69,8 @@ export default function Layout() {
const { params } = useLocation();
const isListPage = getIsListPage( params );
const isEditorPage = ! isListPage;
- const { canvasMode, previousShortcut, nextShortcut } = useSelect(
- ( select ) => {
+ const { hasFixedToolbar, canvasMode, previousShortcut, nextShortcut } =
+ useSelect( ( select ) => {
const { getAllShortcutKeyCombinations } = select(
keyboardShortcutsStore
);
@@ -82,10 +83,10 @@ export default function Layout() {
nextShortcut: getAllShortcutKeyCombinations(
'core/edit-site/next-region'
),
+ hasFixedToolbar:
+ select( preferencesStore ).get( 'fixedToolbar' ),
};
- },
- []
- );
+ }, [] );
const navigateRegionsProps = useNavigateRegions( {
previous: previousShortcut,
next: nextShortcut,
@@ -139,6 +140,7 @@ export default function Layout() {
{
'is-full-canvas': isFullCanvas,
'is-edit-mode': canvasMode === 'edit',
+ 'has-fixed-toolbar': hasFixedToolbar,
}
) }
>
diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss
index 03c206eaf7f5e5..c069c471a361ee 100644
--- a/packages/edit-site/src/components/layout/style.scss
+++ b/packages/edit-site/src/components/layout/style.scss
@@ -221,3 +221,18 @@
border-left: $border-width solid $gray-300;
}
}
+
+.edit-site-layout.has-fixed-toolbar {
+ // making the header be lower than the content
+ // so the fixed toolbar can be positioned on top of it
+ // but only on desktop
+ @include break-medium() {
+ .edit-site-site-hub {
+ z-index: 4;
+ }
+ .edit-site-layout__header:focus-within {
+ z-index: 3;
+ }
+ }
+
+}
diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js
index ba9d28ee266f12..c3f0380728e55c 100644
--- a/packages/icons/src/index.js
+++ b/packages/icons/src/index.js
@@ -120,6 +120,7 @@ export { default as key } from './library/key';
export { default as keyboardClose } from './library/keyboard-close';
export { default as keyboardReturn } from './library/keyboard-return';
export { default as layout } from './library/layout';
+export { default as levelUp } from './library/level-up';
export { default as lifesaver } from './library/lifesaver';
export { default as lineDashed } from './library/line-dashed';
export { default as lineDotted } from './library/line-dotted';
diff --git a/packages/icons/src/library/level-up.js b/packages/icons/src/library/level-up.js
new file mode 100644
index 00000000000000..fc992c8dbada56
--- /dev/null
+++ b/packages/icons/src/library/level-up.js
@@ -0,0 +1,12 @@
+/**
+ * WordPress dependencies
+ */
+import { SVG, Path } from '@wordpress/primitives';
+
+const levelUp = (
+
+);
+
+export default levelUp;