Skip to content

Commit

Permalink
Replace header nav toggle logic with popover api
Browse files Browse the repository at this point in the history
  • Loading branch information
JoelCDL committed Nov 20, 2024
1 parent 8fa0290 commit 9e25d52
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 129 deletions.
116 changes: 48 additions & 68 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,35 @@
}
}

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 {
@media (--min-width2) {

> ul > li {
/* 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 +59,34 @@
}

/* 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;

@supports (position-area: block-end span-inline-end) {
inset: unset;
position-area: block-end span-inline-end;
position-try-fallbacks: flip-inline;
}

&: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 +150,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;
}
}
}
}
4 changes: 4 additions & 0 deletions css/components/mobilenav.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
mask: url('data-url:npm:fa-light/angle-right.svg') center / 0.7rem no-repeat;
}
}

ul {
display: none;
}
}
}
}
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
2 changes: 1 addition & 1 deletion elements/_template-default.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="no-popover">
<head>
<meta charset="utf-8">
<title>CDLIB UI</title>
Expand Down
2 changes: 1 addition & 1 deletion elements/_template-page.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="no-popover">
<head>
<meta charset="utf-8">
<title>CDLIB UI</title>
Expand Down
19 changes: 19 additions & 0 deletions js/anchor-positioning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// ***** Anchor Positioning Polyfill ***** //

// Required for positioning elements that use the popover API.

// https://github.com/oddbird/css-anchor-positioning

const anchorPositioningPolyfill = async () => {
const { default: polyfill } = await import('@oddbird/css-anchor-positioning/dist/css-anchor-positioning-fn.js')

polyfill({
elements: undefined,
excludeInlineStyles: false,
useAnimationFrame: false
})
}

if (!('anchorName' in document.documentElement.style)) {
anchorPositioningPolyfill()
}
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)
2 changes: 2 additions & 0 deletions js/main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import './anchor-positioning.js'
import './headernav.js'
import './mediaqueries.js'
import './newsreel.js'
import './popover-support.js'
import './sidebarposts.js'
import './slideshow.js'
import './toggles.js'
5 changes: 5 additions & 0 deletions js/popover-support.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// ***** Popover Support for CSS ***** //

if (window.HTMLElement.prototype.hasOwnProperty('popover')) { // eslint-disable-line no-prototype-builtins
document.querySelector('html').classList.remove('no-popover')
}
Loading

0 comments on commit 9e25d52

Please sign in to comment.