diff --git a/README.md b/README.md index dcdec1b1fa..798920dd41 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Milo is a shared set of features and services to power Franklin-based websites o ### Detailed 1. Fork this repo. -1. Install the [AEM Code Sync](https://github.com/apps/aem-code-sync) on your forked repo. +1. Install the [AEM Code Sync](https://github.com/apps/aem-code-sync) on your forked repo. Make sure that a `main` branch exists in your forked repository. 1. Clone your forked repo down to your computer. 1. Install the [AEM CLI](https://github.com/adobe/helix-cli) using your terminal: `sudo npm install -g @adobe/aem-cli` 1. In a terminal, run `aem up` your repo's folder on your computer. It will open a browser. diff --git a/libs/blocks/fallback/fallback.js b/libs/blocks/fallback/fallback.js index fc04eb71f3..33e650829e 100644 --- a/libs/blocks/fallback/fallback.js +++ b/libs/blocks/fallback/fallback.js @@ -18,6 +18,7 @@ const SYNTHETIC_BLOCKS = [ 'search', 'social', 'product-entry-cta', + 'gnav-image', ]; // eslint-disable-next-line import/prefer-default-export diff --git a/libs/blocks/global-navigation/base.css b/libs/blocks/global-navigation/base.css index 15a7925834..4f21bd9296 100644 --- a/libs/blocks/global-navigation/base.css +++ b/libs/blocks/global-navigation/base.css @@ -35,6 +35,15 @@ --feds-background-footer: #fafafa; --feds-borderColor-featuredProducts: #999; --feds-gutter-footer: 32px; + + /* mobile gnav redesign */ + --feds-color-white-v2: #fff; + --feds-color-black-v2: #000; + --feds-background-cloudmenu-v2: #111; + --feds-borderColor-link-v2: #eaeaea; + --feds-backgroundColor-tabs-v2: #f8f8f8; + --feds-backgroundColor-tabContent-v2: #f3f3f3; + --feds-borderColor-localnav-v2: #eee; } /* Nav Link styles */ diff --git a/libs/blocks/global-navigation/dark-nav.css b/libs/blocks/global-navigation/dark-nav.css index 22585dc606..1191d2f676 100644 --- a/libs/blocks/global-navigation/dark-nav.css +++ b/libs/blocks/global-navigation/dark-nav.css @@ -40,6 +40,10 @@ --feds-color-profile--dark: #dbdbdb; --feds-color-profile--emphasis--dark: #f2f2f2; --feds-border-profile--dark: 1px solid var(--feds-borderColor); + + /* mobile gnav redesign */ + --feds-borderColor-link-v2: #323232; + --feds-borderColor-localnav-v2: #323232; } .feds--dark .feds-navLink--hoverCaret:hover, @@ -84,6 +88,44 @@ color: var(--feds-color-link--dark); } +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .top-bar { + background-color: var(--feds-background-nav--desktop); + border-bottom: 1px solid var(--feds-background-nav--desktop); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .title { + background-color: var(--background-color); + border-bottom: 1px solid var(--feds-borderColor-link-v2); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .tabs { + background-color: var(--background-color); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .tab-content { + background-color: var(--background-color); + border-left: 1px solid var(--feds-borderColor-link-v2); +} + +[dir = "rtl"] header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .tab-content { + border-left: none; + border-right: 1px solid var(--feds-borderColor-link-v2); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .tabs button[aria-selected="true"] { + color: var(--background-color); + background-color: var(--feds-borderColor-navLink); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .sticky-cta { + background-color: var(--background-color); + border-top: 1px solid var(--feds-borderColor-link-v2); +} + +header.new-nav.feds--dark .feds-nav > section.feds-navItem > .feds-popup .top-bar .main-menu { + color: var(--text-color); +} + /* Popup */ .feds--dark .feds-popup { background-color: #151515; diff --git a/libs/blocks/global-navigation/features/aside/aside.js b/libs/blocks/global-navigation/features/aside/aside.js index 2abbf96eaa..0d956460ee 100644 --- a/libs/blocks/global-navigation/features/aside/aside.js +++ b/libs/blocks/global-navigation/features/aside/aside.js @@ -2,8 +2,9 @@ import { loadBlock, decorateAutoBlock } from '../../../../utils/utils.js'; import { toFragment, lanaLog } from '../../utilities/utilities.js'; import { processTrackingLabels } from '../../../../martech/attributes.js'; -export default async function decorateAside({ headerElem, promoPath } = {}) { +export default async function decorateAside({ headerElem, fedsPromoWrapper, promoPath } = {}) { const onError = () => { + fedsPromoWrapper?.remove(); headerElem?.classList.remove('has-promo'); lanaLog({ message: 'Gnav Promo fragment not replaced, potential CLS' }); return ''; diff --git a/libs/blocks/global-navigation/global-navigation.css b/libs/blocks/global-navigation/global-navigation.css index f361fa0066..94bea4769b 100644 --- a/libs/blocks/global-navigation/global-navigation.css +++ b/libs/blocks/global-navigation/global-navigation.css @@ -61,11 +61,12 @@ header.global-navigation { right: 20px; /* hamburger menu gutter */ display: none; flex-direction: column; - height: calc(100vh - 100% - 1px); + height: calc(100dvh - 100% - 1px); border-top: 1px solid var(--feds-borderColor); background-color: var(--feds-background-nav); } + [dir = "rtl"] .feds-nav-wrapper { left: 20px; right: 0; @@ -119,6 +120,15 @@ header.global-navigation { flex-shrink: 0; } +.feds-brand-container > .feds-brand:first-child { + margin-left: 12px; +} + +[dir = "rtl"] .feds-brand-container > .feds-brand:first-child { + margin-left: 0; + margin-right: 12px; +} + .feds-brand, .feds-logo { align-items: center; @@ -594,13 +604,13 @@ header.global-navigation { .feds-navItem--megaMenu .feds-popup { right: 0; padding: 40px 0 0; - max-height: calc(100vh - 100%); + max-height: calc(100dvh - 100%); overflow: auto; box-sizing: border-box; } .global-navigation.has-promo .feds-navItem--megaMenu .feds-popup { - max-height: calc(100vh - 100% - var(--global-height-navPromo)); + max-height: calc(100dvh - 100% - var(--global-height-navPromo)); } [dir = "rtl"] .feds-navItem--megaMenu .feds-popup { @@ -716,6 +726,9 @@ header.global-navigation { .feds-brand-image + .feds-brand-label { display: none; } + .feds-localnav { + display: none; + } } /* Desktop styles */ @@ -735,4 +748,577 @@ header.global-navigation { .feds-navItem--megaMenu .feds-popup { align-items: center; } + + .feds-localnav { + display: none; + } +} + +/* + * + * + * mobile gnav redesign + * + * + */ + +/* new mobile gnav is not sticky when localnav */ +header.new-nav.local-nav { + position: relative; +} + +/* Don't blur the background when opening the mega menu on a local nav page */ +header.new-nav.local-nav .feds-curtain--open { + display: none; +} + +header.new-nav:has(.feds-dropdown--active) { + z-index: 12; +} + +/* main-menu screen */ + +header.new-nav .feds-nav-wrapper { + width: 100%; + background-color: var(--feds-background-cloudmenu-v2); + translate: -200vw 0; + opacity: 0; + transition: translate 0.4s ease-out, opacity 0.4s ease, visibility 0s linear 0.5s; + display: flex; + visibility: hidden; +} + +[dir = "rtl"] header.new-nav .feds-nav-wrapper { + translate: 200vw 0; +} + +header.new-nav.local-nav .feds-nav-wrapper { + visibility: hidden; /* we don't want to see the main menu on a local nav page */ +} + +header.new-nav .feds-nav-wrapper--expanded { + translate: 0; + opacity: 1; + z-index: 2; + transition: translate 0.4s ease-out, opacity 0.4s ease, visibility 0s linear 0s; + visibility: visible; +} + +[dir = "rtl"] header.new-nav .feds-nav-wrapper--expanded { + translate: 0; +} + +header.new-nav .feds-nav-wrapper--expanded .feds-nav > section.feds-navItem:first-child { + padding-top: 25px; +} + +header.new-nav .feds-nav-wrapper--expanded .feds-nav > .feds-navItem.feds-navItem--mobile-only:last-of-type { + margin-bottom: 10px; + border-bottom: 2px solid var(--feds-color-white-v2); +} + +header.new-nav .feds-nav-wrapper--expanded .feds-nav > section.feds-navItem > button.feds-navLink { + color: var(--feds-color-white-v2); + font-size: 20px; + font-weight: 400; + line-height: 25px; + border: none; + opacity:0; + translate: -100px 0; + animation: slideleft 0.4s ease, fadein 0.2s ease; + animation-fill-mode: forwards; + padding: 15px 20px; +} + +[dir = "rtl"] header.new-nav .feds-nav-wrapper--expanded .feds-nav > section.feds-navItem > button.feds-navLink { + translate: 100px 0; + animation: rtlslideleft 0.4s ease, fadein 0.2s ease; + animation-fill-mode: forwards; +} + +header.new-nav .feds-nav > section.feds-navItem > button.feds-navLink { + opacity: 0; + border: none; + animation: slideright 0.4s ease, fadeout 0.2s ease; + animation-fill-mode: forwards; +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem > button.feds-navLink { + animation: rtlslideright 0.4s ease, fadeout 0.2s ease; + animation-fill-mode: forwards; +} + +header.new-nav .feds-nav > section.feds-navItem > button.feds-navLink::after { + content: url(''); + width: 9.271px; + height: 9.179px; + transform: rotate(-45deg); + border-color: var(--feds-color-white-v2); + right: 20px; + top: calc(50% - 4px); +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem > button.feds-navLink::after { + transform: rotate(135deg); + right: unset; + left: 20px; +} + +header.new-nav .feds-nav > .feds-navItem > a.feds-navLink { + border-bottom: 1px solid #2c2c2c; + padding: 20px; + font-size: 20px; + color: var(--feds-color-white-v2); +} + +/* + +/* Mega Menu */ + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup { + position: fixed; + transform: translate3d(0,0,0); /* render correctly on iOS Safari */ + -webkit-transform: translate3d(0,0,0); /* older iOS versions */ + top: calc(-1 * var(--feds-height-nav)); + visibility: hidden; + width: 100%; + height: calc(var(--feds-height-nav) + 100%); + background-color: var(--feds-background-nav); + transition: translate 0.4s ease-out, opacity 0.2s ease, visibility 0s linear 0.5s; + translate: 300vw 0; + display: grid; + grid-template-rows: 56px max-content auto 80px; + grid-template-columns: 111px 1fr; + grid-template-areas: + "top-bar top-bar" + "title title" + "tabs tab-panel" + "cta cta"; + box-sizing: content-box; /* Some DC pages use non default box-sizing globally */ +} + +.feds-promo-aside-wrapper + header.new-nav .feds-nav > section.feds-navItem > .feds-popup { + top: calc(0px - var(--feds-height-nav) - var(--global-height-navPromo)); +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem > .feds-popup { + translate: -300vw 0; +} + +header.new-nav.local-nav .feds-nav > section.feds-navItem > .feds-popup { + translate: 0; +} + +header.new-nav .feds-nav > section.feds-navItem.feds-dropdown--active::before { + width: 0; +} + +header.new-nav .feds-nav > section.feds-navItem.feds-dropdown--active > .feds-popup { + translate: 0; + opacity: 1; + z-index: 2; + transition: translate 0.4s ease-out, opacity 0.2s ease, visibility 0s linear 0s; + visibility: visible; +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem.feds-dropdown--active > .feds-popup { + translate: 0; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar { + grid-area: top-bar; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--feds-borderColor-link-v2); + padding: 0 20px; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar .feds-brand { + padding-left: 0; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar .close-icon { + width:14px; + height:14px; + padding: 0; + border: 0; + font-family: var(--body-font-family); + margin-top: 2px; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar .main-menu { + font-size: 14px; + font-weight: 700; + line-height: 16px; + padding: 1px 0; + border: 0; + font-family: var(--body-font-family); + color: var(--text-color); +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar .main-menu svg { + translate: 0 2px; + margin-right: 7px; +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem > .feds-popup .top-bar .main-menu svg { + transform: rotate(180deg); + margin-right: unset; + margin-left: 7px; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .title { + grid-area: title; + display: flex; + flex-direction: column; + border-bottom: 1px solid var(--feds-borderColor-link-v2); + padding: 0 20px; + box-sizing: inherit; +} +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .title .breadcrumbs { + display: block; /* even if the actual breadcrumbs are display none, we need the space to visible for the mobile gnav */ + height: 23px; + padding-top: 12px; + box-sizing: inherit; +} +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .title h7 { + height: 25px; + font-size: 28px; + font-weight: 700; + line-height: 25px; + padding: 8px 0 24px; + box-sizing: inherit; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tabs { + grid-area: tabs; + display: flex; + flex-direction: column; + padding-top: 16px; + background-color: var(--feds-backgroundColor-tabs-v2); + overflow-y: auto; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tabs button { + min-height:48px; + width: 111px; + border: none; + padding: 8px 11px 8px 20px; + white-space: normal; + font-weight: 700; + text-align: unset; + color: var(--text-color); + font-family: var(--body-font-family); + font-size: 14px; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tabs button[aria-selected="true"] { + background-color: var(--feds-color-black-v2); + color: var(--feds-color-white-v2); +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content { + background-color: var(--feds-backgroundColor-tabContent-v2); + padding: 10px 13px 25px; + display: flex; + flex-direction: column; + gap: 28px; + font-size:14px; + line-height: 16px; + overflow-y: scroll; + border-left: 1px solid var(--feds-borderColor-link-v2); +} + +[dir = "rtl"] header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content { + border-left: none; + border-right: 1px solid var(--feds-borderColor-link-v2); +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content a { + translate: 0 12px; + opacity: 0; + animation: slideup 0.6s ease, fadein 0.8s ease; + animation-fill-mode: forwards; + font-weight: 700; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content .feds-navLink-title { + font-weight: 700; + white-space: break-spaces; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content .feds-navLink--blue .feds-navLink-title { + font-weight: 400; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .tab-content .feds-navLink-description { + display: inline-block; + font-weight: 400; + white-space: break-spaces; +} + +header.new-nav .feds-nav > section.feds-navItem > .feds-popup .sticky-cta { + grid-area: cta; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + position:sticky; + border-top: 1px solid var(--feds-borderColor-link-v2); +} + +header.new-nav .feds-nav > section.feds-navItem.feds-dropdown--active > .feds-popup .sticky-cta a { + width: 320px; + height: 39px; + border-radius: 50px; +} + +/* breadcrumbs new nav*/ +header.new-nav .feds-breadcrumbs-wrapper { + display: none; +} + +header.new-nav .feds-navItem--section { + display: flex; +} + +header.new-nav .feds-breadcrumbs { + padding: 2px 0 0; +} + +header.new-nav .feds-breadcrumbs ul { + font-weight: 700; +} + +header.new-nav .feds-breadcrumbs li:not(:first-of-type)::before { + padding: 0 4px; + color: var(--feds-color-link-breadcrumbs); +} + +header.new-nav .feds-breadcrumbs li:first-child:not(:nth-last-child(-n+3)):after { + content: '/ …'; + padding: 0 0 0 4px; + color: var(--feds-color-link-breadcrumbs); +} + +/* local-nav */ +.feds-localnav { + position: sticky; + top: 0px; + width: 100%; + left: 0px; + z-index: 9; +} + +.feds-localnav.has-promo { + top: var(--global-height-navPromo); +} + +.local-nav .feds-toggle[aria-expanded = "true"]:before { + content: "\2630"; +} + +.feds-localnav a { + text-decoration: unset; +} + +.feds-localnav-title { + width: 100%; + height: 40px; + font-size: 16px; + font-weight: 700; + border: 0; + padding: 0 20px; + text-align: justify; + color: var(--text-color); + cursor: pointer; + border-top: 1px solid var(--feds-borderColor-localnav-v2); + border-bottom: 1px solid var(--feds-borderColor-localnav-v2); + background-color: var(--feds-background-nav); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: var(--body-font-family); + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2); +} + +.feds-localnav-items { + padding: 20px 0 24px; + background: var(--feds-background-popup); + border-bottom: 1px solid var(--feds-borderColor-localnav-v2); + display: none; + box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); +} + +.feds-localnav-items .feds-navLink { + border: 0; +} + +.feds-localnav-items .feds-navItem.feds-navItem--active > .feds-navLink { + font-weight: 600; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items { + display: block; + width: 100%; + position: absolute; + overflow: auto; + top: var(--feds-localnav-height); +} + +.feds-localnav .feds-localnav-items li { + list-style: none; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items .feds-menu-items { + padding: 0; + border-bottom: 0; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items .feds-menu-column > ul { + padding: 0; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items .feds-menu-column .feds-navLink { + padding-left: 38px; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items .feds-menu-items .feds-navLink { + padding: 12px 56px; +} + +.feds-localnav .feds-localnav-items .feds-navLink { + border-bottom: 0; + 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; + color: var(--feds-color-link); +} + +.feds-localnav .feds-localnav-items .feds-navItem--centered { + padding: 10px 20px; +} + +.feds-localnav.feds-localnav--active .feds-localnav-items { + box-sizing: border-box; + max-height: calc(100dvh - (var(--global-height-nav) + var(--feds-localnav-height))); +} + +.feds-localnav.has-promo.feds-localnav--active .feds-localnav-items { + max-height: calc(100dvh - (var(--global-height-nav) + var(--feds-localnav-height) + var(--global-height-navPromo))); +} + +.feds-localnav.feds-localnav--active.is-sticky .feds-localnav-items { + max-height: calc(100dvh - var(--feds-localnav-height)); +} + +.feds-localnav .feds-localnav--active::before { + display: none; +} + +.feds-localnav .feds-localnav-exit { + display: none; +} + +.feds-localnav.feds-localnav--active .feds-localnav-title::after { + transform: rotateZ(-135deg); +} + +.feds-localnav.feds-localnav--active .feds-localnav-curtain { + width: 100%; + height: 101lvh; + background: var(--feds-color-black-v2); + opacity: 0.7; +} + +.feds-localnav.feds-localnav--active .feds-localnav-exit { + display: block; +} + +@keyframes slideright { + from { + translate: 0 0; + } + to { + translate: -100px 0; + } +} + +@keyframes rtlslideright { + from { + translate: 0 0; + } + to { + translate: 100px 0; + } +} + +@keyframes slideleft { + from { + translate: -100px 0; + } + to { + translate: 0 0; + } +} + +@keyframes rtlslideleft { + from { + translate: 100px 0; + } + to { + translate: 0 0; + } +} + +@keyframes slideup { + from { + translate: 0 40px; + } + to { + translate: 0 0; + } +} + +@keyframes fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes fadeout { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +/* */ + +.disable-ios-scroll { + overflow: hidden; + width: 100%; + min-height: 100dvh; + position: fixed; +} + +html:has(body.disable-ios-scroll) { /* this class is only added on iOS */ + height: 100dvh; + position: fixed; } diff --git a/libs/blocks/global-navigation/global-navigation.js b/libs/blocks/global-navigation/global-navigation.js index 515d97480f..0790786ae4 100644 --- a/libs/blocks/global-navigation/global-navigation.js +++ b/libs/blocks/global-navigation/global-navigation.js @@ -6,6 +6,7 @@ import { loadIms, decorateLinks, loadScript, + getGnavSource, } from '../../utils/utils.js'; import { closeAllDropdowns, @@ -38,6 +39,11 @@ import { darkIcons, setDisableAEDState, getDisableAEDState, + animateInSequence, + transformTemplateToMobile, + closeAllTabs, + disableMobileScroll, + enableMobileScroll, } from './utilities/utilities.js'; import { getFedsPlaceholderConfig } from '../../utils/federated.js'; @@ -227,10 +233,10 @@ const decorateProfileTrigger = async ({ avatar }) => { }; let keyboardNav; -const setupKeyboardNav = async () => { +const setupKeyboardNav = async (newMobileWithLnav) => { keyboardNav = keyboardNav || new Promise(async (resolve) => { const { default: KeyboardNavigation } = await import('./utilities/keyboard/index.js'); - const instance = new KeyboardNavigation(); + const instance = new KeyboardNavigation(newMobileWithLnav); resolve(instance); }); }; @@ -252,10 +258,12 @@ const getBrandImage = (image, brandImageOnly) => { return brandImageOnly ? CONFIG.icons.brand : CONFIG.icons.company; }; -const closeOnClickOutside = (e) => { - if (!isDesktop.matches) return; +const closeOnClickOutside = (e, isLocalNav, navWrapper) => { + if (isLocalNav && navWrapper.classList.contains('feds-nav-wrapper--expanded')) return; + const newMobileNav = getMetadata('mobile-gnav-v2') !== 'false'; + if (!isDesktop.matches && !newMobileNav) return; - const openElemSelector = `${selectors.globalNav} [aria-expanded = "true"]`; + const openElemSelector = `${selectors.globalNav} [aria-expanded = "true"], ${selectors.localNav} [aria-expanded = "true"]`; const isClickedElemOpen = [...document.querySelectorAll(openElemSelector)] .find((openItem) => openItem.parentElement.contains(e.target)); @@ -285,7 +293,7 @@ const convertToPascalCase = (str) => str .join(' '); class Gnav { - constructor({ content, block } = {}) { + constructor({ content, block, newMobileNav } = {}) { this.content = content; this.block = block; this.customLinks = getConfig()?.customLinks?.split(',') || []; @@ -301,8 +309,12 @@ class Gnav { this.setupUniversalNav(); this.elements = {}; + this.newMobileNav = newMobileNav; } + // eslint-disable-next-line no-return-assign + getOriginalTitle = (firstElem) => this.originalTitle ||= firstElem.textContent?.split('::'); + setupUniversalNav = () => { const meta = getMetadata('universal-nav')?.toLowerCase(); this.universalNavComponents = meta?.split(',').map((option) => option.trim()) @@ -323,24 +335,34 @@ class Gnav { // Order is important, decorateTopnavWrapper will render the nav // Ensure any critical task is executed before it const tasks = [ + // decorateAside is the only async function that fires prior to rendering + // (at time of writing). If there is no aside it returns sync -- no problem. + // But if there is, we need those functions (import + decorate) to enter the event loop + // before the delayed decorateDropdown function does. + // the rest is taken care of by the 'await' semantics + // We needn't worry about delays now since decorateAside + // needed to run anyway prior to decorateTopNavWrapper + this.decorateAside, this.decorateMainNav, this.decorateTopNav, - this.decorateAside, this.decorateTopnavWrapper, loadBaseStyles, 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); - for await (const task of tasks) { + setTimeout(fetchKeyboardNav, CONFIG.delays.keyboardNav); + for (const task of tasks) { await yieldToMain(); await task(); } - document.addEventListener('click', closeOnClickOutside); + document.addEventListener('click', (e) => closeOnClickOutside(e, this.isLocalNav(), this.elements.navWrapper)); isDesktop.addEventListener('change', closeAllDropdowns); }, 'Error in global navigation init', 'errorType=error,module=gnav'); @@ -380,17 +402,85 @@ class Gnav { `; }; + decorateLocalNav = async () => { + if (!this.isLocalNav()) return; + const localNavItems = this.elements.navWrapper.querySelector('.feds-nav').querySelectorAll('.feds-navItem:not(.feds-navItem--section, .feds-navItem--mobile-only)'); + const firstElem = localNavItems[0]?.querySelector('a'); + if (!firstElem) { + lanaLog({ message: 'GNAV: Incorrect authoring of localnav found.', tags: 'errorType=info,module=gnav' }); + return; + } + const [title, navTitle = ''] = this.getOriginalTitle(firstElem); + 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`
`; + this.block.after(localNav); + } + localNav.setAttribute('daa-lh', `${title}_localNav`); + localNav.append(toFragment``, toFragment` `, toFragment` `, toFragment`.`); + + 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'); + + if (idx === 0) { + localNav.querySelector('.feds-localnav-title').innerText = title.trim(); + link.textContent = navTitle.trim() || titleLabel; + } + + itemWrapper.appendChild(clonedItem); + }); + + localNav.querySelector('.feds-localnav-title').addEventListener('click', () => { + localNav.classList.toggle('feds-localnav--active'); + const isActive = localNav.classList.contains('feds-localnav--active'); + localNav.querySelector('.feds-localnav-title').setAttribute('aria-expanded', isActive); + localNav.querySelector('.feds-localnav-title').setAttribute('daa-ll', `${title}_localNav|${isActive ? 'close' : 'open'}`); + }); + + localNav.querySelector('.feds-localnav-curtain').addEventListener('click', (e) => { + trigger({ element: e.currentTarget, event: e, type: 'localNav-curtain' }); + }); + const promo = document.querySelector('.feds-promo-aside-wrapper'); + if (promo) localNav.classList.add('has-promo'); + this.elements.localNav = localNav; + localNavItems[0].querySelector('a').textContent = title.trim(); + const isAtTop = () => { + const rect = this.elements.localNav.getBoundingClientRect(); + // note: ios safari changes between -0.34375, 0, and 0.328125 + 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 () => { const breadcrumbs = isDesktop.matches ? await this.decorateBreadcrumbs() : ''; this.elements.topnavWrapper = toFragment`