Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 37 additions & 36 deletions blocks/header/header.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ header nav[aria-expanded='true'] {
max-width: 1264px;
padding: 0 32px;
}

header nav[aria-expanded='true'] {
min-height: 0;
overflow: visible;
}
}

header nav p {
Expand Down Expand Up @@ -177,6 +172,38 @@ header nav .nav-sections ul > li {
font-weight: 500;
}

header nav .nav-sections .nav-drop > button {
all: unset;
display: inline-block;
position: relative;
padding-right: 16px;
cursor: pointer;
}

header nav .nav-sections .nav-drop > button::after {
content: '';
position: absolute;
top: 0.5em;
right: 2px;
transform: rotate(135deg);
width: 6px;
height: 6px;
border: 2px solid currentcolor;
border-radius: 0 1px 0 0;
border-width: 2px 2px 0 0;
}

header nav .nav-sections .nav-drop > button[aria-expanded='true']::after {
top: unset;
bottom: 0.5em;
transform: rotate(315deg);
}

header nav .nav-sections .nav-drop > button:focus-visible {
outline: 2px solid currentcolor;
outline-offset: 2px;
}

header nav .nav-sections ul > li > ul {
margin-top: 0;
}
Expand All @@ -185,43 +212,17 @@ header nav .nav-sections ul > li > ul > li {
font-weight: 400;
}

header nav .nav-sections .nav-drop > button[aria-expanded='false'] + ul {
display: none;
}

@media (width >= 900px) {
header nav .nav-sections {
display: block;
visibility: visible;
white-space: nowrap;
}

header nav[aria-expanded='true'] .nav-sections {
align-self: unset;
}

header nav .nav-sections .nav-drop {
position: relative;
padding-right: 16px;
cursor: pointer;
}

header nav .nav-sections .nav-drop::after {
content: '';
display: inline-block;
position: absolute;
top: 0.5em;
right: 2px;
transform: rotate(135deg);
width: 6px;
height: 6px;
border: 2px solid currentcolor;
border-radius: 0 1px 0 0;
border-width: 2px 2px 0 0;
}

header nav .nav-sections .nav-drop[aria-expanded='true']::after {
top: unset;
bottom: 0.5em;
transform: rotate(315deg);
}

header nav .nav-sections ul {
display: flex;
gap: 24px;
Expand All @@ -238,7 +239,7 @@ header nav .nav-sections ul > li > ul > li {
position: relative;
}

header nav .nav-sections .default-content-wrapper > ul > li[aria-expanded='true'] > ul {
header nav .nav-sections .default-content-wrapper > ul > li > button[aria-expanded='true'] + ul {
display: block;
position: absolute;
left: -24px;
Expand Down
69 changes: 29 additions & 40 deletions blocks/header/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { loadFragment } from '../fragment/fragment.js';
// media query match that indicates mobile/tablet width
const isDesktop = window.matchMedia('(min-width: 900px)');

/**
* Closes the open nav dropdown (desktop) or the nav menu (mobile) on Escape
* @param {KeyboardEvent} e keydown event
*/
function closeOnEscape(e) {
if (e.code === 'Escape') {
const nav = document.getElementById('nav');
Expand All @@ -22,6 +26,10 @@ function closeOnEscape(e) {
}
}

/**
* Closes the open nav dropdown (desktop) or the nav menu (mobile) when focus leaves the nav
* @param {FocusEvent} e focusout event
*/
function closeOnFocusLost(e) {
const nav = e.currentTarget;
if (!nav.contains(e.relatedTarget)) {
Expand All @@ -38,30 +46,15 @@ function closeOnFocusLost(e) {
}
}

function openOnKeydown(e) {
const focused = document.activeElement;
const isNavDrop = focused.className === 'nav-drop';
if (isNavDrop && (e.code === 'Enter' || e.code === 'Space')) {
const dropExpanded = focused.getAttribute('aria-expanded') === 'true';
// eslint-disable-next-line no-use-before-define
toggleAllNavSections(focused.closest('.nav-sections'));
focused.setAttribute('aria-expanded', dropExpanded ? 'false' : 'true');
}
}

function focusNavSection() {
document.activeElement.addEventListener('keydown', openOnKeydown);
}

/**
* Toggles all nav sections
* @param {Element} sections The container element
* @param {Boolean} expanded Whether the element should be expanded or collapsed
* @param {Boolean|string} expanded Whether the element should be expanded or collapsed
*/
function toggleAllNavSections(sections, expanded = false) {
if (!sections) return;
sections.querySelectorAll('.nav-sections .default-content-wrapper > ul > li').forEach((section) => {
section.setAttribute('aria-expanded', expanded);
sections.querySelectorAll('.nav-drop > button').forEach((button) => {
button.setAttribute('aria-expanded', expanded);
});
}

Expand All @@ -75,26 +68,11 @@ function toggleMenu(nav, navSections, forceExpanded = null) {
const expanded = forceExpanded !== null ? !forceExpanded : nav.getAttribute('aria-expanded') === 'true';
const button = nav.querySelector('.nav-hamburger button');
document.body.style.overflowY = (expanded || isDesktop.matches) ? '' : 'hidden';
nav.setAttribute('aria-expanded', expanded ? 'false' : 'true');
// the desktop nav is always expanded, so aria-expanded only applies to the mobile menu
if (isDesktop.matches) nav.removeAttribute('aria-expanded');
else nav.setAttribute('aria-expanded', expanded ? 'false' : 'true');
toggleAllNavSections(navSections, expanded || isDesktop.matches ? 'false' : 'true');
button.setAttribute('aria-label', expanded ? 'Open navigation' : 'Close navigation');
// enable nav dropdown keyboard accessibility
if (navSections) {
const navDrops = navSections.querySelectorAll('.nav-drop');
if (isDesktop.matches) {
navDrops.forEach((drop) => {
if (!drop.hasAttribute('tabindex')) {
drop.setAttribute('tabindex', 0);
drop.addEventListener('focus', focusNavSection);
}
});
} else {
navDrops.forEach((drop) => {
drop.removeAttribute('tabindex');
drop.removeEventListener('focus', focusNavSection);
});
}
}

// enable menu collapse on escape keypress
if (!expanded || isDesktop.matches) {
Expand Down Expand Up @@ -140,13 +118,24 @@ export default async function decorate(block) {
const navSections = nav.querySelector('.nav-sections');
if (navSections) {
navSections.querySelectorAll(':scope .default-content-wrapper > ul > li').forEach((navSection) => {
if (navSection.querySelector('ul')) navSection.classList.add('nav-drop');
navSection.addEventListener('click', () => {
const subList = navSection.querySelector(':scope > ul');
if (!subList) return;
navSection.classList.add('nav-drop');
// wrap the dropdown label in a button so it is announced as expandable
const button = document.createElement('button');
button.type = 'button';
button.setAttribute('aria-expanded', false);
[...navSection.childNodes].forEach((node) => {
if (node !== subList) button.append(node);
});
navSection.prepend(button);
button.addEventListener('click', () => {
const expanded = button.getAttribute('aria-expanded') === 'true';
if (isDesktop.matches) {
const expanded = navSection.getAttribute('aria-expanded') === 'true';
button.focus();
toggleAllNavSections(navSections);
navSection.setAttribute('aria-expanded', expanded ? 'false' : 'true');
}
button.setAttribute('aria-expanded', !expanded);
});
});
}
Expand Down
Loading