diff --git a/parts/site-header.html b/parts/site-header.html
index dfe55ed3..383b5cff 100644
--- a/parts/site-header.html
+++ b/parts/site-header.html
@@ -1,29 +1,21 @@
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/src/js/components/main-nav.js b/src/js/components/main-nav.js
index b60d2675..6b96153d 100644
--- a/src/js/components/main-nav.js
+++ b/src/js/components/main-nav.js
@@ -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 )
@@ -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
@@ -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() ) {
@@ -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 );
@@ -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 );
@@ -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;
diff --git a/src/scss/theme-regions/_site-header.scss b/src/scss/theme-regions/_site-header.scss
index 921499b7..f93a39e6 100644
--- a/src/scss/theme-regions/_site-header.scss
+++ b/src/scss/theme-regions/_site-header.scss
@@ -1,29 +1,65 @@
// Site Header
+////////////////////////////////////////////////////////////////////////////////
+// Truss Header
+////////////////////////////////////////////////////////////////////////////////
+
+// In order for the sticky header to show at the top of the viewport while
+// open on mobile, the Truss header above it needs to collapse.
+.sc-trss-ucsc-header-h {
+ display: grid;
+ grid-template-rows: 1fr;
+ transition: grid-template-rows 0.2s ease;
+ background-color: var(--wp--preset--color--ucsc-royal-blue);
+
+ .main-nav--is-open & {
+ grid-template-rows: 0fr;
+ }
+
+ .main-nav--is-open &,
+ .main-nav--is-closing & {
+
+ .trss-ucsc-header {
+ overflow: hidden;
+ }
+ }
+
+ .header-region--stuck & {
+ grid-template-rows: 1fr;
+
+ .trss-ucsc-header {
+ overflow: auto;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Header Region
+////////////////////////////////////////////////////////////////////////////////
+
.header-region {
position: sticky;
top: 0;
z-index: 10;
width: 100%;
- max-height: 100vh;
- overflow-x: hidden;
- overflow-y: auto;
- background-color: var(--wp--preset--color--white);
+ background-color: var(--wp--preset--color--ucsc-royal-blue);
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.25);
- // At this breakpoint, the WP admin bar is also sticky.
- @media (min-width: 783px) {
- top: var(--wp-admin--admin-bar--height, 0);
- }
-
@include media-query($desktop-menu) {
+ background-color: var(--wp--preset--color--white);
overflow: visible;
}
}
+////////////////////////////////////////////////////////////////////////////////
+// Site Header Template
+////////////////////////////////////////////////////////////////////////////////
+
.site-header {
position: relative;
+}
+.site-header__header {
// Leave room for the main nav menu toggle button.
> .wp-block-group:first-child {
padding-right: 60px;
@@ -55,7 +91,8 @@
.site-header__separator {
margin: 0;
- height: 2px !important;
+ border-bottom: 2px solid !important;
+ height: 0 !important;
}
////////////////////////////////////////////////////////////////////////////////
@@ -63,7 +100,7 @@
////////////////////////////////////////////////////////////////////////////////
.site-header__inner {
- margin-top: 0;
+ margin: 0;
padding: 0;
@include media-query($desktop-menu) {
@@ -142,6 +179,29 @@
margin: 0 !important;
color: var(--wp--preset--color--white);
+ background-color: var(--wp--preset--color--ucsc-royal-blue);
+ overflow: hidden;
+ width: 100%;
+ position: absolute;
+ height: 0;
+ transition: height 0.2s ease;
+
+ // CASE: The mobile nav is open or opening.
+ .main-nav--is-open & {
+ height: calc(100svh - var(--site-header-height));
+ }
+
+ // CASE: The mobile nav's opening animation is complete and it is fully open.
+ .main-nav--is-open:not(.main-nav--is-opening) & {
+ overflow: auto;
+ }
+
+ @include media-query($desktop-menu) {
+ height: auto;
+ position: static;
+ background-color: transparent;
+ overflow: visible;
+ }
.menu-item {
list-style: none;
@@ -171,11 +231,17 @@
}
////////////////////////////////////////////////////////////////////////////////
-// Logo
+// Site title
////////////////////////////////////////////////////////////////////////////////
-.ucsc__logo a:hover {
- transform: none;
+.site-header .wp-block-site-title a {
+ color: var(--wp--preset--color--white);
+ transition: 0.2s ease;
+
+ &:hover,
+ &:focus-visible {
+ color: var(--wp--preset--color--ucsc-primary-yellow);
+ }
}
////////////////////////////////////////////////////////////////////////////////
@@ -224,21 +290,16 @@
.site-header__navigation .menu {
margin: 0;
padding: 0;
- display: none;
+ display: flex;
flex-direction: column;
@include media-query($desktop-menu) {
flex-direction: row;
flex-wrap: wrap;
- display: flex;
max-width: var(--wp--style--global--content-size);
margin-left: auto;
margin-right: auto;
}
-
- .main-nav--is-open & {
- display: flex;
- }
}
////////////////////////////////////////////////////////////////////////////////