diff --git a/lib/blocks.php b/lib/blocks.php
index 8dad24f09d189f..5e4d2e9e745151 100644
--- a/lib/blocks.php
+++ b/lib/blocks.php
@@ -87,6 +87,7 @@ function gutenberg_reregister_core_block_types() {
'navigation.php' => 'core/navigation',
'navigation-link.php' => 'core/navigation-link',
'navigation-submenu.php' => 'core/navigation-submenu',
+ 'navigation-overlay-close.php' => 'core/navigation-overlay-close',
'page-list.php' => 'core/page-list',
'page-list-item.php' => 'core/page-list-item',
'pattern.php' => 'core/pattern',
diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php
index b4bf0d409b1596..cefee9f3bc53d7 100644
--- a/lib/experimental/blocks.php
+++ b/lib/experimental/blocks.php
@@ -78,44 +78,17 @@ function wp_enqueue_block_view_script( $block_name, $args ) {
}
}
-/**
- * Registers a new block style for one or more block types.
- *
- * WP_Block_Styles_Registry was marked as `final` in core so it cannot be
- * updated via Gutenberg to allow registration of a style across multiple
- * block types as well as with an optional style object. This function will
- * support the desired functionality until the styles registry can be updated
- * in core.
- *
- * @param string|array $block_name Block type name including namespace or array of namespaced block type names.
- * @param array $style_properties Array containing the properties of the style name, label,
- * style_handle (name of the stylesheet to be enqueued),
- * inline_style (string containing the CSS to be added),
- * style_data (theme.json-like object to generate CSS from).
- *
- * @return bool True if all block styles were registered with success and false otherwise.
- */
-function gutenberg_register_block_style( $block_name, $style_properties ) {
- if ( ! is_string( $block_name ) && ! is_array( $block_name ) ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'Block name must be a string or array.', 'gutenberg' ),
- '6.6.0'
- );
-
- return false;
- }
-
- $block_names = is_string( $block_name ) ? array( $block_name ) : $block_name;
- $result = true;
-
- foreach ( $block_names as $name ) {
- if ( ! WP_Block_Styles_Registry::get_instance()->register( $name, $style_properties ) ) {
- $result = false;
- }
- }
-
- return $result;
+function gutenberg_add_navigation_overlay_area( $areas ) {
+ $areas[] = array(
+ 'area' => 'navigation-overlay',
+ 'label' => _x( 'Navigation Overlay', 'template part area' ),
+ 'description' => __(
+ 'An area for navigation overlay content.'
+ ),
+ 'area_tag' => 'section',
+ 'icon' => 'handle',
+ );
+ return $areas;
}
/**
@@ -136,3 +109,55 @@ function gutenberg_block_core_form_view_script_module( $data ) {
'script_module_data_@wordpress/block-library/form/view',
'gutenberg_block_core_form_view_script_module'
);
+add_filter( 'default_wp_template_part_areas', 'gutenberg_add_navigation_overlay_area', 10, 1 );
+
+function gutenberg_add_default_navigation_overlay_template_part( $block_template, $id, $template_type ) {
+
+ // if the template type is not template part, return the block template
+ if ( 'wp_template_part' !== $template_type ) {
+ return $block_template;
+ }
+
+ // If its not the "Core" Navigation Overlay, return the block template.
+ if ( $id !== 'core//navigation-overlay' ) {
+ return $block_template;
+ }
+
+ // If the block template is not empty, return the "found" block template.
+ // Failure to do this will override any "found" overlay template part from the Theme.
+ if ( ! empty( $block_template ) ) {
+ return $block_template;
+ }
+
+ // Return a default template part for the Navigation Overlay.
+ // This is essentially a "Core" fallback in case the Theme does not provide one.
+ $template = new WP_Block_Template();
+
+ // TODO: should we provide "$theme" here at all as this is a "Core" template.
+ $template->id = 'core' . '//' . 'navigation-overlay';
+ $template->theme = 'core';
+ $template->slug = 'navigation-overlay';
+ $template->source = 'custom';
+ $template->type = 'wp_template_part';
+ $template->title = 'Navigation Overlay';
+ $template->status = 'publish';
+ $template->has_theme_file = null;
+ $template->is_custom = false;
+ $template->modified = null;
+ $template->origin = null;
+ $template->author = null;
+
+ // Set the area to match the Navigation Overlay area.
+ $template->area = 'navigation-overlay';
+
+ // The content is the default Navigation Overlay template part. This will only be used
+ // if the Theme does not provide a template part for the Navigation Overlay.
+ // PHP is used here to allow for translation of the default template part.
+ ob_start();
+ include __DIR__ . '/navigation-overlay.php';
+ $template->content = ob_get_clean();
+
+ return $template;
+}
+
+add_filter( 'get_block_file_template', 'gutenberg_add_default_navigation_overlay_template_part', 10, 3 );
diff --git a/lib/experimental/navigation-overlay.php b/lib/experimental/navigation-overlay.php
new file mode 100644
index 00000000000000..c41a7541789100
--- /dev/null
+++ b/lib/experimental/navigation-overlay.php
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js
index ef0b1b2ffde21c..e39b055149a419 100644
--- a/packages/block-library/src/index.js
+++ b/packages/block-library/src/index.js
@@ -80,6 +80,7 @@ import * as more from './more';
import * as navigation from './navigation';
import * as navigationLink from './navigation-link';
import * as navigationSubmenu from './navigation-submenu';
+import * as navigationOverlayClose from './navigation-overlay-close';
import * as nextpage from './nextpage';
import * as pattern from './pattern';
import * as pageList from './page-list';
@@ -208,6 +209,7 @@ const getAllBlocks = () => {
navigation,
navigationLink,
navigationSubmenu,
+ navigationOverlayClose,
siteLogo,
siteTitle,
siteTagline,
diff --git a/packages/block-library/src/navigation-overlay-close/block.json b/packages/block-library/src/navigation-overlay-close/block.json
new file mode 100644
index 00000000000000..8df3fcc3aa74b8
--- /dev/null
+++ b/packages/block-library/src/navigation-overlay-close/block.json
@@ -0,0 +1,49 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 3,
+ "name": "core/navigation-overlay-close",
+ "title": "Navigation Overlay Close",
+ "category": "design",
+ "description": "Add a Close button to your Navigation Overlay.",
+ "textdomain": "default",
+ "icon": "dismiss",
+ "attributes": {
+ "hasIcon": {
+ "type": "boolean",
+ "default": true
+ }
+ },
+ "usesContext": [
+ "textColor",
+ "customTextColor",
+ "backgroundColor",
+ "customBackgroundColor"
+ ],
+ "supports": {
+ "multiple": false,
+ "reusable": false,
+ "html": false,
+ "dimensions": {
+ "width": true,
+ "height": true
+ },
+ "color": {
+ "link": false,
+ "text": true,
+ "background": true
+ },
+ "interactivity": true,
+ "anchor": true,
+ "spacing": {
+ "margin": true,
+ "padding": true,
+ "units": [ "px", "em", "rem", "vh", "vw" ],
+ "__experimentalDefaultControls": {
+ "margin": true,
+ "padding": true
+ }
+ }
+ },
+ "editorStyle": "wp-block-navigation-overlay-close-editor",
+ "style": "wp-block-navigation-overlay-close"
+}
diff --git a/packages/block-library/src/navigation-overlay-close/edit.js b/packages/block-library/src/navigation-overlay-close/edit.js
new file mode 100644
index 00000000000000..72e09f77f17366
--- /dev/null
+++ b/packages/block-library/src/navigation-overlay-close/edit.js
@@ -0,0 +1,55 @@
+/**
+ * WordPress dependencies
+ */
+import { Button, Icon, ToggleControl, PanelBody } from '@wordpress/components';
+import { close } from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
+import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
+import { privateApis as routerPrivateApis } from '@wordpress/router';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../lock-unlock';
+
+const { useHistory } = unlock( routerPrivateApis );
+
+export default function Edit( { attributes, isSelected, setAttributes } ) {
+ const blockProps = useBlockProps();
+ const history = useHistory();
+ const { hasIcon } = attributes;
+
+ const closeText = __( 'Close' );
+
+ const onClick = () => {
+ if ( isSelected ) {
+ // Exit navigation overlay edit mode.
+ history.back();
+ }
+ };
+
+ blockProps.onClick = onClick;
+
+ return (
+ <>
+
+
+
+ setAttributes( { hasIcon: value } )
+ }
+ checked={ hasIcon }
+ />
+
+
+
+ >
+ );
+}
diff --git a/packages/block-library/src/navigation-overlay-close/index.js b/packages/block-library/src/navigation-overlay-close/index.js
new file mode 100644
index 00000000000000..0a048e1e6409da
--- /dev/null
+++ b/packages/block-library/src/navigation-overlay-close/index.js
@@ -0,0 +1,16 @@
+/**
+ * Internal dependencies
+ */
+import initBlock from '../utils/init-block';
+import metadata from './block.json';
+import edit from './edit';
+
+const { name } = metadata;
+
+export { metadata, name };
+
+export const settings = {
+ edit,
+};
+
+export const init = () => initBlock( { name, metadata, settings } );
diff --git a/packages/block-library/src/navigation-overlay-close/index.php b/packages/block-library/src/navigation-overlay-close/index.php
new file mode 100644
index 00000000000000..95f3f5d56a30d6
--- /dev/null
+++ b/packages/block-library/src/navigation-overlay-close/index.php
@@ -0,0 +1,48 @@
+';
+
+ $hasIcon = ! empty( $attributes['hasIcon'] );
+
+ $wrapper_attributes = get_block_wrapper_attributes(
+ array_filter( // Removes any empty attributes.
+ // Attributes
+ array(
+ // This directive is duplicated in the Navigation Block itself.
+ // See WP_Navigation_Block_Renderer::get_responsive_container_markup().
+ // Changes to this directive should be reflected there as well.
+ 'data-wp-on--click' => 'actions.closeMenuOnClick',
+ 'aria-label' => $hasIcon ? __( 'Close menu' ) : false,
+ )
+ )
+ );
+
+ $content = $hasIcon ? $close_icon : __( 'Close menu' );
+
+ return sprintf(
+ '',
+ $wrapper_attributes,
+ $content,
+ );
+
+}
+
+
+/**
+ * Registers the `core/navigation-overlay-close` block on server.
+ */
+function register_block_core_navigation_overlay_close() {
+ register_block_type_from_metadata(
+ __DIR__ . '/navigation-overlay-close',
+ array(
+ 'render_callback' => 'render_block_core_navigation_overlay_close',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_navigation_overlay_close' );
diff --git a/packages/block-library/src/navigation-overlay-close/init.js b/packages/block-library/src/navigation-overlay-close/init.js
new file mode 100644
index 00000000000000..79f0492c2cb2f8
--- /dev/null
+++ b/packages/block-library/src/navigation-overlay-close/init.js
@@ -0,0 +1,6 @@
+/**
+ * Internal dependencies
+ */
+import { init } from './';
+
+export default init();
diff --git a/packages/block-library/src/navigation-overlay-close/style.scss b/packages/block-library/src/navigation-overlay-close/style.scss
new file mode 100644
index 00000000000000..acfd3bc9f2b9fc
--- /dev/null
+++ b/packages/block-library/src/navigation-overlay-close/style.scss
@@ -0,0 +1,34 @@
+
+// Size of burger and close icons.
+$navigation-icon-size: 24px;
+
+// Menu and close buttons.
+.wp-block-navigation-overlay-close {
+ height: auto; // remove default height applied to button component
+ vertical-align: middle;
+ cursor: pointer;
+ border: none;
+ margin: 0;
+ padding: 0;
+ text-transform: inherit;
+ z-index: 2; // Needs to be above the modal z index itself.
+ background-color: inherit; // remove user agent stylesheet default.
+ color: inherit; // remove user agent stylesheet default.
+
+ // When set to collapse into a text button, it should inherit the parent font.
+ // This needs specificity to override inherited properties by the button element and component.
+ &.wp-block-navigation-overlay-close {
+ font-family: inherit;
+ font-weight: inherit;
+ font-size: inherit;
+ }
+
+ svg {
+ fill: currentColor;
+ pointer-events: none;
+ display: block;
+ width: $navigation-icon-size;
+ height: $navigation-icon-size;
+ }
+}
+
diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json
index 249193e1cc234a..79b7dcd6ce482d 100644
--- a/packages/block-library/src/navigation/block.json
+++ b/packages/block-library/src/navigation/block.json
@@ -22,7 +22,7 @@
"textdomain": "default",
"attributes": {
"ref": {
- "type": "number"
+ "type": [ "number", "string" ]
},
"textColor": {
"type": "string"
@@ -84,6 +84,9 @@
"templateLock": {
"type": [ "string", "boolean" ],
"enum": [ "all", "insert", "contentOnly", false ]
+ },
+ "overlayId": {
+ "type": "string"
}
},
"providesContext": {
diff --git a/packages/block-library/src/navigation/constants.js b/packages/block-library/src/navigation/constants.js
index efb4d584f81051..ae0dc9baa57d29 100644
--- a/packages/block-library/src/navigation/constants.js
+++ b/packages/block-library/src/navigation/constants.js
@@ -11,6 +11,8 @@ export const PRIORITIZED_INSERTER_BLOCKS = [
'core/navigation-link',
];
+export const NAVIGATION_OVERLAY_TEMPLATE_PART_AREA = 'navigation-overlay';
+
// These parameters must be kept aligned with those in
// lib/compat/wordpress-6.3/navigation-block-preloading.php
// and
diff --git a/packages/block-library/src/navigation/edit/edit-overlay-button.js b/packages/block-library/src/navigation/edit/edit-overlay-button.js
new file mode 100644
index 00000000000000..2043e6246527ca
--- /dev/null
+++ b/packages/block-library/src/navigation/edit/edit-overlay-button.js
@@ -0,0 +1,172 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import {
+ Button,
+ MenuGroup,
+ MenuItem,
+ MenuItemsChoice,
+ DropdownMenu,
+} from '@wordpress/components';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { privateApis as routerPrivateApis } from '@wordpress/router';
+import { store as coreStore } from '@wordpress/core-data';
+import { parse, serialize } from '@wordpress/blocks';
+import { moreVertical } from '@wordpress/icons';
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../lock-unlock';
+import useGoToOverlayEditor from './use-go-to-overlay-editor';
+import useOverlay from './use-overlay';
+
+const { useHistory } = unlock( routerPrivateApis );
+
+export default function EditOverlayButton( {
+ navRef,
+ attributes,
+ setAttributes,
+} ) {
+ const currentOverlayId = attributes?.overlayId;
+
+ // Get any custom overlay attached to this block,
+ // falling back to the one provided by the Theme.
+ const overlay = useOverlay( currentOverlayId );
+
+ const { coreOverlay, allOverlays } = useSelect( ( select ) => {
+ return {
+ // Get the default template part that core provides.
+ coreOverlay: select( coreStore ).getEntityRecord(
+ 'postType',
+ 'wp_template_part',
+ `core//navigation-overlay`
+ ),
+ // Get all the overlays.
+ allOverlays: select( coreStore ).getEntityRecords(
+ 'postType',
+ 'wp_template_part',
+ {
+ area: 'navigation-overlay',
+ }
+ ),
+ };
+ }, [] );
+
+ const { saveEntityRecord } = useDispatch( coreStore );
+
+ const history = useHistory();
+
+ const goToOverlayEditor = useGoToOverlayEditor();
+
+ async function handleEditOverlay( event ) {
+ event.preventDefault();
+
+ // There may already be an overlay with the slug `navigation-overlay`.
+ // This might be a user created one, or one provided by the theme.
+ // If so, then go directly to the editor for that overlay template part.
+ if ( overlay ) {
+ goToOverlayEditor( overlay.id, navRef );
+ return;
+ }
+
+ // If there is not overlay then create one using the base template part
+ // provided by Core.
+ // TODO: catch and handle errors.
+ const overlayBlocks = buildOverlayBlocks( coreOverlay.content.raw );
+
+ // The new overlay should use the current Theme's slug.
+ const newOverlay = await createOverlay( overlayBlocks );
+
+ goToOverlayEditor( newOverlay?.id, navRef );
+ }
+
+ async function handleCreateNewOverlay() {
+ const overlayBlocks = buildOverlayBlocks( overlay.content.raw );
+
+ const newOverlay = await createOverlay( overlayBlocks );
+
+ setAttributes( {
+ overlayId: newOverlay?.id,
+ } );
+
+ goToOverlayEditor( newOverlay?.id, navRef );
+ }
+
+ function buildOverlayBlocks( content ) {
+ const parsedBlocks = parse( content );
+ return parsedBlocks;
+ }
+
+ async function createOverlay( overlayBlocks ) {
+ return await saveEntityRecord(
+ 'postType',
+ 'wp_template_part',
+ {
+ slug: `navigation-overlay`, // `theme//` prefix is appended automatically.
+ title: `Navigation Overlay`,
+ content: serialize( overlayBlocks ),
+ area: 'navigation-overlay',
+ },
+ { throwOnError: true }
+ );
+ }
+
+ // Map the overlay records to format
+ const overlayChoices = allOverlays?.map( ( overlayRecord ) => {
+ return {
+ label: overlayRecord.title.rendered, // decodeEntities required
+ value: overlayRecord.id,
+ };
+ } );
+
+ if ( ! history || ( ! coreOverlay && ! overlay ) ) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+ { () => (
+ <>
+
+ {
+ setAttributes( {
+ overlayId: newOverlayId,
+ } );
+ } }
+ choices={ overlayChoices }
+ disabled={ overlayChoices?.length === 0 }
+ />
+
+
+
+
+
+ >
+ ) }
+
+ >
+ );
+}
diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js
index 74b29f2fc4a07d..8115fd379b83ca 100644
--- a/packages/block-library/src/navigation/edit/index.js
+++ b/packages/block-library/src/navigation/edit/index.js
@@ -38,6 +38,7 @@ import {
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
__experimentalVStack as VStack,
ToggleControl,
+ __experimentalHStack as HStack,
Button,
Spinner,
Notice,
@@ -49,6 +50,7 @@ import { speak } from '@wordpress/a11y';
import { close, Icon, page } from '@wordpress/icons';
import { createBlock } from '@wordpress/blocks';
import { useInstanceId } from '@wordpress/compose';
+import { privateApis as routerPrivateApis } from '@wordpress/router';
/**
* Internal dependencies
@@ -80,6 +82,12 @@ import AccessibleMenuDescription from './accessible-menu-description';
import { unlock } from '../../lock-unlock';
import { useToolsPanelDropdownMenuProps } from '../../utils/hooks';
import { DEFAULT_BLOCK } from '../constants';
+import EditOverlayButton from './edit-overlay-button';
+import useIsWithinOverlay from './use-is-within-overlay';
+import useGoToOverlayEditor from './use-go-to-overlay-editor';
+import useOverlay from './use-overlay';
+
+const { useLocation } = unlock( routerPrivateApis );
/**
* Component that renders the Add page button for the Navigation block.
@@ -239,6 +247,14 @@ function ColorTools( {
);
}
+function useInheritedRef() {
+ const {
+ params: { myNavRef },
+ } = useLocation();
+
+ return myNavRef;
+}
+
function Navigation( {
attributes,
setAttributes,
@@ -274,7 +290,13 @@ function Navigation( {
icon = 'handle',
} = attributes;
- const ref = attributes.ref;
+ const [ tempRef, setTempRef ] = useState( null );
+
+ const ref = attributes.ref || tempRef;
+
+ const inheritedRef = useInheritedRef();
+
+ const isInheritRefMode = !! inheritedRef;
const setRef = useCallback(
( postId ) => {
@@ -288,6 +310,15 @@ function Navigation( {
const blockEditingMode = useBlockEditingMode();
+ const isInsideOverlay = useIsWithinOverlay();
+
+ const showOverlayControls = ! isInsideOverlay;
+
+ const customOverlay = useOverlay( attributes?.overlayId );
+ const goToOverlayEditor = useGoToOverlayEditor();
+
+ const hasCustomOverlay = !! customOverlay;
+
// Preload classic menus, so that they don't suddenly pop-in when viewing
// the Select Menu dropdown.
const { menus: classicMenus } = useNavigationEntities();
@@ -399,11 +430,21 @@ function Navigation( {
: null;
useEffect( () => {
+ // Todo: set the ref based on context.
+ if ( isInheritRefMode ) {
+ setTempRef( inheritedRef );
+ return;
+ }
+
// If:
// - there is an existing menu, OR
// - there are existing (uncontrolled) inner blocks
// ...then don't request a fallback menu.
- if ( ref || hasUnsavedBlocks || ! navigationFallbackId ) {
+ if (
+ ( ref && ! isInheritRefMode ) ||
+ hasUnsavedBlocks ||
+ ! navigationFallbackId
+ ) {
return;
}
@@ -421,6 +462,8 @@ function Navigation( {
hasUnsavedBlocks,
navigationFallbackId,
__unstableMarkNextChangeAsNotPersistent,
+ isInheritRefMode,
+ inheritedRef,
] );
const navRef = useRef();
@@ -504,6 +547,17 @@ function Navigation( {
handleUpdateMenu( menuId );
};
+ const onToggleOverlayMenu = ( _toggleVal ) => {
+ if ( hasCustomOverlay && _toggleVal ) {
+ // If there is a Custom Overlay and the user is trying to open the menu
+ // then edit the overlay template part.
+ goToOverlayEditor( customOverlay?.id, ref );
+ } else {
+ // Otherwise just toggle the default overlay witin the editor.
+ setResponsiveMenuVisibility( _toggleVal );
+ }
+ };
+
useEffect( () => {
hideNavigationMenuStatusNotice();
@@ -658,7 +712,7 @@ function Navigation( {
} }
dropdownMenuProps={ dropdownMenuProps }
>
- { isResponsive && (
+ { isResponsive && showOverlayControls && (
<>
) }
+ { showOverlayControls && (
+
+
+ { __( 'Overlay Menu' ) }
+
+ { isResponsive && (
+
+ ) }
+
+ ) }
+
overlayMenu !== 'mobile' }
label={ __( 'Overlay Menu' ) }
@@ -868,11 +937,12 @@ function Navigation( {
onSelectNavigationMenu={ onSelectNavigationMenu }
isLoading={ isLoading }
blockEditingMode={ blockEditingMode }
+ isInheritRefMode={ isInheritRefMode }
/>
{ blockEditingMode === 'default' && stylingInspectorControls }
{ blockEditingMode === 'default' && stylingInspectorControls }
{ blockEditingMode === 'contentOnly' && isEntityAvailable && (
@@ -1032,7 +1104,7 @@ function Navigation( {
/>
{
onSelectNavigationMenu,
isManageMenusButtonDisabled,
blockEditingMode,
+ isInheritRefMode,
} = props;
return (
@@ -207,7 +209,9 @@ const MenuInspectorControls = ( props ) => {
className="wp-block-navigation-off-canvas-editor__title"
level={ 2 }
>
- { __( 'Menu' ) }
+ { isInheritRefMode
+ ? __( 'Menu (Synced)' )
+ : __( 'Menu' ) }
{ blockEditingMode === 'default' && (
{
/>
) }
+ { isInheritRefMode && (
+
+ { __(
+ 'Edits to this Navigation Menu will apply across your website.'
+ ) }
+
+ ) }
diff --git a/packages/block-library/src/navigation/edit/use-go-to-overlay-editor.js b/packages/block-library/src/navigation/edit/use-go-to-overlay-editor.js
new file mode 100644
index 00000000000000..65663e2ae4c76e
--- /dev/null
+++ b/packages/block-library/src/navigation/edit/use-go-to-overlay-editor.js
@@ -0,0 +1,30 @@
+/**
+ * WordPress dependencies
+ */
+import { privateApis as routerPrivateApis } from '@wordpress/router';
+import { addQueryArgs } from '@wordpress/url';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../lock-unlock';
+
+const { useHistory, useLocation } = unlock( routerPrivateApis );
+
+export default function useGoToOverlayEditor() {
+ const history = useHistory();
+ const { path } = useLocation();
+
+ function goToOverlayEditor( overlayId, navRef ) {
+ history.navigate(
+ addQueryArgs( path, {
+ postId: overlayId,
+ postType: 'wp_template_part',
+ canvas: 'edit',
+ myNavRef: navRef,
+ } )
+ );
+ }
+
+ return goToOverlayEditor;
+}
diff --git a/packages/block-library/src/navigation/edit/use-is-within-overlay.js b/packages/block-library/src/navigation/edit/use-is-within-overlay.js
new file mode 100644
index 00000000000000..ab3594998ddbf9
--- /dev/null
+++ b/packages/block-library/src/navigation/edit/use-is-within-overlay.js
@@ -0,0 +1,7 @@
+import { useEntityProp } from '@wordpress/core-data';
+import { NAVIGATION_OVERLAY_TEMPLATE_PART_AREA } from '../constants';
+
+export default function useIsWithinOverlay() {
+ const [ area ] = useEntityProp( 'postType', 'wp_template_part', 'area' );
+ return area === NAVIGATION_OVERLAY_TEMPLATE_PART_AREA;
+}
diff --git a/packages/block-library/src/navigation/edit/use-overlay.js b/packages/block-library/src/navigation/edit/use-overlay.js
new file mode 100644
index 00000000000000..c91a30addf4abb
--- /dev/null
+++ b/packages/block-library/src/navigation/edit/use-overlay.js
@@ -0,0 +1,32 @@
+/**
+ * WordPress dependencies
+ */
+import { store as coreStore } from '@wordpress/core-data';
+import { useSelect } from '@wordpress/data';
+
+export default function useOverlay( currentOverlayId ) {
+ return useSelect(
+ ( select ) => {
+ const themeSlug = select( coreStore ).getCurrentTheme()?.stylesheet;
+
+ const themeOverlay = themeSlug
+ ? select( coreStore ).getEntityRecord(
+ 'postType',
+ 'wp_template_part',
+ `${ themeSlug }//navigation-overlay`
+ )
+ : null;
+
+ const customOverlay = themeSlug
+ ? select( coreStore ).getEntityRecord(
+ 'postType',
+ 'wp_template_part',
+ currentOverlayId
+ )
+ : null;
+
+ return customOverlay || themeOverlay;
+ },
+ [ currentOverlayId ]
+ );
+}
diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss
index 4132b5467afb26..77e1caac34de56 100644
--- a/packages/block-library/src/navigation/editor.scss
+++ b/packages/block-library/src/navigation/editor.scss
@@ -196,6 +196,15 @@ $color-control-label-height: 20px;
}
}
+// Overlay UI controls
+.wp-block-navigation__edit-overlay-button {
+ text-transform: uppercase;
+ font-size: 11px;
+ &.is-link {
+ text-decoration: none;
+ }
+}
+
// Override inner padding on default appender.
// This should be a temporary fix, to be replaced by improvements to
// the sibling inserter. Or by the dropdown appender that is used in Group, instead of the "Button block appender".
@@ -645,3 +654,11 @@ body.editor-styles-wrapper .wp-block-navigation__responsive-container.is-menu-op
.wp-block-navigation__submenu-accessibility-notice {
grid-column: span 2;
}
+
+.wp-block-navigation__menu-inspector-controls__overlay-menu {
+ margin-bottom: $grid-unit-20;
+
+ .wp-block-navigation__menu-inspector-controls__overlay-menu-heading {
+ margin-bottom: 0;
+ }
+}
diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss
index a32e90d60a7d6b..4ea857d39a12b8 100644
--- a/packages/block-library/src/navigation/style.scss
+++ b/packages/block-library/src/navigation/style.scss
@@ -519,12 +519,14 @@ button.wp-block-navigation-item__content {
animation-fill-mode: forwards;
}
+
// Try to inherit any root paddings set, so the X can align to a top-right aligned menu.
padding-top: clamp(1rem, var(--wp--style--root--padding-top), 20rem);
padding-right: clamp(1rem, var(--wp--style--root--padding-right), 20rem);
padding-bottom: clamp(1rem, var(--wp--style--root--padding-bottom), 20rem);
padding-left: clamp(1rem, var(--wp--style--root--padding-left), 20rem);
+
// Allow modal to scroll.
overflow: auto;
@@ -644,6 +646,38 @@ button.wp-block-navigation-item__content {
}
}
+
+// Custom Overlay Overides
+// Removes any default styles from the overlay and its containers to afford the abilityt
+// for blocks within the editor to control the "styling" of the overlay.
+.wp-block-navigation__responsive-container.has-custom-overlay.is-menu-open {
+ padding: 0;
+
+ // Allow overlay to occupy 100% of the available vertical space.
+ &,
+ .wp-block-navigation__responsive-close,
+ .wp-block-navigation__responsive-dialog,
+ .wp-block-navigation__responsive-container-content,
+ .wp-block-navigation__overlay {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ }
+
+ .wp-block-navigation__overlay > .wp-block-group {
+ flex-grow: 1; // force first block to occupy all the space.
+ }
+
+ .wp-block-navigation__responsive-close {
+ max-width: none;
+ }
+
+ .wp-block-navigation__responsive-container-content {
+ padding-top: 0;
+ }
+}
+
+
// Default menu background and font color.
.wp-block-navigation:not(.has-background)
.wp-block-navigation__responsive-container.is-menu-open {
@@ -750,7 +784,7 @@ button.wp-block-navigation-item__content {
// Adjust open dialog top margin when admin-bar is visible.
// Needs to be scoped to .is-menu-open, or it will shift the position of any other navigations that may be present.
-.has-modal-open .admin-bar .is-menu-open .wp-block-navigation__responsive-dialog {
+.has-modal-open .admin-bar .is-menu-open .wp-block-navigation__responsive-container:not(.has-custom-overlay) .wp-block-navigation__responsive-dialog {
margin-top: $admin-bar-height-big;
// Handle smaller admin-bar.
@@ -763,3 +797,20 @@ button.wp-block-navigation-item__content {
html.has-modal-open {
overflow: hidden;
}
+
+
+.wp-block-navigation__overlay {
+ display: none;
+ width: 100%; // fill the overlay
+ height: 100%; // fill the overlay
+}
+
+.is-menu-open.has-custom-overlay {
+ .wp-block-navigation__overlay {
+ display: initial;
+ }
+
+ .wp-block-navigation__default {
+ display: none;
+ }
+}
diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss
index 3e40e89b8e0083..5a4827af3717ea 100644
--- a/packages/block-library/src/style.scss
+++ b/packages/block-library/src/style.scss
@@ -35,6 +35,7 @@
@import "./media-text/style.scss";
@import "./navigation/style.scss";
@import "./navigation-link/style.scss";
+@import "./navigation-overlay-close/style.scss";
@import "./page-list/style.scss";
@import "./paragraph/style.scss";
@import "./post-author/style.scss";
diff --git a/packages/edit-site/src/components/block-editor/editor-canvas.js b/packages/edit-site/src/components/block-editor/editor-canvas.js
new file mode 100644
index 00000000000000..eaa35f9fe4d616
--- /dev/null
+++ b/packages/edit-site/src/components/block-editor/editor-canvas.js
@@ -0,0 +1,130 @@
+/**
+ * External dependencies
+ */
+import clsx from 'clsx';
+
+/**
+ * WordPress dependencies
+ */
+import { store as blockEditorStore } from '@wordpress/block-editor';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { ENTER, SPACE } from '@wordpress/keycodes';
+import { useState, useEffect, useMemo } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { privateApis as editorPrivateApis } from '@wordpress/editor';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../lock-unlock';
+import { store as editSiteStore } from '../../store';
+import {
+ FOCUSABLE_ENTITIES,
+ NAVIGATION_POST_TYPE,
+} from '../../utils/constants';
+import useIsNavigationOverlay from './use-is-navigation-overlay';
+
+const { EditorCanvas: EditorCanvasRoot } = unlock( editorPrivateApis );
+
+function EditorCanvas( { enableResizing, settings, children, ...props } ) {
+ const { hasBlocks, isFocusMode, templateType, canvasMode, isZoomOutMode } =
+ useSelect( ( select ) => {
+ const { getBlockCount, __unstableGetEditorMode } =
+ select( blockEditorStore );
+ const { getEditedPostType, getCanvasMode } = unlock(
+ select( editSiteStore )
+ );
+ const _templateType = getEditedPostType();
+
+ return {
+ templateType: _templateType,
+ isFocusMode: FOCUSABLE_ENTITIES.includes( _templateType ),
+ isZoomOutMode: __unstableGetEditorMode() === 'zoom-out',
+ canvasMode: getCanvasMode(),
+ hasBlocks: !! getBlockCount(),
+ };
+ }, [] );
+ const { setCanvasMode } = unlock( useDispatch( editSiteStore ) );
+ const [ isFocused, setIsFocused ] = useState( false );
+ const isNavigationOverlayTemplate = useIsNavigationOverlay();
+
+ useEffect( () => {
+ if ( canvasMode === 'edit' ) {
+ setIsFocused( false );
+ }
+ }, [ canvasMode ] );
+
+ const viewModeProps = {
+ 'aria-label': __( 'Editor Canvas' ),
+ role: 'button',
+ tabIndex: 0,
+ onFocus: () => setIsFocused( true ),
+ onBlur: () => setIsFocused( false ),
+ onKeyDown: ( event ) => {
+ const { keyCode } = event;
+ if ( keyCode === ENTER || keyCode === SPACE ) {
+ event.preventDefault();
+ setCanvasMode( 'edit' );
+ }
+ },
+ onClick: () => setCanvasMode( 'edit' ),
+ readonly: true,
+ };
+ const isTemplateTypeNavigation = templateType === NAVIGATION_POST_TYPE;
+ const isNavigationFocusMode = isTemplateTypeNavigation && isFocusMode;
+ // Hide the appender when:
+ // - In navigation focus mode (should only allow the root Nav block).
+ // - editing the navigation overlay template.
+ // - In view mode (i.e. not editing).
+ const showBlockAppender =
+ ( isNavigationFocusMode && hasBlocks ) ||
+ isNavigationOverlayTemplate ||
+ canvasMode === 'view'
+ ? false
+ : undefined;
+
+ const styles = useMemo(
+ () => [
+ ...settings.styles,
+ {
+ // Forming a "block formatting context" to prevent margin collapsing.
+ // @see https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context
+
+ css: `.is-root-container{display:flow-root;${
+ // Some themes will have `min-height: 100vh` for the root container,
+ // which isn't a requirement in auto resize mode.
+ enableResizing ? 'min-height:0!important;' : ''
+ }}body{position:relative; ${
+ canvasMode === 'view'
+ ? 'cursor: pointer; min-height: 100vh;'
+ : ''
+ }}}`,
+ },
+ ],
+ [ settings.styles, enableResizing, canvasMode ]
+ );
+
+ return (
+
+ { children }
+
+ );
+}
+
+export default EditorCanvas;
diff --git a/packages/edit-site/src/components/block-editor/use-is-navigation-overlay.js b/packages/edit-site/src/components/block-editor/use-is-navigation-overlay.js
new file mode 100644
index 00000000000000..53bd7b19feec7e
--- /dev/null
+++ b/packages/edit-site/src/components/block-editor/use-is-navigation-overlay.js
@@ -0,0 +1,9 @@
+/**
+ * WordPress dependencies
+ */
+import { useEntityProp } from '@wordpress/core-data';
+
+export default function useIsNavigationOverlay() {
+ const [ area ] = useEntityProp( 'postType', 'wp_template_part', 'area' );
+ return area === 'navigation-overlay';
+}