Skip to content

Commit

Permalink
Replace header nav logic and styles with popover equivalents
Browse files Browse the repository at this point in the history
  • Loading branch information
JoelCDL committed Nov 21, 2024
1 parent 12ee155 commit 21412b7
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 129 deletions.
113 changes: 42 additions & 71 deletions css/components/headernav.css
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
/***** Header Nav Component *****/

/* Read notes in component config */

.c-headernav {
--headernav-section-padding: var(--space3);
--headernav-item-margin: var(--space3);

position: relative;
.no-popover & {
ul ul {
display: none;
}
}

ul {
all: unset;
margin: unset;
padding: unset;
list-style-type: none;

li {
Expand All @@ -18,39 +21,32 @@
}
}

a {
display: block;
}

/* Sections: */
> ul {
@media (--min-width2) {
@media (--min-width2) {
a {
display: block;
}

/* Sections: */
> ul {
display: flex;

> li {
flex: 1 1 auto;
}
}
}

> ul > li {
flex: 1 1 auto;

> ul > li {
@media (--min-width2) {
/* Nav bar main items: */
&:not(:last-child) {
border-inline-end: 1px solid oklch(55% 0 0deg);
}
}
}

/* Nav bar links: */
> ul > li > a {
@media (--min-width2) {

/* Nav bar links: */
> ul > li > a {
padding: var(--space1);
background: linear-gradient(oklch(95% 0 0deg), oklch(88% 0 0deg));
color: oklch(36% 0 0deg);
text-align: center;
text-decoration: none;

&:focus,
&:hover {
color: var(--color-blue);
Expand All @@ -60,9 +56,28 @@
}

/* Panels: */
> ul > li > ul {
display: none;

[popover] {
position: absolute;
top: anchor(bottom);
left: anchor(left);
margin: 0;
transition-property: opacity, display, overlay;
transition-duration: .3s;
transition-behavior: allow-discrete;
border: unset;
opacity: 0;

&:popover-open {
opacity: 1;

@starting-style {
opacity: 0;
}
}
}

> ul > li > ul {
padding: var(--headernav-section-padding);
column-gap: calc(var(--headernav-section-padding) * 2);
column-rule: 1px solid oklch(88% 0 0deg);
Expand Down Expand Up @@ -126,48 +141,4 @@
> ul > li li {
margin-block-end: var(--headernav-item-margin);
}

/**** Panel Toggle ****/

/* If c-headernav JS not supported: */

&.c-headernav-no-js {
> ul > li:hover > ul {
@media (--min-width2) {
display: block;
z-index: 10;
}
}

/* If :focus-within not supported. Only primary links accessible via tabkey: */

> ul > li a:focus {
@media (--min-width2) {
display: block;
z-index: 10;
}
}

/* If :focus-within supported. All links accessbile via focus. No toggle state on primary links, so user must tab through every secondary link in each section to reach the next section: */

> ul > li:focus-within > ul {
@media (--min-width2) {
display: block;
z-index: 10;
}
}
}

/* If c-headernav JS supported: */

&.c-headernav-js {
/* Primary links disabled via JS and used as a toggle for each section. Hover and focus states handled by JS and set via 'open' class: */

li.open ul {
@media (--min-width2) {
display: block;
z-index: 10;
}
}
}
}
3 changes: 1 addition & 2 deletions elements/3-components/headernav.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{{!-- Read notes in component config --}}
<nav aria-label="global" class="c-headernav c-headernav-no-js c-mobilenav">
<nav aria-label="global" class="c-headernav c-mobilenav js-headernav">
<ul>
{{> @menu-items}}
</ul>
Expand Down
115 changes: 59 additions & 56 deletions js/headernav.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,65 @@
// Headernav Component:
// Headernav Component //

const headerNav = document.querySelector('.js-headernav')
const subNavs = headerNav.querySelectorAll(':scope > ul > li:has(> ul)')
const headerNavMediaQuery = window.matchMedia('(min-width: 760px)')
let counter = 1

function clickLink (event) {
if (this.parentElement.classList.contains('open') === false) {
this.parentElement.classList.add('open')
this.setAttribute('aria-expanded', 'true')
} else {
this.parentElement.classList.remove('open')
this.setAttribute('aria-expanded', 'false')
}
event.preventDefault()
}
if (document.querySelector('.c-headernav')) {
for (const subNav of subNavs) {
const subNavSiblingLink = subNav.querySelector('a')
const subNavPopover = subNav.querySelector('ul')
subNavPopover.popover = ''

// Anchor each sibling link to its popover using unique anchor names:
const anchorName = '--anchor' + counter++

// The properties 'anchor-name' and 'position-anchor' can't be set using style.setProperty in browsers that don't support them. Instead, use setAttribute to force them to appear in so that the anchor positioning polyfill sees them:
subNavSiblingLink.setAttribute('style', 'anchor-name: ' + anchorName)
subNavPopover.setAttribute('style', 'position-anchor: ' + anchorName)

const headerNavToggles = mq => {
const expandedState = () => {
if (subNavPopover.matches(':popover-open')) {
subNavSiblingLink.setAttribute('aria-expanded', 'true')
} else {
subNavSiblingLink.setAttribute('aria-expanded', 'false')
}
}

if (mq.matches) {
// Only a <button> as a popover control has built-in accessiblity bindings, so set and toggle sibling link aria expanded state:
subNavSiblingLink.setAttribute('aria-expanded', 'false') // initial state

const headerNavToggles = e => {
if (document.querySelector('.c-headernav') && e.matches) {
const allMenuItems = document.querySelectorAll('.c-headernav > ul > li');

[].forEach.call(allMenuItems, function (el) {
document.querySelector('.c-headernav').classList.remove('c-headernav-no-js')
document.querySelector('.c-headernav').classList.add('c-headernav-js')
el.querySelector('a').setAttribute('aria-haspopup', 'true')
el.querySelector('a').setAttribute('aria-expanded', 'false')

el.addEventListener('mouseover', function (event) {
this.classList.add('open')
this.querySelector('a').setAttribute('aria-expanded', 'true')
})

el.addEventListener('mouseout', function (event) {
this.classList.remove('open')
this.querySelector('a').setAttribute('aria-expanded', 'false')
})

el.querySelector('a').addEventListener('click', clickLink)
});

[].forEach.call(allMenuItems, function (el) {
el.querySelector('a').addEventListener('focus', function (event) {
[].forEach.call(
allMenuItems,
function (el) {
if (el !== this.parentElement) {
el.classList.remove('open')
el.querySelector('a').setAttribute('aria-expanded', 'false')
}
}, this
)
})
})
} else {
const allMenuItems = document.querySelectorAll('.c-headernav > ul > li');

[].forEach.call(allMenuItems, function (el) {
el.querySelector('a').removeEventListener('click', clickLink)
})
// Show/hide subnav on mouse pointer over and out:
subNav.addEventListener('mouseover', () => {
subNavPopover.showPopover()
expandedState()
})

subNav.addEventListener('mouseout', () => {
subNavPopover.hidePopover()
expandedState()
})

// Toggle subnav if sibling link is clicked by keyboard return key:
subNavSiblingLink.addEventListener('click', (e) => {
subNavPopover.togglePopover()
e.preventDefault() // disable link from going to its URL when clicked
expandedState()
})

// Hide subnav when keyboard focus leaves its popover control:
subNav.addEventListener('focusout', (e) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
subNavPopover.hidePopover()
expandedState()
}
})
}
}

headerNavMediaQuery.addEventListener('change', headerNavToggles)
headerNavToggles(headerNavMediaQuery)
}
}

headerNavMediaQuery.addEventListener('change', headerNavToggles)
headerNavToggles(headerNavMediaQuery)

0 comments on commit 21412b7

Please sign in to comment.