Skip to content

Commit

Permalink
Add a block selection breadcrumb to the bottom of the editor (#17838)
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowriad committed Oct 25, 2019
1 parent 56e730b commit f8b62bd
Show file tree
Hide file tree
Showing 15 changed files with 275 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,19 @@ _Returns_

- `Array`: Ordered client IDs of editor blocks.

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

Given a block client ID, returns the list of all its parents from top to bottom.

_Parameters_

- _state_ `Object`: Editor state.
- _clientId_ `string`: Block from which to find root client ID.

_Returns_

- `Array`: ClientIDs of the parent blocks.

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

Given a block client ID, returns the root block from which the block is
Expand Down
1 change: 1 addition & 0 deletions packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ $z-layers: (
".block-editor-warning": 5,
".block-library-gallery-item__inline-menu": 20,
".block-editor-url-input__suggestions": 30,
".edit-post-layout__footer": 30,
".edit-post-header": 30,
".edit-widgets-header": 30,
".block-library-button__inline-link .block-editor-url-input__suggestions": 6, // URL suggestions for button block above sibling inserter
Expand Down
8 changes: 8 additions & 0 deletions packages/block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ _Related_

Undocumented declaration.

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

Block breadcrumb component, displaying the hierarchy of the current block selection as a breadcrumb.

_Returns_

- `WPElement`: Block Breadcrumb.

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

Undocumented declaration.
Expand Down
77 changes: 77 additions & 0 deletions packages/block-editor/src/components/block-breadcrumb/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* WordPress dependencies
*/
import { Button } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import BlockTitle from '../block-title';

/**
* Block breadcrumb component, displaying the hierarchy of the current block selection as a breadcrumb.
*
* @return {WPElement} Block Breadcrumb.
*/
const BlockBreadcrumb = function() {
const { selectBlock, clearSelectedBlock } = useDispatch( 'core/block-editor' );
const { clientId, parents, hasSelection } = useSelect( ( select ) => {
const {
getSelectionStart,
getSelectedBlockClientId,
getBlockParents,
} = select( 'core/block-editor' );
const selectedBlockClientId = getSelectedBlockClientId();
return {
parents: getBlockParents( selectedBlockClientId ),
clientId: selectedBlockClientId,
hasSelection: !! getSelectionStart().clientId,
};
}, [] );

/*
* Disable reason: The `list` ARIA role is redundant but
* Safari+VoiceOver won't announce the list otherwise.
*/
/* eslint-disable jsx-a11y/no-redundant-roles */
return (
<ul className="block-editor-block-breadcrumb" role="list" aria-label={ __( 'Block breadcrumb' ) }>
<li
className={ ! hasSelection ? 'block-editor-block-breadcrumb__current' : undefined }
aria-current={ ! hasSelection ? 'true' : undefined }
>
{ hasSelection && (
<Button
className="block-editor-block-breadcrumb__button"
isTertiary
onClick={ clearSelectedBlock }
>
{ __( 'Document' ) }
</Button>
) }
{ ! hasSelection && __( 'Document' ) }
</li>
{ parents.map( ( parentClientId ) => (
<li key={ parentClientId }>
<Button
className="block-editor-block-breadcrumb__button"
isTertiary
onClick={ () => selectBlock( parentClientId ) }
>
<BlockTitle clientId={ parentClientId } />
</Button>
</li>
) ) }
{ !! clientId && (
<li className="block-editor-block-breadcrumb__current" aria-current="true">
<BlockTitle clientId={ clientId } />
</li>
) }
</ul>
/* eslint-enable jsx-a11y/no-redundant-roles */
);
};

export default BlockBreadcrumb;
41 changes: 41 additions & 0 deletions packages/block-editor/src/components/block-breadcrumb/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.block-editor-block-breadcrumb {
list-style: none;
padding: 0;
margin: 0;

li {
display: inline-block;
margin: 0;

&:not(:last-child)::after {
content: "\2192"; // This becomes →.
}
}
}

.block-editor-block-breadcrumb__button.components-button {
height: $icon-button-size-small;
line-height: $icon-button-size-small;
padding: 0;

&:hover {
text-decoration: underline;
}

&:focus {
@include square-style__focus();
outline-offset: -2px;
box-shadow: none;
}
}

.block-editor-block-breadcrumb__current {
cursor: default;
}

.block-editor-block-breadcrumb__button.components-button,
.block-editor-block-breadcrumb__current {
color: $dark-gray-500;
padding: 0 $grid-size;
font-size: inherit;
}
1 change: 1 addition & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './font-sizes';
export { default as AlignmentToolbar } from './alignment-toolbar';
export { default as Autocomplete } from './autocomplete';
export { default as BlockAlignmentToolbar } from './block-alignment-toolbar';
export { default as BlockBreadcrumb } from './block-breadcrumb';
export { default as BlockControls } from './block-controls';
export { default as BlockEdit } from './block-edit';
export { default as BlockFormatControls } from './block-format-controls';
Expand Down
24 changes: 24 additions & 0 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,30 @@ export function getBlockRootClientId( state, clientId ) {
null;
}

/**
* Given a block client ID, returns the list of all its parents from top to bottom.
*
* @param {Object} state Editor state.
* @param {string} clientId Block from which to find root client ID.
*
* @return {Array} ClientIDs of the parent blocks.
*/
export const getBlockParents = createSelector(
( state, clientId ) => {
const parents = [];
let current = clientId;
while ( !! state.blocks.parents[ current ] ) {
current = state.blocks.parents[ current ];
parents.push( current );
}

return parents.reverse();
},
( state ) => [
state.blocks.parents,
]
);

/**
* Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks.
*
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@import "./components/block-inspector/style.scss";
@import "./components/block-list/style.scss";
@import "./components/block-list-appender/style.scss";
@import "./components/block-breadcrumb/style.scss";
@import "./components/block-card/style.scss";
@import "./components/block-compare/style.scss";
@import "./components/block-mover/style.scss";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ describe( 'Navigating the block hierarchy', () => {
await pressKeyWithModifier( 'ctrl', '`' );
await pressKeyWithModifier( 'ctrl', '`' );
await pressKeyWithModifier( 'ctrl', '`' );
await pressKeyWithModifier( 'ctrl', '`' );
await pressKeyTimes( 'Tab', 4 );

// Tweak the columns count by increasing it by one.
Expand Down
1 change: 1 addition & 0 deletions packages/e2e-tests/specs/editor/various/sidebar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ describe( 'Sidebar', () => {
await pressKeyWithModifier( 'ctrl', '`' );
await pressKeyWithModifier( 'ctrl', '`' );
await pressKeyWithModifier( 'ctrl', '`' );
await pressKeyWithModifier( 'ctrl', '`' );

// Tab lands at first (presumed selected) option "Document".
await page.keyboard.press( 'Tab' );
Expand Down
20 changes: 15 additions & 5 deletions packages/edit-post/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
EditorNotices,
PostPublishPanel,
} from '@wordpress/editor';
import { BlockBreadcrumb } from '@wordpress/block-editor';
import { withDispatch, withSelect } from '@wordpress/data';
import { PluginArea } from '@wordpress/plugins';
import { withViewportMatch } from '@wordpress/viewport';
Expand Down Expand Up @@ -59,7 +60,7 @@ function Layout( {
} ) {
const sidebarIsOpened = editorSidebarOpened || pluginSidebarOpened || publishSidebarOpened;

const className = classnames( 'edit-post-layout', {
const className = classnames( 'edit-post-layout', 'is-mode-' + mode, {
'is-sidebar-opened': sidebarIsOpened,
'has-fixed-toolbar': hasFixedToolbar,
'has-metaboxes': hasActiveMetaboxes,
Expand All @@ -80,7 +81,7 @@ function Layout( {
<LocalAutosaveMonitor />
<Header />
<div
className="edit-post-layout__content"
className="edit-post-layout__content edit-post-layout__scrollable-container"
role="region"
/* translators: accessibility text for the content landmark region. */
aria-label={ __( 'Editor content' ) }
Expand All @@ -99,7 +100,19 @@ function Layout( {
<div className="edit-post-layout__metaboxes">
<MetaBoxes location="advanced" />
</div>
{ isMobileViewport && sidebarIsOpened && <ScrollLock /> }
</div>
{ isRichEditingEnabled && mode === 'visual' && (
<div
className="edit-post-layout__footer"
role="region"
/* translators: accessibility text for the content landmark region. */
aria-label={ __( 'Editor footer' ) }
tabIndex="-1"
>
<BlockBreadcrumb />
</div>
) }
{ publishSidebarOpened ? (
<PostPublishPanel
{ ...publishLandmarkProps }
Expand All @@ -124,9 +137,6 @@ function Layout( {
</div>
<SettingsSidebar />
<Sidebar.Slot />
{
isMobileViewport && sidebarIsOpened && <ScrollLock />
}
</>
) }
<Popover.Slot />
Expand Down
81 changes: 55 additions & 26 deletions packages/edit-post/src/components/layout/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
// Because the body element scrolls the navigation sidebar, we have to use position fixed here.
// Otherwise you would scroll the editing canvas out of view when you scroll the sidebar.
position: fixed;
bottom: 0;
bottom: $footer-height;
left: 0;
right: 0;

Expand All @@ -70,6 +70,12 @@
// Because we are beyond the medium breakpoint, we only have to worry about folded, auto-folded, and default.
margin-left: $admin-sidebar-width;

// Make room for the footer
.edit-post-layout.is-mode-visual & {
bottom: $footer-height;
min-height: calc(100% - #{ $header-height + $admin-bar-height + $footer-height });
}

// Auto fold is when on smaller breakpoints, nav menu auto colllapses.
body.auto-fold & {
margin-left: $admin-sidebar-width-collapsed;
Expand Down Expand Up @@ -104,31 +110,6 @@
}
}

// Pad the scroll box so content on the bottom can be scrolled up.
padding-bottom: 50vh;
@include break-small {
padding-bottom: 0;
}

// On mobile the main content (html or body) area has to scroll.
// If, like we do on the desktop, scroll an element (.edit-post-layout__content) you can invoke
// the overscroll bounce on the non-scrolling container, causing for a frustrating scrolling experience.
// The following rule enables this scrolling beyond the mobile breakpoint, because on the desktop
// breakpoints scrolling an isolated element helps avoid scroll bleed.
@include break-small() {
overflow-y: auto;
}
-webkit-overflow-scrolling: touch;

// This rule ensures that if you've scrolled to the end of a container,
// then pause, then keep scrolling downwards, the browser doesn't try to scroll
// the parent element, usually invoking a "bounce" effect and then preventing you
// from scrolling upwards until you pause again.
// This is only necessary beyond the small breakpoint because that's when the scroll container changes.
@include break-small() {
overscroll-behavior-y: none;
}

.edit-post-visual-editor {
flex: 1 1 auto;

Expand Down Expand Up @@ -234,3 +215,51 @@
}
}
}

.edit-post-layout__footer {
display: none;
z-index: z-index(".edit-post-layout__footer");

// Stretch to mimic outline padding on desktop.
@include break-medium() {
display: flex;
position: fixed;
bottom: 0;
right: 0;
background: $white;
height: $footer-height;
padding: 0 $grid-size;
align-items: center;
border-top: $border-width solid $light-gray-500;
font-size: $default-font-size;
box-sizing: border-box;
}
}
@include editor-left(".edit-post-layout__footer");

.edit-post-layout__scrollable-container {
// On mobile the main content (html or body) area has to scroll.
// If, like we do on the desktop, scroll an element (.edit-post-layout__content) you can invoke
// the overscroll bounce on the non-scrolling container, causing for a frustrating scrolling experience.
// The following rule enables this scrolling beyond the mobile breakpoint, because on the desktop
// breakpoints scrolling an isolated element helps avoid scroll bleed.
@include break-small() {
overflow-y: auto;
}
-webkit-overflow-scrolling: touch;

// This rule ensures that if you've scrolled to the end of a container,
// then pause, then keep scrolling downwards, the browser doesn't try to scroll
// the parent element, usually invoking a "bounce" effect and then preventing you
// from scrolling upwards until you pause again.
// This is only necessary beyond the small breakpoint because that's when the scroll container changes.
@include break-small() {
overscroll-behavior-y: none;
}

// Pad the scroll box so content on the bottom can be scrolled up.
padding-bottom: 50vh;
@include break-small {
padding-bottom: 0;
}
}
4 changes: 4 additions & 0 deletions packages/edit-post/src/components/sidebar/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
@include break-medium() {
top: $admin-bar-height + $header-height;

.edit-post-layout.is-mode-visual & {
bottom: $footer-height;
}

body.is-fullscreen-mode & {
top: $header-height;
}
Expand Down
Loading

0 comments on commit f8b62bd

Please sign in to comment.