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

Make parts of the BlockNavigationList overridable using slots #21948

57 changes: 57 additions & 0 deletions packages/block-editor/src/components/block-navigation/branch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* WordPress dependencies
*/
import { Children, cloneElement, useContext } from '@wordpress/element';
import { Fill, Slot } from '@wordpress/components';

/**
* Internal dependencies
*/
import BlockNavigationListItem from './list-item';
import { BlockNavigationContext } from './list';
import { BlockListBlockContext } from '../block-list/block';

const BlockNavigationBranch = ( { children, ...props } ) => {
const { withBlockNavigationSlots } = useContext( BlockNavigationContext );
if ( ! withBlockNavigationSlots ) {
return (
<li>
<BlockNavigationListItem { ...props } />
{ children }
</li>
);
}

return (
<li>
<BlockNavigationListItemSlot blockId={ props.block.clientId }>
{ ( fills ) => {
if ( ! fills.length ) {
return <BlockNavigationListItem { ...props } />;
}

return Children.map( fills, ( fill ) =>
cloneElement( fill, {
...props,
...fill.props,
} )
);
} }
</BlockNavigationListItemSlot>
{ children }
</li>
);
};

export default BlockNavigationBranch;

const listItemSlotName = ( blockId ) => `BlockNavigationList-item-${ blockId }`;

export const BlockNavigationListItemSlot = ( { blockId, ...props } ) => (
<Slot { ...props } name={ listItemSlotName( blockId ) } />
);

export const BlockNavigationListItemFill = ( props ) => {
const { clientId } = useContext( BlockListBlockContext );
return <Fill { ...props } name={ listItemSlotName( clientId ) } />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function BlockNavigationDropdownToggle( { isEnabled, onToggle, isOpen } ) {
);
}

