Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stacked/unified block toolbar #31134

Merged
merged 3 commits into from
May 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion packages/block-editor/README.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ _This package assumes that your code will run in an **ES2015+** environment. If
import { import {
BlockEditorProvider, BlockEditorProvider,
BlockList, BlockList,
BlockTools,
WritingFlow, WritingFlow,
ObserveTyping, ObserveTyping,
} from '@wordpress/block-editor'; } from '@wordpress/block-editor';
Expand All @@ -34,12 +35,13 @@ function MyEditorComponent() {
onChange={ ( blocks ) => updateBlocks( blocks ) } onChange={ ( blocks ) => updateBlocks( blocks ) }
> >
<SlotFillProvider> <SlotFillProvider>
<Popover.Slot name="block-toolbar" /> <BlockTools>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@youknowriad I think you'll be happy to see this 😄

<WritingFlow> <WritingFlow>
<ObserveTyping> <ObserveTyping>
<BlockList /> <BlockList />
</ObserveTyping> </ObserveTyping>
</WritingFlow> </WritingFlow>
</BlockTools>
<Popover.Slot /> <Popover.Slot />
</SlotFillProvider> </SlotFillProvider>
</BlockEditorProvider> </BlockEditorProvider>
Expand Down Expand Up @@ -226,6 +228,17 @@ _Returns_


Undocumented declaration. Undocumented declaration.


<a name="BlockTools" href="#BlockTools">#</a> **BlockTools**

Renders block tools (the block toolbar, select/navigation mode toolbar, the
insertion point and a slot for the inline rich text toolbar). Must be wrapped
around the block content and editor styles wrapper or iframe.

_Parameters_

- _$0_ `Object`: Props.
- _$0.children_ `Object`: The block content and style container.

<a name="BlockVerticalAlignmentControl" href="#BlockVerticalAlignmentControl">#</a> **BlockVerticalAlignmentControl** <a name="BlockVerticalAlignmentControl" href="#BlockVerticalAlignmentControl">#</a> **BlockVerticalAlignmentControl**


Undocumented declaration. Undocumented declaration.
Expand Down
6 changes: 3 additions & 3 deletions packages/block-editor/src/components/block-list/index.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import BlockListBlock from './block';
import BlockListAppender from '../block-list-appender'; import BlockListAppender from '../block-list-appender';
import useBlockDropZone from '../use-block-drop-zone'; import useBlockDropZone from '../use-block-drop-zone';
import { useInBetweenInserter } from './use-in-between-inserter'; import { useInBetweenInserter } from './use-in-between-inserter';
import BlockTools from '../block-tools';
import { store as blockEditorStore } from '../../store'; import { store as blockEditorStore } from '../../store';
import { usePreParsePatterns } from '../../utils/pre-parse-patterns'; import { usePreParsePatterns } from '../../utils/pre-parse-patterns';
import { LayoutProvider, defaultLayout } from './layout'; import { LayoutProvider, defaultLayout } from './layout';
import BlockToolsBackCompat from '../block-tools/back-compat';


function Root( { className, children } ) { function Root( { className, children } ) {
const isLargeViewport = useViewportMatch( 'medium' ); const isLargeViewport = useViewportMatch( 'medium' );
Expand Down Expand Up @@ -67,11 +67,11 @@ function Root( { className, children } ) {
export default function BlockList( { className, __experimentalLayout } ) { export default function BlockList( { className, __experimentalLayout } ) {
usePreParsePatterns(); usePreParsePatterns();
return ( return (
<BlockTools> <BlockToolsBackCompat>
<Root className={ className }> <Root className={ className }>
<BlockListItems __experimentalLayout={ __experimentalLayout } /> <BlockListItems __experimentalLayout={ __experimentalLayout } />
</Root> </Root>
</BlockTools> </BlockToolsBackCompat>
); );
} }


Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
} }
} }


