Skip to content

Commit ccf43a3

Browse files
authored
Merge pull request #6660 from ampproject/enhancement/4400-twentynineteen-mobile-menu
Add support of nested AMP navigation for the main menu in twenty nineteen theme.
2 parents d434e47 + 74c408a commit ccf43a3

File tree

3 files changed

+589
-2
lines changed

3 files changed

+589
-2
lines changed

includes/sanitizers/class-amp-core-theme-sanitizer.php

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
*/
88

99
use AmpProject\Amp;
10+
use AmpProject\AmpWP\Services;
11+
use AmpProject\Dom\Element;
12+
use AmpProject\Extension;
1013
use AmpProject\Html\Attribute;
1114
use AmpProject\Html\Tag;
1215
use AmpProject\Html\Role;
16+
use AmpProject\Layout;
1317

1418
/**
1519
* Class AMP_Core_Theme_Sanitizer
@@ -162,8 +166,13 @@ protected static function get_theme_features_config( $theme_slug, $args = [] ) {
162166
'wp_print_footer_scripts' => [
163167
'twentynineteen_skip_link_focus_fix', // See <https://github.com/WordPress/twentynineteen/pull/47>.
164168
],
169+
'wp_nav_menu' => [
170+
'twentynineteen_add_ellipses_to_nav',
171+
],
165172
],
166173
'adjust_twentynineteen_images' => [],
174+
'update_twentynineteen_mobile_main_menu' => [],
175+
'add_nav_menu_styles' => [],
167176
];
168177

169178
// Twenty Seventeen.
@@ -1150,6 +1159,118 @@ static function() use ( $args ) {
11501159
}
11511160
}
11521161

1162+
<?php elseif ( 'twentynineteen' === get_template() ) : ?>
1163+
.amp-twentynineteen-main-navigation {
1164+
max-width: 100vw;
1165+
width: 100vw;
1166+
height: 100vh;
1167+
}
1168+
.admin-bar .amp-twentynineteen-main-navigation {
1169+
top: 46px;
1170+
height: calc(100vh - 46px);
1171+
}
1172+
1173+
.amp-twentynineteen-main-navigation .sub-menu > li > a:hover,
1174+
.amp-twentynineteen-main-navigation .sub-menu > li > a:focus,
1175+
.amp-twentynineteen-main-navigation .sub-menu > li > .menu-item-link-return:hover,
1176+
.amp-twentynineteen-main-navigation .sub-menu > li > .menu-item-link-return:focus {
1177+
background: inherit;
1178+
}
1179+
1180+
.amp-twentynineteen-main-navigation .sub-menu > li {
1181+
position: static;
1182+
display: flex;
1183+
}
1184+
1185+
.amp-twentynineteen-main-navigation .sub-menu > li.menu-item-has-children .submenu-expand {
1186+
position: static;
1187+
}
1188+
1189+
.amp-twentynineteen-main-navigation .sub-menu.expanded-true {
1190+
display: table;
1191+
margin-top: 0;
1192+
opacity: 1;
1193+
padding-left: 0;
1194+
left: 0;
1195+
top: 0;
1196+
right: 0;
1197+
bottom: 0;
1198+
position: static;
1199+
z-index: 100000; /* Make sure appears above mobile admin bar */
1200+
width: 100vw;
1201+
height: 100%;
1202+
max-width: 100vw;
1203+
}
1204+
1205+
.amp-twentynineteen-main-navigation .sub-menu > li.mobile-parent-nav-menu-item {
1206+
display: block;
1207+
}
1208+
1209+
.amp-twentynineteen-main-navigation .submenu-expand .svg-icon {
1210+
<?php if ( is_rtl() ) : ?>
1211+
transform: rotate(90deg);
1212+
<?php else : ?>
1213+
transform: rotate(270deg);
1214+
<?php endif; ?>
1215+
}
1216+
1217+
.amp-twentynineteen-main-navigation .sub-menu > li > a,
1218+
.amp-twentynineteen-main-navigation .sub-menu > li > .menu-item-link-return {
1219+
flex-grow: 1;
1220+
max-width: none;
1221+
}
1222+
1223+
.amp-twentynineteen-main-navigation .sub-menu > li > .menu-item-link-return .svg-icon {
1224+
<?php if ( is_rtl() ) : ?>
1225+
transform: rotate(180deg);
1226+
<?php endif; ?>
1227+
}
1228+
1229+
.main-navigation .main-menu > li.menu-item-has-children > .submenu-expand.display-on-mobile,
1230+
.display-on-mobile {
1231+
display: none;
1232+
}
1233+
1234+
@media only screen and (max-width: 768px) {
1235+
.display-on-mobile {
1236+
display: block;
1237+
}
1238+
1239+
.main-navigation .main-menu .menu-item-has-children:not(.off-canvas):focus-within > .sub-menu,
1240+
.main-navigation .main-menu .menu-item-has-children:not(.off-canvas):hover > .sub-menu,
1241+
.main-navigation .main-menu .menu-item-has-children:not(.off-canvas):focus > .sub-menu,
1242+
.main-navigation .main-menu > li.menu-item-has-children .submenu-expand.display-on-desktop,
1243+
.display-on-desktop {
1244+
display: none;
1245+
}
1246+
1247+
.main-navigation .main-menu > li.menu-item-has-children > .submenu-expand.display-on-mobile svg {
1248+
position: relative;
1249+
top: -2px;
1250+
<?php if ( is_rtl() ) : ?>
1251+
right: -2px;
1252+
<?php else : ?>
1253+
left: -2px;
1254+
<?php endif; ?>
1255+
}
1256+
1257+
.main-navigation .main-menu > li.menu-item-has-children > .submenu-expand.display-on-mobile {
1258+
display: inline-block;
1259+
<?php if ( is_rtl() ) : ?>
1260+
margin-left: 0.5rem;
1261+
margin-right: 0.25rem;
1262+
<?php else : ?>
1263+
margin-left: 0.25rem;
1264+
margin-right: 0.5rem;
1265+
<?php endif; ?>
1266+
background: #0073aa;
1267+
color: #FFF;
1268+
border-radius: 50%;
1269+
width: 20px;
1270+
height: 20px;
1271+
vertical-align: middle;
1272+
}
1273+
}
11531274
<?php elseif ( 'twentyseventeen' === get_template() ) : ?>
11541275
/* Show the button*/
11551276
.no-js .menu-toggle {
@@ -2343,4 +2464,159 @@ protected function guess_modal_role( DOMElement $modal ) {
23432464
// None of the roles we are looking for match any of the classes.
23442465
return Role::DIALOG;
23452466
}
2467+
2468+
/**
2469+
* Evaluates the given XPath expression and give first Element if exist.
2470+
*
2471+
* @param string $expression The XPath expression to execute.
2472+
* @param Element $context_node The optional context node can be specified for doing relative XPath queries.
2473+
* By default, the queries are relative to the root element.
2474+
*
2475+
* @return Element|null Return Element if exists otherwise null.
2476+
*/
2477+
protected function get_first_element( $expression, $context_node = null ) {
2478+
/** @var DOMNodeList $dom_node_list */
2479+
$dom_node_list = $this->dom->xpath->query( $expression, $context_node );
2480+
2481+
$dom_node = $dom_node_list->item( 0 );
2482+
return $dom_node instanceof Element ? $dom_node : null;
2483+
}
2484+
2485+
/**
2486+
* Update main navigation menu for "twentynineteen" theme.
2487+
*
2488+
* @return void
2489+
*/
2490+
public function update_twentynineteen_mobile_main_menu() {
2491+
2492+
$xpaths = [
2493+
'main_menu' => '//nav[ @id = "site-navigation" ]//ul[ @class = "main-menu"]',
2494+
'top_menu_items' => './li[ contains( @class, "menu-item" ) and contains( @class, "menu-item-has-children" ) ]',
2495+
'expand_button' => './button[ @class = "submenu-expand" ]',
2496+
'submenu' => './ul[ @class = "sub-menu" ]',
2497+
'close_button_in_submenu' => './li[ contains( @class, "mobile-parent-nav-menu-item" ) ]//button[ contains( @class, "menu-item-link-return" ) ]',
2498+
];
2499+
2500+
$main_menu = $this->get_first_element( $xpaths['main_menu'] );
2501+
if ( empty( $main_menu ) ) {
2502+
return;
2503+
}
2504+
2505+
$menu_items = $this->dom->xpath->query( $xpaths['top_menu_items'], $main_menu );
2506+
2507+
/** @var Element $menu_item */
2508+
foreach ( $menu_items as $menu_item ) {
2509+
$expand_button = $this->get_first_element( $xpaths['expand_button'], $menu_item );
2510+
$sub_menu = $this->get_first_element( $xpaths['submenu'], $menu_item );
2511+
2512+
if ( empty( $expand_button ) || empty( $sub_menu ) ) {
2513+
continue;
2514+
}
2515+
2516+
// AMP Sidebar.
2517+
$amp_sidebar = AMP_DOM_Utils::create_node(
2518+
$this->dom,
2519+
Extension::SIDEBAR,
2520+
[
2521+
Attribute::LAYOUT => Layout::NODISPLAY,
2522+
Attribute::CLASS_ => 'amp-twentynineteen-main-navigation',
2523+
Attribute::SIDE => is_rtl() ? 'left' : 'right',
2524+
]
2525+
);
2526+
2527+
$sidebar_id = $this->dom->getElementId( $amp_sidebar );
2528+
2529+
// AMP nested menu.
2530+
$amp_nested_menu = AMP_DOM_Utils::create_node(
2531+
$this->dom,
2532+
Extension::NESTED_MENU,
2533+
[
2534+
Attribute::LAYOUT => Layout::FILL,
2535+
]
2536+
);
2537+
2538+
// Clone the sub-menu and expand button for AMP navigation.
2539+
/** @var Element $amp_sub_menu */
2540+
$amp_sub_menu = $sub_menu->cloneNode( true );
2541+
2542+
/** @var Element $amp_expand_button */
2543+
$amp_expand_button = $expand_button->cloneNode( true );
2544+
2545+
$sub_menu->setAttribute( Attribute::CLASS_, $sub_menu->getAttribute( Attribute::CLASS_ ) . ' display-on-desktop' );
2546+
$expand_button->setAttribute( Attribute::CLASS_, $expand_button->getAttribute( Attribute::CLASS_ ) . ' display-on-desktop' );
2547+
2548+
$menu_item->appendChild( $amp_expand_button );
2549+
$menu_item->appendChild( $amp_sub_menu );
2550+
$this->update_twentynineteen_main_nested_menu( $amp_sub_menu );
2551+
2552+
$amp_sub_menu->setAttribute( Attribute::CLASS_, $amp_sub_menu->getAttribute( Attribute::CLASS_ ) . ' expanded-true display-on-mobile' );
2553+
$amp_expand_button->setAttribute( Attribute::CLASS_, $amp_expand_button->getAttribute( Attribute::CLASS_ ) . ' display-on-mobile' );
2554+
2555+
// Handle buttons.
2556+
$amp_expand_button->addAmpAction( 'tap', "$sidebar_id.open" );
2557+
$back_button = $this->get_first_element( $xpaths['close_button_in_submenu'], $amp_sub_menu );
2558+
if ( ! empty( $back_button ) ) {
2559+
$back_button->addAmpAction( 'tap', "$sidebar_id.close" );
2560+
}
2561+
2562+
$amp_nested_menu->appendChild( $amp_sub_menu );
2563+
$amp_sidebar->appendChild( $amp_nested_menu );
2564+
$menu_item->appendChild( $amp_sidebar );
2565+
}
2566+
}
2567+
2568+
/**
2569+
* Update the markup of the nested menu to AMP compatible markup.
2570+
*
2571+
* @param Element $sub_menu Element of sub-menu from main navigation.
2572+
*
2573+
* @return void
2574+
*/
2575+
public function update_twentynineteen_main_nested_menu( Element $sub_menu ) {
2576+
2577+
$xpaths = [
2578+
'menu_items' => './li[ contains( @class, "menu-item" ) and contains( @class, "menu-item-has-children" ) ]',
2579+
'expand_button' => './button[ @class = "submenu-expand" ]',
2580+
'back_button' => './li[ contains( @class, "mobile-parent-nav-menu-item" ) ]//button[ contains( @class, "menu-item-link-return" ) ]',
2581+
'submenu' => './ul[ @class = "sub-menu" ]',
2582+
];
2583+
2584+
$menu_items = $this->dom->xpath->query( $xpaths['menu_items'], $sub_menu );
2585+
if ( 0 === $menu_items->length ) {
2586+
return;
2587+
}
2588+
2589+
/** @var Element $menu_item */
2590+
foreach ( $menu_items as $menu_item ) {
2591+
$nested_sub_menu = $this->get_first_element( $xpaths['submenu'], $menu_item );
2592+
2593+
if ( empty( $nested_sub_menu ) ) {
2594+
continue;
2595+
}
2596+
2597+
$this->update_twentynineteen_main_nested_menu( $nested_sub_menu );
2598+
2599+
$back_button = $this->get_first_element( $xpaths['back_button'], $nested_sub_menu );
2600+
if ( ! empty( $back_button ) ) {
2601+
$back_button->setAttribute( Attribute::AMP_NESTED_SUBMENU_CLOSE, '' );
2602+
}
2603+
2604+
$open_button = $this->get_first_element( $xpaths['expand_button'], $menu_item );
2605+
if ( ! empty( $open_button ) ) {
2606+
$open_button->setAttribute( Attribute::AMP_NESTED_SUBMENU_OPEN, '' );
2607+
}
2608+
2609+
$amp_nested_menu_div = AMP_DOM_Utils::create_node(
2610+
$this->dom,
2611+
Tag::DIV,
2612+
[
2613+
Attribute::AMP_NESTED_SUBMENU => '',
2614+
]
2615+
);
2616+
2617+
$nested_sub_menu->setAttribute( Attribute::CLASS_, $nested_sub_menu->getAttribute( Attribute::CLASS_ ) . ' expanded-true' );
2618+
$amp_nested_menu_div->appendChild( $nested_sub_menu );
2619+
$menu_item->appendChild( $amp_nested_menu_div );
2620+
}
2621+
}
23462622
}

tests/e2e/specs/core-themes/twentynineteen.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ describe( 'Twenty Nineteen theme on AMP', () => {
4646

4747
expect( menuItemWithSubmenu ).not.toBeNull();
4848

49-
await expect( menuItemWithSubmenu ).toMatchElement( '.submenu-expand' );
49+
await expect( menuItemWithSubmenu ).toMatchElement( '.display-on-mobile' );
5050
await expect( menuItemWithSubmenu ).toMatchElement( '.sub-menu', { visible: false } );
5151

52-
await expect( menuItemWithSubmenu ).toClick( '.submenu-expand' );
52+
await expect( menuItemWithSubmenu ).toClick( '.display-on-mobile' );
5353
await expect( menuItemWithSubmenu ).toMatchElement( '.sub-menu', { visible: true } );
5454
} );
5555
} );

0 commit comments

Comments
 (0)