Skip to content

Commit

Permalink
Site Editor: Persistent List View (#28637)
Browse files Browse the repository at this point in the history
Convert the List View dropdown into a persistent sidebar occupying the same interface skeleton slot as the Inserter (`secondarySidebar`).

This change is currently experimental, and is only enabled in the Site Editor.

Props @Addison-Stavlo ✨

Co-authored-by: James Koster <james@jameskoster.co.uk>
  • Loading branch information
Copons and jameskoster committed Mar 2, 2021
1 parent a5b0824 commit 5a98254
Show file tree
Hide file tree
Showing 24 changed files with 492 additions and 127 deletions.
45 changes: 36 additions & 9 deletions packages/block-editor/src/components/block-navigation/block.js
Expand Up @@ -43,7 +43,6 @@ export default function BlockNavigationBlock( {
} ) {
const cellRef = useRef( null );
const [ isHovered, setIsHovered ] = useState( false );
const [ isFocused, setIsFocused ] = useState( false );
const { clientId } = block;
const { isDragging, blockParents } = useSelect(
( select ) => {
Expand All @@ -63,38 +62,66 @@ export default function BlockNavigationBlock( {
[ clientId ]
);

const { selectBlock: selectEditorBlock } = useDispatch( blockEditorStore );
const {
selectBlock: selectEditorBlock,
toggleBlockHighlight,
} = useDispatch( blockEditorStore );

const hasSiblings = siblingBlockCount > 0;
const hasRenderedMovers = showBlockMovers && hasSiblings;
const hasVisibleMovers = isHovered || isFocused;
const moverCellClassName = classnames(
'block-editor-block-navigation-block__mover-cell',
{ 'is-visible': hasVisibleMovers }
{ 'is-visible': isHovered }
);
const {
__experimentalFeatures: withExperimentalFeatures,
__experimentalPersistentListViewFeatures: withExperimentalPersistentListViewFeatures,
} = useBlockNavigationContext();
const blockNavigationBlockSettingsClassName = classnames(
'block-editor-block-navigation-block__menu-cell',
{ 'is-visible': hasVisibleMovers }
{ 'is-visible': isHovered }
);

// If BlockNavigation has experimental features related to the Persistent List View,
// only focus the selected list item on mount; otherwise the list would always
// try to steal the focus from the editor canvas.
useEffect( () => {
if ( withExperimentalPersistentListViewFeatures && isSelected ) {
cellRef.current.focus();
}
}, [] );

// If BlockNavigation has experimental features (such as drag and drop) enabled,
// leave the focus handling as it was before, to avoid accidental regressions.
useEffect( () => {
if ( withExperimentalFeatures && isSelected ) {
cellRef.current.focus();
}
}, [ withExperimentalFeatures, isSelected ] );

const highlightBlock = withExperimentalPersistentListViewFeatures
? toggleBlockHighlight
: () => {};

const onMouseEnter = () => {
setIsHovered( true );
highlightBlock( clientId, true );
};
const onMouseLeave = () => {
setIsHovered( false );
highlightBlock( clientId, false );
};

return (
<BlockNavigationLeaf
className={ classnames( {
'is-selected': isSelected,
'is-dragging': isDragging,
} ) }
onMouseEnter={ () => setIsHovered( true ) }
onMouseLeave={ () => setIsHovered( false ) }
onFocus={ () => setIsFocused( true ) }
onBlur={ () => setIsFocused( false ) }
onMouseEnter={ onMouseEnter }
onMouseLeave={ onMouseLeave }
onFocus={ onMouseEnter }
onBlur={ onMouseLeave }
level={ level }
position={ position }
rowCount={ rowCount }
Expand Down
Expand Up @@ -5,6 +5,7 @@ import { createContext, useContext } from '@wordpress/element';

export const BlockNavigationContext = createContext( {
__experimentalFeatures: false,
__experimentalPersistentListViewFeatures: false,
} );

export const useBlockNavigationContext = () =>
Expand Down
18 changes: 4 additions & 14 deletions packages/block-editor/src/components/block-navigation/dropdown.js
@@ -1,32 +1,22 @@
/**
* WordPress dependencies
*/
import { Button, Dropdown, SVG, Path } from '@wordpress/components';
import { Button, Dropdown } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
import {
useShortcut,
store as keyboardShortcutsStore,
} from '@wordpress/keyboard-shortcuts';
import { useCallback, forwardRef } from '@wordpress/element';
import { listView } from '@wordpress/icons';

/**
* Internal dependencies
*/
import BlockNavigation from './';
import { store as blockEditorStore } from '../../store';

const MenuIcon = (
<SVG
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<Path d="M13.8 5.2H3v1.5h10.8V5.2zm-3.6 12v1.5H21v-1.5H10.2zm7.2-6H6.6v1.5h10.8v-1.5z" />
</SVG>
);

function BlockNavigationDropdownToggle( {
isEnabled,
onToggle,
Expand Down Expand Up @@ -54,12 +44,12 @@ function BlockNavigationDropdownToggle( {
<Button
{ ...props }
ref={ innerRef }
icon={ MenuIcon }
icon={ listView }
aria-expanded={ isOpen }
aria-haspopup="true"
onClick={ isEnabled ? onToggle : undefined }
/* translators: button label text should, if possible, be under 16 characters. */
label={ __( 'Outline' ) }
label={ __( 'List view' ) }
className="block-editor-block-navigation"
shortcut={ shortcut }
aria-disabled={ ! isEnabled }
Expand Down
13 changes: 10 additions & 3 deletions packages/block-editor/src/components/block-navigation/tree.js
Expand Up @@ -18,11 +18,13 @@ import useBlockNavigationDropZone from './use-block-navigation-drop-zone';
* recursive component (it renders itself), so this ensures TreeGrid is only
* present at the very top of the navigation grid.
*
* @param {Object} props Components props.
* @param {Object} props.__experimentalFeatures Object used in context provider.
* @param {Object} props Components props.
* @param {boolean} props.__experimentalFeatures Flag to enable experimental features.
* @param {boolean} props.__experimentalPersistentListViewFeatures Flag to enable features for the Persistent List View experiment.
*/
export default function BlockNavigationTree( {
__experimentalFeatures,
__experimentalPersistentListViewFeatures,
...props
} ) {
const treeGridRef = useRef();
Expand All @@ -35,9 +37,14 @@ export default function BlockNavigationTree( {
const contextValue = useMemo(
() => ( {
__experimentalFeatures,
__experimentalPersistentListViewFeatures,
blockDropTarget,
} ),
[ __experimentalFeatures, blockDropTarget ]
[
__experimentalFeatures,
__experimentalPersistentListViewFeatures,
blockDropTarget,
]
);

return (
Expand Down
16 changes: 3 additions & 13 deletions packages/block-library/src/navigation/use-block-navigator.js
Expand Up @@ -2,25 +2,15 @@
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
import { ToolbarButton, SVG, Path, Modal } from '@wordpress/components';
import { ToolbarButton, Modal } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { listView } from '@wordpress/icons';

/**
* Internal dependencies
*/
import BlockNavigationList from './block-navigation-list';

const NavigatorIcon = (
<SVG
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<Path d="M13.8 5.2H3v1.5h10.8V5.2zm-3.6 12v1.5H21v-1.5H10.2zm7.2-6H6.6v1.5h10.8v-1.5z" />
</SVG>
);

export default function useBlockNavigator( clientId, __experimentalFeatures ) {
const [ isNavigationListOpen, setIsNavigationListOpen ] = useState( false );

Expand All @@ -29,7 +19,7 @@ export default function useBlockNavigator( clientId, __experimentalFeatures ) {
className="components-toolbar__control"
label={ __( 'Open block navigator' ) }
onClick={ () => setIsNavigationListOpen( true ) }
icon={ NavigatorIcon }
icon={ listView }
/>
);

Expand Down
2 changes: 1 addition & 1 deletion packages/e2e-tests/specs/editor/blocks/columns.test.js
Expand Up @@ -18,7 +18,7 @@ describe( 'Columns', () => {
await insertBlock( 'Columns' );
await closeGlobalBlockInserter();
await page.click( '[aria-label="Two columns; equal split"]' );
await page.click( '[aria-label="Outline"]' );
await page.click( '[aria-label="List view"]' );
const columnBlockMenuItem = (
await page.$x(
'//button[contains(concat(" ", @class, " "), " block-editor-block-navigation-block-select-button ")][text()="Column"]'
Expand Down
2 changes: 1 addition & 1 deletion packages/e2e-tests/specs/editor/blocks/cover.test.js
Expand Up @@ -24,7 +24,7 @@ describe( 'Cover', () => {
);

// Select the cover block.By default the child paragraph gets selected.
await page.click( 'button[aria-label="Outline"]' );
await page.click( 'button[aria-label="List view"]' );
await page.click(
'.block-editor-block-navigation-block__contents-container button'
);
Expand Down
Expand Up @@ -50,7 +50,7 @@ describe( 'Navigating the block hierarchy', () => {
await page.keyboard.type( 'First column' );

// Navigate to the columns blocks.
await page.click( '[aria-label="Outline"]' );
await page.click( '[aria-label="List view"]' );
const columnsBlockMenuItem = (
await page.$x(
"//button[contains(@class,'block-editor-block-navigation-block-select-button') and contains(text(), 'Columns')]"
Expand All @@ -69,7 +69,7 @@ describe( 'Navigating the block hierarchy', () => {
await page.keyboard.type( '3' );

// Navigate to the last column block.
await page.click( '[aria-label="Outline"]' );
await page.click( '[aria-label="List view"]' );
const lastColumnsBlockMenuItem = (
await page.$x(
"//button[contains(@class,'block-editor-block-navigation-block-select-button') and contains(text(), 'Column')]"
Expand Down Expand Up @@ -175,7 +175,7 @@ describe( 'Navigating the block hierarchy', () => {
await page.click( '.editor-post-title' );

// Try selecting the group block using the Outline
await page.click( '[aria-label="Outline"]' );
await page.click( '[aria-label="List view"]' );
const groupMenuItem = (
await page.$x(
"//button[contains(@class,'block-editor-block-navigation-block-select-button') and contains(text(), 'Group')]"
Expand Down
63 changes: 17 additions & 46 deletions packages/edit-site/src/components/editor/index.js
Expand Up @@ -10,11 +10,7 @@ import {
Button,
} from '@wordpress/components';
import { EntityProvider } from '@wordpress/core-data';
import {
BlockContextProvider,
BlockBreadcrumb,
__experimentalLibrary as Library,
} from '@wordpress/block-editor';
import { BlockContextProvider, BlockBreadcrumb } from '@wordpress/block-editor';
import {
FullscreenMode,
InterfaceSkeleton,
Expand All @@ -24,11 +20,6 @@ import {
import { EntitiesSavedStates, UnsavedChangesWarning } from '@wordpress/editor';
import { __ } from '@wordpress/i18n';
import { PluginArea } from '@wordpress/plugins';
import { close } from '@wordpress/icons';
import {
useViewportMatch,
__experimentalUseDialog as useDialog,
} from '@wordpress/compose';

/**
* Internal dependencies
Expand All @@ -41,6 +32,8 @@ import KeyboardShortcuts from '../keyboard-shortcuts';
import GlobalStylesProvider from './global-styles-provider';
import NavigationSidebar from '../navigation-sidebar';
import URLQueryController from '../url-query-controller';
import InserterSidebar from '../secondary-sidebar/inserter-sidebar';
import ListViewSidebar from '../secondary-sidebar/list-view-sidebar';
import { store as editSiteStore } from '../../store';

const interfaceLabels = {
Expand All @@ -52,6 +45,7 @@ function Editor( { initialSettings } ) {
const {
isFullscreenActive,
isInserterOpen,
isListViewOpen,
sidebarIsOpened,
settings,
entityId,
Expand All @@ -63,6 +57,7 @@ function Editor( { initialSettings } ) {
const {
isFeatureActive,
isInserterOpened,
isListViewOpened,
getSettings,
getEditedPostType,
getEditedPostId,
Expand All @@ -79,6 +74,7 @@ function Editor( { initialSettings } ) {
// The currently selected entity to display. Typically template or template part.
return {
isInserterOpen: isInserterOpened(),
isListViewOpen: isListViewOpened(),
isFullscreenActive: isFeatureActive( 'fullscreenMode' ),
sidebarIsOpened: !! select(
interfaceStore
Expand Down Expand Up @@ -155,17 +151,21 @@ function Editor( { initialSettings } ) {
}
}, [ isNavigationOpen ] );

const isMobile = useViewportMatch( 'medium', '<' );

const [ inserterDialogRef, inserterDialogProps ] = useDialog( {
onClose: () => setIsInserterOpened( false ),
} );

// Don't render the Editor until the settings are set and loaded
if ( ! settings?.siteUrl ) {
return null;
}

const secondarySidebar = () => {
if ( isInserterOpen ) {
return <InserterSidebar />;
}
if ( isListViewOpen ) {
return <ListViewSidebar />;
}
return null;
};

return (
<>
<URLQueryController />
Expand Down Expand Up @@ -197,36 +197,7 @@ function Editor( { initialSettings } ) {
<InterfaceSkeleton
labels={ interfaceLabels }
drawer={ <NavigationSidebar /> }
secondarySidebar={
isInserterOpen ? (
<div
ref={
inserterDialogRef
}
{ ...inserterDialogProps }
className="edit-site-editor__inserter-panel"
>
<div className="edit-site-editor__inserter-panel-header">
<Button
icon={ close }
onClick={ () =>
setIsInserterOpened(
false
)
}
/>
</div>
<div className="edit-site-editor__inserter-panel-content">
<Library
showInserterHelpPanel
shouldFocusBlock={
isMobile
}
/>
</div>
</div>
) : null
}
secondarySidebar={ secondarySidebar() }
sidebar={
sidebarIsOpened && (
<ComplementaryArea.Slot scope="core/edit-site" />
Expand Down

0 comments on commit 5a98254

Please sign in to comment.