Skip to content

Commit

Permalink
Header: Switch to submenu blocks for navigation items with submenus (#…
Browse files Browse the repository at this point in the history
…210)

* Header: Switch to submenu blocks for navigation items with submenus

Fixes #207

* Fix typo

* Display second level items inline in the overflow menu

* Use the toggle button pattern for the overflow menu
  • Loading branch information
ryelle authored Jun 28, 2022
1 parent 054f240 commit 023c9c7
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 59 deletions.
1 change: 1 addition & 0 deletions mu-plugins/blocks/global-header-footer/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ function register_block_assets() {
array(
'openSearchLabel' => __( 'Open Search', 'wporg' ),
'closeSearchLabel' => __( 'Close Search', 'wporg' ),
'overflowMenuLabel' => __( 'More menu', 'wporg' ),
)
);
}
Expand Down
77 changes: 44 additions & 33 deletions mu-plugins/blocks/global-header-footer/header.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

namespace WordPressdotorg\MU_Plugins\Global_Header_Footer\Header;

use function WordPressdotorg\MU_Plugins\Global_Header_Footer\{ get_home_url, get_download_url };

defined( 'WPINC' ) || die();
Expand All @@ -26,6 +27,45 @@
'formAction' => 'https://wordpress.org/search/do-search.php',
);

/**
* Output menu items (`navigation-link`) & submenus (`navigation-submenu`). If a submenu, recursively iterate
* through submenu items to output links.
*
* @param array $menu_item An item from the array in `get_global_menu_items` or `get_rosetta_menu_items`.
* @param boolean $top_level Whether the menu item is a top-level link.
* @return string
*/
function recursive_menu( $menu_item, $top_level = true ) {
$has_submenu = ! empty( $menu_item['submenu'] );

if ( ! $has_submenu ) {
return sprintf(
'<!-- wp:navigation-link {"label":"%1$s","url":"%2$s","kind":"%3$s","isTopLevelLink":%4$s,"className":"%5$s"} /-->',
$menu_item['title'],
$menu_item['url'],
$menu_item['type'],
$top_level ? 'true' : 'false',
$menu_item['classes'] ?? '',
);
}

$output = sprintf(
'<!-- wp:navigation-submenu {"label":"%1$s","url":"%2$s","kind":"%3$s","className":"%4$s"} -->',
$menu_item['title'],
$menu_item['url'],
$menu_item['type'],
$menu_item['classes'] ?? '',
);

foreach ( $menu_item['submenu'] as $submenu_item ) {
$output .= recursive_menu( $submenu_item, false );
}

$output .= '<!-- /wp:navigation-submenu -->';

return $output;
}

?>

<!-- wp:group {"tagName":"header","align":"full","className":"<?php echo esc_attr( $container_class ); ?>"} -->
Expand Down Expand Up @@ -53,44 +93,15 @@
<!-- /wp:paragraph -->
<?php endif; ?>

<!-- wp:navigation {"orientation":"horizontal","className":"global-header__navigation","overlayMenu":"mobile"} -->
<!-- wp:navigation {"orientation":"horizontal","className":"global-header__navigation","overlayMenu":"mobile","openSubmenusOnClick":false} -->
<?php

