Skip to content

Commit

Permalink
Add global inserter to Nav editor screen (#34619)
Browse files Browse the repository at this point in the history
* Scaffold out very basic interface

* Wire up store state and UI

* Add inserter button to header toolbar

* Use correct classname

* Use optimal props on inserter toggle

* Allow global inserter to insert blocks at root level of Nav block

* Avoid displaying inserter if there are no blocks to insert

* Hide previews as they don't provide much value here

* Refactor Inserter to dedicate component

* Remove hardcoded conditional

* Remove unwanted ref usage

* Add toggle button styles and remove close button on larger screens

* Allow tools to show on mobile viewports

* Conditionalise render of sidebar

Ensures main content is visible on mobile devices when sidebar is disabled. If we return a component to the secondarySidebar then the interface component will render the wrapping div which will obscure the wrapping content.

* Only hide undo/redo on smaller viewports

* Simplify Navigation Block hook

* Ensure global inserter always targets root nav block

* Ensure items always inserted at end if root nav block is selected

* Update to use correct exported variable name

* Handle focus and tab trapping via hook

* Rename component to make it clear it's the inserter button

* Simplify secondary sidebar

* Alter directory structure in order to improve portability of code

* Make insertion point logic explicit

* Add docblocks to store

* Add selector state guard

* Implement Browse All on quick inserter.

Required refactor of initial file.

* Move constant outside component body

Addresses #34619 (comment)

* Add dep to useSelect

* Add another dep to useSelect

* Remove ref

* Sort out Prettier's mess

* Rename Inserter Button to Inserter Toggle for clarity and consistency

* Apply correct CSS naming convention

Addresses #34619 (comment)
  • Loading branch information
getdave authored Sep 14, 2021
1 parent df8b97a commit 5710513
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 23 deletions.
33 changes: 22 additions & 11 deletions packages/edit-navigation/src/components/header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { decodeEntities } from '@wordpress/html-entities';
import SaveButton from './save-button';
import UndoButton from './undo-button';
import RedoButton from './redo-button';
import InserterToggle from './inserter-toggle';
import MenuSwitcher from '../menu-switcher';
import { useMenuEntityProp } from '../../hooks';

Expand All @@ -26,7 +27,9 @@ export default function Header( {
navigationPost,
} ) {
const isMediumViewport = useViewportMatch( 'medium' );

const [ menuName ] = useMenuEntityProp( 'name', selectedMenuId );

let actionHeaderText;

if ( menuName ) {
Expand All @@ -44,23 +47,31 @@ export default function Header( {

return (
<div className="edit-navigation-header">
{ isMediumViewport && (
<div className="edit-navigation-header__toolbar-wrapper">
<div className="edit-navigation-header__toolbar-wrapper">
{ isMediumViewport && (
<h1 className="edit-navigation-header__title">
{ __( 'Navigation' ) }
</h1>
<NavigableToolbar
className="edit-navigation-header__toolbar"
aria-label={ __( 'Document tools' ) }
>
<UndoButton />
<RedoButton />
</NavigableToolbar>
</div>
) }
) }

<NavigableToolbar
className="edit-navigation-header__toolbar"
aria-label={ __( 'Document tools' ) }
>
<InserterToggle />
{ isMediumViewport && (
<>
<UndoButton />
<RedoButton />
</>
) }
</NavigableToolbar>
</div>

<h2 className="edit-navigation-header__subtitle">
{ isMenuSelected && decodeEntities( actionHeaderText ) }
</h2>

{ isMenuSelected && (
<div className="edit-navigation-header__actions">
<DropdownMenu
Expand Down
57 changes: 57 additions & 0 deletions packages/edit-navigation/src/components/header/inserter-toggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* WordPress dependencies
*/
import { store as blockEditorStore } from '@wordpress/block-editor';
import { Button, ToolbarItem } from '@wordpress/components';
import { _x } from '@wordpress/i18n';
import { useSelect, useDispatch } from '@wordpress/data';
import { plus } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { useNavigationEditorRootBlock } from '../../hooks';
import { store as editNavigationStore } from '../../store';

function InserterToggle() {
const { navBlockClientId } = useNavigationEditorRootBlock();

const { isInserterOpened, hasInserterItems } = useSelect(
( select ) => {
return {
hasInserterItems: select( blockEditorStore ).hasInserterItems(
navBlockClientId
),
isInserterOpened: select(
editNavigationStore
).isInserterOpened(),
};
},
[ navBlockClientId ]
);

const { setIsInserterOpened } = useDispatch( editNavigationStore );

return (
<ToolbarItem
as={ Button }
className="edit-navigation-header-inserter-toggle"
variant="primary"
isPressed={ isInserterOpened }
onMouseDown={ ( event ) => {
event.preventDefault();
} }
onClick={ () => setIsInserterOpened( ! isInserterOpened ) }
icon={ plus }
/* translators: button label text should, if possible, be under 16
characters. */
label={ _x(
'Toggle block inserter',
'Generic label for block inserter button'
) }
disabled={ ! hasInserterItems }
/>
);
}

export default InserterToggle;
31 changes: 31 additions & 0 deletions packages/edit-navigation/src/components/header/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,34 @@
display: none;
}
}


// INSERTER TOGGLE
.edit-navigation-header-inserter-toggle {

svg {
transition: transform cubic-bezier(0.165, 0.84, 0.44, 1) 0.2s;
@include reduce-motion("transition");
}

// Make the button appear like a "X" close button.
&.is-pressed {
svg {
transform: rotate(45deg);
}
}
}

// INSERTER PANEL
.edit-navigation-layout__inserter-panel-header {
padding-top: $grid-unit-10;
padding-right: $grid-unit-10;
display: flex;
justify-content: flex-end;

// Hide close button within panel on larger screens as this
// action is provided by the inserter toggle or ESC key.
@include break-medium() {
display: none;
}
}
95 changes: 95 additions & 0 deletions packages/edit-navigation/src/components/inserter-sidebar/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* WordPress dependencies
*/
import { Button } from '@wordpress/components';
import { close } from '@wordpress/icons';
import {
__experimentalLibrary as Library,
store as blockEditorStore,
} from '@wordpress/block-editor';
import {
useViewportMatch,
__experimentalUseDialog as useDialog,
} from '@wordpress/compose';
import { useDispatch, useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as editNavigationStore } from '../../store';
import { useNavigationEditorRootBlock } from '../../hooks';

const SHOW_PREVIEWS = false;

function InserterSidebar() {
const isMobileViewport = useViewportMatch( 'medium', '<' );

const {
navBlockClientId,
lastNavBlockItemIndex,
} = useNavigationEditorRootBlock();

const { hasInserterItems, selectedBlockClientId } = useSelect(
( select ) => {
return {
hasInserterItems: select( blockEditorStore ).hasInserterItems(
navBlockClientId
),
selectedBlockClientId: select(
blockEditorStore
).getSelectedBlock()?.clientId,
};
},
[ navBlockClientId ]
);

const { setIsInserterOpened } = useDispatch( editNavigationStore );

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

// Only concerned with whether there are items to display. If not then
// we shouldn't render.
if ( ! hasInserterItems ) {
return null;
}

const shouldInsertInNavBlock =
! selectedBlockClientId || navBlockClientId === selectedBlockClientId;

return (
<div
ref={ inserterDialogRef }
{ ...inserterDialogProps }
className="edit-navigation-layout__inserter-panel"
>
<div className="edit-navigation-layout__inserter-panel-header">
<Button
icon={ close }
onClick={ () => setIsInserterOpened( false ) }
/>
</div>
<div className="edit-navigation-layout__inserter-panel-content">
<Library
// If the root Nav block is selected then any items inserted by the
// global inserter should append after the last nav item. Otherwise
// simply allow default Gutenberg behaviour.
rootClientId={
shouldInsertInNavBlock ? navBlockClientId : undefined
} // If set, insertion will be into the block with this ID.
__experimentalInsertionIndex={
// If set, insertion will be into this explicit position.
shouldInsertInNavBlock
? lastNavBlockItemIndex
: undefined
}
shouldFocusBlock={ isMobileViewport }
showInserterHelpPanel={ SHOW_PREVIEWS }
/>
</div>
</div>
);
}

export default InserterSidebar;
8 changes: 7 additions & 1 deletion packages/edit-navigation/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import Sidebar from '../sidebar';
import Header from '../header';
import Notices from '../notices';
import Editor from '../editor';
import InserterSidebar from '../inserter-sidebar';
import UnsavedChangesWarning from './unsaved-changes-warning';
import { store as editNavigationStore } from '../../store';

Expand All @@ -44,6 +45,7 @@ const interfaceLabels = {
body: __( 'Navigation menu blocks' ),
/* translators: accessibility text for the navigation screen settings landmark region. */
sidebar: __( 'Navigation settings' ),
secondarySidebar: __( 'Block library' ),
};

export default function Layout( { blockEditorSettings } ) {
Expand All @@ -70,11 +72,12 @@ export default function Layout( { blockEditorSettings } ) {
navigationPost
);

const { hasSidebarEnabled } = useSelect(
const { hasSidebarEnabled, isInserterOpened } = useSelect(
( select ) => ( {
hasSidebarEnabled: !! select(
interfaceStore
).getActiveComplementaryArea( 'core/edit-navigation' ),
isInserterOpened: select( editNavigationStore ).isInserterOpened(),
} ),
[]
);
Expand Down Expand Up @@ -175,6 +178,9 @@ export default function Layout( { blockEditorSettings } ) {
<ComplementaryArea.Slot scope="core/edit-navigation" />
)
}
secondarySidebar={
isInserterOpened && <InserterSidebar />
}
/>
{ isMenuSelected && (
<Sidebar
Expand Down
1 change: 1 addition & 0 deletions packages/edit-navigation/src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export { default as useNavigationBlockEditor } from './use-navigation-block-edit
export { default as useMenuNotifications } from './use-menu-notifications';
export { default as useSelectedMenuId } from './use-selected-menu-id';
export { default as useMenuLocations } from './use-menu-locations';
export { default as useNavigationEditorRootBlock } from './use-navigation-editor-root-block';
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';

/**
* Internal dependencies
*/

const useNavigationEditorRootBlock = () => {
return useSelect( ( select ) => {
const { getBlockOrder } = select( blockEditorStore );

const lockedNavigationBlock = getBlockOrder()[ 0 ];

return {
navBlockClientId: lockedNavigationBlock,
lastNavBlockItemIndex: getBlockOrder( lockedNavigationBlock )
.length,
};
}, [] );
};

export default useNavigationEditorRootBlock;
53 changes: 44 additions & 9 deletions packages/edit-navigation/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,68 @@ import {
registerCoreBlocks,
__experimentalRegisterExperimentalCoreBlocks,
} from '@wordpress/block-library';
import { render } from '@wordpress/element';
import { render, useMemo } from '@wordpress/element';
import { __experimentalFetchLinkSuggestions as fetchLinkSuggestions } from '@wordpress/core-data';
import { useDispatch } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as editNavigationStore } from './store';
import { addFilters } from './filters';
import Layout from './components/layout';

function NavEditor( { settings } ) {
const { setIsInserterOpened } = useDispatch( editNavigationStore );

// Allows the QuickInserter to toggle the sidebar inserter.
// This is marked as experimental to give time for the quick inserter to mature.
const __experimentalSetIsInserterOpened = setIsInserterOpened;

// Provide link suggestions handler to fetch search results for Link UI.
const __experimentalFetchLinkSuggestions = ( search, searchOptions ) =>
fetchLinkSuggestions( search, searchOptions, settings );

const editorSettings = useMemo( () => {
return {
...settings,
__experimentalFetchLinkSuggestions,
__experimentalSetIsInserterOpened,
};
}, [
settings,
__experimentalFetchLinkSuggestions,
__experimentalSetIsInserterOpened,
] );

return <Layout blockEditorSettings={ editorSettings } />;
}

/**
* Internal dependencies
* Setup and registration of editor.
*
* @param {Object} settings blockEditor settings.
*/
import Layout from './components/layout';
import './store';

export function initialize( id, settings ) {
function setUpEditor( settings ) {
addFilters( ! settings.blockNavMenus );
registerCoreBlocks();

if ( process.env.GUTENBERG_PHASE === 2 ) {
__experimentalRegisterExperimentalCoreBlocks();
}
}

settings.__experimentalFetchLinkSuggestions = ( search, searchOptions ) =>
fetchLinkSuggestions( search, searchOptions, settings );
/**
* Initalise and render editor into DOM.
*
* @param {string} id ID of HTML element into which the editor will be rendered.
* @param {Object} settings blockEditor settings.
*/
export function initialize( id, settings ) {
setUpEditor( settings );

render(
<Layout blockEditorSettings={ settings } />,
<NavEditor settings={ settings } />,
document.getElementById( id )
);
}
Loading

0 comments on commit 5710513

Please sign in to comment.