diff --git a/package-lock.json b/package-lock.json
index 589041da45289b..9a482bd601967d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10980,6 +10980,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",
@@ -24913,7 +24914,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"
}
@@ -46042,8 +46042,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",
@@ -47397,7 +47396,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 31647fcb2742b5..fd7ab7a57c396a 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 297d3839df4aa9..f2b35e4b5301f8 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/customizable-content.js b/packages/block-editor/src/components/block-toolbar/customizable-content.js
new file mode 100644
index 00000000000000..2c8bcdd60653ab
--- /dev/null
+++ b/packages/block-editor/src/components/block-toolbar/customizable-content.js
@@ -0,0 +1,134 @@
+/**
+ * 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 CustomizableBlockToolbarContent( {
+ children,
+ className,
+} ) {
+ return (
+
+ { ( fills ) => {
+ return (
+
+ { children }
+
+ );
+ } }
+
+ );
+}
+
+function CustomizableBlockToolbarContentChildren( {
+ fills,
+ className = '',
+ children,
+} ) {
+ const containerRef = useRef();
+ const fillsRef = useRef();
+ const toolbarRef = useRef();
+ const [ dimensions, setDimensions ] = useState( {} );
+
+ const fillsPropRef = useRef();
+ fillsPropRef.current = fills;
+ const resize = useCallback(
+ throttle( ( elem ) => {
+ if ( ! elem ) {
+ elem = fillsPropRef.current.length
+ ? fillsRef.current
+ : toolbarRef.current;
+ }
+ if ( ! elem ) {
+ return;
+ }
+ elem.style.position = 'absolute';
+ elem.style.width = 'auto';
+ const css = window.getComputedStyle( elem, null );
+ setDimensions( {
+ width: css.getPropertyValue( 'width' ),
+ height: css.getPropertyValue( 'height' ),
+ } );
+ elem.style.position = '';
+ elem.style.width = '';
+ }, 100 ),
+ []
+ );
+
+ useEffect( () => {
+ // Create an observer instance linked to the callback function
+ const observer = new window.MutationObserver( function (
+ mutationsList
+ ) {
+ const hasChildList = mutationsList.find(
+ ( { type } ) => type === 'childList'
+ );
+ if ( hasChildList ) {
+ resize();
+ }
+ } );
+
+ // Start observing the target node for configured mutations
+ 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 ] );
+
+ return (
+
+
+ { fills.length ? (
+
+
+ { fills[ 0 ] }
+
+
+ ) : (
+
+
+ { children }
+
+
+ ) }
+
+
+ );
+}
diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js
index f4014ff2fe4294..10b1165344157d 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 CustomizableBlockToolbarContent from './customizable-content';
-export default function BlockToolbar( { hideDragHandle } ) {
+export default function BlockToolbar( {
+ hideDragHandle,
+ __experimentalCustomizableContent = false,
+} ) {
const {
blockClientIds,
blockClientId,
@@ -100,8 +104,12 @@ export default function BlockToolbar( { hideDragHandle } ) {
shouldShowMovers && 'is-showing-movers'
);
+ const Wrapper = __experimentalCustomizableContent
+ ? CustomizableBlockToolbarContent
+ : '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 dd9e83f5c658a5..d5ce086ba0915c 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-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/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js
index 29d30643acb8f8..5617922d7e8ff5 100644
--- a/packages/block-library/src/navigation-link/edit.js
+++ b/packages/block-library/src/navigation-link/edit.js
@@ -10,6 +10,7 @@ import { escape, get, head, find } from 'lodash';
import { compose } from '@wordpress/compose';
import { createBlock } from '@wordpress/blocks';
import { withDispatch, withSelect } from '@wordpress/data';
+import { external as externalIcon } from '@wordpress/icons';
import {
ExternalLink,
KeyboardShortcuts,
@@ -19,6 +20,7 @@ import {
ToggleControl,
ToolbarButton,
ToolbarGroup,
+ __experimentalToolbarItem as ToolbarItem,
} from '@wordpress/components';
import { rawShortcut, displayShortcut } from '@wordpress/keycodes';
import { __ } from '@wordpress/i18n';
@@ -136,6 +138,42 @@ function NavigationLinkEdit( {
return (
+ { isLinkOpen && (
+
+
+
+ { ( toolbarItemProps ) => (
+ {} }
+ />
+ ) }
+
+
+
+
+ setIsLinkOpen( false ) }
+ className="navigation-link-edit-link-done"
+ >
+ Done
+
+
+
+ ) }
- { isLinkOpen && (
- setIsLinkOpen( false ) }
- >
-
- setAttributes( {
- url: encodeURI( newURL ),
- label: ( () => {
- const normalizedTitle = newTitle.replace(
- /http(s?):\/\//gi,
- ''
- );
- const normalizedURL = newURL.replace(
- /http(s?):\/\//gi,
- ''
- );
- if (
- newTitle !== '' &&
- normalizedTitle !==
- normalizedURL &&
- label !== newTitle
- ) {
- return escape( newTitle );
- } else if ( label ) {
- return label;
- }
- // If there's no label, add the URL.
- return escape( normalizedURL );
- } )(),
- opensInNewTab: newOpensInNewTab,
- id,
- } )
- }
- />
-
- ) }
{ showSubmenuIcon && (
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 37161baf64ba8b..1a5692e8f6efc7 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 && (
-
+
) }