Skip to content

Commit

Permalink
Merge pull request #246 from ucsc/feature/UCSC-112/mobile-nav-behavior
Browse files Browse the repository at this point in the history
[UCSC-112] Mobile nav behavior
  • Loading branch information
bjcooper authored Jul 19, 2023
2 parents b8c8ea3 + 5039a6d commit f9c18a6
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 91 deletions.
46 changes: 19 additions & 27 deletions parts/site-header.html
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
<!-- wp:group {"gradient":"ucsc-header-gradient","layout":{"type":"constrained"}} -->
<div class="wp-block-group has-ucsc-header-gradient-gradient-background has-background"><!-- wp:columns -->
<div class="wp-block-columns">
<!-- wp:column {"verticalAlignment":"center","style":{"spacing":{"padding":{"right":"0"}}}} -->
<div class="wp-block-column is-vertically-aligned-center" style="padding-right:0">
<!-- wp:template-part {"slug":"ucsc-logo","theme":"ucsc-2022","area":"uncategorized"} /-->
</div>
<!-- wp:group {"style":{"spacing":{"padding":{"top":"0","right":"0","bottom":"0","left":"0"},"blockGap":"0"}},"className":"site-header__header","layout":{"type":"default"}} -->
<div class="wp-block-group site-header__header" style="padding-top:0;padding-right:0;padding-bottom:0;padding-left:0"><!-- wp:group {"gradient":"ucsc-header-gradient","layout":{"type":"constrained"}} -->
<div class="wp-block-group has-ucsc-header-gradient-gradient-background has-background"><!-- wp:columns -->
<div class="wp-block-columns"><!-- wp:column {"verticalAlignment":"center","style":{"spacing":{"padding":{"right":"0"}}}} -->
<div class="wp-block-column is-vertically-aligned-center" style="padding-right:0"><!-- wp:site-title /--></div>
<!-- /wp:column -->

<!-- wp:column {"style":{"spacing":{"padding":{"top":"0","right":"0","bottom":"0","left":"0"}}},"layout":{"type":"default"}} -->
<div class="wp-block-column" style="padding-top:0;padding-right:0;padding-bottom:0;padding-left:0">
<!-- wp:site-tagline {"style":{"typography":{"textTransform":"uppercase","fontStyle":"normal","fontWeight":"700"}},"textColor":"ucsc-primary-yellow"} /-->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->
</div>
<!-- /wp:group -->

<!-- wp:separator {"backgroundColor":"ucsc-primary-yellow","className":"is-style-wide site-header__separator"} -->
<hr
class="wp-block-separator has-text-color has-ucsc-primary-yellow-color has-alpha-channel-opacity has-ucsc-primary-yellow-background-color has-background is-style-wide site-header__separator" />
<!-- /wp:separator -->

<!-- wp:group {"style":{"border":{"width":"0px","style":"none"}},"className":"site-header__inner is-layout-constrained","layout":{"inherit":true,"type":"constrained"}} -->
<div class="wp-block-group site-header__inner is-layout-constrained">
<!-- wp:acf/main-nav {"name":"acf/main-nav","mode":"preview"} /-->
</div>
<!-- /wp:group -->
<!-- wp:column {"style":{"spacing":{"padding":{"top":"0","right":"0","bottom":"0","left":"0"}}},"layout":{"type":"default"}} -->
<div class="wp-block-column" style="padding-top:0;padding-right:0;padding-bottom:0;padding-left:0"><!-- wp:site-tagline {"style":{"typography":{"textTransform":"uppercase","fontStyle":"normal","fontWeight":"700"}},"textColor":"ucsc-primary-yellow"} /--></div>
<!-- /wp:column --></div>
<!-- /wp:columns --></div>
<!-- /wp:group -->

