-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #66 from Netcentric/26-in-page-nav-pdp
26 in page nav pdp
- Loading branch information
Showing
6 changed files
with
530 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
:root { | ||
--inpage-navigation-height: 48px; | ||
} | ||
|
||
.v2-inpage-navigation-wrapper { | ||
background-color: var(--c-primary-white); | ||
box-shadow: 0 4px 24px 0 rgb(0 0 0 / 16%); | ||
height: auto !important; | ||
left: 0; | ||
overflow: unset !important; | ||
position: sticky; | ||
top: var(--nav-height); | ||
width: 100%; | ||
z-index: 2; | ||
} | ||
|
||
.v2-inpage-navigation__wrapper { | ||
display: flex; | ||
margin: 0 auto; | ||
} | ||
|
||
.v2-inpage-navigation__dropdown { | ||
flex-grow: 1; | ||
position: relative; | ||
} | ||
|
||
.v2-inpage-navigation__items { | ||
background-color: var(--c-primary-white); | ||
box-shadow: 0 4px 24px 0 rgb(0 0 0 / 16%); | ||
display: none; | ||
left: 0; | ||
list-style: none; | ||
margin: 0; | ||
padding: 0; | ||
position: absolute; | ||
top: 100%; | ||
width: 100%; | ||
z-index: -1; | ||
} | ||
|
||
.v2-inpage-navigation__item--active { | ||
display: none; | ||
} | ||
|
||
.v2-inpage-navigation__item button, | ||
.v2-inpage-navigation__selected-item-wrapper { | ||
background: none; | ||
border: 0; | ||
color: var(--c-primary-black); | ||
cursor: pointer; | ||
display: block; | ||
font-family: var(--ff-body-bold); | ||
font-size: var(--body-2-font-size); | ||
line-height: var(--body-2-line-height); | ||
margin: 0; | ||
padding: 14px 24px; | ||
width: 100%; | ||
} | ||
|
||
/* stylelint-disable-next-line no-descending-specificity */ | ||
.v2-inpage-navigation__item button:hover, | ||
.v2-inpage-navigation__item button:active, | ||
.v2-inpage-navigation__item button:focus, | ||
.v2-inpage-navigation__selected-item-wrapper:hover, | ||
.v2-inpage-navigation__selected-item-wrapper:active | ||
.v2-inpage-navigation__selected-item-wrapper:focus, | ||
.v2-inpage-navigation__dropdown--open .v2-inpage-navigation__selected-item-wrapper { | ||
background-color: #F1F1F1; | ||
} | ||
|
||
/* stylelint-disable-next-line no-descending-specificity */ | ||
.v2-inpage-navigation__item button { | ||
max-width: none; | ||
text-align: left; | ||
} | ||
|
||
/* stylelint-disable-next-line no-descending-specificity */ | ||
.v2-inpage-navigation__selected-item-wrapper { | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
} | ||
|
||
.v2-inpage-navigation__selected-item-wrapper svg { | ||
--color-icon: var(--c-accent-red); | ||
|
||
height: 16px; | ||
transition: transform var(--duration-small) var(--easing-standard); | ||
width: 16px; | ||
} | ||
|
||
/* Customization when dropdown is open */ | ||
.v2-inpage-navigation__dropdown--open .v2-inpage-navigation__items { | ||
display: block; | ||
} | ||
|
||
.v2-inpage-navigation__dropdown--open .v2-inpage-navigation__selected-item-wrapper svg { | ||
transform: rotate(180deg); | ||
} | ||
|
||
/* END Customization when dropdown is open */ | ||
|
||
/* Red button */ | ||
.v2-inpage-navigation__cta:any-link { | ||
align-items: center; | ||
background-color: var(--button-primary-red-enabled); | ||
color: var(--c-primary-white); | ||
display: flex; | ||
font-family: var(--ff-body); | ||
font-size: 14px; | ||
font-style: normal; | ||
font-weight: 500; | ||
letter-spacing: 1.12px; | ||
line-height: 18px; | ||
padding: 0 20px; | ||
text-decoration: none; | ||
} | ||
|
||
.v2-inpage-navigation__cta:hover, | ||
.v2-inpage-navigation__cta:focus { | ||
background-color: var(--button-primary-red-hover); | ||
} | ||
|
||
.v2-inpage-navigation__cta:active { | ||
background-color: var(--button-primary-red-pressed); | ||
} | ||
|
||
.v2-inpage-navigation__cta--desktop { | ||
display: none; | ||
} | ||
|
||
@media (min-width: 1200px) { | ||
:root { | ||
--inpage-navigation-height: 96px; | ||
} | ||
|
||
.v2-inpage-navigation__wrapper { | ||
align-items: center; | ||
gap: 24px; | ||
max-width: var(--wrapper-width); | ||
padding: 24px 0; | ||
} | ||
|
||
.v2-inpage-navigation__selected-item-wrapper { | ||
display: none; | ||
} | ||
|
||
.v2-inpage-navigation__items, | ||
.v2-inpage-navigation__dropdown--open .v2-inpage-navigation__items { | ||
display: flex; | ||
} | ||
|
||
/* stylelint-disable-next-line no-descending-specificity */ | ||
.v2-inpage-navigation__items { | ||
box-shadow: none; | ||
gap: 24px; | ||
justify-content: space-between; | ||
position: unset; | ||
} | ||
|
||
.v2-inpage-navigation__item { | ||
margin-right: auto; | ||
} | ||
|
||
.v2-inpage-navigation__item--active { | ||
display: block; | ||
} | ||
|
||
.v2-inpage-navigation__item button { | ||
padding: 10px 0; | ||
position: relative; | ||
} | ||
|
||
.v2-inpage-navigation__item button:hover, | ||
.v2-inpage-navigation__item button:focus { | ||
background: none; | ||
} | ||
|
||
.v2-inpage-navigation__item button::after { | ||
bottom: 0; | ||
content: ''; | ||
display: block; | ||
height: 4px; | ||
position: absolute; | ||
transition: background-color var(--duration-small) var(--easing-standard); | ||
width: 100%; | ||
} | ||
|
||
.v2-inpage-navigation__item--active button::after, | ||
.v2-inpage-navigation__item button:hover::after, | ||
.v2-inpage-navigation__item button:focus::after { | ||
background-color: var(--c-accent-red); | ||
} | ||
|
||
/* Red button */ | ||
.v2-inpage-navigation__cta:any-link { | ||
border-radius: 2px; | ||
padding: 15px 20px; | ||
} | ||
|
||
.v2-inpage-navigation__cta--mobile { | ||
display: none; | ||
} | ||
|
||
.v2-inpage-navigation__cta--desktop { | ||
display: block; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
import { getMetadata } from '../../scripts/lib-franklin.js'; | ||
import { createElement } from '../../scripts/common.js'; | ||
|
||
const blockName = 'v2-inpage-navigation'; | ||
|
||
const scrollToSection = (id) => { | ||
let timeout; | ||
|
||
const container = document.querySelector(`main .section[data-inpageid='${id}']`); | ||
container?.scrollIntoView({ behavior: 'smooth' }); | ||
|
||
// Checking if the height of the main element changes while scrolling (caused by layout shift) | ||
const main = document.querySelector('main'); | ||
const resizeObserver = new ResizeObserver(() => { | ||
clearTimeout(timeout); | ||
container?.scrollIntoView({ behavior: 'smooth' }); | ||
|
||
timeout = setTimeout(() => { | ||
resizeObserver.disconnect(); | ||
}, 500); | ||
}); | ||
resizeObserver.observe(main); | ||
}; | ||
|
||
const inpageNavigationRedButton = () => { | ||
// if we have a button title & button link | ||
if (getMetadata('inpage-button') && getMetadata('inpage-link')) { | ||
const titleMobile = getMetadata('inpage-button'); | ||
const url = getMetadata('inpage-link'); | ||
const link = createElement('a', { | ||
classes: `${blockName}__cta`, | ||
props: { | ||
href: url, | ||
title: titleMobile, | ||
}, | ||
}); | ||
const mobileText = createElement('span', { classes: `${blockName}__cta--mobile` }); | ||
mobileText.textContent = titleMobile; | ||
link.appendChild(mobileText); | ||
|
||
const titleDesktop = getMetadata('inpage-button-large'); | ||
if (titleDesktop) { | ||
const desktopText = createElement('span', { classes: `${blockName}__cta--desktop` }); | ||
desktopText.textContent = titleDesktop; | ||
link.setAttribute('title', titleDesktop); | ||
link.appendChild(desktopText); | ||
} | ||
|
||
return link; | ||
} | ||
|
||
return null; | ||
}; | ||
|
||
const gotoSection = (event) => { | ||
const { target } = event; | ||
const button = target.closest('button'); | ||
|
||
if (button) { | ||
const { id } = button.dataset; | ||
|
||
scrollToSection(id); | ||
} | ||
}; | ||
|
||
const updateActive = (id) => { | ||
const activeItemInList = document.querySelector(`.${blockName}__item--active`); | ||
|
||
// Prevent reassign active value | ||
if (activeItemInList?.firstElementChild?.dataset.id === id) return; | ||
|
||
// Remove focus position | ||
document.activeElement.blur(); | ||
|
||
// check active id is equal to id dont do anything | ||
const selectedItem = document.querySelector(`.${blockName}__selected-item`); | ||
activeItemInList?.classList.remove(`${blockName}__item--active`); | ||
const itemsButton = document.querySelectorAll(`.${blockName}__items button`); | ||
const { pathname } = window.location; | ||
|
||
if (id) { | ||
const selectedButton = [...itemsButton].find((button) => button.dataset.id === id); | ||
if (!selectedButton) return; | ||
selectedItem.textContent = selectedButton.textContent; | ||
selectedButton.parentNode.classList.add(`${blockName}__item--active`); | ||
|
||
window.history.replaceState({}, '', `${pathname}#${id}`); | ||
} else { | ||
window.history.replaceState({}, '', `${pathname}`); | ||
} | ||
}; | ||
|
||
const listenScroll = () => { | ||
let timeout; | ||
const elements = document.querySelectorAll('main .section'); | ||
|
||
const io = new IntersectionObserver((entries) => { | ||
// Reduce entries to the one with higher intersectionRatio | ||
const intersectedEntry = entries.reduce((prev, current) => ( | ||
prev.intersectionRatio > current.intersectionRatio ? prev : current | ||
)); | ||
|
||
if (intersectedEntry.isIntersecting && intersectedEntry.target.dataset?.inpageid) { | ||
clearTimeout(timeout); | ||
|
||
// wait to update the active item | ||
timeout = setTimeout(() => { | ||
updateActive(intersectedEntry.target.dataset.inpageid); | ||
}, 500); | ||
} else { | ||
updateActive(); | ||
} | ||
}, { | ||
threshold: [0.2, 0.5, 0.7, 1], | ||
}); | ||
|
||
elements.forEach((el) => { | ||
io.observe(el); | ||
}); | ||
}; | ||
|
||
export default async function decorate(block) { | ||
const redButton = inpageNavigationRedButton(); | ||
|
||
const wrapper = block.querySelector(':scope > div'); | ||
wrapper.classList.add(`${blockName}__wrapper`); | ||
const itemsWrapper = block.querySelector(':scope > div > div'); | ||
|
||
const dropdownWrapper = createElement('div', { classes: `${blockName}__dropdown` }); | ||
const selectedItemWrapper = createElement('div', { classes: `${blockName}__selected-item-wrapper` }); | ||
const selectedItem = createElement('div', { classes: `${blockName}__selected-item` }); | ||
|
||
const list = createElement('ul', { classes: `${blockName}__items` }); | ||
|
||
[...itemsWrapper.children].forEach((item, index) => { | ||
const classes = [`${blockName}__item`]; | ||
if (index === 0) { // Default selected item | ||
classes.push(`${blockName}__item--active`); | ||
selectedItem.textContent = item.textContent; | ||
} | ||
const listItem = createElement('li', { classes }); | ||
|
||
listItem.innerHTML = item.innerHTML; | ||
list.appendChild(listItem); | ||
}); | ||
|
||
const dropdownArrowIcon = document.createRange().createContextualFragment(` | ||
<svg xmlns="http://www.w3.org/2000/svg"><use href="#icons-sprite-dropdown-caret"></use></svg>`); | ||
selectedItemWrapper.append(selectedItem); | ||
selectedItemWrapper.appendChild(...dropdownArrowIcon.children); | ||
|
||
dropdownWrapper.append(selectedItemWrapper); | ||
dropdownWrapper.append(list); | ||
wrapper.append(dropdownWrapper); | ||
|
||
itemsWrapper.remove(); | ||
|
||
if (redButton) { | ||
wrapper.appendChild(redButton); | ||
} | ||
|
||
list.addEventListener('click', gotoSection); | ||
|
||
// on load Go to section if defined | ||
const hash = window.location.hash.substring(1); | ||
if (hash) { | ||
updateActive(hash); | ||
|
||
setTimeout(() => { | ||
scrollToSection(hash); | ||
}, 1000); | ||
} | ||
|
||
// Listener to toggle the dropdown (open / close) | ||
document.addEventListener('click', (e) => { | ||
if (e.target.closest(`.${blockName}__selected-item-wrapper`)) { | ||
dropdownWrapper.classList.toggle(`${blockName}__dropdown--open`); | ||
} else { | ||
dropdownWrapper.classList.remove(`${blockName}__dropdown--open`); | ||
} | ||
}); | ||
|
||
// listen scroll to change the url | ||
listenScroll(); | ||
} |
Oops, something went wrong.