diff --git a/package-lock.json b/package-lock.json
index ae8caf7b48e14..94c021c3f7a12 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10987,6 +10987,7 @@
"@wordpress/token-list": "file:packages/token-list",
"@wordpress/url": "file:packages/url",
"@wordpress/viewport": "file:packages/viewport",
+ "@wordpress/warning": "file:packages/warning",
"@wordpress/wordcount": "file:packages/wordcount",
"classnames": "^2.2.5",
"css-mediaquery": "^0.1.2",
@@ -10997,6 +10998,7 @@
"memize": "^1.1.0",
"react-autosize-textarea": "^3.0.2",
"react-spring": "^8.0.19",
+ "react-transition-group": "^2.9.0",
"redux-multi": "^0.1.12",
"refx": "^3.0.0",
"rememo": "^3.0.0",
@@ -24939,7 +24941,6 @@
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
- "dev": true,
"requires": {
"@babel/runtime": "^7.1.2"
}
@@ -46068,8 +46069,7 @@
"react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
- "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
- "dev": true
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-moment-proptypes": {
"version": "1.6.0",
@@ -47423,7 +47423,6 @@
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
- "dev": true,
"requires": {
"dom-helpers": "^3.4.0",
"loose-envify": "^1.4.0",
diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json
index 97f692f8079f6..1d9942b3bedfb 100644
--- a/packages/block-editor/package.json
+++ b/packages/block-editor/package.json
@@ -50,6 +50,7 @@
"@wordpress/token-list": "file:../token-list",
"@wordpress/url": "file:../url",
"@wordpress/viewport": "file:../viewport",
+ "@wordpress/warning": "file:../warning",
"@wordpress/wordcount": "file:../wordcount",
"classnames": "^2.2.5",
"css-mediaquery": "^0.1.2",
@@ -60,6 +61,7 @@
"memize": "^1.1.0",
"react-autosize-textarea": "^3.0.2",
"react-spring": "^8.0.19",
+ "react-transition-group": "^2.9.0",
"redux-multi": "^0.1.12",
"refx": "^3.0.0",
"rememo": "^3.0.0",
diff --git a/packages/block-editor/src/components/block-controls/index.js b/packages/block-editor/src/components/block-controls/index.js
index 297d3839df4aa..f2b35e4b5301f 100644
--- a/packages/block-editor/src/components/block-controls/index.js
+++ b/packages/block-editor/src/components/block-controls/index.js
@@ -20,18 +20,24 @@ import useDisplayBlockControls from '../use-display-block-controls';
const { Fill, Slot } = createSlotFill( 'BlockControls' );
-function BlockControlsSlot( props ) {
+function BlockControlsSlot( { __experimentalIsExpanded = false, ...props } ) {
const accessibleToolbarState = useContext( ToolbarContext );
- return ;
+ return (
+
+ );
}
-function BlockControlsFill( { controls, children } ) {
+function BlockControlsFill( { controls, __experimentalIsExpanded, children } ) {
if ( ! useDisplayBlockControls() ) {
return null;
}
return (
-
+
{ ( fillProps ) => {
// Children passed to BlockControlsFill will not have access to any
// React Context whose Provider is part of the BlockControlsSlot tree.
@@ -48,6 +54,9 @@ function BlockControlsFill( { controls, children } ) {
);
}
+const buildSlotName = ( isExpanded ) =>
+ `BlockControls${ isExpanded ? '-expanded' : '' }`;
+
const BlockControls = BlockControlsFill;
BlockControls.Slot = BlockControlsSlot;
diff --git a/packages/block-editor/src/components/block-toolbar/expanded-block-controls-container.js b/packages/block-editor/src/components/block-toolbar/expanded-block-controls-container.js
new file mode 100644
index 0000000000000..f32a61e3953ae
--- /dev/null
+++ b/packages/block-editor/src/components/block-toolbar/expanded-block-controls-container.js
@@ -0,0 +1,130 @@
+/**
+ * External dependencies
+ */
+import { TransitionGroup, CSSTransition } from 'react-transition-group';
+import { throttle } from 'lodash';
+
+/**
+ * WordPress dependencies
+ */
+import { useRef, useState, useEffect, useCallback } from '@wordpress/element';
+import warning from '@wordpress/warning';
+
+/**
+ * Internal dependencies
+ */
+import BlockControls from '../block-controls';
+
+export default function ExpandedBlockControlsContainer( {
+ children,
+ className,
+} ) {
+ return (
+
+ { ( fills ) => {
+ return (
+
+ { children }
+
+ );
+ } }
+
+ );
+}
+
+function ExpandedBlockControlsHandler( { fills, className = '', children } ) {
+ const containerRef = useRef();
+ const fillsRef = useRef();
+ const toolbarRef = useRef();
+ const [ dimensions, setDimensions ] = useState( {} );
+
+ const fillsPropRef = useRef();
+ fillsPropRef.current = fills;
+ const resizeToolbar = useCallback(
+ throttle( () => {
+ const toolbarContentElement = fillsPropRef.current.length
+ ? fillsRef.current
+ : toolbarRef.current;
+ if ( ! toolbarContentElement ) {
+ return;
+ }
+ toolbarContentElement.style.position = 'absolute';
+ toolbarContentElement.style.width = 'auto';
+ const contentCSS = window.getComputedStyle(
+ toolbarContentElement,
+ null
+ );
+ setDimensions( {
+ width: contentCSS.getPropertyValue( 'width' ),
+ height: contentCSS.getPropertyValue( 'height' ),
+ } );
+ toolbarContentElement.style.position = '';
+ toolbarContentElement.style.width = '';
+ }, 100 ),
+ []
+ );
+
+ useEffect( () => {
+ const observer = new window.MutationObserver( function (
+ mutationsList
+ ) {
+ const hasChildList = mutationsList.find(
+ ( { type } ) => type === 'childList'
+ );
+ if ( hasChildList ) {
+ resizeToolbar();
+ }
+ } );
+
+ observer.observe( containerRef.current, {
+ childList: true,
+ subtree: true,
+ } );
+
+ return () => observer.disconnect();
+ }, [] );
+
+ useEffect( () => {
+ if ( fills.length > 1 ) {
+ warning(
+ `${ fills.length } slots were registered but only one may be displayed.`
+ );
+ }
+ }, [ fills.length ] );
+
+ const displayFill = fills[ 0 ];
+ return (
+
+
+ { displayFill ? (
+
+
+ { displayFill }
+
+
+ ) : (
+
+
+ { children }
+
+
+ ) }
+
+
+ );
+}
diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js
index daab7d6fe91db..7e3a25c3999de 100644
--- a/packages/block-editor/src/components/block-toolbar/index.js
+++ b/packages/block-editor/src/components/block-toolbar/index.js
@@ -22,8 +22,12 @@ import BlockFormatControls from '../block-format-controls';
import BlockSettingsMenu from '../block-settings-menu';
import BlockDraggable from '../block-draggable';
import { useShowMoversGestures, useToggleBlockHighlight } from './utils';
+import ExpandedBlockControlsContainer from './expanded-block-controls-container';
-export default function BlockToolbar( { hideDragHandle } ) {
+export default function BlockToolbar( {
+ hideDragHandle,
+ __experimentalExpandedControl = false,
+} ) {
const {
blockClientIds,
blockClientId,
@@ -94,8 +98,12 @@ export default function BlockToolbar( { hideDragHandle } ) {
shouldShowMovers && 'is-showing-movers'
);
+ const Wrapper = __experimentalExpandedControl
+ ? ExpandedBlockControlsContainer
+ : 'div';
+
return (
-
+
);
}
diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss
index 63763752f08cd..dd31af089e651 100644
--- a/packages/block-editor/src/components/block-toolbar/style.scss
+++ b/packages/block-editor/src/components/block-toolbar/style.scss
@@ -103,3 +103,31 @@
display: block;
}
}
+
+.block-editor-block-toolbar-animated-width-container {
+ position: relative;
+ overflow: hidden;
+ transition: width 300ms;
+}
+
+.block-editor-block-toolbar-content-enter {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: auto;
+ opacity: 0;
+}
+.block-editor-block-toolbar-content-enter-active {
+ position: absolute;
+ opacity: 1;
+ transition: opacity 300ms;
+}
+.block-editor-block-toolbar-content-exit {
+ width: auto;
+ opacity: 1;
+ pointer-events: none;
+}
+.block-editor-block-toolbar-content-exit-active {
+ opacity: 0;
+ transition: opacity 300ms;
+}
diff --git a/packages/edit-navigation/src/components/navigation-editor/block-editor-area.js b/packages/edit-navigation/src/components/navigation-editor/block-editor-area.js
index 37161baf64ba8..19dc32e59ea27 100644
--- a/packages/edit-navigation/src/components/navigation-editor/block-editor-area.js
+++ b/packages/edit-navigation/src/components/navigation-editor/block-editor-area.js
@@ -109,7 +109,10 @@ export default function BlockEditorArea( {
aria-label={ __( 'Block tools' ) }
>
{ hasSelectedBlock && ! isRootBlockSelected && (
-
+
) }