<!-- wp:separator {"backgroundColor":"ucsc-primary-yellow","className":"is-style-wide site-header__separator"} -->
<hr class="wp-block-separator has-text-color has-ucsc-primary-yellow-color has-alpha-channel-opacity has-ucsc-primary-yellow-background-color has-background is-style-wide site-header__separator"/>
<!-- /wp:separator --></div>
<!-- /wp:group -->

<!-- wp:group {"className":"site-header__inner is-layout-constrained"} -->
<div class="wp-block-group site-header__inner is-layout-constrained"><!-- wp:acf/main-nav {"name":"acf/main-nav","mode":"preview"} /--></div>
<!-- /wp:group -->
155 changes: 111 additions & 44 deletions src/js/components/main-nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,102 @@ import state from '../config/state';
import { NAVIGATION_BREAKPOINT } from '../config/options';
import * as bodyLock from '../utils/body-lock';

// Cache some elements.
// Cache some elements and state.
const cache = {
desktopBreakpoint: NAVIGATION_BREAKPOINT,
trussHeader: null,
stickyHeader: null,
stickyHeaderObserver: null,
nav: null,
navIsOpen: false,
navToggle: null,
menuItems: [],
};

/**
* Show the main-nav menu on mobile.
*/
const showMenu = () => {
// Stop observing the sticky header when we open the menu, because it's going
// to float to the top of the viewport and not reflect our actual scroll
// position.
cache.stickyHeaderObserver.unobserve( cache.stickyHeader );
bodyLock.lock();

document.documentElement.classList.add( 'main-nav--is-open' );
if ( ! cache.navIsOpen ) {
document.documentElement.classList.add( 'main-nav--is-opening' );
}

cache.nav.setAttribute( 'aria-hidden', false );
cache.navToggle.setAttribute( 'aria-label', 'Hide main navigation menu' );
cache.navIsOpen = true;
};

/**
* Hide the main-nav menu on mobile.
*/
const hideMenu = () => {
document.documentElement.classList.remove( 'main-nav--is-open' );
if ( cache.navIsOpen ) {
document.documentElement.classList.add( 'main-nav--is-closing' );
}

cache.nav.setAttribute( 'aria-hidden', true );
cache.navToggle.setAttribute( 'aria-label', 'Show main navigation menu' );

// Reset submenus when the main nav is hidden.
cache.menuItems.forEach( ( data ) => {
deactivateMenuItem( data );
} );

// If we unlock the body before our closing animation completes, the scroll
// position will be off in some cases. For that reason, we unlock the body in
// our transitionended event handler. However, if the nav is already hidden,
// no transition will occur, so we handle that here.
if ( ! cache.navIsOpen && bodyLock.isLocked() ) {
bodyLock.unlock();
}

cache.stickyHeaderObserver.observe( cache.stickyHeader );
cache.navIsOpen = false;
};

/**
* Toggle the main-nav menu on mobile.
*/
const toggleMenu = () => {
if ( cache.navIsOpen ) {
hideMenu();
} else {
showMenu();
}
};

