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

Reuse BlockControls in block navigation ellipsis menu #22462

Closed
wants to merge 6 commits into from
Closed
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
12 changes: 12 additions & 0 deletions packages/block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,18 @@ Ensures that the text selection keeps the same vertical distance from the
viewport during keyboard events within this component. The vertical distance
can vary. It is the last clicked or scrolled to position.

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

Undocumented declaration.

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

Undocumented declaration.

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

Undocumented declaration.

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

_Related_
Expand Down
25 changes: 25 additions & 0 deletions packages/block-editor/src/components/block-navigation/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import {
} from '../block-mover/button';
import DescenderLines from './descender-lines';
import BlockNavigationBlockContents from './block-contents';
import BlockSettingsMenu from '../block-settings-menu';
import { useBlockNavigationContext } from './context';
import EllipsisMenu from './ellipsis-menu';

export default function BlockNavigationBlock( {
block,
Expand All @@ -45,6 +48,13 @@ export default function BlockNavigationBlock( {
'block-editor-block-navigation-block__mover-cell',
{ 'is-visible': hasVisibleMovers }
);
const {
__experimentalWithEllipsisMenu: withEllipsisMenu,
} = useBlockNavigationContext();
const ellipsisMenuClassName = classnames(
'block-editor-block-navigation-block__menu-cell',
{ 'is-visible': hasVisibleMovers }
);

return (
<BlockNavigationLeaf
Expand Down Expand Up @@ -105,6 +115,21 @@ export default function BlockNavigationBlock( {
</TreeGridCell>
</>
) }

{ withEllipsisMenu && level > 1 && (
<TreeGridCell className={ ellipsisMenuClassName }>
{ ( props ) => (
<>
<BlockSettingsMenu
clientIds={ [ clientId ] }
{ ...props }
>
<EllipsisMenu.Slot bubblesVirtually />
</BlockSettingsMenu>
</>
) }
</TreeGridCell>
) }
</BlockNavigationLeaf>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createContext, useContext } from '@wordpress/element';

export const BlockNavigationContext = createContext( {
__experimentalWithBlockNavigationSlots: false,
__experimentalWithEllipsisMenu: false,
} );

export const useBlockNavigationContext = () =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* External dependencies
*/
import { isEmpty } from 'lodash';

/**
* WordPress dependencies
*/
import { useContext } from '@wordpress/element';
import {
__experimentalToolbarContext as ToolbarContext,
createSlotFill,
ToolbarGroup,
} from '@wordpress/components';

/**
* Internal dependencies
*/
import { ifBlockEditSelected } from '../block-edit/context';

const { Fill, Slot } = createSlotFill( 'EllipsisMenu' );

function EllipsisMenuSlot( props ) {
const accessibleToolbarState = useContext( ToolbarContext );
return <Slot { ...props } fillProps={ accessibleToolbarState } />;
}

function EllipsisMenuFill( { controls, children } ) {
return (
<Fill>
{ ( fillProps ) => {
// Children passed to EllipsisMenuFill will not have access to any
// React Context whose Provider is part of the EllipsisMenuSlot tree.
// So we re-create the Provider in this subtree.
const value = ! isEmpty( fillProps ) ? fillProps : null;
return (
<ToolbarContext.Provider value={ value }>
<ToolbarGroup controls={ controls } />
{ children }
</ToolbarContext.Provider>
);
} }
</Fill>
);
}

const EllipsisMenu = ifBlockEditSelected( EllipsisMenuFill );

EllipsisMenu.Slot = EllipsisMenuSlot;

export default EllipsisMenu;
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function BlockNavigation( {
rootBlocks,
selectedBlockClientId,
selectBlock,
__experimentalWithEllipsisMenu,
__experimentalWithBlockNavigationSlots,
} ) {
if ( ! rootBlocks || rootBlocks.length === 0 ) {
Expand All @@ -41,6 +42,9 @@ function BlockNavigation( {
blocks={ [ rootBlock ] }
selectedBlockClientId={ selectedBlockClientId }
selectBlock={ selectBlock }
__experimentalWithEllipsisMenu={
__experimentalWithEllipsisMenu
}
__experimentalWithBlockNavigationSlots={
__experimentalWithBlockNavigationSlots
}
Expand All @@ -52,6 +56,9 @@ function BlockNavigation( {
blocks={ rootBlocks }
selectedBlockClientId={ selectedBlockClientId }
selectBlock={ selectBlock }
__experimentalWithEllipsisMenu={
__experimentalWithEllipsisMenu
}
__experimentalWithBlockNavigationSlots={
__experimentalWithBlockNavigationSlots
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ $tree-item-height: 36px;
border-radius: $border-width;
}

.block-editor-block-navigation-block__menu-cell,
.block-editor-block-navigation-block__mover-cell {
width: $button-size;
opacity: 0;
Expand All @@ -66,6 +67,10 @@ $tree-item-height: 36px;
opacity: 1;
@include edit-post__fade-in-animation;
}

.components-toolbar {
border: 0;
}
}

.block-editor-block-mover-button {
Expand Down
11 changes: 9 additions & 2 deletions packages/block-editor/src/components/block-navigation/tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,18 @@ import { BlockNavigationContext } from './context';
*/
export default function BlockNavigationTree( {
__experimentalWithBlockNavigationSlots,
__experimentalWithEllipsisMenu,
...props
} ) {
const contextValue = useMemo(
() => ( { __experimentalWithBlockNavigationSlots } ),
[ __experimentalWithBlockNavigationSlots ]
() => ( {
__experimentalWithBlockNavigationSlots,
__experimentalWithEllipsisMenu,
} ),
[
__experimentalWithBlockNavigationSlots,
__experimentalWithEllipsisMenu,
]
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const POPOVER_PROPS = {
isAlternate: true,
};

export function BlockSettingsMenu( { clientIds } ) {
export function BlockSettingsMenu( { clientIds, children } ) {
const blockClientIds = castArray( clientIds );
const count = blockClientIds.length;
const firstBlockClientId = blockClientIds[ 0 ];
Expand Down Expand Up @@ -86,6 +86,7 @@ export function BlockSettingsMenu( { clientIds } ) {
>
{ ( { onClose } ) => (
<>
{ children }
<MenuGroup>
<__experimentalBlockSettingsMenuFirstItem.Slot
fillProps={ { onClose } }
Expand Down
6 changes: 6 additions & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export { default as BlockFormatControls } from './block-format-controls';
export { default as BlockIcon } from './block-icon';
export { default as BlockNavigationDropdown } from './block-navigation/dropdown';
export { BlockNavigationBlockFill as __experimentalBlockNavigationBlockFill } from './block-navigation/block-contents';
export { default as __experimentalEllipsisMenu } from './block-navigation/ellipsis-menu';
export { default as __experimentalBlockNavigationEditor } from './block-navigation/editor';
export { default as __experimentalBlockNavigationTree } from './block-navigation/tree';
export { default as __experimentalBlockVariationPicker } from './block-variation-picker';
Expand Down Expand Up @@ -88,6 +89,11 @@ export { default as ObserveTyping } from './observe-typing';
export { default as PreserveScrollInReorder } from './preserve-scroll-in-reorder';
export { default as SkipToSelectedBlock } from './skip-to-selected-block';
export { default as Typewriter } from './typewriter';
export {
default as UniversalBlockControls,
UniversalControlsGroup,
UniversalControlsButton,
} from './universal-block-controls';
export { default as Warning } from './warning';
export { default as WritingFlow } from './writing-flow';

Expand Down
100 changes: 100 additions & 0 deletions packages/block-editor/src/components/universal-block-controls/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* External dependencies
*/
import { identity } from 'lodash';

/**
* WordPress dependencies
*/
import {
MenuItem,
MenuGroup,
ToolbarButton,
ToolbarGroup,
} from '@wordpress/components';
import { Children, cloneElement } from '@wordpress/element';

/**
* Internal dependencies
*/
import BlockControls from '../block-controls';
import BlockNavigationEllipsisMenu from '../block-navigation/ellipsis-menu';

export const UniversalBlockControls = ( { children } ) => {
return (
<>
{ withoutChildren( children, 'UniversalControlsGroup' ) }
<BlockControls>
{ withChildren( children, 'UniversalControlsGroup', ( child ) =>
cloneElement( child, { as: ToolbarGroup } )
) }
</BlockControls>
<BlockNavigationEllipsisMenu>
{ withChildren( children, 'UniversalControlsGroup', ( child ) =>
cloneElement( child, { as: MenuGroup } )
) }
</BlockNavigationEllipsisMenu>
</>
);
};

export const UniversalControlsGroup = ( { children, as: Component } ) => {
const ChildComponent =
Component === ToolbarGroup ? ToolbarButton : MenuItem;
return (
<Component>
{ withoutChildren( children, 'UniversalControlsButton' ) }
{ withChildren( children, 'UniversalControlsButton', ( child ) =>
cloneElement( child, { as: ChildComponent } )
) }
</Component>
);
};

UniversalControlsGroup.displayName = 'UniversalControlsGroup';

export const UniversalControlsButton = ( {
name,
icon,
title,
shortcut,
onClick,
as: Component,
} ) => {
if ( Component === ToolbarButton ) {
return (
<ToolbarButton
name={ name }
icon={ icon }
title={ title }
shortcut={ shortcut }
onClick={ onClick }
/>
);
}
if ( Component === MenuItem ) {
return (
<MenuItem shortcut={ shortcut } onClick={ onClick }>
{ title }
</MenuItem>
);
}
throw new Error(
'Unsupported component passed to UniversalControlsButton: ' +
Component.displayName
);
};

UniversalControlsButton.displayName = 'UniversalControlsButton';

export default UniversalBlockControls;

const withChildren = ( children, displayName, callback = identity ) =>
Children.map( children, ( child ) =>
child.type.displayName === displayName ? callback( child ) : false
);

const withoutChildren = ( children, displayName, callback = identity ) =>
Children.map( children, ( child ) =>
child.type.displayName !== displayName ? callback( child ) : false
);
19 changes: 10 additions & 9 deletions packages/block-library/src/navigation-link/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ import {
Popover,
TextareaControl,
ToggleControl,
ToolbarButton,
ToolbarGroup,
} from '@wordpress/components';
import { rawShortcut, displayShortcut } from '@wordpress/keycodes';
import { __ } from '@wordpress/i18n';
import {
BlockControls,
InnerBlocks,
InspectorControls,
RichText,
UniversalBlockControls,
UniversalControlsGroup,
UniversalControlsButton,
__experimentalLinkControl as LinkControl,
__experimentalBlock as Block,
__experimentalBlockNavigationEditor as BlockNavigationEditor,
Expand Down Expand Up @@ -134,30 +134,31 @@ function NavigationLinkEdit( {

return (
<Fragment>
<BlockControls>
<ToolbarGroup>
<UniversalBlockControls>
<UniversalControlsGroup>
<KeyboardShortcuts
bindGlobal
shortcuts={ {
[ rawShortcut.primary( 'k' ) ]: () =>
setIsLinkOpen( true ),
} }
/>
<ToolbarButton
<UniversalControlsButton
name="link"
icon={ linkIcon }
title={ __( 'Link' ) }
shortcut={ displayShortcut.primary( 'k' ) }
onClick={ () => setIsLinkOpen( true ) }
/>
<ToolbarButton
<UniversalControlsButton
name="submenu"
icon={ <ToolbarSubmenuIcon /> }
title={ __( 'Add submenu' ) }
onClick={ insertLinkBlock }
/>
</ToolbarGroup>
</BlockControls>
</UniversalControlsGroup>
</UniversalBlockControls>

<InspectorControls>
<PanelBody title={ __( 'SEO settings' ) }>
<ToggleControl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default function NavigationStructurePanel( { blocks, initialOpen } ) {
selectedBlockClientId={ selectedBlockClientIds[ 0 ] }
selectBlock={ selectBlock }
__experimentalWithBlockNavigationSlots={ true }
__experimentalWithEllipsisMenu={ true }
showNestedBlocks
showAppender
showBlockMovers
Expand Down