/*
* Loop though menu items and create `navigation-link` block comments.
*
* This only supports 1 level deep, but that's currently enough for our needs. More than that could be an
* information architecture smell anyways.
* Loop though menu items and create navigation item blocks. Recurses through any submenu items to output dropdowns.
*/
foreach ( $menu_items as $item ) {
$is_top_level_link = empty( $item['submenu'] );

printf(
'<!-- wp:navigation-link {"label":"%s","url":"%s","kind":"%s","isTopLevelLink":%s,"className":"%s"} %s-->',
// These sometimes come from user input (like with Rosetta menus), but `render_block_core_navigation_link()` will escape the values.
$item['title'],
$item['url'],
$item['type'],
json_encode( $is_top_level_link ),
$item['classes'] ?? '',
$is_top_level_link ? '/' : ''
);

if ( ! $is_top_level_link ) {
foreach( $item['submenu'] as $submenu_item ) {
printf(
'<!-- wp:navigation-link {"label":"%s","url":"%s","kind":"%s","isTopLevelLink":true,"className":"%s"} /-->',
$submenu_item['title'],
$submenu_item['url'],
$submenu_item['type'],
$submenu_item['classes'] ?? '',
);
}

echo '<!-- /wp:navigation-link -->';
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo recursive_menu( $item );
}

?>
<!-- /wp:navigation -->

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,34 +93,44 @@
this.wrapper.classList.add( 'has-menu-loaded' );

if ( this.hasHiddenItems ) {
const labels = window.wporgGlobalHeaderI18n || {};
this.removeSubMenu();

let itemsContainer = this.wrapper.querySelector( '.wp-block-navigation__container' );
const itemsContainer = this.wrapper.querySelector( '.wp-block-navigation__container' );

// Create the ... menu list item.
let newItem = document.createElement( 'li' );
const newItem = document.createElement( 'li' );
newItem.classList.add(
'wp-block-navigation-item',
'wp-block-navigation-link',
'wp-block-navigation-submenu',
'has-child',
'open-on-hover-click',
'global-header__overflow-menu'
);

let newLink = document.createElement( 'a' );
newLink.classList.add( 'wp-block-navigation-item__content' );
newLink.setAttribute( 'href', '#' );
newLink.appendChild( document.createTextNode( '...' ) );
newItem.appendChild( newLink );
const newButton = document.createElement( 'button' );
newButton.classList.add(
'wp-block-navigation__submenu-icon',
'wp-block-navigation-submenu__toggle'
);
newButton.appendChild( document.createTextNode( '...' ) );
newButton.setAttribute( 'aria-label', labels.overflowMenuLabel );
newButton.setAttribute( 'aria-expanded', 'false' );
newButton.addEventListener( 'click', function ( event ) {
const isOpen = event.target.getAttribute( 'aria-expanded' ) === 'true';
event.target.setAttribute( 'aria-expanded', isOpen ? 'false' : 'true' );
} );
newItem.appendChild( newButton );

// Create the submenu where the hidden links will live.
let newSubMenu = document.createElement( 'ul' );
const newSubMenu = document.createElement( 'ul' );
newSubMenu.classList.add( 'wp-block-navigation__submenu-container' );
newItem.appendChild( newSubMenu );

// Populate submenu with clones of the hidden menu items.
for ( const el of this.listItems ) {
if ( el.classList.contains( 'global-header__overflow-item' ) ) {
let clone = el.cloneNode( true );
const clone = el.cloneNode( true );
newSubMenu.appendChild( clone );
}
}
Expand Down
151 changes: 135 additions & 16 deletions mu-plugins/blocks/global-header-footer/postcss/header/menu.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@
}
}

& .wp-block-navigation-item a {
& .wp-block-navigation-item a,
& .global-header__overflow-menu > button {
padding-left: var(--wp--style--block-gap);
padding-top: calc(var(--wp--style--block-gap) / 2);
padding-bottom: calc(var(--wp--style--block-gap) / 2);
Expand Down Expand Up @@ -157,9 +158,13 @@
@media (--tablet) {

/* Add extra space around the overflow … to make a bigger tap/hover target. */
& a {
& button {
margin: 0;
padding-left: var(--wp--style--block-gap);
padding-right: var(--wp--style--block-gap);
height: auto;
width: auto;
line-height: inherit;
}
}

Expand All @@ -178,6 +183,33 @@
padding-left: var(--wp--style--block-gap);
}
}

/* Hide the submenu toggle & display child items inline. */
& .wp-block-navigation__submenu-container {
& .wp-block-navigation-submenu {
display: block;
}

& .wp-block-navigation-submenu__toggle {
display: none;
}

& .wp-block-navigation__submenu-container {
position: static;
height: auto;
min-width: 200px;
opacity: 1;
overflow: visible;
width: auto;
padding-left: 1em;
}
}

&:hover .wp-block-navigation__submenu-container,
& [aria-expanded="true"] ~ .wp-block-navigation__submenu-container,
& [aria-expanded="true"] ~ .wp-block-navigation__submenu-container .wp-block-navigation__submenu-container {
visibility: visible;
}
}

