Skip to content

Commit

Permalink
[MWPW-163735] - Added Local Nav keyboard navigation for mobile GNAV r…
Browse files Browse the repository at this point in the history
…edesign (#3321)

* Added localnav keyboard navigation for mobile gnav redesign

* Added fixes for Local nav along with keyboard navigation

* lint fix
  • Loading branch information
Deva309 authored Dec 9, 2024
1 parent e9bc3b8 commit 2b91760
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 46 deletions.
34 changes: 27 additions & 7 deletions libs/blocks/global-navigation/global-navigation.css
Original file line number Diff line number Diff line change
Expand Up @@ -699,10 +699,6 @@ header.global-navigation {
.feds-breadcrumbs a:hover {
text-decoration: underline;
}

header + div.feds-localnav {
display: none;
}
}

/* Small desktop styles */
Expand Down Expand Up @@ -1051,9 +1047,8 @@ header.new-nav .feds-breadcrumbs li:first-child:not(:nth-last-child(-n+3)):after

/* local-nav */

header + .feds-localnav {
.feds-localnav {
position: sticky;
display: block;
top: 0;
width: 100%;
left: 0;
Expand Down Expand Up @@ -1099,6 +1094,7 @@ header + .feds-localnav {
display: block;
width: 100%;
position: absolute;
overflow: auto;
}

.feds-localnav.active .feds-localnav-items .feds-menu-items {
Expand Down Expand Up @@ -1127,35 +1123,59 @@ header + .feds-localnav {
font-size: 14px;
padding: 12px 20px;
background: var(--feds-background-popup);
outline-offset: -1px;
cursor: pointer;
}

.feds-localnav .feds-localnav-items .feds-menu-headline {
background: var(--feds-background-popup);
font-weight: 400;
border-bottom: 0;
padding: 12px 38px;
outline-offset: -1px;
}

.feds-localnav .feds-localnav-items .feds-navItem--centered {
padding: 10px 20px;
}

.feds-localnav.active .feds-localnav-items {
box-sizing: border-box;
max-height: calc(100vh - (var(--global-height-nav) + var(--feds-localnav-height)));
}

.feds-localnav.active.is-sticky .feds-localnav-items {
max-height: calc(100vh - var(--feds-localnav-height));
}

.feds-localnav .feds-dropdown--active::before {
display: none;
}

.feds-localnav .feds-localnav-exit {
display: none;
}

.feds-localnav.active .feds-localnav-title::after {
transform: rotateZ(-135deg);
}

.feds-localnav.active .feds-localnav-curtain {
width: 100%;
height: 100vh;
height: calc(100vh - (var(--feds-height-nav) + var(--feds-localnav-height)));
position: absolute;
background: var(--feds-color-black-v2);
opacity: 0.7;
}

.feds-localnav.active.is-sticky .feds-localnav-curtain {
height: calc(100vh - var(--feds-localnav-height));
}

.feds-localnav.active .feds-localnav-exit {
display: block;
}

@keyframes slideright {
from {
translate: 0 0;
Expand Down
87 changes: 54 additions & 33 deletions libs/blocks/global-navigation/global-navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,10 @@ const decorateProfileTrigger = async ({ avatar }) => {
};

let keyboardNav;
const setupKeyboardNav = async () => {
const setupKeyboardNav = async (newMobileWithLnav) => {
keyboardNav = keyboardNav || new Promise(async (resolve) => {
const KeyboardNavigation = await loadBlock('./keyboard/index.js');
const instance = new KeyboardNavigation();
const instance = new KeyboardNavigation(newMobileWithLnav);
resolve(instance);
});
};
Expand Down Expand Up @@ -331,10 +331,13 @@ class Gnav {
this.ims,
this.addChangeEventListeners,
];
const fetchKeyboardNav = () => {
setupKeyboardNav(this.newMobileNav && this.isLocalNav());
};
this.block.addEventListener('click', this.loadDelayed);
this.block.addEventListener('keydown', setupKeyboardNav);
this.block.addEventListener('keydown', fetchKeyboardNav);
setTimeout(this.loadDelayed, CONFIG.delays.loadDelayed);
setTimeout(setupKeyboardNav, CONFIG.delays.keyboardNav);
setTimeout(fetchKeyboardNav, CONFIG.delays.keyboardNav);
for await (const task of tasks) {
await yieldToMain();
await task();
Expand Down Expand Up @@ -375,38 +378,50 @@ class Gnav {
if (!this.isLocalNav()) return;
const localNavItems = this.elements.navWrapper.querySelector('.feds-nav').querySelectorAll('.feds-navItem:not(.feds-navItem--section)');
const [title, navTitle = ''] = this.getOriginalTitle(localNavItems);
let localNav = document.querySelector('.feds-localnav');
if (!localNav) {
lanaLog({ message: 'GNAV: Localnav does not include \'localnav\' in its name.', tags: 'errorType=info,module=gnav' });
localNav = toFragment`<div class="feds-localnav"/>`;
this.block.after(localNav);
}
localNav.append(toFragment`<button class="feds-navLink--hoverCaret feds-localnav-title" aria-haspopup="true" aria-expanded="false"></button>`, toFragment` <div class="feds-localnav-curtain"></div>`, toFragment` <div class="feds-localnav-items"></div>`, toFragment`<a href="#" class="feds-sr-only feds-localnav-exit">.</a>`);

if (this.elements.localNav || !this.newMobileNav || isDesktop.matches) {
localNavItems[0].querySelector('a').textContent = title.trim();
} else {
let localNav = document.querySelector('.feds-localnav');
if (!localNav) {
lanaLog({ message: 'GNAV: Localnav does not include \'localnav\' in its name.', tags: 'errorType=info,module=gnav' });
localNav = toFragment`<div class="feds-localnav"/>`;
this.block.after(localNav);
}
localNav.append(toFragment`<button class="feds-navLink--hoverCaret feds-localnav-title"></button>`, toFragment` <div class="feds-localnav-curtain"></div>`, toFragment` <div class="feds-localnav-items"></div>`);

const itemWrapper = localNav.querySelector('.feds-localnav-items');
const titleLabel = await replaceKey('overview', getFedsPlaceholderConfig());
const itemWrapper = localNav.querySelector('.feds-localnav-items');
const titleLabel = await replaceKey('overview', getFedsPlaceholderConfig());

localNavItems.forEach((elem, idx) => {
const clonedItem = elem.cloneNode(true);
const link = clonedItem.querySelector('a');
localNavItems.forEach((elem, idx) => {
const clonedItem = elem.cloneNode(true);
const link = clonedItem.querySelector('a');

if (idx === 0) {
localNav.querySelector('.feds-localnav-title').innerText = title.trim();
link.textContent = navTitle.trim() || titleLabel;
}
if (idx === 0) {
localNav.querySelector('.feds-localnav-title').innerText = title.trim();
link.textContent = navTitle.trim() || titleLabel;
}

itemWrapper.appendChild(clonedItem);
});
itemWrapper.appendChild(clonedItem);
});

localNav.querySelector('.feds-localnav-title').addEventListener('click', () => {
localNav.classList.toggle('active');
});
this.elements.localNav = localNav;
}
localNav.querySelector('.feds-localnav-title').addEventListener('click', () => {
localNav.classList.toggle('active');
const isActive = localNav.classList.contains('active');
localNav.querySelector('.feds-localnav-title').setAttribute('aria-expanded', isActive);
});
this.elements.localNav = localNav;
localNavItems[0].querySelector('a').textContent = title.trim();
const isAtTop = () => {
const rect = this.elements.localNav.getBoundingClientRect();
return rect.top === 0;
};
window.addEventListener('scroll', () => {
const classList = this.elements.localNav?.classList;
if (isAtTop()) {
if (!classList?.contains('is-sticky')) {
classList?.add('is-sticky');
}
} else {
classList?.remove('is-sticky');
}
});
};

decorateTopnavWrapper = async () => {
Expand All @@ -423,7 +438,9 @@ class Gnav {
this.elements.topnavWrapper,
);

this.decorateLocalNav();
if (this.newMobileNav) {
await this.decorateLocalNav();
}
};

addChangeEventListeners = () => {
Expand Down Expand Up @@ -454,7 +471,6 @@ class Gnav {
this.elements.navWrapper.prepend(this.elements.breadcrumbsWrapper);
}
}
this.decorateLocalNav();
});

// Add a modifier when the nav is tangent to the viewport and content is partly hidden
Expand Down Expand Up @@ -1003,6 +1019,11 @@ class Gnav {

elements.querySelectorAll('.feds-menu-headline').forEach((elem) => {
// Reattach click event listener to headlines
elem?.setAttribute('role', 'button');
elem?.setAttribute('tabindex', 0);
elem?.removeAttribute('aria-level');
elem?.setAttribute('aria-haspopup', true);
elem?.setAttribute('aria-expanded', false);
elem?.addEventListener('click', (e) => {
trigger({ element: e.currentTarget, event: e, type: 'headline' });
setActiveDropdown(e.currentTarget);
Expand Down
16 changes: 14 additions & 2 deletions libs/blocks/global-navigation/utilities/keyboard/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable class-methods-use-this */
import { getNextVisibleItemPosition, getPreviousVisibleItemPosition, selectors } from './utils.js';
import MainNav from './mainNav.js';
import { closeAllDropdowns, lanaLog, logErrorFor } from '../utilities.js';
import { closeAllDropdowns, lanaLog, logErrorFor, loadBlock } from '../utilities.js';

const cycleOnOpenSearch = ({ e, isDesktop }) => {
const withoutBreadcrumbs = [
Expand Down Expand Up @@ -73,16 +73,28 @@ const focusPrevProfileItem = ({ e }) => {
};

class KeyboardNavigation {
constructor() {
constructor(newNavWithLnav) {
try {
this.addEventListeners();
this.mainNav = new MainNav();
if (newNavWithLnav) {
this.loadLnavNavigation();
}
this.desktop = window.matchMedia('(min-width: 900px)');
} catch (e) {
lanaLog({ message: 'Keyboard Navigation failed to load', e, tags: 'errorType=error,module=gnav-keyboard' });
}
}

loadLnavNavigation = async () => {
this.localNav = this.localNav || new Promise((resolve) => {
loadBlock('./keyboard/localNav.js').then((LnavNavigation) => {
const instance = new LnavNavigation();
resolve(instance);
});
});
};

addEventListeners = () => {
[...document.querySelectorAll(`${selectors.globalNav}, ${selectors.globalFooter}`)]
.forEach((el) => {
Expand Down
38 changes: 38 additions & 0 deletions libs/blocks/global-navigation/utilities/keyboard/localNav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { selectors } from './utils.js';
import { trigger, setActiveDropdown } from '../utilities.js';

class LocalNavItem {
constructor() {
this.localNav = document.querySelector(selectors.localNav);
this.localNavTrigger = this.localNav?.querySelector(selectors.localNavTitle);
this.exitLink = this.localNav?.querySelector(selectors.localNavExit);
this.addEventListeners();
}

static handleKeyDown = (e) => {
const isHeadline = e.target.classList.contains(selectors.headline.slice(1));
switch (e.code) {
case 'Space':
case 'Enter':
if (isHeadline) {
e.preventDefault(); // Prevent default scrolling behavior for Space key
trigger({ element: e.target, event: e, type: 'headline' });
setActiveDropdown(e.target);
}
break;
default:
break;
}
};

addEventListeners = () => {
this.localNav?.addEventListener('keydown', LocalNavItem.handleKeyDown);
this.exitLink?.addEventListener('focus', (e) => {
e.preventDefault();
this.localNavTrigger?.click();
this.localNavTrigger?.focus();
});
};
}

export default LocalNavItem;
3 changes: 3 additions & 0 deletions libs/blocks/global-navigation/utilities/keyboard/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ const selectors = {
activeTabpanel: '.tab-content [role="tabpanel"]',
activeLinks: '.tab-content [role="tabpanel"]:not([hidden="true"]) a',
stickyCta: 'header.new-nav .feds-popup .sticky-cta a',
localNav: '.feds-localnav',
localNavTitle: '.feds-localnav-title',
localNavExit: '.feds-localnav-exit',
};

selectors.profileDropdown = `
Expand Down
4 changes: 2 additions & 2 deletions libs/blocks/global-navigation/utilities/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,9 +418,9 @@ export const [setUserProfile, getUserProfile] = (() => {

export const transformTemplateToMobile = async (popup, item, localnav = false) => {
const notMegaMenu = popup.parentElement.tagName === 'DIV';
if (notMegaMenu) return null;

const originalContent = popup.innerHTML;
if (notMegaMenu) return originalContent;

const tabs = [...popup.querySelectorAll('.feds-menu-section')]
.filter((section) => !section.querySelector('.feds-promo') && section.textContent)
.map((section) => {
Expand Down
22 changes: 20 additions & 2 deletions libs/styles/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/* stylelint-disable-next-line custom-property-pattern */
--global-height-navPromo: 72px;
--feds-totalheight-nav: calc(var(--feds-height-nav, --global-height-nav) + var(--feds-height-breadcrumbs, --global-height-breadcrumbs));
--feds-localnav-height: 40px;

/* Colors */
--link-color: #035fe6;
Expand Down Expand Up @@ -724,14 +725,31 @@ header.global-navigation a {
text-decoration: unset;
}

.feds-localnav {
height: 40px;
header.global-navigation + .feds-localnav {
display: block;
height: var(--feds-localnav-height);
}

.feds-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

@media (min-width: 900px) {
header.global-navigation.has-breadcrumbs {
padding-bottom: var(--global-height-breadcrumbs);
}

header.global-navigation + .feds-localnav {
display: none;
}
}

.breadcrumbs {
Expand Down

0 comments on commit 2b91760

Please sign in to comment.