.block-editor-block-contextual-toolbar.has-parent { .block-editor-block-contextual-toolbar.has-parent:not(.is-fixed) {
margin-left: calc(#{$grid-unit-60} + #{$grid-unit-10}); margin-left: calc(#{$grid-unit-60} + #{$grid-unit-10});


.show-icon-labels & { .show-icon-labels & {
Expand Down
33 changes: 33 additions & 0 deletions packages/block-editor/src/components/block-tools/back-compat.js
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* WordPress dependencies
*/
import { useContext } from '@wordpress/element';
import { Disabled } from '@wordpress/components';
import deprecated from '@wordpress/deprecated';

/**
* Internal dependencies
*/
import InsertionPoint, { InsertionPointOpenRef } from './insertion-point';
import BlockPopover from './block-popover';

export default function BlockToolsBackCompat( { children } ) {
const openRef = useContext( InsertionPointOpenRef );
const isDisabled = useContext( Disabled.Context );

// If context is set, `BlockTools` is a parent component.
if ( openRef || isDisabled ) {
return children;
}

deprecated( 'wp.components.Popover.Slot name="block-toolbar"', {
alternative: 'wp.blockEditor.BlockTools',
} );

return (
<InsertionPoint __unstablePopoverSlot="block-toolbar">
<BlockPopover __unstablePopoverSlot="block-toolbar" />
{ children }
</InsertionPoint>
);
}
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import { useSelect } from '@wordpress/data';
* Internal dependencies * Internal dependencies
*/ */
import NavigableToolbar from '../navigable-toolbar'; import NavigableToolbar from '../navigable-toolbar';
import { BlockToolbar } from '../'; import BlockToolbar from '../block-toolbar';
import { store as blockEditorStore } from '../../store'; import { store as blockEditorStore } from '../../store';


function BlockContextualToolbar( { focusOnMount, ...props } ) { function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
const { blockType, hasParents } = useSelect( ( select ) => { const { blockType, hasParents } = useSelect( ( select ) => {
const { const {
getBlockName, getBlockName,
Expand All @@ -43,20 +43,19 @@ function BlockContextualToolbar( { focusOnMount, ...props } ) {
// Shifts the toolbar to make room for the parent block selector. // Shifts the toolbar to make room for the parent block selector.
const classes = classnames( 'block-editor-block-contextual-toolbar', { const classes = classnames( 'block-editor-block-contextual-toolbar', {
'has-parent': hasParents, 'has-parent': hasParents,
'is-fixed': isFixed,
} ); } );


return ( return (
<div className="block-editor-block-contextual-toolbar-wrapper">
<NavigableToolbar <NavigableToolbar
focusOnMount={ focusOnMount } focusOnMount={ focusOnMount }
className={ classes } className={ classes }
/* translators: accessibility text for the block toolbar */ /* translators: accessibility text for the block toolbar */
aria-label={ __( 'Block tools' ) } aria-label={ __( 'Block tools' ) }
{ ...props } { ...props }
> >
<BlockToolbar /> <BlockToolbar hideDragHandle={ isFixed } />
</NavigableToolbar> </NavigableToolbar>
</div>
); );
} }


Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function BlockPopover( {
isValid, isValid,
isEmptyDefaultBlock, isEmptyDefaultBlock,
capturingClientId, capturingClientId,
__unstablePopoverSlot,
} ) { } ) {
const { const {
isNavigationMode, isNavigationMode,
Expand Down Expand Up @@ -180,7 +181,9 @@ function BlockPopover( {
anchorRef={ anchorRef } anchorRef={ anchorRef }
className="block-editor-block-list__block-popover" className="block-editor-block-list__block-popover"
__unstableStickyBoundaryElement={ stickyBoundaryElement } __unstableStickyBoundaryElement={ stickyBoundaryElement }
__unstableSlotName="block-toolbar" // Render in the old slot if needed for backward compatibility,
// otherwise render in place (not in the the default popover slot).
__unstableSlotName={ __unstablePopoverSlot || null }
__unstableBoundaryParent __unstableBoundaryParent
// Observe movement for block animations (especially horizontal). // Observe movement for block animations (especially horizontal).
__unstableObserveElement={ node } __unstableObserveElement={ node }
Expand Down Expand Up @@ -293,7 +296,7 @@ function wrapperSelector( select ) {
}; };
} }


export default function WrappedBlockPopover() { export default function WrappedBlockPopover( { __unstablePopoverSlot } ) {
const selected = useSelect( wrapperSelector, [] ); const selected = useSelect( wrapperSelector, [] );


if ( ! selected ) { if ( ! selected ) {
Expand All @@ -320,6 +323,7 @@ export default function WrappedBlockPopover() {
isValid={ isValid } isValid={ isValid }
isEmptyDefaultBlock={ isEmptyDefaultBlock } isEmptyDefaultBlock={ isEmptyDefaultBlock }
capturingClientId={ capturingClientId } capturingClientId={ capturingClientId }
__unstablePopoverSlot={ __unstablePopoverSlot }
/> />
); );
} }
32 changes: 32 additions & 0 deletions packages/block-editor/src/components/block-tools/index.js
Original file line number Original file line Diff line number Diff line change
@@ -1,14 +1,46 @@
/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { useViewportMatch } from '@wordpress/compose';
import { Popover } from '@wordpress/components';

/** /**
* Internal dependencies * Internal dependencies
*/ */
import InsertionPoint from './insertion-point'; import InsertionPoint from './insertion-point';
import BlockPopover from './block-popover'; import BlockPopover from './block-popover';
import { store as blockEditorStore } from '../../store';
import BlockContextualToolbar from './block-contextual-toolbar';


/**
* Renders block tools (the block toolbar, select/navigation mode toolbar, the
* insertion point and a slot for the inline rich text toolbar). Must be wrapped
* around the block content and editor styles wrapper or iframe.
*
* @param {Object} $0 Props.
* @param {Object} $0.children The block content and style container.
*/
export default function BlockTools( { children } ) { export default function BlockTools( { children } ) {
const isLargeViewport = useViewportMatch( 'medium' );
const hasFixedToolbar = useSelect(
( select ) => select( blockEditorStore ).getSettings().hasFixedToolbar,
[]
);

return ( return (
<InsertionPoint> <InsertionPoint>
{ ( hasFixedToolbar || ! isLargeViewport ) && (
Copy link
Contributor

@talldan talldan May 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an earlier iteration this component was just implementing the two forms of the block toolbar, and I think it was good to include the viewport logic in that iteration. The UnifiedBlockToolbar component became a convenient and quick way to implement that kind of responsive toolbar.

But now this is bundled with some pretty key functionality for the block editor it seems fairly opinionated to say that someone implementing an editor should have a fixed toolbar on small viewports.

I don't think it's a blocker, this PR still makes things much easier overall. And an implementer can still reimplement this component without the viewport code. But I think it does pose a question about whether BlockEditor should be exporting high-level or low-level components.

Copy link
Member Author

@ellatrix ellatrix May 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can have a prop on this component hasFixedToolbar that overrides the setting? That's easy to add. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No rush to do that right now, just some food for thought. 😄

<BlockContextualToolbar isFixed />
) }
{ /* Even if the toolbar is fixed, the block popover is still
needed for navigation mode. */ }
<BlockPopover /> <BlockPopover />
{ /* Used for the inline rich text toolbar. */ }
<Popover.Slot name="block-toolbar" />
{ children } { children }
{ /* Forward compatibility: a place to render block tools behind the
content so it can be tabbed to properly. */ }
</InsertionPoint> </InsertionPoint>
); );
} }
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-


export const InsertionPointOpenRef = createContext(); export const InsertionPointOpenRef = createContext();


function InsertionPointPopover() { function InsertionPointPopover( { __unstablePopoverSlot } ) {
const { selectBlock } = useDispatch( blockEditorStore ); const { selectBlock } = useDispatch( blockEditorStore );
const openRef = useContext( InsertionPointOpenRef ); const openRef = useContext( InsertionPointOpenRef );
const ref = useRef(); const ref = useRef();
Expand Down Expand Up @@ -117,6 +117,7 @@ function InsertionPointPopover() {
}, [ previousElement, nextElement ] ); }, [ previousElement, nextElement ] );


const getAnchorRect = useCallback( () => { const getAnchorRect = useCallback( () => {
const { ownerDocument } = previousElement;
const previousRect = previousElement.getBoundingClientRect(); const previousRect = previousElement.getBoundingClientRect();
const nextRect = nextElement const nextRect = nextElement
? nextElement.getBoundingClientRect() ? nextElement.getBoundingClientRect()
Expand All @@ -128,6 +129,7 @@ function InsertionPointPopover() {
left: previousRect.right, left: previousRect.right,
right: previousRect.left, right: previousRect.left,
bottom: nextRect ? nextRect.top : previousRect.bottom, bottom: nextRect ? nextRect.top : previousRect.bottom,
ownerDocument,
}; };
} }


Expand All @@ -136,6 +138,7 @@ function InsertionPointPopover() {
left: previousRect.left, left: previousRect.left,
right: previousRect.right, right: previousRect.right,
bottom: nextRect ? nextRect.top : previousRect.bottom, bottom: nextRect ? nextRect.top : previousRect.bottom,
ownerDocument,
}; };
} }


Expand All @@ -145,6 +148,7 @@ function InsertionPointPopover() {
left: nextRect ? nextRect.right : previousRect.left, left: nextRect ? nextRect.right : previousRect.left,
right: previousRect.left, right: previousRect.left,
bottom: previousRect.bottom, bottom: previousRect.bottom,
ownerDocument,
}; };
} }


Expand All @@ -153,6 +157,7 @@ function InsertionPointPopover() {
left: previousRect.right, left: previousRect.right,
right: nextRect ? nextRect.left : previousRect.right, right: nextRect ? nextRect.left : previousRect.right,
bottom: previousRect.bottom, bottom: previousRect.bottom,
ownerDocument,
}; };
}, [ previousElement, nextElement ] ); }, [ previousElement, nextElement ] );


Expand Down Expand Up @@ -205,7 +210,9 @@ function InsertionPointPopover() {
getAnchorRect={ getAnchorRect } getAnchorRect={ getAnchorRect }
focusOnMount={ false } focusOnMount={ false }
className="block-editor-block-list__insertion-point-popover" className="block-editor-block-list__insertion-point-popover"
__unstableSlotName="block-toolbar" // Render in the old slot if needed for backward compatibility,
// otherwise render in place (not in the the default popover slot).
__unstableSlotName={ __unstablePopoverSlot || null }
> >
<div <div
ref={ ref } ref={ ref }
Expand Down
28 changes: 20 additions & 8 deletions packages/block-editor/src/components/block-tools/style.scss
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -124,6 +124,25 @@
border-right-color: $gray-900; border-right-color: $gray-900;
} }


&.is-fixed {
position: sticky;
top: 0;
width: 100%;
z-index: z-index(".block-editor-block-list__block-popover");
// Fill up when empty
min-height: $block-toolbar-height;
display: block;

border: none;
border-bottom: $border-width solid $gray-200;
border-radius: 0;

.block-editor-block-toolbar .components-toolbar-group,
.block-editor-block-toolbar .components-toolbar {
border-right-color: $gray-200;
}
}

.block-editor-block-mover-button { .block-editor-block-mover-button {
overflow: hidden; overflow: hidden;
} }
Expand All @@ -140,12 +159,7 @@
} }