& .global-header__overflow-item {
Expand All @@ -194,7 +226,7 @@

& svg {
fill: none;
stroke: currentColor;
stroke: currentcolor;
}
}
}
Expand Down Expand Up @@ -226,39 +258,106 @@
}
}

/*
Copied Gutenberg styles to provide broader browser support.
The code below is only applied via CSS :where selector in default Gutenberg styles.
See: https://github.com/WordPress/wporg-mu-plugins/issues/159
*/

& .wp-block-navigation__container {

@media (--tablet) {
background: var(--wp--preset--color--dark-grey);
}
}
}

/*
Copied Gutenberg styles to provide broader browser support.
The code below is only applied via CSS :where selector in default Gutenberg styles.
& .wp-block-navigation .has-child .wp-block-navigation__submenu-container {
See: https://github.com/WordPress/wporg-mu-plugins/issues/159
Source: https://github.com/WordPress/gutenberg/blob/3b2eccc289cfc90bd99252b12fc4c6e470ce4c04/packages/block-library/src/navigation/style.scss#L157
*/

.wp-block-navigation .has-child {
& .wp-block-navigation__submenu-container {
background-color: inherit;
color: inherit;
position: absolute;
z-index: 2;
display: flex;
flex-direction: column;
align-items: normal;

/* Hide until hover or focus within. */
opacity: 0;
transition: opacity 0.1s linear;
visibility: hidden;

/* Don't take up space when the menu is collapsed. */
width: 0;
height: 0;
overflow: hidden;
left: -1px;
overflow: hidden; /* Overflow is necessary to set, otherwise submenu items will take up space. */

/* Submenu items. */
& > .wp-block-navigation-item {
& > .wp-block-navigation-item__content {
display: flex;
flex-grow: 1;

/* Right-align the chevron in submenus. */
& .wp-block-navigation__submenu-icon {
margin-right: 0;
margin-left: auto;
}
}
}

/* Spacing in all submenus. */
& .wp-block-navigation-item__content {
margin: 0;
}

/* Submenu indentation when there's no background. */
left: -1px; /* Border width. */
top: 100%;

/*
* Indentation for all submenus.
* Nested submenus sit to the left on large breakpoints.
* On smaller breakpoints, they open vertically, accordion-style.
*/

@media (--tablet) {
& .wp-block-navigation__submenu-container {
left: 100%;
top: -1px; /* Border width. */

/* Prevent the menu from disappearing when the mouse is over the gap */
&::before {
content: "";
position: absolute;
right: 100%;
height: 100%;
display: block;
width: 0.5em;
background: transparent;
}
}

/* Push inwards from right edge of submenu. */
& .wp-block-navigation__submenu-icon {
margin-right: 0.25em;
}

/* Reset the submenu indicator for horizontal flyouts. */
& .wp-block-navigation__submenu-icon svg {
transform: rotate(-90deg);
}
}
}

& .wp-block-navigation .has-child:focus-within > .wp-block-navigation__submenu-container,
& .wp-block-navigation .has-child:hover > .wp-block-navigation__submenu-container {
/*
* Custom menu items.
* Show submenus on hover unless they open on click.
*/

&:not(.open-on-click):hover > .wp-block-navigation__submenu-container {
visibility: visible;
overflow: visible;
opacity: 1;
Expand All @@ -267,9 +366,29 @@
min-width: 200px;
}

/* End :where fix */
/* Keep submenus open when focus is within. */
&:not(.open-on-click):not(.open-on-hover-click):focus-within > .wp-block-navigation__submenu-container {
visibility: visible;
overflow: visible;
opacity: 1;
width: auto;
height: auto;
min-width: 200px;
}

/* Show submenus on click. */
& .wp-block-navigation-submenu__toggle[aria-expanded="true"] ~ .wp-block-navigation__submenu-container {
visibility: visible;
overflow: visible;
opacity: 1;
width: auto;
height: auto;
min-width: 200px;
}
}

/* End :where fix */

/* Gutenberg bug: Close button is not visible in Safari; override it locally */
.global-header__navigation .is-menu-open {
overflow: visible;
Expand Down

0 comments on commit 023c9c7

Please sign in to comment.