function BlockNavigationDropdown( { isDisabled } ) {
function BlockNavigationDropdown( { isDisabled, withBlockNavigationSlots } ) {
Copy link
Contributor

@talldan talldan May 7, 2020

Choose a reason for hiding this comment

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

I think it'd be good to make this __experimentalWithBlockNavigationSlots, as it's a publicly exported prop, and it's hard to say what the future is, this may default to true at some point at which point we'd want to remove it gracefully without having to support backwards compatibility.

const hasBlocks = useSelect(
( select ) => !! select( 'core/block-editor' ).getBlockCount(),
[]
Expand All @@ -71,7 +71,10 @@ function BlockNavigationDropdown( { isDisabled } ) {
/>
) }
renderContent={ ( { onClose } ) => (
<BlockNavigation onSelect={ onClose } />
<BlockNavigation
onSelect={ onClose }
withBlockNavigationSlots={ withBlockNavigationSlots }
/>
) }
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function BlockNavigation( {
rootBlocks,
selectedBlockClientId,
selectBlock,
withBlockNavigationSlots,
} ) {
if ( ! rootBlocks || rootBlocks.length === 0 ) {
return null;
Expand All @@ -44,6 +45,7 @@ function BlockNavigation( {
blocks={ [ rootBlock ] }
selectedBlockClientId={ selectedBlockClientId }
selectBlock={ selectBlock }
withBlockNavigationSlots={ withBlockNavigationSlots }
showNestedBlocks
/>
) }
Expand All @@ -52,6 +54,7 @@ function BlockNavigation( {
blocks={ rootBlocks }
selectedBlockClientId={ selectedBlockClientId }
selectBlock={ selectBlock }
withBlockNavigationSlots={ withBlockNavigationSlots }
/>
) }
</NavigableMenu>
Expand Down
58 changes: 58 additions & 0 deletions packages/block-editor/src/components/block-navigation/list-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { Button, VisuallyHidden } from '@wordpress/components';
import {
__experimentalGetBlockLabel as getBlockLabel,
getBlockType,
} from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import BlockIcon from '../block-icon';

export default function BlockNavigationListItem( {
block,
onClick,
isSelected,
wrapperComponent: WrapperComponent,
children,
} ) {
const blockType = getBlockType( block.name );

return (
<div className="block-editor-block-navigation__item">
Copy link
Contributor

Choose a reason for hiding this comment

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

I just noticed some of the class names don't seem to be right after moving the code around. The root element should have the class name block-editor-block-navigation-list-item to reflect the component name.

<WrapperComponent
className={ classnames(
'block-editor-block-navigation__item-button',
{
'is-selected': isSelected,
}
) }
onClick={ onClick }
>
<BlockIcon icon={ blockType.icon } showColors />
{ children
? children
: getBlockLabel( blockType, block.attributes ) }
{ isSelected && (
<VisuallyHidden as="span">
{ __( '(selected block)' ) }
</VisuallyHidden>
) }
</WrapperComponent>
</div>
);
}

BlockNavigationListItem.defaultProps = {
onClick: () => {},
wrapperComponent: ( props ) => <Button { ...props } />,
};
65 changes: 33 additions & 32 deletions packages/block-editor/src/components/block-navigation/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,23 @@
* External dependencies
*/
import { isNil, map, omitBy } from 'lodash';
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { Button, VisuallyHidden } from '@wordpress/components';
import {
__experimentalGetBlockLabel as getBlockLabel,
getBlockType,
} from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { useMemo, createContext } from '@wordpress/element';

/**
* Internal dependencies
*/
import BlockIcon from '../block-icon';
import ButtonBlockAppender from '../button-block-appender';
import BlockNavigationBranch from './branch';

export default function BlockNavigationList( {
export const BlockNavigationContext = createContext( {
withBlockNavigationSlots: false,
} );

function BlockNavigationList( {
blocks,
selectedBlockClientId,
selectBlock,
Expand All @@ -40,30 +38,14 @@ export default function BlockNavigationList( {
/* eslint-disable jsx-a11y/no-redundant-roles */
<ul className="block-editor-block-navigation__list" role="list">
{ map( omitBy( blocks, isNil ), ( block ) => {
const blockType = getBlockType( block.name );
const isSelected = block.clientId === selectedBlockClientId;

return (
<li key={ block.clientId }>
<div className="block-editor-block-navigation__item">
<Button
className={ classnames(
'block-editor-block-navigation__item-button',
{
'is-selected': isSelected,
}
) }
onClick={ () => selectBlock( block.clientId ) }
>
<BlockIcon icon={ blockType.icon } showColors />
{ getBlockLabel( blockType, block.attributes ) }
{ isSelected && (
<VisuallyHidden as="span">
{ __( '(selected block)' ) }
</VisuallyHidden>
) }
</Button>
</div>
<BlockNavigationBranch
block={ block }
key={ block.clientId }
isSelected={ isSelected }
onClick={ () => selectBlock( block.clientId ) }
>
{ showNestedBlocks &&
!! block.innerBlocks &&
!! block.innerBlocks.length && (
Expand All @@ -78,7 +60,7 @@ export default function BlockNavigationList( {
showNestedBlocks
/>
) }
</li>
</BlockNavigationBranch>
);
} ) }
{ shouldShowAppender && (
Expand All @@ -95,3 +77,22 @@ export default function BlockNavigationList( {
/* eslint-enable jsx-a11y/no-redundant-roles */
);
}

BlockNavigationList.defaultProps = {
selectBlock: () => {},
};

export default function BlockNavigationListWrapper( {
withBlockNavigationSlots,
...props
} ) {
const blockNavigationContext = useMemo(
() => ( { withBlockNavigationSlots } ),
[ withBlockNavigationSlots ]
);
return (
<BlockNavigationContext.Provider value={ blockNavigationContext }>
<BlockNavigationList { ...props } />
</BlockNavigationContext.Provider>
);
}
7 changes: 6 additions & 1 deletion packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ export { default as BlockEdit, useBlockEditContext } from './block-edit';
export { default as BlockFormatControls } from './block-format-controls';
export { default as BlockIcon } from './block-icon';
export { default as BlockNavigationDropdown } from './block-navigation/dropdown';
export { default as __experimentalBlockNavigationList } from './block-navigation/list';
export {
default as __experimentalBlockNavigationList,
BlockNavigationContext as __experimentalBlockNavigationContext,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this no longer needs to be exported now that it's only used internally in the BlockNavigation components

} from './block-navigation/list';
export { BlockNavigationListItemFill as __experimentalBlockNavigationListItemFill } from './block-navigation/branch';
export { default as __experimentalBlockNavigationListItem } from './block-navigation/list-item';
export { default as __experimentalBlockVariationPicker } from './block-variation-picker';
export { default as BlockVerticalAlignmentToolbar } from './block-vertical-alignment-toolbar';
export { default as ButtonBlockerAppender } from './button-block-appender';
Expand Down
57 changes: 39 additions & 18 deletions packages/block-library/src/navigation-link/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,17 @@ import {
RichText,
__experimentalLinkControl as LinkControl,
__experimentalBlock as Block,
__experimentalBlockNavigationListItem as BlockNavigationListItem,
__experimentalBlockNavigationListItemFill as BlockNavigationListItemFill,
} from '@wordpress/block-editor';
import { isURL, prependHTTP } from '@wordpress/url';
import { Fragment, useState, useEffect, useRef } from '@wordpress/element';
import {
Fragment,
useState,
useEffect,
useRef,
cloneElement,
} from '@wordpress/element';
import { placeCaretAtHorizontalEdge } from '@wordpress/dom';
import { link as linkIcon } from '@wordpress/icons';

Expand All @@ -40,6 +48,8 @@ import { link as linkIcon } from '@wordpress/icons';
*/
import { ToolbarSubmenuIcon, ItemSubmenuIcon } from './icons';

const noop = () => {};

function NavigationLinkEdit( {
attributes,
hasDescendants,
Expand Down Expand Up @@ -129,6 +139,25 @@ function NavigationLinkEdit( {
};
}

const editField = (
<RichText
className="wp-block-navigation-link__label"
value={ label }
onChange={ ( labelValue ) =>
setAttributes( { label: labelValue } )
}
placeholder={ itemLabelPlaceholder }
keepPlaceholderOnFocus
withoutInteractiveFormatting
allowedFormats={ [
'core/bold',
'core/italic',
'core/image',
'core/strikethrough',
] }
/>
);

return (
<Fragment>
<BlockControls>
Expand Down Expand Up @@ -193,6 +222,14 @@ function NavigationLinkEdit( {
/>
</PanelBody>
</InspectorControls>
<BlockNavigationListItemFill>
<BlockNavigationListItem
wrapperComponent="div"
onClick={ noop }
>
{ editField }
talldan marked this conversation as resolved.
Show resolved Hide resolved
</BlockNavigationListItem>
</BlockNavigationListItemFill>
<Block.li
className={ classnames( {
'is-editing': isSelected || isParentOfSelectedBlock,
Expand All @@ -210,23 +247,7 @@ function NavigationLinkEdit( {
} }
>
<div className="wp-block-navigation-link__content">
<RichText
ref={ ref }
className="wp-block-navigation-link__label"
value={ label }
onChange={ ( labelValue ) =>
setAttributes( { label: labelValue } )
}
placeholder={ itemLabelPlaceholder }
keepPlaceholderOnFocus
withoutInteractiveFormatting
allowedFormats={ [
'core/bold',
'core/italic',
'core/image',
'core/strikethrough',
] }
/>
{ cloneElement( editField, { ref } ) }
{ isLinkOpen && (
<Popover
position="bottom center"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import { __experimentalBlockNavigationList } from '@wordpress/block-editor';
import { useSelect, useDispatch } from '@wordpress/data';

export default function BlockNavigationList( { clientId } ) {
export default function BlockNavigationList( {
clientId,
withBlockNavigationSlots,
} ) {
const { block, selectedBlockClientId } = useSelect(
( select ) => {
const { getSelectedBlockClientId, getBlock } = select(
Expand All @@ -26,6 +29,7 @@ export default function BlockNavigationList( { clientId } ) {
blocks={ [ block ] }
selectedBlockClientId={ selectedBlockClientId }
selectBlock={ selectBlock }
withBlockNavigationSlots={ withBlockNavigationSlots }
showNestedBlocks
showAppender
/>
Expand Down
Loading