/**
* Add JS behaviors to the main-nav menu.
*/
function initMainNav() {
const initMainNav = () => {
cache.nav = document.querySelector( '[data-js="main-nav"]' );
if ( ! cache.nav ) {
return;
}

// Observer when our sticky header becomes "stuck".
cache.stickyHeader = document.querySelector( '.header-region' );
cache.stickyHeaderObserver = new window.IntersectionObserver(
( entries ) => {
document.documentElement.classList.toggle(
'header-region--stuck',
entries[ 0 ].intersectionRatio < 1
);
},
{
threshold: 1,
rootMargin: '-1px 0px 0px 0px',
}
);

// Get our mobile-to-desktop breakpoint.
const breakpoint = window
.getComputedStyle( cache.nav )
Expand All @@ -34,17 +113,24 @@ function initMainNav() {
cache.desktopBreakpoint = parseInt( breakpoint );
}

// Update CSS classes for navigation animation states.
const navigation = document.querySelector( '.site-header__navigation' );
navigation.addEventListener( 'transitionend', () => {
document.documentElement.classList.remove(
'main-nav--is-opening',
'main-nav--is-closing'
);
// Unlock the body after the nav is closed.
if ( ! cache.navIsOpen && bodyLock.isLocked() ) {
bodyLock.unlock();
}
} );

// Add mobile nav toggle.
cache.navToggle = document.querySelector(
'.site-header__navigation-toggle'
);
cache.navToggle.addEventListener( 'click', () => {
if ( document.body.classList.contains( 'main-nav--is-open' ) ) {
hideMenu();
} else {
showMenu();
}
} );
cache.navToggle.addEventListener( 'click', toggleMenu );

// Add menu item behaviors.
cache.nav
Expand Down Expand Up @@ -108,51 +194,26 @@ function initMainNav() {

// Start closed.
hideMenu();
}

/**
* Show the main-nav menu on mobile.
*/
function showMenu() {
bodyLock.lock();
document.body.classList.add( 'main-nav--is-open' );
cache.nav.setAttribute( 'aria-hidden', false );
cache.navToggle.setAttribute( 'aria-label', 'Hide main navigation menu' );
}

/**
* Hide the main-nav menu on mobile.
*/
function hideMenu() {
bodyLock.unlock();
document.body.classList.remove( 'main-nav--is-open' );
cache.nav.setAttribute( 'aria-hidden', true );
cache.navToggle.setAttribute( 'aria-label', 'Show main navigation menu' );

// Reset submenus on hide.
cache.menuItems.forEach( ( data ) => {
deactivateMenuItem( data );
} );
}
};

/**
* Check if our main-nav menu is in mobile mode (vs desktop mode).
*/
function menuIsMobile() {
const menuIsMobile = () => {
return (
Math.max(
document.documentElement.clientWidth || 0,
window.innerWidth || 0
) < cache.desktopBreakpoint
);
}
};

/**
* Update a top-level menu item's active status.
*
* @param {Object} data The menu item data object from cache.menuItems.
*/
function updateMenuItem( data ) {
const updateMenuItem = ( data ) => {
// On mobile, only open menu items when the associated toggle button is
// pressed.
if ( menuIsMobile() ) {
Expand All @@ -168,14 +229,14 @@ function updateMenuItem( data ) {
} else if ( ! data.isActive && shouldBeActive ) {
activateMenuItem( data );
}
}
};

/**
* Mark a top-level menu item as active and show its submenu if it has one.
*
* @param {Object} data The menu item data object from cache.menuItems.
*/
function activateMenuItem( data ) {
const activateMenuItem = ( data ) => {
data.menuItem.classList.add( 'menu-item--is-active' );
if ( data.submenu ) {
data.submenu.setAttribute( 'aria-hidden', false );
Expand All @@ -185,14 +246,14 @@ function activateMenuItem( data ) {
} );
}
data.isActive = true;
}
};

/**
* Mark a top-level menu item as inactive and hide its submenu if it has one.
*
* @param {Object} data The menu item data object from cache.menuItems.
*/
function deactivateMenuItem( data ) {
const deactivateMenuItem = ( data ) => {
data.menuItem.classList.remove( 'menu-item--is-active' );
if ( data.submenu ) {
data.submenu.setAttribute( 'aria-hidden', true );
Expand All @@ -202,11 +263,17 @@ function deactivateMenuItem( data ) {
} );
}
data.isActive = false;
}
};

const handleResize = () => {
const navElement = document.querySelector( '[data-js="main-nav"]' );
// Find the site header height.
const siteHeader = document.querySelector( '.site-header__header' );
document.documentElement.style.setProperty(
'--site-header-height',
siteHeader.clientHeight + 'px'
);

const navElement = document.querySelector( '[data-js="main-nav"]' );
if ( state.v_width >= NAVIGATION_BREAKPOINT ) {
navElement.setAttribute( 'aria-hidden', 'false' );
return;
Expand Down
Loading

0 comments on commit f9c18a6

Please sign in to comment.