// Position mover arrows for both toolbars. // Position mover arrows for both toolbars.
.block-editor-block-contextual-toolbar, .block-editor-block-contextual-toolbar .block-editor-block-mover:not(.is-horizontal) {
.edit-post-header-toolbar__block-toolbar,
.edit-site-header-toolbar__block-toolbar,
.edit-navigation-layout__block-toolbar,
.edit-widgets-header__block-toolbar {
.block-editor-block-mover:not(.is-horizontal) {
// Position SVGs. // Position SVGs.
.block-editor-block-mover-button { .block-editor-block-mover-button {
&:focus::before { &:focus::before {
Expand All @@ -165,7 +179,6 @@
} }
} }
} }
}




/** /**
Expand Down Expand Up @@ -234,7 +247,6 @@
} }
} }



/** /**
* Popovers. * Popovers.
*/ */
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/components/index.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export { default as BlockSettingsMenu } from './block-settings-menu';
export { default as BlockSettingsMenuControls } from './block-settings-menu-controls'; export { default as BlockSettingsMenuControls } from './block-settings-menu-controls';
export { default as BlockTitle } from './block-title'; export { default as BlockTitle } from './block-title';
export { default as BlockToolbar } from './block-toolbar'; export { default as BlockToolbar } from './block-toolbar';
export { default as BlockTools } from './block-tools';
export { export {
default as CopyHandler, default as CopyHandler,
useClipboardHandler as __unstableUseClipboardHandler, useClipboardHandler as __unstableUseClipboardHandler,
Expand Down
4 changes: 3 additions & 1 deletion packages/components/src/disabled/index.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import { focus } from '@wordpress/dom';
*/ */
import { StyledWrapper } from './styles/disabled-styles'; import { StyledWrapper } from './styles/disabled-styles';


const { Consumer, Provider } = createContext( false ); const Context = createContext( false );
const { Consumer, Provider } = Context;


/** /**
* Names of control nodes which qualify for disabled behavior. * Names of control nodes which qualify for disabled behavior.
Expand Down Expand Up @@ -111,6 +112,7 @@ function Disabled( { className, children, isDisabled = true, ...props } ) {
); );
} }


Disabled.Context = Context;
Disabled.Consumer = Consumer; Disabled.Consumer = Consumer;


export default Disabled; export default Disabled;
5 changes: 3 additions & 2 deletions packages/components/src/popover/index.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ function computeAnchorRect(
return; return;
} }


const rect = getAnchorRect( anchorRefFallback.current );
return offsetIframe( return offsetIframe(
getAnchorRect( anchorRefFallback.current ), rect,
anchorRefFallback.current.ownerDocument rect.ownerDocument || anchorRefFallback.current.ownerDocument
); );
} }


Expand Down
Loading