From 8e64ae83ac19735022c562d903167a280b945ec4 Mon Sep 17 00:00:00 2001 From: Marko V <139957716+markovukiceviccn@users.noreply.github.com> Date: Tue, 7 Nov 2023 12:13:17 +0100 Subject: [PATCH 001/250] #55 All trucks - Increase the page title top padding (#161) Increase the page title top padding for red marker to become visible below the header --- blocks/v2-all-trucks/v2-all-trucks.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/v2-all-trucks/v2-all-trucks.css b/blocks/v2-all-trucks/v2-all-trucks.css index 4579112bd..c80caf343 100644 --- a/blocks/v2-all-trucks/v2-all-trucks.css +++ b/blocks/v2-all-trucks/v2-all-trucks.css @@ -8,7 +8,7 @@ font-family: var(--ff-headline-medium); letter-spacing: -.02em; margin-bottom: 24px; - padding-top: 32px; + padding-top: 42px; } .redesign-v2 .section.v2-all-trucks-container .default-content-wrapper h1 + p { From 11325318e6c8e27f87ba9629e7be843d19d5fcce Mon Sep 17 00:00:00 2001 From: Syb Wartna Date: Tue, 7 Nov 2023 13:56:05 +0100 Subject: [PATCH 002/250] fixes for dealer locator on mobile #157 --- blocks/dealer-locator/dealer-locator.css | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/blocks/dealer-locator/dealer-locator.css b/blocks/dealer-locator/dealer-locator.css index 305258c31..7ebbca9ad 100644 --- a/blocks/dealer-locator/dealer-locator.css +++ b/blocks/dealer-locator/dealer-locator.css @@ -103,12 +103,6 @@ main .section.dealer-locator-container > div { justify-content: space-around; } -@media (max-width: 992px) { - .dealer-locator { - height:calc(100% - 6.1em)!important; - } -} - .dealer-locator .partsasist-form { z-index: 1050; display: none; @@ -728,7 +722,7 @@ main .section.dealer-locator-container > div { } .dealer-locator #map { - height: 50% + height: calc(50% - 39px); } } From 93ce3921ba6fff4b0787a1453e5426d05f917858 Mon Sep 17 00:00:00 2001 From: Jonatan Lledo <117984204+jonatan-lledo-netcentric@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:25:54 +0100 Subject: [PATCH 003/250] oneTrust run only in production --- scripts/delayed.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/delayed.js b/scripts/delayed.js index bf6bc4d8b..c1bc60d41 100644 --- a/scripts/delayed.js +++ b/scripts/delayed.js @@ -21,7 +21,7 @@ if (isPerformanceAllowed) { // Prevent the cookie banner from loading when running in library if (!window.location.pathname.includes('srcdoc') - && !['localhost', 'hlx.page'].includes(window.location.host)) { + && !['localhost', 'hlx.page'].some((url) => window.location.host.includes(url))) { loadScript('https://cdn.cookielaw.org/scripttemplates/otSDKStub.js', { type: 'text/javascript', charset: 'UTF-8', From 071f5070bfe7b9f77813694bde11c549c78cdde2 Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Wed, 8 Nov 2023 08:40:16 +0100 Subject: [PATCH 004/250] #38 Create modal content --- blocks/v2-images-grid/v2-images-grid.css | 73 +++++++++++++++-- blocks/v2-images-grid/v2-images-grid.js | 99 +++++++++++++++++++++++- 2 files changed, 164 insertions(+), 8 deletions(-) diff --git a/blocks/v2-images-grid/v2-images-grid.css b/blocks/v2-images-grid/v2-images-grid.css index c4e5ea3e1..9e3f6a985 100644 --- a/blocks/v2-images-grid/v2-images-grid.css +++ b/blocks/v2-images-grid/v2-images-grid.css @@ -8,7 +8,7 @@ display: grid; grid-template-columns: 50%; gap: var(--v2-images-grid-gutter); - grid-template-areas: + grid-template-areas: 'area1 area2' 'area1 area4' 'area3 area4'; @@ -27,10 +27,21 @@ height: 100%; } -.v2-images-grid__item:nth-child(1) { grid-area: area1; } -.v2-images-grid__item:nth-child(2) { grid-area: area2; } -.v2-images-grid__item:nth-child(3) { grid-area: area3; } -.v2-images-grid__item:nth-child(4) { grid-area: area4; } +.v2-images-grid__item:nth-child(1) { + grid-area: area1; +} + +.v2-images-grid__item:nth-child(2) { + grid-area: area2; +} + +.v2-images-grid__item:nth-child(3) { + grid-area: area3; +} + +.v2-images-grid__item:nth-child(4) { + grid-area: area4; +} .v2-images-grid__item img { object-fit: cover; @@ -48,11 +59,59 @@ .v2-images-grid__item:nth-child(3) img { aspect-ratio: 4/3; } - + .v2-images-grid__figcaption { display: none; } +.v2-images-grid__modal-content { + display: flex; + flex-direction: column; +} + +.v2-images-grid__carousel-items-list, +.v2-images-grid__carousel-preview-list { + display: flex; + padding: 0; + list-style: none; + margin: 0; + max-width: 100%; + scroll-behavior: smooth; + scroll-snap-type: x mandatory; + scrollbar-width: none; + overflow: scroll hidden; + flex-wrap: nowrap; +} + +.v2-images-grid__carousel-items-list::-webkit-scrollbar, +.v2-images-grid__carousel-preview-list::-webkit-scrollbar { + display: none; +} + +.v2-images-grid__carousel-preview-list { + gap: 10px; +} + +.v2-images-grid__carousel-item, +.v2-images-grid__carousel-preview-item { + scroll-snap-align: center; +} + +.v2-images-grid__carousel-item { + min-width: 100%; +} + +.v2-images-grid__carousel-preview-item { + min-width: 80px; +} + +.v2-images-grid__carousel-preview-list { + display: flex; + list-style: none; + margin: 0; + padding: 0; +} + @media screen and (min-width: 1200px) { :root { --v2-images-grid-gutter: 16px; @@ -72,4 +131,4 @@ font: 12px/16px var(--ff-body-bold); letter-spacing: 1.92px; } -} \ No newline at end of file +} diff --git a/blocks/v2-images-grid/v2-images-grid.js b/blocks/v2-images-grid/v2-images-grid.js index 25b0dd071..fdf506310 100644 --- a/blocks/v2-images-grid/v2-images-grid.js +++ b/blocks/v2-images-grid/v2-images-grid.js @@ -1,8 +1,103 @@ import { createOptimizedPicture } from '../../scripts/lib-franklin.js'; -import { createElement, removeEmptyTags, getTextLabel } from '../../scripts/common.js'; +import { + createElement, removeEmptyTags, getTextLabel, debounce, +} from '../../scripts/common.js'; import { getAllElWithChildren } from '../../scripts/scripts.js'; const blockClassName = 'v2-images-grid'; + +const createModalContent = (content) => { + const carouselItemsList = createElement('ul', { classes: `${blockClassName}__carousel-items-list` }); + const carouselImagesList = createElement('ul', { classes: `${blockClassName}__carousel-preview-list` }); + + const debouncedOnItemChange = debounce((index) => { + carouselImagesList.scrollTo({ + left: index * 90, + behavior: 'smooth', + }); + }, 100); + + [...content.querySelectorAll('.v2-images-grid__item')].forEach((el, index) => { + const image = el.querySelector('img'); + + // adding image to carousel preview + const carouselImage = createOptimizedPicture(image.src, image.alt, false, [{ width: '80' }]); + const carousePreviewlItem = createElement('li', { classes: `${blockClassName}__carousel-preview-item` }); + const buttonWithImage = createElement('button'); + + buttonWithImage.addEventListener('click', () => { + const itemWidth = carouselItemsList.getBoundingClientRect().width; + + carouselItemsList.scrollTo({ + left: index * itemWidth, + behavior: 'smooth', + }); + }); + + buttonWithImage.append(carouselImage); + carousePreviewlItem.append(buttonWithImage); + carouselImagesList.append(carousePreviewlItem); + + // creating item content + const itemImage = createOptimizedPicture(image.src, image.alt, false); + const [, caption, description] = el.querySelectorAll('p'); + const carouselItem = createElement('li', { classes: `${blockClassName}__carousel-item` }); + + carouselItem.append(itemImage, description || caption); + carouselItemsList.append(carouselItem); + + const options = { + root: carouselItemsList, + rootMargin: '0px', + threshold: 1.0, + }; + + const observer = new IntersectionObserver(() => { + debouncedOnItemChange(index); + }, options); + + observer.observe(carouselItem); + }); + + const wrapper = createElement('div', { classes: `${blockClassName}__modal-content` }); + + wrapper.innerHTML = ` +
+ + +
+ `; + + [...wrapper.querySelectorAll('button')].forEach((el, elIndex) => { + const modifiers = [-1, 1]; + + el.addEventListener('click', () => { + const itemWidth = carouselItemsList.getBoundingClientRect().width; + let index = Math.round(carouselItemsList.scrollLeft / itemWidth) + modifiers[elIndex]; + + if (index < 0) { + index = carouselItemsList.children.length - 1; + } + + if (index > carouselItemsList.children.length - 1) { + index = 0; + } + + carouselImagesList.scrollTo({ + left: index * 90, + behavior: 'smooth', + }); + + carouselItemsList.scrollTo({ + left: index * itemWidth, + behavior: 'smooth', + }); + }); + }); + wrapper.append(carouselItemsList, carouselImagesList); + document.querySelector('.v2-images-grid.block').append(wrapper); +}; + export default function decorate(block) { // all items are inside a ul list with classname called 'v2-images-grid__items' const ul = createElement('ul', { classes: `${blockClassName}__items` }); @@ -17,6 +112,8 @@ export default function decorate(block) { }); block.append(ul); + createModalContent(ul); + // give format to the first 4 list items [...ul.children].forEach((li, idx) => { if (idx < 4) { From c91063e6ef70e6c613f4d2c271cd7f38d113fbdc Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Wed, 8 Nov 2023 11:01:20 +0100 Subject: [PATCH 005/250] #509 Fix displaying header on dealer page --- blocks/header/header.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blocks/header/header.css b/blocks/header/header.css index 879f3a83d..bfc46641f 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -1,6 +1,7 @@ .header-wrapper { display: flex; justify-content: center; + z-index: 100; --menu-cards-gutter: 16px; --menu-animation: right 450ms ease-in-out; @@ -771,6 +772,7 @@ a.header__link { .header__accordion-container.header__main-link-wrapper { height: auto; + box-shadow: 0 0 3px 0 rgb(66 68 90 / 59%); } /* tabs styles - START */ From dd6468451dff5218c992002cac4a46c6fe81c6cf Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Wed, 8 Nov 2023 20:57:51 +0530 Subject: [PATCH 006/250] 39 wire up feature carousel --- blocks/v2-all-trucks/v2-all-trucks.js | 5 +- .../v2-feature-carousel.css | 154 ++++++++++++++++++ .../v2-feature-carousel.js | 150 +++++++++++++++++ blocks/v2-specifications/v2-specifications.js | 2 +- scripts/common.js | 7 + 5 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 blocks/v2-feature-carousel/v2-feature-carousel.css create mode 100644 blocks/v2-feature-carousel/v2-feature-carousel.js diff --git a/blocks/v2-all-trucks/v2-all-trucks.js b/blocks/v2-all-trucks/v2-all-trucks.js index a5de59652..3bd9684dc 100644 --- a/blocks/v2-all-trucks/v2-all-trucks.js +++ b/blocks/v2-all-trucks/v2-all-trucks.js @@ -1,4 +1,4 @@ -import { createElement, getTextLabel } from '../../scripts/common.js'; +import { createElement, getTextLabel, makeBlockFullWidth } from '../../scripts/common.js'; // Define break point/s const blockName = 'v2-all-trucks'; @@ -29,8 +29,7 @@ export default function decorate(block) { }); }); - const trucksWrapper = document.querySelector(`.${blockName}-wrapper`); - trucksWrapper.classList.add('full-width'); + makeBlockFullWidth(`.${blockName}-wrapper`); const trucks = document.querySelectorAll(`.${blockName}__truck`); const setSecondaryButton = document.querySelectorAll(`.${blockName}__truck .${blockName}__truck-info .button-container:last-of-type a`); diff --git a/blocks/v2-feature-carousel/v2-feature-carousel.css b/blocks/v2-feature-carousel/v2-feature-carousel.css new file mode 100644 index 000000000..3dd87f27e --- /dev/null +++ b/blocks/v2-feature-carousel/v2-feature-carousel.css @@ -0,0 +1,154 @@ +@keyframes slide-in { + 0% { + opacity:0; + } + + 100% { + opacity:1; + } +} + +.v2-feature-carousel { + --v2-feature-carousel-content-gap: 12px; + --v2-feature-carousel-size: 512px; +} + +.v2-feature-carousel-container { + background-color: var(--c-primary-black); +} + +.v2-feature-carousel__arrowcontrols, +.v2-feature-carousel__list { + list-style-type: none; + padding: 0; + margin: 0; + display: flex; +} + +.section.v2-feature-carousel-container .v2-feature-carousel-wrapper { + padding-left: 0; + padding-right: 0; +} + +.v2-feature-carousel__image-wrapper picture img { + aspect-ratio: 1/1; + display: block; +} + +.v2-feature-carousel__list-container { + padding: 40px 16px; + position: relative; + background-color: var(--c-primary-white); +} + +.v2-feature-carousel__arrowcontrols { + position: absolute; + right: 0; + top: 0; +} + +.v2-feature-carousel__button { + width: 60px; + height: 60px; + padding: 15px 10px; + margin: 0; + background-color: var(--c-primary-gold); +} + +.v2-feature-carousel__button:hover, +.v2-feature-carousel__button:focus { + background-color: var(--button-secondary-gold-hover); +} + +.v2-feature-carousel__button:active { + background-color: var(--button-secondary-gold-pressed); +} + +.v2-feature-carousel__list { + flex-direction: row; + overflow: scroll; + gap: 16px; + scroll-behavior: smooth; + scroll-snap-align: start; + scroll-snap-type: x mandatory; +} + +.v2-feature-carousel__list-item { + display: flex; + gap: var(--v2-feature-carousel-content-gap); + flex-direction: column; + flex: 0 0 100%; + scroll-snap-align: start; + justify-content: center; +} + +.v2-feature-carousel__list-item.active { + animation: slide-in ease-in-out 5s; +} + +.v2-feature-carousel__list::-webkit-scrollbar { + display: none; +} + +.v2-feature-carousel__title { + font: var(--headline-1-font-size)/var(--headline-1-line-height) var(--ff-headline-medium); + letter-spacing: var(--headline-1-letter-spacing); + margin: 0; +} + +.v2-feature-carousel__list-item p { + margin: 0; +} + +@media (min-width: 992px) { + .v2-feature-carousel { + --v2-feature-carousel-content-gap: 24px; + + position: relative; + } + + .v2-feature-carousel__image-wrapper { + width: 60%; + margin-left: auto; + } + + .v2-feature-carousel__list-container { + padding: 0; + width: var(--v2-feature-carousel-size); + height: var(--v2-feature-carousel-size); + background-color: var(--c-primary-white); + display: flex; + flex-direction: column; + position: absolute; + left: 17%; + z-index: 2; + top: 20%; + } + + .v2-feature-carousel__list { + overflow: hidden; + scroll-snap-type: none; + flex: 1; + align-items: center; + gap: 0; + } + + .v2-feature-carousel__list-item { + padding-left: 56px; + padding-right: 56px; + } + + /* .v2-feature-carousel__list-container--single { + height: 587px; + width: 587px; + } + + .v2-feature-carousel__list-container--single .v2-feature-carousel__list-item { + padding: 137.5px 99px 144.5px 103px; + } */ + + .v2-feature-carousel__arrowcontrols { + position: unset; + align-self: flex-end; + } +} \ No newline at end of file diff --git a/blocks/v2-feature-carousel/v2-feature-carousel.js b/blocks/v2-feature-carousel/v2-feature-carousel.js new file mode 100644 index 000000000..b2d1100a5 --- /dev/null +++ b/blocks/v2-feature-carousel/v2-feature-carousel.js @@ -0,0 +1,150 @@ +import { + createElement, + unwrapDivs, + adjustPretitle, + makeBlockFullWidth, +} from '../../scripts/common.js'; + +const blockName = 'v2-feature-carousel'; + +const updateActiveClass = (elements, targetElement) => { + elements.forEach((el, index) => { + if (el === targetElement) { + el.classList.add('active'); + let arrowControl = document.querySelector(`.${blockName}__button:disabled`); + + if (arrowControl) { + arrowControl.disabled = false; + arrowControl = null; + } + // disable arrow buttons + if (index === 0) { + arrowControl = document.querySelector(`.${blockName}__button-prev`); + } else if (index === el.parentNode.children.length - 1) { + arrowControl = document.querySelector(`.${blockName}__button-next`); + } + if (arrowControl) { + arrowControl.disabled = true; + } + } else { + el.classList.remove('active'); + } + }); +}; + +const listenScroll = (carousel) => { + const elements = carousel.querySelectorAll(`.${blockName}__list-item`); + const io = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + updateActiveClass(elements, entry.target); + } + }); + }, { + root: carousel, + threshold: 0.75, + }); + + elements.forEach((el) => { + io.observe(el); + }); +}; + +const setCarouselPosition = (carousel, index) => { + const firstEl = carousel.firstElementChild; + const scrollOffset = firstEl.getBoundingClientRect().width; + const style = window.getComputedStyle(firstEl); + const marginleft = parseFloat(style.marginLeft); + + carousel.scrollTo({ + left: index * scrollOffset + marginleft, + behavior: 'smooth', + }); +}; + +const navigate = (carousel, direction) => { + if (carousel.classList.contains('is-animating')) return; + + const activeItem = carousel.querySelector(`.${blockName}__list-item.active`); + let index = [...activeItem.parentNode.children].indexOf(activeItem); + + if (direction === 'left') { + index -= 1; + if (index === -1) { // Go to the last item if at the start + index = carousel.childElementCount - 1; + } + } else { + index += 1; + if (index > carousel.childElementCount - 1) { + index = 0; // Go to the first item if at the end + } + } + + setCarouselPosition(carousel, index); +}; + +const createArrowControls = (carousel) => { + const arrowControls = createElement('ul', { classes: [`${blockName}__arrowcontrols`] }); + const arrows = document.createRange().createContextualFragment(` +
  • + +
  • +
  • + +
  • + `); + arrowControls.append(...arrows.children); + carousel.insertAdjacentElement('beforebegin', arrowControls); + + const [prevButton, nextButton] = arrowControls.querySelectorAll(':scope button'); + prevButton.addEventListener('click', () => navigate(carousel, 'left')); + nextButton.addEventListener('click', () => navigate(carousel, 'right')); +}; + +export default async function decorate(block) { + const imageRow = block.firstElementChild; + imageRow.classList.add(`${blockName}__image-wrapper`); + unwrapDivs(imageRow); + + const carouselList = createElement('ul', { classes: `${blockName}__list` }); + + // select 2nd div of the block to attach the carousel list + const carouselContainer = imageRow.nextElementSibling; + carouselContainer.classList.add(`${blockName}__list-container`); + + // select all div except first , as it contains image + const textElements = block.querySelectorAll('div:not(:first-child)'); + textElements.forEach((textCol) => { + // creating li element for carousel + const li = createElement('li', { classes: `${blockName}__list-item` }); + li.innerHTML = textCol.innerHTML; + + const headings = li.querySelectorAll('h1, h2, h3, h4'); + [...headings].forEach((heading) => heading.classList.add(`${blockName}__title`)); + + adjustPretitle(li); + carouselList.append(li); + textCol.innerHTML = ''; + }); + + carouselContainer.append(carouselList); + makeBlockFullWidth(blockName); + + if (textElements.length > 1) { + createArrowControls(carouselList); + + listenScroll(carouselList); + } else { + carouselContainer.classList.add(`${blockName}__list-container--single`); + } + + unwrapDivs(block); +} diff --git a/blocks/v2-specifications/v2-specifications.js b/blocks/v2-specifications/v2-specifications.js index cba04a76b..72ac45fe0 100644 --- a/blocks/v2-specifications/v2-specifications.js +++ b/blocks/v2-specifications/v2-specifications.js @@ -32,7 +32,7 @@ export default async function decorate(block) { // Hx tag used for the titles of the accordion const titleMeta = block.closest('.section').dataset.header || 3; - const headerTag = titleMeta.charAt(titleMeta.length - 1); + const headerTag = titleMeta?.charAt(titleMeta.length - 1); const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].slice(headerTag); [...items].forEach((item) => { diff --git a/scripts/common.js b/scripts/common.js index f515f5c73..aaad066ce 100644 --- a/scripts/common.js +++ b/scripts/common.js @@ -235,3 +235,10 @@ export const adjustPretitle = (element) => { } }); }; + +export const makeBlockFullWidth = (blockName) => { + const fullWidthWrappers = document.querySelectorAll(`.${blockName}-wrapper`); + fullWidthWrappers.forEach((wrapper) => { + wrapper.classList.add('full-width'); + }); +}; From 9e9b71ad14e35f4f31179b0bf0525e7328165517 Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Thu, 9 Nov 2023 14:46:29 +0530 Subject: [PATCH 007/250] crate carousel helper --- .../v2-feature-carousel.js | 104 ++++-------------- .../v2-tabbed-carousel/v2-tabbed-carousel.js | 88 +++++---------- blocks/v2-truck-lineup/v2-truck-lineup.js | 101 ++++------------- scripts/carousel-helper.js | 57 ++++++++++ 4 files changed, 133 insertions(+), 217 deletions(-) create mode 100644 scripts/carousel-helper.js diff --git a/blocks/v2-feature-carousel/v2-feature-carousel.js b/blocks/v2-feature-carousel/v2-feature-carousel.js index b2d1100a5..33ca62e97 100644 --- a/blocks/v2-feature-carousel/v2-feature-carousel.js +++ b/blocks/v2-feature-carousel/v2-feature-carousel.js @@ -4,12 +4,16 @@ import { adjustPretitle, makeBlockFullWidth, } from '../../scripts/common.js'; +import { + listenScroll, + createArrowControls, +} from '../../scripts/carousel-helper.js'; const blockName = 'v2-feature-carousel'; -const updateActiveClass = (elements, targetElement) => { +const updateActiveClass = (elements, entry) => { elements.forEach((el, index) => { - if (el === targetElement) { + if (el === entry.target) { el.classList.add('active'); let arrowControl = document.querySelector(`.${blockName}__button:disabled`); @@ -32,82 +36,20 @@ const updateActiveClass = (elements, targetElement) => { }); }; -const listenScroll = (carousel) => { - const elements = carousel.querySelectorAll(`.${blockName}__list-item`); - const io = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - updateActiveClass(elements, entry.target); - } - }); - }, { - root: carousel, - threshold: 0.75, - }); - - elements.forEach((el) => { - io.observe(el); - }); -}; - -const setCarouselPosition = (carousel, index) => { - const firstEl = carousel.firstElementChild; - const scrollOffset = firstEl.getBoundingClientRect().width; - const style = window.getComputedStyle(firstEl); - const marginleft = parseFloat(style.marginLeft); - - carousel.scrollTo({ - left: index * scrollOffset + marginleft, - behavior: 'smooth', - }); -}; - -const navigate = (carousel, direction) => { - if (carousel.classList.contains('is-animating')) return; - - const activeItem = carousel.querySelector(`.${blockName}__list-item.active`); - let index = [...activeItem.parentNode.children].indexOf(activeItem); - - if (direction === 'left') { - index -= 1; - if (index === -1) { // Go to the last item if at the start - index = carousel.childElementCount - 1; - } - } else { - index += 1; - if (index > carousel.childElementCount - 1) { - index = 0; // Go to the first item if at the end - } - } - - setCarouselPosition(carousel, index); -}; - -const createArrowControls = (carousel) => { - const arrowControls = createElement('ul', { classes: [`${blockName}__arrowcontrols`] }); - const arrows = document.createRange().createContextualFragment(` -
  • - -
  • -
  • - -
  • - `); - arrowControls.append(...arrows.children); - carousel.insertAdjacentElement('beforebegin', arrowControls); - - const [prevButton, nextButton] = arrowControls.querySelectorAll(':scope button'); - prevButton.addEventListener('click', () => navigate(carousel, 'left')); - nextButton.addEventListener('click', () => navigate(carousel, 'right')); -}; +const arrowFragment = document.createRange().createContextualFragment(`
  • + +
  • +
  • + +
  • `); export default async function decorate(block) { const imageRow = block.firstElementChild; @@ -139,9 +81,9 @@ export default async function decorate(block) { makeBlockFullWidth(blockName); if (textElements.length > 1) { - createArrowControls(carouselList); - - listenScroll(carouselList); + createArrowControls(carouselList, `.${blockName}__list-item.active`, [`${blockName}__arrowcontrols`], arrowFragment); + const elements = carouselList.querySelectorAll(`.${blockName}__list-item`); + listenScroll(carouselList, elements, 0.75, updateActiveClass); } else { carouselContainer.classList.add(`${blockName}__list-container--single`); } diff --git a/blocks/v2-tabbed-carousel/v2-tabbed-carousel.js b/blocks/v2-tabbed-carousel/v2-tabbed-carousel.js index 839f11efa..bd1d85286 100644 --- a/blocks/v2-tabbed-carousel/v2-tabbed-carousel.js +++ b/blocks/v2-tabbed-carousel/v2-tabbed-carousel.js @@ -1,4 +1,5 @@ import { createElement, removeEmptyTags } from '../../scripts/common.js'; +import { setCarouselPosition, listenScroll } from '../../scripts/carousel-helper.js'; const blockName = 'v2-tabbed-carousel'; @@ -42,65 +43,33 @@ function buildTabNavigation(tabItems, clickHandler) { return tabNavigation; } -const updateActiveItem = (index) => { - const carouselItems = document.querySelector(`.${blockName}__items`); - const navigation = document.querySelector(`.${blockName}__navigation`); - const navigationLine = document.querySelector(`.${blockName}__navigation-line`); - - [carouselItems, navigation].forEach((c) => c.querySelectorAll('.active').forEach((i) => i.classList.remove('active'))); - carouselItems.children[index].classList.add('active'); - navigation.children[index].classList.add('active'); - - const activeNavigationItem = navigation.children[index]; - moveNavigationLine(navigationLine, activeNavigationItem, navigation); - - // Center navigation item - const navigationActiveItem = navigation.querySelector('.active'); - - if (navigation && navigationActiveItem) { - const { clientWidth: itemWidth, offsetLeft } = navigationActiveItem; - // Calculate the scroll position to center the active item - const scrollPosition = offsetLeft - (navigation.clientWidth - itemWidth) / 2; - navigation.scrollTo({ - left: scrollPosition, - behavior: 'smooth', - }); - } -}; - -const listenScroll = (carousel) => { - const elements = carousel.querySelectorAll(':scope > *'); - - const io = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if ( - entry.isIntersecting - && entry.intersectionRatio >= 0.75 - ) { - const activeItem = entry.target; - const currentIndex = [...activeItem.parentNode.children].indexOf(activeItem); - updateActiveItem(currentIndex); +const updateActiveItem = (elements, entry) => { + elements.forEach((el, index) => { + if (el === entry.target && entry.intersectionRatio >= 0.75) { + const carouselItems = document.querySelector(`.${blockName}__items`); + const navigation = document.querySelector(`.${blockName}__navigation`); + const navigationLine = document.querySelector(`.${blockName}__navigation-line`); + + [carouselItems, navigation].forEach((c) => c.querySelectorAll('.active').forEach((i) => i.classList.remove('active'))); + carouselItems.children[index].classList.add('active'); + navigation.children[index].classList.add('active'); + + const activeNavigationItem = navigation.children[index]; + moveNavigationLine(navigationLine, activeNavigationItem, navigation); + + // Center navigation item + const navigationActiveItem = navigation.querySelector('.active'); + + if (navigation && navigationActiveItem) { + const { clientWidth: itemWidth, offsetLeft } = navigationActiveItem; + // Calculate the scroll position to center the active item + const scrollPosition = offsetLeft - (navigation.clientWidth - itemWidth) / 2; + navigation.scrollTo({ + left: scrollPosition, + behavior: 'smooth', + }); } - }); - }, { - root: carousel, - threshold: 0.75, - }); - - elements.forEach((el) => { - io.observe(el); - }); -}; - -const setCarouselPosition = (carousel, index) => { - const firstEl = carousel.firstElementChild; - const scrollOffset = firstEl.getBoundingClientRect().width; - const style = window.getComputedStyle(firstEl); - const marginleft = parseFloat(style.marginLeft); - - carousel.scrollTo({ - left: index * scrollOffset + marginleft, - behavior: 'smooth', + } }); }; @@ -143,5 +112,6 @@ export default function decorate(block) { container.append(tabNavigation); // update the button indicator on scroll - listenScroll(carouselItems); + const elements = carouselItems.querySelectorAll(':scope > *'); + listenScroll(carouselItems, elements, updateActiveItem, 0.75); } diff --git a/blocks/v2-truck-lineup/v2-truck-lineup.js b/blocks/v2-truck-lineup/v2-truck-lineup.js index 94f552f09..996901450 100644 --- a/blocks/v2-truck-lineup/v2-truck-lineup.js +++ b/blocks/v2-truck-lineup/v2-truck-lineup.js @@ -1,5 +1,6 @@ import { decorateIcons } from '../../scripts/lib-franklin.js'; import { createElement } from '../../scripts/common.js'; +import { listenScroll, createArrowControls, setCarouselPosition } from '../../scripts/carousel-helper.js'; const blockName = 'v2-truck-lineup'; @@ -115,84 +116,29 @@ const updateActiveItem = (index) => { }); }; -const listenScroll = (carousel) => { - const elements = carousel.querySelectorAll(':scope > *'); - - const io = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if ( - entry.isIntersecting - && entry.intersectionRatio >= 0.9 - ) { - const activeItem = entry.target; - const currentIndex = [...activeItem.parentNode.children].indexOf(activeItem); - updateActiveItem(currentIndex); - } - }); - }, { - root: carousel, - threshold: 0.9, - }); - - elements.forEach((el) => { - io.observe(el); - }); -}; - -const setCarouselPosition = (carousel, index) => { - const firstEl = carousel.firstElementChild; - const scrollOffset = firstEl.getBoundingClientRect().width; - const style = window.getComputedStyle(firstEl); - const marginleft = parseFloat(style.marginLeft); - - carousel.scrollTo({ - left: index * scrollOffset + marginleft, - behavior: 'smooth', +const scrollObserverFunction = (elements, entry) => { + elements.forEach((el, index) => { + if (el === entry.target && entry.intersectionRatio >= 0.9) { + updateActiveItem(index); + } }); }; -const createArrowControls = (carousel) => { - function scroll(direction) { - const activeItem = carousel.querySelector(`.${blockName}__image-item.active`); - let index = [...activeItem.parentNode.children].indexOf(activeItem); - if (direction === 'left') { - index -= 1; - if (index === -1) { - index = carousel.childElementCount; - } - } else { - index += 1; - if (index > carousel.childElementCount - 1) { - index = 0; - } - } - - setCarouselPosition(carousel, index); - } - - const arrowControls = createElement('ul', { classes: [`${blockName}__arrow-controls`] }); - const arrows = document.createRange().createContextualFragment(` -
  • - -
  • -
  • - -
  • - `); - arrowControls.append(...arrows.children); - carousel.insertAdjacentElement('beforebegin', arrowControls); - const [prevButton, nextButton] = arrowControls.querySelectorAll(':scope button'); - prevButton.addEventListener('click', () => scroll('left')); - nextButton.addEventListener('click', () => scroll('right')); -}; +const arrowFragments = document.createRange().createContextualFragment(` +
  • + +
  • +
  • + +
  • `); export default async function decorate(block) { const descriptionContainer = block.querySelector(':scope > div'); @@ -211,7 +157,7 @@ export default async function decorate(block) { await decorateIcons(tabNavigation); // Arrows - createArrowControls(imagesContainer); + createArrowControls(imagesContainer, `.${blockName}__image-item.active`, [`${blockName}__arrow-controls`], arrowFragments); descriptionContainer.parentNode.append(tabNavigation); @@ -265,7 +211,8 @@ export default async function decorate(block) { }); // update the button indicator on scroll - listenScroll(imagesContainer); + const elements = imagesContainer.querySelectorAll(':scope > *'); + listenScroll(imagesContainer, elements, scrollObserverFunction, 0.9); // Update text position + navigation line when page is resized window.addEventListener('resize', () => { diff --git a/scripts/carousel-helper.js b/scripts/carousel-helper.js new file mode 100644 index 000000000..4c014ceb1 --- /dev/null +++ b/scripts/carousel-helper.js @@ -0,0 +1,57 @@ +import { createElement } from './common.js'; + +export const listenScroll = (carousel, elements, updateFn, threshold = 1) => { + const io = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + updateFn(elements, entry); + } + }); + }, { + root: carousel, + threshold, + }); + + elements.forEach((el) => { + io.observe(el); + }); +}; + +export const setCarouselPosition = (carousel, index) => { + const firstEl = carousel.firstElementChild; + const scrollOffset = firstEl.getBoundingClientRect().width; + const style = window.getComputedStyle(firstEl); + const marginleft = parseFloat(style.marginLeft); + + carousel.scrollTo({ + left: index * scrollOffset + marginleft, + behavior: 'smooth', + }); +}; + +export const createArrowControls = (carousel, scrollSelector, controlClasses, arrowFragment) => { + function navigate(direction) { + const activeItem = carousel.querySelector(scrollSelector); + let index = [...activeItem.parentNode.children].indexOf(activeItem); + if (direction === 'left') { + index -= 1; + if (index === -1) { // Go to the last item if at the start + index = carousel.childElementCount; + } + } else { + index += 1; + if (index > carousel.childElementCount - 1) { + index = 0; // Go to the first item if at the end + } + } + + setCarouselPosition(carousel, index); + } + + const arrowControls = createElement('ul', { classes: controlClasses }); + arrowControls.append(...arrowFragment.children); + carousel.insertAdjacentElement('beforebegin', arrowControls); + const [prevButton, nextButton] = arrowControls.querySelectorAll(':scope button'); + prevButton.addEventListener('click', () => navigate('left')); + nextButton.addEventListener('click', () => navigate('right')); +}; From 25bc37697379eb08860c0758da33c8a6db2fbf0f Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 9 Nov 2023 13:37:56 +0100 Subject: [PATCH 008/250] #43 Show modal on Open gallery button click --- blocks/v2-images-grid/v2-images-grid.css | 110 ++++++++++++++++++++++- blocks/v2-images-grid/v2-images-grid.js | 31 +++++-- common/modal/modal-component.css | 39 +++++--- 3 files changed, 160 insertions(+), 20 deletions(-) diff --git a/blocks/v2-images-grid/v2-images-grid.css b/blocks/v2-images-grid/v2-images-grid.css index 9e3f6a985..c1f22bdc6 100644 --- a/blocks/v2-images-grid/v2-images-grid.css +++ b/blocks/v2-images-grid/v2-images-grid.css @@ -67,6 +67,9 @@ .v2-images-grid__modal-content { display: flex; flex-direction: column; + position: relative; + height: 100%; + justify-content: flex-end; } .v2-images-grid__carousel-items-list, @@ -90,6 +93,12 @@ .v2-images-grid__carousel-preview-list { gap: 10px; + display: flex; + list-style: none; + margin: 0; + padding: 0; + padding: 20px; + background: var(--c-primary-white); } .v2-images-grid__carousel-item, @@ -99,17 +108,94 @@ .v2-images-grid__carousel-item { min-width: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 90px; +} + +.v2-images-grid__carousel-item p { + margin: 0; + padding: 12px 16px; + background: var(--c-primary-white); + line-height: var(--body-2-line-height); + font-size: var(--body-2-font-size); +} + +.v2-images-grid__carousel-item picture { + display: flex; +} + +.v2-images-grid__modal-content .v2-images-grid__carousel-item img { + aspect-ratio: 16/9; + width: 100%; } .v2-images-grid__carousel-preview-item { min-width: 80px; } -.v2-images-grid__carousel-preview-list { - display: flex; - list-style: none; +.v2-images-grid__modal-arrow { margin: 0; padding: 0; + background: var(--c-primary-white); + display: flex; + border: 0; + height: 60px; + width: 60px; + justify-content: center; + align-items: center; +} + +.v2-images-grid__modal-arrow:hover, +.v2-images-grid__modal-arrow:active, +.v2-images-grid__modal-arrow:focus { + background: var(--c-primary-white); +} + +.v2-images-grid__carousel-items-wrapper { + position: relative; +} + +.v2-images-grid__modal-arrows-wrapper { + display: none; + z-index: 1; + position: absolute; + top: 50%; + width: 100%; +} + +.v2-images-grid__modal-arrows-wrapper + .v2-images-grid__modal-arrow:first-of-type { + top: 50%; + position: absolute; +} + +.v2-images-grid__modal-arrows-wrapper + .v2-images-grid__modal-arrow:last-of-type { + top: 50%; + right: 0; + position: absolute; +} + +.v2-images-grid__carousel-preview-item button { + padding: 0; + margin: 0; + display: flex; + border: 0; + background: transparent; +} + +.v2-images-grid__carousel-preview-item picture { + display: flex; +} + +/* stylelint-disable-next-line no-descending-specificity */ +.v2-images-grid__carousel-preview-item img { + width: 80px; + height: 60px; + object-fit: cover; + object-position: center; } @media screen and (min-width: 1200px) { @@ -132,3 +218,21 @@ letter-spacing: 1.92px; } } + +@media screen and (min-width: 768px) { + .v2-images-grid__carousel-item { + gap: 0; + position: relative; + } + + .v2-images-grid__carousel-item p { + position: absolute; + bottom: 0; + max-width: 710px; + margin: 20px; + } + + .v2-images-grid__modal-arrows-wrapper { + display: flex; + } +} diff --git a/blocks/v2-images-grid/v2-images-grid.js b/blocks/v2-images-grid/v2-images-grid.js index fdf506310..3bd4fbb58 100644 --- a/blocks/v2-images-grid/v2-images-grid.js +++ b/blocks/v2-images-grid/v2-images-grid.js @@ -3,6 +3,7 @@ import { createElement, removeEmptyTags, getTextLabel, debounce, } from '../../scripts/common.js'; import { getAllElWithChildren } from '../../scripts/scripts.js'; +import { showModal } from '../../common/modal/modal-component.js'; const blockClassName = 'v2-images-grid'; @@ -59,14 +60,24 @@ const createModalContent = (content) => { observer.observe(carouselItem); }); + const itemsWrapper = createElement('div', { classes: `${blockClassName}__carousel-items-wrapper` }); const wrapper = createElement('div', { classes: `${blockClassName}__modal-content` }); - wrapper.innerHTML = ` -
    - - + itemsWrapper.innerHTML = ` +
    + +
    `; + itemsWrapper.append(carouselItemsList); [...wrapper.querySelectorAll('button')].forEach((el, elIndex) => { const modifiers = [-1, 1]; @@ -94,8 +105,10 @@ const createModalContent = (content) => { }); }); }); - wrapper.append(carouselItemsList, carouselImagesList); - document.querySelector('.v2-images-grid.block').append(wrapper); + + wrapper.append(itemsWrapper, carouselImagesList); + + return wrapper; }; export default function decorate(block) { @@ -112,7 +125,7 @@ export default function decorate(block) { }); block.append(ul); - createModalContent(ul); + const modalContent = createModalContent(ul); // give format to the first 4 list items [...ul.children].forEach((li, idx) => { @@ -145,6 +158,10 @@ export default function decorate(block) { classes: ['button', 'button--large', 'button--primary'], }); button.textContent = getTextLabel('Open Gallery'); + button.addEventListener('click', () => { + showModal(modalContent); + }); + block.append(button); // remove empty tags diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index fb6bfb0df..b34505309 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -54,6 +54,7 @@ position: absolute; opacity: 1; cursor: auto; + height: 100%; } .modal-content .modal-video { @@ -73,23 +74,37 @@ } @media (min-width: 768px) { - .modal-content { + :root:not(.redesign-v2) .modal-content { width: 726px; } } @media (min-width: 992px) { - .modal-content { + :root:not(.redesign-v2) .modal-content { width: 930px; } } @media (min-width: 1300px) { - .modal-content { + :root:not(.redesign-v2) .modal-content { width: 1170px; } } +@media (min-width: 768px) { + .redesign-v2 .modal-content { + max-width: 1040px; + height: auto; + position: relative; + aspect-ratio: unset; + } + + .redesign-v2 .modal-background button.modal-close-button { + position: absolute; + right: 0; + } +} + /* adjustments for soundcloud variant of modal, e.g. https://www.volvotrucks.us/trucks/powertrain/i-torque/ */ .modal-content .modal-soundcloud { display: flex; @@ -154,12 +169,16 @@ opacity: 0.6; } -.modal-content.modal-form .eloqua-form input[type="submit"] { +.modal-content.modal-form .eloqua-form input[type='submit'] { opacity: 1; } -.modal-content.modal-form .eloqua-form .elq-form input[type="checkbox"], -.modal-content.modal-form .eloqua-form .single-checkbox-row input ~ label.checkbox-aligned.elq-item-label::before { +.modal-content.modal-form .eloqua-form .elq-form input[type='checkbox'], +.modal-content.modal-form + .eloqua-form + .single-checkbox-row + input + ~ label.checkbox-aligned.elq-item-label::before { background-color: transparent; } @@ -168,7 +187,7 @@ color: yellow; } -.modal-content.modal-form.modal-red .eloqua-form input[type="submit"] { +.modal-content.modal-form.modal-red .eloqua-form input[type='submit'] { background: var(--c-primary-black); color: var(--c-primary-white); } @@ -176,17 +195,17 @@ .modal-content.modal-red { min-height: 700px; background-color: #7d191b; - background-image: url("../../media/subscribe-to-bulldog-background.webp"); + background-image: url('../../media/subscribe-to-bulldog-background.webp'); } -.modal-content.modal-form.modal-black .eloqua-form input[type="submit"] { +.modal-content.modal-form.modal-black .eloqua-form input[type='submit'] { background: #af9866; } .modal-content.modal-black { min-height: 700px; background-color: #090909; - background-image: url("../../media/share-your-thoughts-background.webp"); + background-image: url('../../media/share-your-thoughts-background.webp'); } @media (min-width: 1200px) { From 3fa9c127db4ac061ae9592d7e909515a71e4fa69 Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 9 Nov 2023 14:14:16 +0100 Subject: [PATCH 009/250] #38 Add click action for images grid tiles --- blocks/v2-images-grid/v2-images-grid.css | 6 ++++++ blocks/v2-images-grid/v2-images-grid.js | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/blocks/v2-images-grid/v2-images-grid.css b/blocks/v2-images-grid/v2-images-grid.css index c1f22bdc6..8412ad7c2 100644 --- a/blocks/v2-images-grid/v2-images-grid.css +++ b/blocks/v2-images-grid/v2-images-grid.css @@ -18,9 +18,15 @@ .v2-images-grid__item { margin: 0; + transition: scale 300ms; width: auto; } +.v2-images-grid__item:hover { + scale: 1.05; + cursor: pointer; +} + .v2-images-grid__picture { display: block; position: relative; diff --git a/blocks/v2-images-grid/v2-images-grid.js b/blocks/v2-images-grid/v2-images-grid.js index 3bd4fbb58..037086ca4 100644 --- a/blocks/v2-images-grid/v2-images-grid.js +++ b/blocks/v2-images-grid/v2-images-grid.js @@ -7,6 +7,20 @@ import { showModal } from '../../common/modal/modal-component.js'; const blockClassName = 'v2-images-grid'; +const setActiveSlide = (activeSlideIndex, carouselItemsList, carouselImagesList) => { + const itemWidth = carouselItemsList.getBoundingClientRect().width; + + carouselImagesList.scrollTo({ + left: activeSlideIndex * 90, + behavior: 'smooth', + }); + + carouselItemsList.scrollTo({ + left: activeSlideIndex * itemWidth, + behavior: 'smooth', + }); +}; + const createModalContent = (content) => { const carouselItemsList = createElement('ul', { classes: `${blockClassName}__carousel-items-list` }); const carouselImagesList = createElement('ul', { classes: `${blockClassName}__carousel-preview-list` }); @@ -149,6 +163,15 @@ export default function decorate(block) { li.innerHTML = ''; li.append(picture); + + li.addEventListener('click', async () => { + const carouselItemsList = modalContent.querySelector(`.${blockClassName}__carousel-items-list`); + const carouselImagesList = modalContent.querySelector(`.${blockClassName}__carousel-preview-list`); + + await showModal(modalContent); + setActiveSlide(idx, carouselItemsList, carouselImagesList); + }); + return; } li.remove(); From 9c177ccbab5c620f74ab2da73c2be89a88a1bd9a Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 9 Nov 2023 14:23:46 +0100 Subject: [PATCH 010/250] 38 Fix arrow buttons --- blocks/v2-images-grid/v2-images-grid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/v2-images-grid/v2-images-grid.js b/blocks/v2-images-grid/v2-images-grid.js index 037086ca4..d9272f34a 100644 --- a/blocks/v2-images-grid/v2-images-grid.js +++ b/blocks/v2-images-grid/v2-images-grid.js @@ -93,7 +93,7 @@ const createModalContent = (content) => { `; itemsWrapper.append(carouselItemsList); - [...wrapper.querySelectorAll('button')].forEach((el, elIndex) => { + [...itemsWrapper.querySelectorAll('button')].forEach((el, elIndex) => { const modifiers = [-1, 1]; el.addEventListener('click', () => { From 166ff7507117310c80da606955e2bdd03f7da457 Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 9 Nov 2023 15:34:19 +0100 Subject: [PATCH 011/250] #38 Fix closing the modal when click the background --- blocks/v2-images-grid/v2-images-grid.css | 2 +- blocks/v2-images-grid/v2-images-grid.js | 4 ++-- common/modal/modal-component.css | 6 ++++++ common/modal/modal-component.js | 12 +++++++----- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/blocks/v2-images-grid/v2-images-grid.css b/blocks/v2-images-grid/v2-images-grid.css index 8412ad7c2..6e54232a1 100644 --- a/blocks/v2-images-grid/v2-images-grid.css +++ b/blocks/v2-images-grid/v2-images-grid.css @@ -74,7 +74,7 @@ display: flex; flex-direction: column; position: relative; - height: 100%; + height: auto; justify-content: flex-end; } diff --git a/blocks/v2-images-grid/v2-images-grid.js b/blocks/v2-images-grid/v2-images-grid.js index d9272f34a..6a6e8554c 100644 --- a/blocks/v2-images-grid/v2-images-grid.js +++ b/blocks/v2-images-grid/v2-images-grid.js @@ -168,7 +168,7 @@ export default function decorate(block) { const carouselItemsList = modalContent.querySelector(`.${blockClassName}__carousel-items-list`); const carouselImagesList = modalContent.querySelector(`.${blockClassName}__carousel-preview-list`); - await showModal(modalContent); + await showModal(modalContent, { classes: ['modal-content--bottom'] }); setActiveSlide(idx, carouselItemsList, carouselImagesList); }); @@ -182,7 +182,7 @@ export default function decorate(block) { }); button.textContent = getTextLabel('Open Gallery'); button.addEventListener('click', () => { - showModal(modalContent); + showModal(modalContent, { classes: ['modal-content--bottom'] }); }); block.append(button); diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index b34505309..0e6c8e96e 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -73,6 +73,12 @@ cursor: default; } +.modal-content--bottom { + display: flex; + justify-content: flex-end; + flex-direction: column; +} + @media (min-width: 768px) { :root:not(.redesign-v2) .modal-content { width: 726px; diff --git a/common/modal/modal-component.js b/common/modal/modal-component.js index 521c43a13..b3ab58326 100644 --- a/common/modal/modal-component.js +++ b/common/modal/modal-component.js @@ -30,11 +30,6 @@ const createModal = () => { modalBackground.style = 'height: 0; opacity: 0;'; document.body.appendChild(modalBackground); - // don't close modal when clicking on modal content - modalContent.addEventListener('click', (event) => { - event.stopPropagation(); - }); - // adding close modal button const closeButton = createElement('button', { classes: ['modal-close-button'] }); const closeIcon = createElement('span', { classes: ['icon', 'icon-close'] }); @@ -108,6 +103,13 @@ const createModal = () => { // disable page scrolling document.body.classList.add('disable-scroll'); + + // don't close modal when clicking on modal content + [...modalContent.querySelectorAll(':scope > *')].forEach((el) => { + el.addEventListener('click', (event) => { + event.stopPropagation(); + }); + }); } function hideModal() { From 71e67da590af295a647ce0bb33003f4b9fdf591d Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 9 Nov 2023 16:05:30 +0100 Subject: [PATCH 012/250] #38 Refactor --- blocks/v2-images-grid/v2-images-grid.js | 39 +++++++++---------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/blocks/v2-images-grid/v2-images-grid.js b/blocks/v2-images-grid/v2-images-grid.js index 6a6e8554c..d67c56f56 100644 --- a/blocks/v2-images-grid/v2-images-grid.js +++ b/blocks/v2-images-grid/v2-images-grid.js @@ -7,18 +7,18 @@ import { showModal } from '../../common/modal/modal-component.js'; const blockClassName = 'v2-images-grid'; -const setActiveSlide = (activeSlideIndex, carouselItemsList, carouselImagesList) => { - const itemWidth = carouselItemsList.getBoundingClientRect().width; - - carouselImagesList.scrollTo({ - left: activeSlideIndex * 90, +const scrollLeft = (el, leftPadding) => { + el.scrollTo({ + left: leftPadding, behavior: 'smooth', }); +}; - carouselItemsList.scrollTo({ - left: activeSlideIndex * itemWidth, - behavior: 'smooth', - }); +const setActiveSlide = (activeSlideIndex, carouselItemsList, carouselImagesList) => { + const itemWidth = carouselItemsList.getBoundingClientRect().width; + + scrollLeft(carouselImagesList, activeSlideIndex * 90); + scrollLeft(carouselItemsList, activeSlideIndex * itemWidth); }; const createModalContent = (content) => { @@ -26,10 +26,7 @@ const createModalContent = (content) => { const carouselImagesList = createElement('ul', { classes: `${blockClassName}__carousel-preview-list` }); const debouncedOnItemChange = debounce((index) => { - carouselImagesList.scrollTo({ - left: index * 90, - behavior: 'smooth', - }); + scrollLeft(carouselImagesList, index * 90); }, 100); [...content.querySelectorAll('.v2-images-grid__item')].forEach((el, index) => { @@ -43,10 +40,7 @@ const createModalContent = (content) => { buttonWithImage.addEventListener('click', () => { const itemWidth = carouselItemsList.getBoundingClientRect().width; - carouselItemsList.scrollTo({ - left: index * itemWidth, - behavior: 'smooth', - }); + scrollLeft(carouselItemsList, index * itemWidth); }); buttonWithImage.append(carouselImage); @@ -108,15 +102,8 @@ const createModalContent = (content) => { index = 0; } - carouselImagesList.scrollTo({ - left: index * 90, - behavior: 'smooth', - }); - - carouselItemsList.scrollTo({ - left: index * itemWidth, - behavior: 'smooth', - }); + scrollLeft(carouselImagesList, index * 90); + scrollLeft(carouselItemsList, index * itemWidth); }); }); From 46475c0539a6fbf138e980cd8e6b6a10799c7807 Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Thu, 9 Nov 2023 22:01:20 +0530 Subject: [PATCH 013/250] fix position of text carousel --- .../v2-feature-carousel.css | 35 ++++++++----------- .../v2-feature-carousel.js | 9 +++-- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/blocks/v2-feature-carousel/v2-feature-carousel.css b/blocks/v2-feature-carousel/v2-feature-carousel.css index 3dd87f27e..ce1578492 100644 --- a/blocks/v2-feature-carousel/v2-feature-carousel.css +++ b/blocks/v2-feature-carousel/v2-feature-carousel.css @@ -13,10 +13,6 @@ --v2-feature-carousel-size: 512px; } -.v2-feature-carousel-container { - background-color: var(--c-primary-black); -} - .v2-feature-carousel__arrowcontrols, .v2-feature-carousel__list { list-style-type: none; @@ -26,13 +22,15 @@ } .section.v2-feature-carousel-container .v2-feature-carousel-wrapper { - padding-left: 0; - padding-right: 0; + padding: 0; + max-width: 100%; + background-color: var(--c-primary-black); } .v2-feature-carousel__image-wrapper picture img { aspect-ratio: 1/1; display: block; + object-fit: cover; } .v2-feature-carousel__list-container { @@ -83,7 +81,7 @@ } .v2-feature-carousel__list-item.active { - animation: slide-in ease-in-out 5s; + animation: slide-in ease-in-out 2s; } .v2-feature-carousel__list::-webkit-scrollbar { @@ -100,16 +98,17 @@ margin: 0; } -@media (min-width: 992px) { +@media (min-width: 1200px) { .v2-feature-carousel { --v2-feature-carousel-content-gap: 24px; + --v2-feature-carousel-padding: calc((60% - 512px)/2); /* calculate paading based on parents width */ - position: relative; + display: flex; + flex-direction: row-reverse; } .v2-feature-carousel__image-wrapper { width: 60%; - margin-left: auto; } .v2-feature-carousel__list-container { @@ -119,10 +118,8 @@ background-color: var(--c-primary-white); display: flex; flex-direction: column; - position: absolute; - left: 17%; z-index: 2; - top: 20%; + margin: auto; } .v2-feature-carousel__list { @@ -138,15 +135,13 @@ padding-right: 56px; } - /* .v2-feature-carousel__list-container--single { - height: 587px; - width: 587px; + .v2-feature-carousel__list-wrapper { + left: calc((100% - var(--wrapper-width))/2); + position: absolute; + padding-top: var(--v2-feature-carousel-padding); + padding-bottom: var(--v2-feature-carousel-padding); } - .v2-feature-carousel__list-container--single .v2-feature-carousel__list-item { - padding: 137.5px 99px 144.5px 103px; - } */ - .v2-feature-carousel__arrowcontrols { position: unset; align-self: flex-end; diff --git a/blocks/v2-feature-carousel/v2-feature-carousel.js b/blocks/v2-feature-carousel/v2-feature-carousel.js index 33ca62e97..30b126cb7 100644 --- a/blocks/v2-feature-carousel/v2-feature-carousel.js +++ b/blocks/v2-feature-carousel/v2-feature-carousel.js @@ -2,7 +2,6 @@ import { createElement, unwrapDivs, adjustPretitle, - makeBlockFullWidth, } from '../../scripts/common.js'; import { listenScroll, @@ -78,12 +77,16 @@ export default async function decorate(block) { }); carouselContainer.append(carouselList); - makeBlockFullWidth(blockName); + + const carouselRow = createElement('div', { classes: `${blockName}__list-wrapper` }); + carouselRow.append(carouselContainer); + + block.append(carouselRow); if (textElements.length > 1) { createArrowControls(carouselList, `.${blockName}__list-item.active`, [`${blockName}__arrowcontrols`], arrowFragment); const elements = carouselList.querySelectorAll(`.${blockName}__list-item`); - listenScroll(carouselList, elements, 0.75, updateActiveClass); + listenScroll(carouselList, elements, updateActiveClass, 0.75); } else { carouselContainer.classList.add(`${blockName}__list-container--single`); } From 8505b3ee258ef955f2d4830546017db2081d4900 Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Thu, 9 Nov 2023 22:10:37 +0530 Subject: [PATCH 014/250] remove commited code by mistake --- blocks/v2-all-trucks/v2-all-trucks.js | 5 +++-- blocks/v2-specifications/v2-specifications.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/blocks/v2-all-trucks/v2-all-trucks.js b/blocks/v2-all-trucks/v2-all-trucks.js index 3bd9684dc..a5de59652 100644 --- a/blocks/v2-all-trucks/v2-all-trucks.js +++ b/blocks/v2-all-trucks/v2-all-trucks.js @@ -1,4 +1,4 @@ -import { createElement, getTextLabel, makeBlockFullWidth } from '../../scripts/common.js'; +import { createElement, getTextLabel } from '../../scripts/common.js'; // Define break point/s const blockName = 'v2-all-trucks'; @@ -29,7 +29,8 @@ export default function decorate(block) { }); }); - makeBlockFullWidth(`.${blockName}-wrapper`); + const trucksWrapper = document.querySelector(`.${blockName}-wrapper`); + trucksWrapper.classList.add('full-width'); const trucks = document.querySelectorAll(`.${blockName}__truck`); const setSecondaryButton = document.querySelectorAll(`.${blockName}__truck .${blockName}__truck-info .button-container:last-of-type a`); diff --git a/blocks/v2-specifications/v2-specifications.js b/blocks/v2-specifications/v2-specifications.js index 72ac45fe0..cba04a76b 100644 --- a/blocks/v2-specifications/v2-specifications.js +++ b/blocks/v2-specifications/v2-specifications.js @@ -32,7 +32,7 @@ export default async function decorate(block) { // Hx tag used for the titles of the accordion const titleMeta = block.closest('.section').dataset.header || 3; - const headerTag = titleMeta?.charAt(titleMeta.length - 1); + const headerTag = titleMeta.charAt(titleMeta.length - 1); const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].slice(headerTag); [...items].forEach((item) => { From a3fe6d1cb28edf6b920ba495b1385ad0bf3e6413 Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Thu, 9 Nov 2023 22:12:09 +0530 Subject: [PATCH 015/250] clean up --- scripts/common.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/scripts/common.js b/scripts/common.js index aaad066ce..f515f5c73 100644 --- a/scripts/common.js +++ b/scripts/common.js @@ -235,10 +235,3 @@ export const adjustPretitle = (element) => { } }); }; - -export const makeBlockFullWidth = (blockName) => { - const fullWidthWrappers = document.querySelectorAll(`.${blockName}-wrapper`); - fullWidthWrappers.forEach((wrapper) => { - wrapper.classList.add('full-width'); - }); -}; From ebebff5b458307667b5d573560447d8e49b5c52c Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Wed, 15 Nov 2023 14:02:21 +0530 Subject: [PATCH 016/250] fix arrows missing in case of multiple carousels in same page --- blocks/v2-feature-carousel/v2-feature-carousel.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/blocks/v2-feature-carousel/v2-feature-carousel.js b/blocks/v2-feature-carousel/v2-feature-carousel.js index 30b126cb7..911b2e442 100644 --- a/blocks/v2-feature-carousel/v2-feature-carousel.js +++ b/blocks/v2-feature-carousel/v2-feature-carousel.js @@ -14,7 +14,7 @@ const updateActiveClass = (elements, entry) => { elements.forEach((el, index) => { if (el === entry.target) { el.classList.add('active'); - let arrowControl = document.querySelector(`.${blockName}__button:disabled`); + let arrowControl = el.parentElement.previousElementSibling.querySelector(`.${blockName}__button:disabled`); if (arrowControl) { arrowControl.disabled = false; @@ -22,9 +22,9 @@ const updateActiveClass = (elements, entry) => { } // disable arrow buttons if (index === 0) { - arrowControl = document.querySelector(`.${blockName}__button-prev`); + arrowControl = el.parentElement.previousElementSibling.querySelector(`.${blockName}__button-prev`); } else if (index === el.parentNode.children.length - 1) { - arrowControl = document.querySelector(`.${blockName}__button-next`); + arrowControl = el.parentElement.previousElementSibling.querySelector(`.${blockName}__button-next`); } if (arrowControl) { arrowControl.disabled = true; @@ -35,7 +35,7 @@ const updateActiveClass = (elements, entry) => { }); }; -const arrowFragment = document.createRange().createContextualFragment(`
  • +const arrowFragment = () => document.createRange().createContextualFragment(`
  • -
  • `; @@ -92,18 +112,9 @@ const createModalContent = (content) => { el.addEventListener('click', () => { const itemWidth = carouselItemsList.getBoundingClientRect().width; - let index = Math.round(carouselItemsList.scrollLeft / itemWidth) + modifiers[elIndex]; - - if (index < 0) { - index = carouselItemsList.children.length - 1; - } + const index = Math.round(carouselItemsList.scrollLeft / itemWidth) + modifiers[elIndex]; - if (index > carouselItemsList.children.length - 1) { - index = 0; - } - - scrollLeft(carouselImagesList, index * 90); - scrollLeft(carouselItemsList, index * itemWidth); + setActiveSlide(index, carouselItemsList, carouselImagesList, content); }); }); @@ -112,6 +123,10 @@ const createModalContent = (content) => { return wrapper; }; +const showImagesGridModal = async (modalContent) => { + await showModal(modalContent, { classes: ['modal-content--bottom'] }); +}; + export default function decorate(block) { // all items are inside a ul list with classname called 'v2-images-grid__items' const ul = createElement('ul', { classes: `${blockClassName}__items` }); @@ -155,8 +170,8 @@ export default function decorate(block) { const carouselItemsList = modalContent.querySelector(`.${blockClassName}__carousel-items-list`); const carouselImagesList = modalContent.querySelector(`.${blockClassName}__carousel-preview-list`); - await showModal(modalContent, { classes: ['modal-content--bottom'] }); - setActiveSlide(idx, carouselItemsList, carouselImagesList); + await showImagesGridModal(modalContent); + setActiveSlide(idx, carouselItemsList, carouselImagesList, modalContent); }); return; @@ -168,8 +183,12 @@ export default function decorate(block) { classes: ['button', 'button--large', 'button--primary'], }); button.textContent = getTextLabel('Open Gallery'); - button.addEventListener('click', () => { - showModal(modalContent, { classes: ['modal-content--bottom'] }); + button.addEventListener('click', async () => { + const carouselItemsList = modalContent.querySelector(`.${blockClassName}__carousel-items-list`); + const carouselImagesList = modalContent.querySelector(`.${blockClassName}__carousel-preview-list`); + + await showImagesGridModal(modalContent); + setActiveSlide(0, carouselItemsList, carouselImagesList, modalContent); }); block.append(button); diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index 0e6c8e96e..7b32f7ae6 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -109,6 +109,11 @@ position: absolute; right: 0; } + + .redesign-v2 .modal-background button.modal-close-button:hover, + .redesign-v2 .modal-background button.modal-close-button:focus-visible { + --color-icon: var(--c-accent-copper); + } } /* adjustments for soundcloud variant of modal, e.g. https://www.volvotrucks.us/trucks/powertrain/i-torque/ */ From 6a1512682b0c6c1f413f6502a84ae7f94b562c38 Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Wed, 15 Nov 2023 13:05:15 +0100 Subject: [PATCH 019/250] #38 Fix clearing modal content --- blocks/v2-images-grid/v2-images-grid.css | 2 +- common/modal/modal-component.js | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/blocks/v2-images-grid/v2-images-grid.css b/blocks/v2-images-grid/v2-images-grid.css index 32fd53e1f..ee58f4df7 100644 --- a/blocks/v2-images-grid/v2-images-grid.css +++ b/blocks/v2-images-grid/v2-images-grid.css @@ -155,7 +155,7 @@ .v2-images-grid__modal-arrow:hover, .v2-images-grid__modal-arrow:active, -.v2-images-grid__modal-arrow:focus +.v2-images-grid__modal-arrow:focus, .v2-images-grid__modal-arrow:focus-visible { background: var(--c-primary-white); diff --git a/common/modal/modal-component.js b/common/modal/modal-component.js index b3ab58326..fc6ae3704 100644 --- a/common/modal/modal-component.js +++ b/common/modal/modal-component.js @@ -121,9 +121,25 @@ const createModal = () => { document.querySelector('.modal-content video')?.pause(); document.querySelector('.modal-content iframe')?.setAttribute('src', ''); - modalContent.addEventListener('transitionend', () => { - clearModalContent(); - }, { once: true }); + let onHideTransitionCancel; + const onHideTransitionEnd = (event) => { + if (event.target === modalBackground) { + clearModalContent(); + + if (onHideTransitionCancel) { + modalBackground.removeEventListener('transitioncancel', onHideTransitionCancel); + } + } + }; + + onHideTransitionCancel = (event) => { + if (event.target === modalBackground) { + modalBackground.removeEventListener('transitionend', onHideTransitionEnd); + } + }; + + modalBackground.addEventListener('transitionend', onHideTransitionEnd, { once: true }); + modalBackground.addEventListener('transitioncancel', onHideTransitionCancel, { once: true }); } return { From ac79c85bafab74a92f6af4ff839fa3abc6fd79de Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Wed, 15 Nov 2023 23:31:41 +0530 Subject: [PATCH 020/250] Tabbed carousel multiple instances get combined #109 (#167) * refactor tabbed carousel --- .../v2-tabbed-carousel/v2-tabbed-carousel.css | 17 ++- .../v2-tabbed-carousel/v2-tabbed-carousel.js | 121 +++++++++--------- scripts/scripts.js | 42 ------ 3 files changed, 73 insertions(+), 107 deletions(-) diff --git a/blocks/v2-tabbed-carousel/v2-tabbed-carousel.css b/blocks/v2-tabbed-carousel/v2-tabbed-carousel.css index 263307e30..53174b224 100644 --- a/blocks/v2-tabbed-carousel/v2-tabbed-carousel.css +++ b/blocks/v2-tabbed-carousel/v2-tabbed-carousel.css @@ -21,7 +21,7 @@ main .section.v2-tabbed-carousel-container { } .v2-tabbed-carousel__items { - align-items: flex-end; + align-items: flex-start; display: flex; flex-flow: row nowrap; margin: 0; @@ -81,6 +81,7 @@ main .section.v2-tabbed-carousel-container { list-style: none; margin: 0; overflow: auto; + scrollbar-width: none; padding: var(--navigation-padding); position: relative; } @@ -130,14 +131,26 @@ main .section.v2-tabbed-carousel-container { padding: 20px 15px 10px; white-space: nowrap; width: 100%; + transition: var(--duration-small) var(--easing-standard); + z-index: 3; + position: relative; } .v2-tabbed-carousel__navigation-item.active button, .v2-tabbed-carousel__navigation-item button:hover, -.v2-tabbed-carousel__navigation-item button:focus { +.v2-tabbed-carousel__navigation-item button:active, +.v2-tabbed-carousel__navigation-item button:focus-visible { color: var(--navigation-active-color); } +.v2-tabbed-carousel__navigation-item button:focus { + outline: 0; +} + +.v2-tabbed-carousel__navigation-item button:focus-visible { + outline: 3px solid var(--navigation-line-active-color); +} + @media (min-width: 1200px) { .v2-tabbed-carousel__figure { position: relative; diff --git a/blocks/v2-tabbed-carousel/v2-tabbed-carousel.js b/blocks/v2-tabbed-carousel/v2-tabbed-carousel.js index 839f11efa..acea54397 100644 --- a/blocks/v2-tabbed-carousel/v2-tabbed-carousel.js +++ b/blocks/v2-tabbed-carousel/v2-tabbed-carousel.js @@ -1,4 +1,4 @@ -import { createElement, removeEmptyTags } from '../../scripts/common.js'; +import { createElement, unwrapDivs } from '../../scripts/common.js'; const blockName = 'v2-tabbed-carousel'; @@ -11,41 +11,10 @@ const moveNavigationLine = (navigationLine, activeTab, tabNavigation) => { }); }; -function buildTabNavigation(tabItems, clickHandler) { - const tabNavigation = createElement('ul', { classes: `${blockName}__navigation` }); - const navigationLine = createElement('li', { classes: `${blockName}__navigation-line` }); - let timeout; - - [...tabItems].forEach((tabItem, i) => { - const listItem = createElement('li', { classes: `${blockName}__navigation-item` }); - const button = createElement('button'); - button.addEventListener('click', () => clickHandler(i)); - button.addEventListener('mouseover', (e) => { - clearTimeout(timeout); - moveNavigationLine(navigationLine, e.currentTarget, tabNavigation); - }); - - button.addEventListener('mouseout', () => { - timeout = setTimeout(() => { - const activeItem = document.querySelector(`.${blockName}__navigation-item.active`); - moveNavigationLine(navigationLine, activeItem, tabNavigation); - }, 600); - }); - - button.innerHTML = tabItem.dataset.carousel; - listItem.append(button); - tabNavigation.append(listItem); - }); - - tabNavigation.append(navigationLine); - - return tabNavigation; -} - -const updateActiveItem = (index) => { - const carouselItems = document.querySelector(`.${blockName}__items`); - const navigation = document.querySelector(`.${blockName}__navigation`); - const navigationLine = document.querySelector(`.${blockName}__navigation-line`); +const updateActiveItem = (index, block) => { + const carouselItems = block.querySelector(`.${blockName}__items`); + const navigation = block.querySelector(`.${blockName}__navigation`); + const navigationLine = block.querySelector(`.${blockName}__navigation-line`); [carouselItems, navigation].forEach((c) => c.querySelectorAll('.active').forEach((i) => i.classList.remove('active'))); carouselItems.children[index].classList.add('active'); @@ -68,7 +37,7 @@ const updateActiveItem = (index) => { } }; -const listenScroll = (carousel) => { +const listenScroll = (carousel, block) => { const elements = carousel.querySelectorAll(':scope > *'); const io = new IntersectionObserver((entries) => { @@ -79,7 +48,7 @@ const listenScroll = (carousel) => { ) { const activeItem = entry.target; const currentIndex = [...activeItem.parentNode.children].indexOf(activeItem); - updateActiveItem(currentIndex); + updateActiveItem(currentIndex, block); } }); }, { @@ -105,43 +74,69 @@ const setCarouselPosition = (carousel, index) => { }; export default function decorate(block) { - const container = block.querySelector(':scope > div'); - container.classList.add(`${blockName}__container`); + const carouselContainer = createElement('div', { classes: `${blockName}__container` }); + const carouselItems = createElement('ul', { classes: `${blockName}__items` }); + carouselContainer.append(carouselItems); - const carouselItems = createElement('div', { classes: `${blockName}__items` }); - container.appendChild(carouselItems); + const tabNavigation = createElement('ul', { classes: `${blockName}__navigation` }); + const navigationLine = createElement('li', { classes: `${blockName}__navigation-line` }); + let timeout; - const tabItems = block.querySelectorAll('.v2-tabbed-carousel__item'); + function buildTabNavigation(buttonContent, index) { + const listItem = createElement('li', { classes: `${blockName}__navigation-item` }); + const button = createElement('button'); - tabItems.forEach((tabItem) => { - const tabContent = tabItem.querySelector(':scope > div'); + button.addEventListener('click', () => setCarouselPosition(carouselItems, index)); + button.addEventListener('mouseover', (e) => { + clearTimeout(timeout); + moveNavigationLine(navigationLine, e.currentTarget, tabNavigation); + }); + button.addEventListener('mouseout', () => { + timeout = setTimeout(() => { + const activeItem = document.querySelector(`.${blockName}__navigation-item.active`); + moveNavigationLine(navigationLine, activeItem, tabNavigation); + }, 600); + }); + + button.innerHTML = buttonContent; + listItem.append(button); + + return listItem; + } + + const tabItems = block.querySelectorAll(':scope > div'); + tabItems.forEach((tabItem, index) => { + const liItem = createElement('li', { classes: `${blockName}__item` }); const figure = createElement('figure', { classes: `${blockName}__figure` }); - const picture = tabItem.querySelector('picture'); - figure.appendChild(picture); + const tabContent = tabItem.querySelector('p'); + + figure.append(tabContent.querySelector('picture')); const figureCaption = createElement('figcaption'); - const text = tabContent?.querySelectorAll(':scope > *'); - if (text) { - figureCaption.append(...text); + const lastItems = [...tabContent.childNodes].at(-1); + if (lastItems.nodeType === Node.TEXT_NODE) { + figureCaption.append(lastItems); + figure.append(figureCaption); } - tabContent.remove(); figure.appendChild(figureCaption); - - tabItem.prepend(figure); - - carouselItems.appendChild(tabItem); + liItem.append(figure); + carouselItems.appendChild(liItem); + + // navigation item + const tabTitle = tabItem.querySelector('h3'); + const navItem = buildTabNavigation(tabTitle.innerHTML, index); + tabNavigation.append(navItem); + tabTitle.remove(); + tabItem.innerHTML = ''; }); - removeEmptyTags(container); - - const tabNavigation = buildTabNavigation(tabItems, (index) => { - setCarouselPosition(carouselItems, index); - }); + tabNavigation.append(navigationLine); + carouselContainer.append(tabNavigation); - container.append(tabNavigation); + block.append(carouselContainer); + listenScroll(carouselItems, block); - // update the button indicator on scroll - listenScroll(carouselItems); + unwrapDivs(block); } diff --git a/scripts/scripts.js b/scripts/scripts.js index c21839c6a..64c13f91e 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -369,46 +369,6 @@ function buildInpageNavigationBlock(main, classname) { } } -function createTabbedSection(tabItems, classname) { - const tabSection = createElement('div', { classes: 'section' }); - tabSection.dataset.sectionStatus = 'initialized'; - const tabBlock = buildBlock(classname, [tabItems]); - tabSection.append(tabBlock); - return tabSection; -} - -function buildTabbedBlock(main, classname) { - let nextElement; - const tabItems = []; - const mainChildren = [...main.querySelectorAll(':scope > div')]; - - mainChildren.forEach((section, i2) => { - const isCarousel = section.dataset.carousel; - if (!isCarousel) return; - - nextElement = mainChildren[i2 + 1]; - const tabContent = createElement('div', { classes: `${classname}__item` }); - tabContent.dataset.carousel = section.dataset.carousel; - tabContent.innerHTML = section.innerHTML; - const image = tabContent.querySelector('p > picture'); - - tabContent.prepend(image); - - tabItems.push(tabContent); - section.remove(); - }); - - if (tabItems.length > 0) { - const tabbedCarouselSection = createTabbedSection(tabItems, classname); - if (nextElement) { // if we saved a position push the carousel in that position if not - main.insertBefore(tabbedCarouselSection, nextElement); - } else { - main.append(tabbedCarouselSection); - } - decorateBlock(tabbedCarouselSection.querySelector(`.${classname}`)); - } -} - /** * Decorates the main element. * @param {Element} main The main element @@ -428,8 +388,6 @@ export function decorateMain(main, head) { buildTruckLineupBlock(main, 'v2-truck-lineup'); // Inpage navigation buildInpageNavigationBlock(main, 'v2-inpage-navigation'); - // V2 tabbed carousel - buildTabbedBlock(main, 'v2-tabbed-carousel'); } async function loadTemplate(doc, templateName) { From e78ed5effd3e9699f9cf5b0fbfdcc7b660e8db4c Mon Sep 17 00:00:00 2001 From: Syb <133873665+cogniSyb@users.noreply.github.com> Date: Wed, 15 Nov 2023 19:02:51 +0100 Subject: [PATCH 021/250] Cards Block Variant: 3-card row with button #115 (#168) * add possibility to use small buttons in v2-cards * add variant without padding/background * add variant with different image aspect ratio * add variant with H4-style heading --- blocks/v2-cards/v2-cards.css | 17 +++++++++++++++++ blocks/v2-cards/v2-cards.js | 20 +++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/blocks/v2-cards/v2-cards.css b/blocks/v2-cards/v2-cards.css index 125b03939..b51976998 100644 --- a/blocks/v2-cards/v2-cards.css +++ b/blocks/v2-cards/v2-cards.css @@ -12,6 +12,10 @@ color: var(--c-primary-black); } +.v2-cards--no-background .v2-cards__card-item { + background: transparent; +} + .v2-cards--gray-cards .v2-cards__card-item { background: var(--c-tertiary-light-warm-gray); } @@ -24,6 +28,10 @@ padding: 24px; } +.v2-cards--no-background .v2-cards__text-wrapper { + padding: 40px 0 0; +} + .v2-cards__picture { display: flex; } @@ -35,6 +43,10 @@ object-fit: cover; } +.v2-cards--image-aspect-ratio-7-5 .v2-cards__image { + aspect-ratio: 7/5; +} + .v2-cards__heading { font-family: var(--ff-subheadings-medium); font-size: var(--headline-5-font-size); @@ -42,6 +54,11 @@ margin: 0 0 7px; } +.v2-cards--large-heading .v2-cards__heading { + font-size: var(--headline-4-font-size); + line-height: var(--headline-4-line-height); +} + .v2-cards__text-wrapper p { margin: 0; color: var(--c-primary-black); diff --git a/blocks/v2-cards/v2-cards.js b/blocks/v2-cards/v2-cards.js index 1c625e703..e67d622a1 100644 --- a/blocks/v2-cards/v2-cards.js +++ b/blocks/v2-cards/v2-cards.js @@ -2,7 +2,7 @@ import { variantsClassesToBEM } from '../../scripts/common.js'; export default async function decorate(block) { const blockName = 'v2-cards'; - const variantClasses = ['gray-cards', 'horizontal']; + const variantClasses = ['no-background', 'gray-cards', 'horizontal', 'image-aspect-ratio-7-5', 'large-heading']; variantsClassesToBEM(block.classList, variantClasses, blockName); const cardsItems = [...block.querySelectorAll(':scope > div')]; @@ -30,8 +30,22 @@ export default async function decorate(block) { buttons.forEach((el) => { el.classList.add(`${blockName}__button-container`); [...el.querySelectorAll('a')].forEach((link) => { - link.classList.add('standalone-link', `${blockName}__button`); - link.classList.remove('button', 'button--primary'); + const up = link.parentElement; + if ( + up.childNodes.length === 1 + && up.tagName === 'STRONG' + ) { + link.className = 'button button--small button--primary'; + } + if ( + up.childNodes.length === 1 + && up.tagName === 'EM' + ) { + link.className = 'button button--small button--secondary'; + } else { + link.classList.add('standalone-link', `${blockName}__button`); + link.classList.remove('button', 'button--primary'); + } }); }); } From 75fa5b8be164cdcd8c28510f6faef1b0f204e53a Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 16 Nov 2023 12:52:18 +0100 Subject: [PATCH 022/250] #38 Update scroling preview images list when slide change --- blocks/v2-images-grid/v2-images-grid.css | 4 ---- blocks/v2-images-grid/v2-images-grid.js | 22 +++++++++++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/blocks/v2-images-grid/v2-images-grid.css b/blocks/v2-images-grid/v2-images-grid.css index ee58f4df7..1edea718c 100644 --- a/blocks/v2-images-grid/v2-images-grid.css +++ b/blocks/v2-images-grid/v2-images-grid.css @@ -99,10 +99,6 @@ .v2-images-grid__carousel-preview-list { gap: 10px; - display: flex; - list-style: none; - margin: 0; - padding: 0; padding: 20px; background: var(--c-primary-white); } diff --git a/blocks/v2-images-grid/v2-images-grid.js b/blocks/v2-images-grid/v2-images-grid.js index 0e99ab575..8f0a2d9fb 100644 --- a/blocks/v2-images-grid/v2-images-grid.js +++ b/blocks/v2-images-grid/v2-images-grid.js @@ -47,7 +47,23 @@ const createModalContent = (content) => { const carouselItemsList = createElement('ul', { classes: `${blockClassName}__carousel-items-list` }); const carouselImagesList = createElement('ul', { classes: `${blockClassName}__carousel-preview-list` }); + let isScrolling = false; + let stopScrolling; + + carouselItemsList.addEventListener('scroll', () => { + isScrolling = true; + + clearTimeout(stopScrolling); + stopScrolling = setTimeout(() => { + isScrolling = false; + }, 50); + }); + const debouncedOnItemChange = debounce((index) => { + if (isScrolling) { + return; + } + scrollLeft(carouselImagesList, index * 90); }, 100); @@ -83,7 +99,11 @@ const createModalContent = (content) => { threshold: 1.0, }; - const observer = new IntersectionObserver(() => { + const observer = new IntersectionObserver((entries) => { + if (!entries[0].isIntersecting) { + return; + } + debouncedOnItemChange(index); udpateArrowsState(index, carouselItemsList.children.length, el.closest(`${blockClassName}__carousel-items-wrapper`)); From abdbfa1cb37f02463e77fa0753e1cd432313e09a Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 16 Nov 2023 14:06:01 +0100 Subject: [PATCH 023/250] #38 Fix grid carousel on mobile safari --- blocks/v2-images-grid/v2-images-grid.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blocks/v2-images-grid/v2-images-grid.css b/blocks/v2-images-grid/v2-images-grid.css index 1edea718c..1b809711e 100644 --- a/blocks/v2-images-grid/v2-images-grid.css +++ b/blocks/v2-images-grid/v2-images-grid.css @@ -76,6 +76,7 @@ position: relative; height: auto; justify-content: flex-end; + max-width: 100vw; } .v2-images-grid__carousel-items-list, @@ -101,6 +102,7 @@ gap: 10px; padding: 20px; background: var(--c-primary-white); + width: 100%; } .v2-images-grid__carousel-item, From 4365a2832b0ee27d0ff1570f6ba82f532705a276 Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 16 Nov 2023 14:22:46 +0100 Subject: [PATCH 024/250] #38 Adjust the mobile margin for modal --- common/modal/modal-component.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index 7b32f7ae6..e29227471 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -77,6 +77,14 @@ display: flex; justify-content: flex-end; flex-direction: column; + margin-bottom: 60px; +} + +@supports (height: 1svh) { + .modal-content--bottom { + height: 100svh; + margin-bottom: 0; + } } @media (min-width: 768px) { From 7d020e34a11955c5a9327e0bd7b2e3016d910c2f Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 16 Nov 2023 14:24:43 +0100 Subject: [PATCH 025/250] #38 Update bottom margin for modal content --- common/modal/modal-component.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index e29227471..bb5bd2b6b 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -77,7 +77,7 @@ display: flex; justify-content: flex-end; flex-direction: column; - margin-bottom: 60px; + margin-bottom: 80px; } @supports (height: 1svh) { From 2f7b65b687234afa59e4536f1a2ec1cae7db541b Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 16 Nov 2023 14:26:24 +0100 Subject: [PATCH 026/250] #38 Update bottom margin for modal content --- common/modal/modal-component.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index bb5bd2b6b..73b85322a 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -77,7 +77,7 @@ display: flex; justify-content: flex-end; flex-direction: column; - margin-bottom: 80px; + margin-bottom: 100px; } @supports (height: 1svh) { From db9795f85a7aaae6da135fba3b6a9b3c36951f8e Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 16 Nov 2023 15:55:20 +0100 Subject: [PATCH 027/250] #38 Update bottom margin for modal content --- common/modal/modal-component.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index 73b85322a..c6e6ebacb 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -77,7 +77,7 @@ display: flex; justify-content: flex-end; flex-direction: column; - margin-bottom: 100px; + margin-bottom: 140px; } @supports (height: 1svh) { From 14844c143685d7054317c30bb9f7f793c5050024 Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 16 Nov 2023 16:07:17 +0100 Subject: [PATCH 028/250] #38 Update bottom margin for modal content --- common/modal/modal-component.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index c6e6ebacb..aab300c6f 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -77,7 +77,7 @@ display: flex; justify-content: flex-end; flex-direction: column; - margin-bottom: 140px; + margin-bottom: 160px; } @supports (height: 1svh) { From 9eced54dc46d17e9a9e21976bb087fb54ab3983f Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Thu, 16 Nov 2023 16:11:21 +0100 Subject: [PATCH 029/250] #38 Update justify content for devices not supporting svh units --- common/modal/modal-component.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index aab300c6f..78b1eb980 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -75,15 +75,14 @@ .modal-content--bottom { display: flex; - justify-content: flex-end; + justify-content: center; flex-direction: column; - margin-bottom: 160px; } @supports (height: 1svh) { .modal-content--bottom { height: 100svh; - margin-bottom: 0; + justify-content: flex-end; } } From 74bbaef9f93eb23b5603f4453a60101dbd8b055c Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Fri, 17 Nov 2023 09:03:03 +0100 Subject: [PATCH 030/250] #38 Update height of the modal content for devices not supporting svh units --- common/modal/modal-component.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index 78b1eb980..7e3d5deed 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -75,14 +75,14 @@ .modal-content--bottom { display: flex; - justify-content: center; + justify-content: flex-end; flex-direction: column; + height: 80vh; } @supports (height: 1svh) { .modal-content--bottom { height: 100svh; - justify-content: flex-end; } } From 0ecdd95fe062f27e6afd90be157099b7b69d60a8 Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Fri, 17 Nov 2023 09:05:05 +0100 Subject: [PATCH 031/250] #38 Update height of the modal content for devices not supporting svh units --- common/modal/modal-component.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index 7e3d5deed..e7929affb 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -77,7 +77,7 @@ display: flex; justify-content: flex-end; flex-direction: column; - height: 80vh; + height: 75vh; } @supports (height: 1svh) { From 13c2c2f2972877a361aac75e473aae8f01d8ae27 Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Fri, 17 Nov 2023 09:38:20 +0100 Subject: [PATCH 032/250] #38 Fix modal aligment --- common/modal/modal-component.css | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index e7929affb..82aa0878a 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -3,7 +3,7 @@ top: 0; left: 0; width: 100vw; - height: 100vh; + height: 100%; background-color: var(--c-secondary-graphite); z-index: 1051; transition: opacity 1s linear, height 1s linear; @@ -77,13 +77,7 @@ display: flex; justify-content: flex-end; flex-direction: column; - height: 75vh; -} - -@supports (height: 1svh) { - .modal-content--bottom { - height: 100svh; - } + height: 100%; } @media (min-width: 768px) { From 26bca36046493355e4fa9f3f37a83b1bb8020403 Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Fri, 17 Nov 2023 09:52:30 +0100 Subject: [PATCH 033/250] #38 Fix positon of the image on mobile --- blocks/v2-images-grid/v2-images-grid.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/blocks/v2-images-grid/v2-images-grid.css b/blocks/v2-images-grid/v2-images-grid.css index 1b809711e..1e6fa9df6 100644 --- a/blocks/v2-images-grid/v2-images-grid.css +++ b/blocks/v2-images-grid/v2-images-grid.css @@ -74,9 +74,9 @@ display: flex; flex-direction: column; position: relative; - height: auto; justify-content: flex-end; max-width: 100vw; + height: 100%; } .v2-images-grid__carousel-items-list, @@ -128,6 +128,9 @@ .v2-images-grid__carousel-item picture { display: flex; + position: relative; + top: 50%; + transform: translateY(-50%); } .v2-images-grid__modal-content .v2-images-grid__carousel-item img { @@ -171,6 +174,9 @@ .v2-images-grid__carousel-items-wrapper { position: relative; + height: 100%; + display: flex; + justify-content: flex-end; } .v2-images-grid__modal-arrows-wrapper { From 44d38934fba42a2e4de5f0731c2b323299572f22 Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Fri, 17 Nov 2023 16:33:09 +0530 Subject: [PATCH 034/250] fix fire fox aspect ratio --- blocks/v2-feature-carousel/v2-feature-carousel.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blocks/v2-feature-carousel/v2-feature-carousel.css b/blocks/v2-feature-carousel/v2-feature-carousel.css index fff7beb0f..9abc798f1 100644 --- a/blocks/v2-feature-carousel/v2-feature-carousel.css +++ b/blocks/v2-feature-carousel/v2-feature-carousel.css @@ -21,6 +21,8 @@ aspect-ratio: 1/1; display: block; object-fit: cover; + width: 100%; + height: 100%; } .v2-feature-carousel__list-container { From f02ed536ad5406eeeff76e7a22d55205b3e4d803 Mon Sep 17 00:00:00 2001 From: Tomasz Dziezyk Date: Fri, 17 Nov 2023 12:56:30 +0100 Subject: [PATCH 035/250] #38 Fix displaying images --- blocks/v2-images-grid/v2-images-grid.css | 1 + 1 file changed, 1 insertion(+) diff --git a/blocks/v2-images-grid/v2-images-grid.css b/blocks/v2-images-grid/v2-images-grid.css index 1e6fa9df6..c6b5d2235 100644 --- a/blocks/v2-images-grid/v2-images-grid.css +++ b/blocks/v2-images-grid/v2-images-grid.css @@ -136,6 +136,7 @@ .v2-images-grid__modal-content .v2-images-grid__carousel-item img { aspect-ratio: 16/9; width: 100%; + object-fit: cover; } .v2-images-grid__carousel-preview-item { From 97cd56802efc232628c835b1a72ecff89f573d08 Mon Sep 17 00:00:00 2001 From: TomaszDziezykNetcentric <125962117+TomaszDziezykNetcentric@users.noreply.github.com> Date: Fri, 17 Nov 2023 13:02:13 +0100 Subject: [PATCH 036/250] Truck features #43 (#155) --- .../v2-truck-features/v2-truck-features.css | 240 ++++++++++++++++++ blocks/v2-truck-features/v2-truck-features.js | 184 ++++++++++++++ styles/styles.css | 2 +- 3 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 blocks/v2-truck-features/v2-truck-features.css create mode 100644 blocks/v2-truck-features/v2-truck-features.js diff --git a/blocks/v2-truck-features/v2-truck-features.css b/blocks/v2-truck-features/v2-truck-features.css new file mode 100644 index 000000000..b3803d315 --- /dev/null +++ b/blocks/v2-truck-features/v2-truck-features.css @@ -0,0 +1,240 @@ +.section.v2-truck-features-container > .v2-truck-features-wrapper { + padding: 0; +} + +.v2-truck-features { + padding: 0; + color: var(--text-color); +} + +.v2-truck-features__content { + width: 100%; + height: calc(100vh - var(--nav-height) - var(--inpage-navigation-height)); + position: sticky; + top: calc(var(--nav-height) + var(--inpage-navigation-height)); + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; +} + +@supports (height: 1svh) { + .v2-truck-features__content { + height: calc(100svh - var(--nav-height) - var(--inpage-navigation-height)); + } +} + +.v2-truck-features__heading { + font-family: var(--ff-subheadings-medium); + font-size: 32px; + line-height: var(--headline-3-line-height); + margin: 0; + padding: 40px 16px; +} + +.v2-truck-features__slides { + margin: 0; + list-style: none; + padding: 0; + flex-grow: 1; +} + +.v2-truck-features ul ul:first-of-type { + display: none; +} + +.v2-truck-features .v2-truck-features__images-list { + margin: 0; + padding: 0; +} + +.v2-truck-features .v2-truck-features__images-list li { + display: none; + width: auto; +} + +.v2-truck-features .v2-truck-features__slide { + display: none; + flex-direction: column; + gap: 8px; + height: 100%; + justify-content: space-evenly; +} + +.v2-truck-features .v2-truck-features__slide.v2-truck-features__slide--active { + display: flex; + flex-direction: column-reverse; +} + +.v2-truck-features .v2-truck-features__slide h4 { + font-family: var(--ff-subheadings-medium); + line-height: var(--headline-5-line-height); + font-size: 18px; + letter-spacing: 0.36px; + margin: 0; +} + +.v2-truck-features .v2-truck-features__slide h4::before { + width: 2px; + height: 27px; + content: ''; + display: block; + position: absolute; + background: var(--c-primary-white); + left: 16px; +} + +.v2-truck-features .v2-truck-features__slide p { + margin: 0; +} + +.v2-truck-features .v2-truck-features__slide--active h4::before { + background: var(--c-accent-red); +} + +.v2-truck-features + .v2-truck-features__images-list + li.v2-truck-features__slide-image--active { + display: flex; + justify-content: center; + position: relative; +} + +.v2-truck-features__slide-image--active::after { + content: ''; + display: block; + height: 100%; + position: absolute; + background: radial-gradient( + 52.65% 52.65% at 50% 56%, + #fff0 31.16%, + #dfdfdf 89.49% + ); + width: 298px; + max-width: 800px; +} + +.v2-truck-features__slide-image--active picture, +.v2-truck-features__slide-image--active img { + display: flex; + height: 275px; + aspect-ratio: 800/740; +} + +.v2-truck-features__text-wrapper { + position: relative; + align-self: flex-start; + padding: 24px 16px 24px 40px; +} + +.v2-truck-features__text-wrapper a:any-link { + color: currentcolor; + text-decoration: underline; + text-decoration-color: var(--link-color); +} + +@media (min-width: 744px) { + .v2-truck-features .v2-truck-features__heading { + width: 368px; + padding: 100px 0 0 32px; + } + + .v2-truck-features .v2-truck-features__slides { + margin: 22px 0; + width: 368px; + flex-grow: 0; + } + + .v2-truck-features .v2-truck-features__slide { + padding: 12px 0 12px 48px; + display: flex; + height: auto; + } + + .v2-truck-features .v2-truck-features__slide h4::before { + height: calc(100% + 24px); + left: -16px; + top: -12px; + } + + .v2-truck-features .v2-truck-features__text-wrapper { + padding: 0; + } + + .v2-truck-features .v2-truck-features__text-wrapper p { + display: none; + } + + .v2-truck-features .v2-truck-features__slide--active p { + display: block; + } + + .v2-truck-features .v2-truck-features__images-list { + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + } + + .v2-truck-features__slide-image--active::after { + width: 51vw; + } + + .v2-truck-features__slide-image--active picture, + .v2-truck-features__slide-image--active img { + width: 50vw; + max-width: 800px; + height: auto; + } + + .v2-truck-features__content { + justify-content: flex-start; + } + + .v2-truck-features.v2-truck-features--image-left .v2-truck-features__content { + align-items: flex-end; + } + + .v2-truck-features.v2-truck-features--image-left + .v2-truck-features__images-list { + left: 0; + right: unset; + } +} + +@media (min-width: 1200px) { + .v2-truck-features { + --image-margin: calc((min(1440px, 100vw) - 1040px) / -2); + } + + .v2-truck-features .v2-truck-features__slide { + padding-left: 36px; + } + + .v2-truck-features__slide-image--active::after { + margin-right: var(--image-margin); + } + + .v2-truck-features .v2-truck-features__slide-image--active picture { + margin-right: var(--image-margin); + } + + .v2-truck-features.v2-truck-features--image-left picture { + margin-left: var(--image-margin); + margin-right: unset; + } + + .v2-truck-features--image-left + .v2-truck-features__slide-image--active::after { + margin-left: var(--image-margin); + margin-right: unset; + } +} + +@media (min-width: 1440px) { + .v2-truck-features__slide-image--active picture, + .v2-truck-features__slide-image--active img, + .v2-truck-features__slide-image--active::after { + min-width: 800px; + } +} diff --git a/blocks/v2-truck-features/v2-truck-features.js b/blocks/v2-truck-features/v2-truck-features.js new file mode 100644 index 000000000..d01836924 --- /dev/null +++ b/blocks/v2-truck-features/v2-truck-features.js @@ -0,0 +1,184 @@ +import { + createElement, unwrapDivs, variantsClassesToBEM, +} from '../../scripts/common.js'; +import { getAllElWithChildren } from '../../scripts/scripts.js'; + +const blockName = 'v2-truck-features'; +const desktopMQ = window.matchMedia('(min-width: 1200px)'); +const DESKTOP_SCROLL_PADDING = 200; +const MOBILE_SCROLL_PADDING = 400; +let slideScrollPaddingInPx = desktopMQ.matches ? DESKTOP_SCROLL_PADDING : MOBILE_SCROLL_PADDING; + +desktopMQ.addEventListener('change', (event) => { + slideScrollPaddingInPx = event.matches ? DESKTOP_SCROLL_PADDING : MOBILE_SCROLL_PADDING; +}); + +const selectImagesList = (slide) => { + const imagesLists = [...getAllElWithChildren(slide.querySelectorAll('ul'), ':scope > li > picture')]; + + if (imagesLists === 0) { + return; + } + + imagesLists.forEach((el) => { + el.style.display = 'none'; + }); + + const selectedImagesListIndex = desktopMQ.matches ? '-1' : '0'; + + imagesLists.at(selectedImagesListIndex).style.display = 'block'; + imagesLists.at(selectedImagesListIndex).classList.add(`${blockName}__images-list`); +}; + +const setContentWrapperHeight = (wrapper, slidesCount) => { + const navHeight = getComputedStyle(document.documentElement).getPropertyValue('--nav-height'); + const navHeightInPx = Number.parseInt(navHeight, 10); // assuming that the --nav-height is in px + const inPageNav = getComputedStyle(document.documentElement).getPropertyValue('--inpage-navigation-height'); + // assuming that the --inpage-navigation-height is in px + const inPageNavInPx = Number.parseInt(inPageNav, 10); + const windowHeightInPx = window.innerHeight; + const availableViewportInPx = windowHeightInPx - navHeightInPx - inPageNavInPx; + // wrapper height is the viewport height without navigations + // (to make sure that the slide will fit inside the block) + scroll padding for every slide + const wrapperHeight = slideScrollPaddingInPx * slidesCount + availableViewportInPx; + wrapper.style.height = `${wrapperHeight}px`; +}; + +export default async function decorate(block) { + const activeSlideClass = `${blockName}__slide--active`; + const activeSlideImageClass = `${blockName}__slide-image--active`; + const variantClasses = ['image-left']; + variantsClassesToBEM(block.classList, variantClasses, blockName); + + [...block.querySelectorAll(':scope > div')].forEach(unwrapDivs); + + const heading = block.querySelector(':scope > div > :is(h1, h2, h3, h4, h5, h6)'); + const rows = [...block.querySelectorAll(':scope > div')].slice(1); + const list = createElement('ul', { classes: `${blockName}__slides` }); + const contentEl = createElement('div', { classes: `${blockName}__content` }); + + heading.parentElement.replaceWith(heading); + heading.classList.add(`${blockName}__heading`); + + contentEl.append(heading, list); + block.append(contentEl); + + // moving the rows to list + rows.forEach((el) => { + const newEl = createElement('li', { classes: `${blockName}__slide` }); + const textWrapper = createElement('div', { classes: `${blockName}__text-wrapper` }); + + newEl.innerHTML = el.innerHTML; + + const descriptionHeading = newEl.querySelector(':scope > :is(h1, h2, h3, h4, h5, h6)'); + const description = newEl.querySelector(':scope > p'); + + descriptionHeading.replaceWith(textWrapper); + textWrapper.append(descriptionHeading, description); + + el.remove(); + list.append(newEl); + selectImagesList(newEl); + }); + + const slidesCount = list.querySelectorAll(`.${blockName}__images-list picture`).length; + setContentWrapperHeight(block, slidesCount); + + // setting the first slide as active + let activeSlide = list.children[0]; + activeSlide.classList.add(activeSlideClass); + // setting the first image in the first slide active + let activePicListItem = activeSlide.querySelector(`.${blockName}__images-list li`); + activePicListItem.classList.add(activeSlideImageClass); + + const showNextSlide = () => { + const nextImageInSlide = block.querySelector(`.${activeSlideImageClass} + li`); + let hasNextSlide = true; + + // if there is a next image in the same slide just switch image + if (nextImageInSlide) { + activePicListItem.classList.remove(activeSlideImageClass); + nextImageInSlide.classList.add(activeSlideImageClass); + activePicListItem = nextImageInSlide; + } else { + // if no next image in slide switch to next slide + const nextSlide = block.querySelector(`.${activeSlideClass} + li`); + + if (nextSlide) { + activeSlide.classList.remove(activeSlideClass); + nextSlide.classList.add(activeSlideClass); + activeSlide = nextSlide; + + activePicListItem.classList.remove(activeSlideImageClass); + activePicListItem = nextSlide.querySelector(`.${blockName}__images-list li`); + activePicListItem.classList.add(activeSlideImageClass); + } else { + hasNextSlide = false; + } + } + + return hasNextSlide; + }; + + const showPrevSlide = () => { + const prevImageInSlide = block.querySelector(`.${activeSlideImageClass}`).previousElementSibling; + let hasPrevSlide = true; + + // if there is a prev image in the same slide just switch image + if (prevImageInSlide) { + activePicListItem.classList.remove(activeSlideImageClass); + prevImageInSlide.classList.add(activeSlideImageClass); + activePicListItem = prevImageInSlide; + } else { + // if no prev image in slide switch to prev slide + const prevSlide = block.querySelector(`.${activeSlideClass}`).previousElementSibling; + + if (prevSlide) { + activeSlide.classList.remove(activeSlideClass); + prevSlide.classList.add(activeSlideClass); + activeSlide = prevSlide; + + activePicListItem.classList.remove(activeSlideImageClass); + activePicListItem = prevSlide.querySelector(`.${blockName}__images-list li:last-of-type`); + activePicListItem.classList.add(activeSlideImageClass); + } else { + hasPrevSlide = false; + } + } + + return hasPrevSlide; + }; + + let slideIndex = 0; + + window.addEventListener('scroll', () => { + const navHeight = getComputedStyle(document.documentElement).getPropertyValue('--nav-height'); + const navHeightInPx = Number.parseInt(navHeight, 10); // assuming that the --nav-height is in px + const inPageNav = getComputedStyle(document.documentElement).getPropertyValue('--inpage-navigation-height'); + // assuming that the --inpage-navigation-height is in px + const inPageNavInPx = Number.parseInt(inPageNav, 10); + const { top: blockTopPosition, bottom: blockBottomPosition } = block.getBoundingClientRect(); + + if ( + blockTopPosition < navHeightInPx + inPageNavInPx + && blockBottomPosition > navHeightInPx + inPageNavInPx + ) { + const blockScrollInPx = Math.abs(blockTopPosition - navHeightInPx - inPageNavInPx); + const newSlideIndex = Math.floor(blockScrollInPx / slideScrollPaddingInPx); + + if (newSlideIndex > slidesCount) { + return; + } + + if (newSlideIndex > slideIndex) { + showNextSlide(); + } + + if (newSlideIndex < slideIndex) { + showPrevSlide(); + } + + slideIndex = newSlideIndex; + } + }); +} diff --git a/styles/styles.css b/styles/styles.css index 49058a87f..8365e8ca9 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -867,7 +867,7 @@ main .section.responsive-title h1 { --easing-standard: cubic-bezier(0.2, 0, 0.1, 1); /* In page navigation */ - --inpage-navigation-height: 0; + --inpage-navigation-height: 0px; } .redesign-v2 body { From f7074145508a1765514de5aad1821fc4afab8043 Mon Sep 17 00:00:00 2001 From: Syb <133873665+cogniSyb@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:01:17 +0100 Subject: [PATCH 037/250] Add configuration to .gitignore for release #170 (#171) prevent config from being marked as change with release --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 6a3bcc995..b924bb2a6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,8 @@ node_modules helix-importer-ui .DS_Store .idea/* + +fstab.yaml +pull_request_template.md +README.md +config.json From 007d0c26290a2695ff065edcb1ef6c0025a4b6fa Mon Sep 17 00:00:00 2001 From: Syb <133873665+cogniSyb@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:14:40 +0100 Subject: [PATCH 038/250] Update release.yml --- .github/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/release.yml b/.github/release.yml index 6809eb9b5..4b96087a5 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -3,7 +3,7 @@ changelog: exclude: labels: - - ignore-for-release + - Ignore for release categories: - title: Functional requirements labels: From 7156450dfb1090dafaee25be0186063e9e565638 Mon Sep 17 00:00:00 2001 From: Syb <133873665+cogniSyb@users.noreply.github.com> Date: Wed, 22 Nov 2023 19:54:26 +0100 Subject: [PATCH 039/250] Update fstab.yaml for redesign --- fstab.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fstab.yaml b/fstab.yaml index 1772b0b51..1b9bab1c5 100644 --- a/fstab.yaml +++ b/fstab.yaml @@ -1,2 +1,2 @@ mountpoints: - /: https://adobe.sharepoint.com/sites/HelixProjects/Shared%20Documents/sites/VolvoGroup/vg-macktrucks-com + /: https://adobe.sharepoint.com/sites/HelixProjects/Shared%20Documents/sites/VolvoGroup/vg-macktrucks-com-redesign From 240fd1835d157e93a97ca9b03208b0b5f1b8347b Mon Sep 17 00:00:00 2001 From: Syb Wartna Date: Wed, 22 Nov 2023 23:03:38 +0100 Subject: [PATCH 040/250] change url and sharepoint to upstream urls --- .github/pull_request_template.md | 4 ++-- README.md | 2 +- fstab.yaml | 2 +- tools/sidekick/config.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a45be7524..25e4336f5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,5 @@ Fix # Test URLs: -- Before: https://main--vg-macktrucks-com--hlxsites.hlx.page/ -- After: https://--vg-macktrucks-com--hlxsites.hlx.page/ +- Before: https://main--vg-macktrucks-com-rd--netcentric.hlx.page/ +- After: https://--vg-macktrucks-com-rd--netcentric.hlx.page/ diff --git a/README.md b/README.md index 907bd842f..d6dae712f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Franklin site redesign for macktrucks.com ## Environments -- Preview: https://main--vg-macktrucks-com--hlxsites.hlx.page/ +- Preview: https://main--vg-macktrucks-com-rd--netcentric.hlx.page/ - Live: https://main--vg-macktrucks-com--hlxsites.hlx.live/ ## Installation diff --git a/fstab.yaml b/fstab.yaml index 1772b0b51..1b9bab1c5 100644 --- a/fstab.yaml +++ b/fstab.yaml @@ -1,2 +1,2 @@ mountpoints: - /: https://adobe.sharepoint.com/sites/HelixProjects/Shared%20Documents/sites/VolvoGroup/vg-macktrucks-com + /: https://adobe.sharepoint.com/sites/HelixProjects/Shared%20Documents/sites/VolvoGroup/vg-macktrucks-com-redesign diff --git a/tools/sidekick/config.json b/tools/sidekick/config.json index 443aa8215..c21a54dcf 100644 --- a/tools/sidekick/config.json +++ b/tools/sidekick/config.json @@ -5,7 +5,7 @@ "id": "library", "title": "Library", "environments": [ "edit" ], - "url": "https://main--vg-macktrucks-com--hlxsites.hlx.live/tools/sidekick/library.html", + "url": "https://main--vg-macktrucks-com-rd--netcentric.hlx.live/tools/sidekick/library.html", "includePaths": [ "**.docx**" ] }, { From 406f3bfe70e1c776179e505882b185ccaa8caf20 Mon Sep 17 00:00:00 2001 From: Syb Wartna Date: Wed, 22 Nov 2023 23:03:38 +0100 Subject: [PATCH 041/250] change url and sharepoint to upstream urls --- .github/pull_request_template.md | 4 ++-- README.md | 2 +- fstab.yaml | 2 +- tools/sidekick/config.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a45be7524..25e4336f5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,5 @@ Fix # Test URLs: -- Before: https://main--vg-macktrucks-com--hlxsites.hlx.page/ -- After: https://--vg-macktrucks-com--hlxsites.hlx.page/ +- Before: https://main--vg-macktrucks-com-rd--netcentric.hlx.page/ +- After: https://--vg-macktrucks-com-rd--netcentric.hlx.page/ diff --git a/README.md b/README.md index 907bd842f..d6dae712f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Franklin site redesign for macktrucks.com ## Environments -- Preview: https://main--vg-macktrucks-com--hlxsites.hlx.page/ +- Preview: https://main--vg-macktrucks-com-rd--netcentric.hlx.page/ - Live: https://main--vg-macktrucks-com--hlxsites.hlx.live/ ## Installation diff --git a/fstab.yaml b/fstab.yaml index 1b9bab1c5..b82eff2dc 100644 --- a/fstab.yaml +++ b/fstab.yaml @@ -1,2 +1,2 @@ mountpoints: - /: https://adobe.sharepoint.com/sites/HelixProjects/Shared%20Documents/sites/VolvoGroup/vg-macktrucks-com-redesign + /: https://adobe.sharepoint.com/sites/HelixProjects/Shared%20Documents/sites/VolvoGroup/vg-macktrucks-com-redesign diff --git a/tools/sidekick/config.json b/tools/sidekick/config.json index 443aa8215..c21a54dcf 100644 --- a/tools/sidekick/config.json +++ b/tools/sidekick/config.json @@ -5,7 +5,7 @@ "id": "library", "title": "Library", "environments": [ "edit" ], - "url": "https://main--vg-macktrucks-com--hlxsites.hlx.live/tools/sidekick/library.html", + "url": "https://main--vg-macktrucks-com-rd--netcentric.hlx.live/tools/sidekick/library.html", "includePaths": [ "**.docx**" ] }, { From 21e352489f464afa3249333f254c57498e6b0e6b Mon Sep 17 00:00:00 2001 From: Syb <133873665+cogniSyb@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:37:17 +0100 Subject: [PATCH 042/250] Inpage navigation is in reversed order on Firefox #173 (#178) * fix Firefox issue with sorting * fix naming issues * refactor focus state, button styles * fix overlap issue --- .../v2-inpage-navigation.css | 54 ++++++++++--------- .../v2-inpage-navigation.js | 12 ++--- scripts/scripts.js | 11 ++-- styles/styles.css | 11 ++++ 4 files changed, 49 insertions(+), 39 deletions(-) diff --git a/blocks/v2-inpage-navigation/v2-inpage-navigation.css b/blocks/v2-inpage-navigation/v2-inpage-navigation.css index 20fc65f85..a7ae9668d 100644 --- a/blocks/v2-inpage-navigation/v2-inpage-navigation.css +++ b/blocks/v2-inpage-navigation/v2-inpage-navigation.css @@ -11,7 +11,7 @@ position: sticky; top: var(--nav-height); width: 100%; - z-index: 2; + z-index: 99; } .v2-inpage-navigation__wrapper { @@ -51,7 +51,7 @@ .v2-inpage-navigation__item button, .v2-inpage-navigation__selected-item-wrapper { background: none; - border: 0; + border: 2px solid transparent; color: var(--c-primary-black); cursor: pointer; display: block; @@ -59,8 +59,11 @@ font-size: var(--body-2-font-size); line-height: var(--body-2-line-height); margin: 0; - padding: 14px 24px; + padding: 10px 20px; width: 100%; + transition: background-color var(--duration-small) var(--easing-standard), + color var(--duration-small) var(--easing-standard), + border-color var(--duration-small) var(--easing-standard); } /* stylelint-disable-next-line no-descending-specificity */ @@ -75,6 +78,14 @@ background-color: #f1f1f1; } +.v2-inpage-navigation__item button:focus { + outline: 0; +} + +.v2-inpage-navigation__item button:focus-visible { + border-color: var(--border-focus); +} + /* stylelint-disable-next-line no-descending-specificity */ .v2-inpage-navigation__item button { max-width: none; @@ -109,25 +120,10 @@ /* 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); +a.v2-inpage-navigation__cta:any-link { + margin: -1px 0 0; + min-width: auto; + border-radius: 0; } .v2-inpage-navigation__cta:active { @@ -180,6 +176,12 @@ position: relative; } + .v2-inpage-navigation__item button, + a.v2-inpage-navigation__cta:any-link { + margin: 0; + border-radius: 2px; + } + .v2-inpage-navigation__item button:hover, .v2-inpage-navigation__item button:focus { background: none; @@ -201,10 +203,10 @@ background-color: var(--c-accent-red); } - /* Red button */ - .v2-inpage-navigation__cta:any-link { - border-radius: 2px; - padding: 15px 20px; + .v2-inpage-navigation__item button:focus-visible { + border-color: transparent; + outline: 2px solid var(--border-focus); + outline-offset: 2px; } .v2-inpage-navigation__cta--mobile { diff --git a/blocks/v2-inpage-navigation/v2-inpage-navigation.js b/blocks/v2-inpage-navigation/v2-inpage-navigation.js index 3988406a1..58e1ad81b 100644 --- a/blocks/v2-inpage-navigation/v2-inpage-navigation.js +++ b/blocks/v2-inpage-navigation/v2-inpage-navigation.js @@ -22,13 +22,13 @@ const scrollToSection = (id) => { resizeObserver.observe(main); }; -const inpageNavigationRedButton = () => { +const inpageNavigationButton = () => { // 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`, + classes: ['button', 'button--large', 'button--cta', `${blockName}__cta`], props: { href: url, title: titleMobile, @@ -95,7 +95,7 @@ const updateActive = (id) => { // Remove focus position document.activeElement.blur(); - // check active id is equal to id dont do anything + // check active id is equal to id don't do anything const selectedItem = document.querySelector(`.${blockName}__selected-item`); activeItemInList?.classList.remove(`${blockName}__item--active`); const itemsButton = document.querySelectorAll(`.${blockName}__items button`); @@ -114,7 +114,7 @@ const updateActive = (id) => { }; export default async function decorate(block) { - const redButton = inpageNavigationRedButton(); + const ctaButton = inpageNavigationButton(); const wrapper = block.querySelector(':scope > div'); wrapper.classList.add(`${blockName}__wrapper`); @@ -149,8 +149,8 @@ export default async function decorate(block) { itemsWrapper.remove(); - if (redButton) { - wrapper.appendChild(redButton); + if (ctaButton) { + wrapper.appendChild(ctaButton); } list.addEventListener('click', gotoSection); diff --git a/scripts/scripts.js b/scripts/scripts.js index 64c13f91e..9295275d2 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -323,13 +323,10 @@ const createInpageNavigation = (main) => { // Sort the object by order const sortedObject = tabItemsObj.slice().sort((obj1, obj2) => { - if (!obj1.order) { - return 1; // Move 'a' to the end - } - if (!obj2.order) { - return -1; // Move 'b' to the end - } - return obj1.order - obj2.order; + const order1 = obj1.order ?? Infinity; // Fallback to a large number if 'order' is not present + const order2 = obj2.order ?? Infinity; + + return order1 - order2; }); // From the array of objects create the DOM diff --git a/styles/styles.css b/styles/styles.css index 8365e8ca9..3c1f1aed3 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -958,6 +958,17 @@ main .section.responsive-title h1 { border-color var(--duration-small) var(--easing-standard); } +.redesign-v2 a.button:focus, +.redesign-v2 button.button:focus { + outline: 0; +} + +.redesign-v2 a.button:focus-visible, +.redesign-v2 button.button:focus-visible { + outline: 2px solid var(--border-focus); + outline-offset: 2px; +} + .redesign-v2 a.button--small, .redesign-v2 button.button--small { min-width: 96px; From 976bfd5083823e8fb15354757a08aec64964a1ce Mon Sep 17 00:00:00 2001 From: Syb <133873665+cogniSyb@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:37:34 +0100 Subject: [PATCH 043/250] Hero V2 Block displays different images for mobile and desktop. #106 (#179) * refactor naming structure * refactor adaptive images mechanism --- blocks/v2-hero/v2-hero.js | 53 +++++++++++++++++++------ scripts/common.js | 81 +++++++++++++++++++++++++++++++++++++++ scripts/scripts.js | 45 ---------------------- 3 files changed, 122 insertions(+), 57 deletions(-) diff --git a/blocks/v2-hero/v2-hero.js b/blocks/v2-hero/v2-hero.js index 2ae2becea..d51f923fd 100644 --- a/blocks/v2-hero/v2-hero.js +++ b/blocks/v2-hero/v2-hero.js @@ -1,32 +1,61 @@ +import { + getImageURLs, + createResponsivePicture, +} from '../../scripts/common.js'; + export default async function decorate(block) { - const heroClass = 'v2-hero'; - const picture = block.querySelector('picture'); - const img = picture.querySelector('img'); - img.classList.add(`${heroClass}__image`); + const blockName = 'v2-hero'; + const images = [...block.querySelectorAll('p > picture')]; + const imageURLs = getImageURLs(images); + const imageData = imageURLs.map((src) => ({ src, breakpoints: [] })); + + if (imageData.length === 1) { + imageData[0].breakpoints = [ + { media: '(min-width: 600px)', width: 600 }, + { media: '(min-width: 1200px)', width: 1200 }, + { media: '(min-width: 1440px)', width: 1440 }, + { media: '(min-width: 1920px)', width: 1920 }, + { width: 750 }, + ]; + } - if (picture.parentElement.tagName === 'P') { - picture.parentElement.remove(); + if (imageData.length > 1) { + imageData[0].breakpoints = [ + { media: '(min-width: 600px)', width: 600 }, + { width: 750 }, + ]; + + imageData[1].breakpoints = [ + { media: '(min-width: 1200px)', width: 1200 }, + { media: '(min-width: 1440px)', width: 1440 }, + { media: '(min-width: 1920px)', width: 1920 }, + ]; } + const altText = [...block.querySelectorAll('p > picture img.alt')]; + const newPicture = createResponsivePicture(imageData, true, altText, `${blockName}__image`); + images.forEach((image) => image.parentNode.remove()); + + block.prepend(newPicture); + const contentWrapper = block.querySelector(':scope > div'); - contentWrapper.classList.add(`${heroClass}__content-wrapper`); + contentWrapper.classList.add(`${blockName}__content-wrapper`); const content = block.querySelector(':scope > div > div'); - content.classList.add(`${heroClass}__content`); + content.classList.add(`${blockName}__content`); const headings = [...content.querySelectorAll('h1, h2, h3, h4, h5, h6')]; - headings.forEach((h) => h.classList.add(`${heroClass}__heading`)); + headings.forEach((h) => h.classList.add(`${blockName}__heading`)); const firstHeading = headings[0]; firstHeading.classList.add('with-marker'); const ctaButtons = content.querySelectorAll('.button-container > a'); [...ctaButtons].forEach((b) => { - b.classList.add(`${heroClass}__cta`, 'button--cta'); + b.classList.add(`${blockName}__cta`, 'button--cta'); b.classList.remove('button--primary'); - b.parentElement.classList.add(`${heroClass}__cta-wrapper`); + b.parentElement.classList.add(`${blockName}__cta-wrapper`); }); - block.prepend(picture); block.parentElement.classList.add('full-width'); } diff --git a/scripts/common.js b/scripts/common.js index 8b470ec79..f58272e17 100644 --- a/scripts/common.js +++ b/scripts/common.js @@ -253,3 +253,84 @@ export const adjustPretitle = (element) => { } }); }; + +/** + * Extracts the URL without query parameters of images from an array of picture elements + * @param {HTMLElement} images - An array of picture elements + * @returns {Array} Array of src strings + */ +export function getImageURLs(pictures) { + return pictures.map((picture) => { + const imgElement = picture.querySelector('img'); + return imgElement.getAttribute('src').split('?')[0]; + }); +} + +/** + * Creates a picture element based on provided image data and breakpoints + * @param {Array} images - Array of objects defining image data and breakpoints + * @param {boolean} eager - Whether to load images eagerly + * @param {string} alt - Alt text for the image + * @param {string[]|string} imageClass - Class for the image + * @returns {HTMLElement} The created picture element + */ +export function createResponsivePicture(images, eager, alt, imageClass) { + const picture = document.createElement('picture'); + let fallbackWidth = ''; + let fallbackSrc = ''; + + function constructSrcset(src, width, format) { + const baseUrl = `${src}?format=${format}&optimize=medium`; + return `${baseUrl}&width=${width} 1x, ${baseUrl}&width=${width * 2} 2x`; + } + + images.forEach((image) => { + const originalFormat = image.src.split('.').pop(); + + image.breakpoints.forEach((bp) => { + if (!bp.media) return; + + const srcsetWebp = constructSrcset(image.src, bp.width, 'webp'); + const srcsetOriginal = constructSrcset(image.src, bp.width, originalFormat); + + const webpSource = createElement('source', { + props: { + type: 'image/webp', + srcset: srcsetWebp, + media: bp.media, + }, + }); + + const originalSource = createElement('source', { + props: { + type: `image/${originalFormat}`, + srcset: srcsetOriginal, + media: bp.media, + }, + }); + + picture.insertBefore(originalSource, picture.firstChild); + picture.insertBefore(webpSource, originalSource); + }); + + const fallbackBreakpoint = image.breakpoints.find((bp) => !bp.media); + if (fallbackBreakpoint && !fallbackSrc) { + fallbackWidth = fallbackBreakpoint.width; + fallbackSrc = `${image.src}?width=${fallbackWidth}&format=${originalFormat}&optimize=medium`; + } + }); + + const img = createElement('img', { + classes: imageClass, + props: { + src: fallbackSrc, + alt, + loading: eager ? 'eager' : 'lazy', + width: fallbackWidth, + }, + }); + + picture.appendChild(img); + + return picture; +} diff --git a/scripts/scripts.js b/scripts/scripts.js index 9295275d2..1c7865980 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -16,7 +16,6 @@ import { toCamelCase, toClassName, loadScript, - getHref, createOptimizedPicture, } from './lib-franklin.js'; @@ -152,50 +151,6 @@ export function findAndCreateImageLink(node) { } }); } -/** - * Returns a picture element with webp and fallbacks / allow multiple src paths for every breakpoint - * @param {string} src Default image URL (if no src is passed to breakpoints object) - * @param {boolean} eager load image eager - * @param {Array} breakpoints breakpoints and corresponding params (eg. src, width, media) - */ -export function createCustomOptimizedPicture(src, alt = '', eager = false, breakpoints = [{ media: '(min-width: 400px)', width: '2000' }, { width: '750' }]) { - const url = new URL(src, getHref()); - const picture = document.createElement('picture'); - let { pathname } = url; - const ext = pathname.substring(pathname.lastIndexOf('.') + 1); - - breakpoints.forEach((br) => { - // custom src path in breakpoint - if (br.src) { - const customUrl = new URL(br.src, getHref()); - pathname = customUrl.pathname; - } - - const source = document.createElement('source'); - if (br.media) source.setAttribute('media', br.media); - source.setAttribute('type', 'image/webp'); - source.setAttribute('srcset', `${pathname}?width=${br.width}&format=webply&optimize=medium`); - picture.appendChild(source); - }); - - // fallback - breakpoints.forEach((br, j) => { - if (j < breakpoints.length - 1) { - const source = document.createElement('source'); - if (br.media) source.setAttribute('media', br.media); - source.setAttribute('srcset', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`); - picture.appendChild(source); - } else { - const image = document.createElement('img'); - image.setAttribute('loading', eager ? 'eager' : 'lazy'); - image.setAttribute('alt', alt); - picture.appendChild(image); - image.setAttribute('src', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`); - } - }); - - return picture; -} /** * Builds hero block and prepends to main in a new section. From 8319bb0dae01396823d207cc484952d7cb0822d1 Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Fri, 27 Oct 2023 15:35:57 +0200 Subject: [PATCH 044/250] 483 configure footer --- blocks/footer/footer.css | 19 +++++++++++++++ blocks/footer/footer.js | 51 ++++++++++++++++++++++++++++++---------- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index b9ee02ef9..6bbe9f35a 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -507,3 +507,22 @@ margin: 0 auto; } } + +/* cutsom footer styles */ +.footer__custom .footer-menu { + grid-template-areas: 'logo' + 'social'; +} + +@media (min-width: 1140px) { + .footer__custom .footer-menu { + grid-template-columns: repeat(2, 1fr); + grid-template-rows: 1fr; + /* stylelint-disable-next-line declaration-block-no-redundant-longhand-properties */ + grid-template-areas: 'logo social'; + } + + .footer__custom .footer-menu__socialmedia { + justify-self: flex-end; + } +} diff --git a/blocks/footer/footer.js b/blocks/footer/footer.js index 1e5708ee9..4348b05de 100644 --- a/blocks/footer/footer.js +++ b/blocks/footer/footer.js @@ -1,4 +1,9 @@ -import { readBlockConfig, decorateIcons, loadBlocks } from '../../scripts/lib-franklin.js'; +import { + readBlockConfig, + decorateIcons, + loadBlocks, + getMetadata, +} from '../../scripts/lib-franklin.js'; import { createElement, getTextLabel, isEloquaFormAllowed, } from '../../scripts/common.js'; @@ -78,7 +83,14 @@ export default async function decorate(block) { const cfg = readBlockConfig(block); block.textContent = ''; - const footerPath = cfg.footer || '/footer'; + let footerPath = cfg.footer || '/footer'; + const isCustomFooter = getMetadata('custom-footer'); + + if (isCustomFooter) { + footerPath = isCustomFooter; + block.classList.add(`${blockName}__custom`); + } + const resp = await fetch(`${footerPath}.plain.html`); const html = await resp.text(); @@ -157,11 +169,16 @@ export default async function decorate(block) { // Menu Columns: Newsletter form const newsletter = createElement('div', { classes: blockNameNewsletter }); const oldNews = footerMenu.querySelector(':scope > div:last-child'); - newsletter.appendChild(oldNews); + + if (oldNews) { + newsletter.appendChild(oldNews); + } const eloquaForm = block.querySelector('.eloqua-form'); - eloquaForm?.setAttribute('data-block-name', 'eloqua-form'); - newsletter.append(eloquaForm); + if (eloquaForm) { + eloquaForm?.setAttribute('data-block-name', 'eloqua-form'); + newsletter.append(eloquaForm); + } addClassToTitle(newsletter, `${blockNameNewsletter}__title`); // Menu Columns: menu @@ -170,8 +187,14 @@ export default async function decorate(block) { const menuList = menu.querySelectorAll(':scope > div'); menuList.forEach((item) => item.classList.add(`${blockNameMenu}__column`)); - newMenu.appendChild(menu); - newMenu.appendChild(newsletter); + if (menu.children.length) { + newMenu.appendChild(menu); + } + + if (newsletter.children.length) { + newMenu.appendChild(newsletter); + } + newFooter.append(newMenu); } @@ -243,12 +266,14 @@ export default async function decorate(block) { }; const eloquaForm = block.querySelector('.eloqua-form'); - observer = new MutationObserver(onFormLoaded); - observer.observe(eloquaForm, { - childList: true, - attributes: false, - subtree: true, - }); + if (eloquaForm) { + observer = new MutationObserver(onFormLoaded); + observer.observe(eloquaForm, { + childList: true, + attributes: false, + subtree: true, + }); + } block.addEventListener('click', (e) => { if (e.target.classList.contains(`${blockNameTruckList}__title`)) { From 2d95815a0e7157d0536e9d62388eba12c3052c4f Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Mon, 20 Nov 2023 14:48:20 +0530 Subject: [PATCH 045/250] 498, 496 wire up prod url and refresh date api --- blocks/vin-number/vin-number.css | 4 + blocks/vin-number/vin-number.js | 221 ++++++++++++++++++++++--------- 2 files changed, 159 insertions(+), 66 deletions(-) diff --git a/blocks/vin-number/vin-number.css b/blocks/vin-number/vin-number.css index 118b379b9..4ff9385a3 100644 --- a/blocks/vin-number/vin-number.css +++ b/blocks/vin-number/vin-number.css @@ -2,6 +2,10 @@ padding: 0; } +.vin-number__refresh-date-wrapper { + text-align: center; +} + .vin-number__form { display: flex; gap: 8px; diff --git a/blocks/vin-number/vin-number.js b/blocks/vin-number/vin-number.js index a1bb75ce4..6e31d0f6c 100644 --- a/blocks/vin-number/vin-number.js +++ b/blocks/vin-number/vin-number.js @@ -1,9 +1,22 @@ import { getTextLabel, createElement, getJsonFromUrl } from '../../scripts/common.js'; -const VIN_URL = 'https://vinlookup-dev-euw-ase-01.azurewebsites.net/v1/api/vin/'; -const API_KEY = '0e13506b59674706ad9bae72d94fc83c'; - const docRange = document.createRange(); +const blockName = 'vin-number'; + +const apiConfig = { + dev: { + url: 'https://vinlookup-dev-euw-ase-01.azurewebsites.net/v1/api/', + key: '0e13506b59674706ad9bae72d94fc83c', + }, + qa: { + url: 'https://vinlookup-qa-euw-ase-01.azurewebsites.net/v1/api/', + key: '0e13506b59674706ad9bae72d94fc83c', + }, + prod: { + url: 'https://vinlookup-prod-euw-ase-01.azurewebsites.net/v1/api/', + key: '0e13506b59674706ad9bae72d94fc83c', + }, +}; // list of things to be display for each recall const valueDisplayList = [{ @@ -20,17 +33,17 @@ const valueDisplayList = [{ }, { key: 'recall_description', - class: 'vin-number__detail-item--column', + class: `${blockName}__detail-item--column`, }, { key: 'safety_risk_description', - class: 'vin-number__detail-item--column', + class: `${blockName}__detail-item--column`, }, { key: 'remedy_description', - class: 'vin-number__detail-item--column', + class: `${blockName}__detail-item--column`, }, { key: 'mfr_notes', - class: 'vin-number__detail-item--column', + class: `${blockName}__detail-item--column`, }]; // use this to map values from API @@ -40,52 +53,109 @@ const recallStatus = { 12: 'recall_incomplete_no_remedy', }; +function getAPIConfig() { + let env = 'prod'; + + if (window.location.host.includes('hlx.page')) { + env = 'qa'; + } else if (window.location.host.includes('localhost')) { + env = 'dev'; + } + + return apiConfig[env]; +} + +function getStorageItem(key) { + // get the parsed value of the given key + const result = JSON.parse(window.localStorage.getItem(key)); + + // if the key has value + if (result) { + // if the entry is expired remove the entry and return null + if (result.expireTime <= Date.now()) { + window.localStorage.removeItem(key); + return null; + } + // else return the value + return result.data; + } + // if the key does not have value + return null; +} + +function setStorageItem(key, value) { + // store the value as object along with expiry date + const result = { + data: value, + expireTime: Date.now() + (60 * 60 * 1000), // set the expiry from the current date for a day + }; + + // stringify the result and the data in original storage + window.localStorage.setItem(key, JSON.stringify(result)); +} + +async function fetchRefreshDate() { + const refreshDate = getStorageItem('refreshDate-MT'); + if (!refreshDate) { + const { url, key } = getAPIConfig(); + try { + const response = await getJsonFromUrl(`${url}refreshdate?api_key=${key}`); + setStorageItem('refreshDate-MT', response.refresh_date); + return response.refresh_date; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error fetching refresh date:', error); + } + } + return refreshDate; +} + function capitalize(text) { return text.toLowerCase().split('').map((char, index) => (index === 0 ? char.toUpperCase() : char)).join(''); } function renderRecalls(recallsData) { - const resultText = document.querySelector('.vin-number__results-text'); + const resultText = document.querySelector(`.${blockName}__results-text`); resultText.innerText = getTextLabel('result text').replace(/\${count}/, recallsData.number_of_recalls).replace(/\${vin}/, recallsData.vin); if (recallsData.recalls_available) { - const blockEl = document.querySelector('.vin-number__recalls-wrapper'); + const blockEl = document.querySelector(`.${blockName}__recalls-wrapper`); const listWrapperFragment = docRange.createContextualFragment(` -
    - +
    + -

    ${getTextLabel('recalls')}    

    -

    [${getTextLabel('published_info')}: ${recallsData.refresh_date} | ${getTextLabel('recall_oldest_info')}]

    +

    ${getTextLabel('recalls')}    

    +

    [${getTextLabel('published_info')}: ${recallsData.refresh_date} | ${getTextLabel('recall_oldest_info')}]

    `); // create each recall - const list = createElement('ul', { classes: 'vin-number__list' }); + const list = createElement('ul', { classes: `${blockName}__list` }); recallsData.recalls.forEach((recall) => { const liEl = createElement('li', { - classes: 'vin-number__list-item', + classes: `${blockName}__list-item`, }); // map the number from api to correct status recall.mfr_recall_status = recallStatus[recall.mfr_recall_status]; - const recallDetailsList = createElement('ul', { classes: 'vin-number__detail-list' }); + const recallDetailsList = createElement('ul', { classes: `${blockName}__detail-list` }); valueDisplayList.forEach((item) => { if (recall[item.key]) { - const recallClass = item.key === 'mfr_recall_status' ? `vin-number__${recall.mfr_recall_status.replace(/_/g, '-').toLowerCase()}` : ''; + const recallClass = item.key === 'mfr_recall_status' ? `${blockName}__${recall.mfr_recall_status.replace(/_/g, '-').toLowerCase()}` : ''; let itemValue = item.class ? capitalize(recall[item.key]) : recall[item.key]; if (recallClass) { itemValue = getTextLabel(recall[item.key]); } - const itemFragment = docRange.createContextualFragment(`
  • -
    ${getTextLabel(item.key)}
    - ${itemValue} + const itemFragment = docRange.createContextualFragment(`
  • +
    ${getTextLabel(item.key)}
    + ${itemValue}
  • `); recallDetailsList.append(...itemFragment.children); } @@ -106,10 +176,10 @@ async function fetchRecalls(e) { const submitBtn = e.target.querySelector('button'); submitBtn.disabled = true; - const recalls = document.querySelector('.vin-number__recalls-wrapper'); + const recalls = document.querySelector(`.${blockName}__recalls-wrapper`); recalls.innerHTML = ''; - const resultText = document.querySelector('.vin-number__results-text'); + const resultText = document.querySelector(`.${blockName}__results-text`); resultText.innerText = getTextLabel('loading recalls'); const formData = new FormData(e.target); @@ -117,7 +187,8 @@ async function fetchRecalls(e) { if (vin) { try { - getJsonFromUrl(`${VIN_URL}${vin}?api_key=${API_KEY}&mode=company`) + const { url, key } = getAPIConfig(); + getJsonFromUrl(`${url}vin/${vin}?api_key=${key}&mode=company`) .then((response) => { if (response.error_code) { resultText.innerHTML = `${getTextLabel('no recalls')} ${vin}`; @@ -142,48 +213,66 @@ async function fetchRecalls(e) { } export default async function decorate(block) { - const form = createElement('form', { - classes: ['vin-number__form'], - }); - const formChildren = document.createRange().createContextualFragment(` -
    - - -
    - - `); - - const vinResultsContainer = createElement('div', { classes: 'vin-number__results-container' }); - const innerContent = docRange.createContextualFragment(` - -
    - `); - - vinResultsContainer.append(innerContent); - - form.addEventListener('submit', fetchRecalls, false); - form.append(...formChildren.children); - block.append(form); - block.append(vinResultsContainer); - - const vinInput = block.querySelector('.vin-number__input'); - - vinInput.oninvalid = (e) => { - e.target.setCustomValidity(getTextLabel('vinformat')); - }; + fetchRefreshDate().then((response) => { + let refreshDate = response || 'XX-XX-XXXX'; + if (response.refreshDate) { + refreshDate = new Date(response.refreshDate).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric' }); + } - vinInput.oninput = (e) => { - e.target.setCustomValidity(''); - }; + const refresDateWrapper = createElement('div', { + classes: `${blockName}__refresh-date-wrapper`, + }); + const refreshFragment = docRange.createContextualFragment(` + ${getTextLabel('published_info')}: + + + ${refreshDate} + `); + + const form = createElement('form', { + classes: [`${blockName}__form`], + }); + const formChildren = document.createRange().createContextualFragment(` +
    + + +
    + + `); + + const vinResultsContainer = createElement('div', { classes: `${blockName}__results-container` }); + const innerContent = docRange.createContextualFragment(` + +
    + `); + + vinResultsContainer.append(innerContent); + + form.addEventListener('submit', fetchRecalls, false); + form.append(...formChildren.children); + refresDateWrapper.append(...refreshFragment.children); + block.append(form, refresDateWrapper); + block.append(vinResultsContainer); + + const vinInput = block.querySelector(`.${blockName}__input`); + + vinInput.oninvalid = (e) => { + e.target.setCustomValidity(getTextLabel('vinformat')); + }; + + vinInput.oninput = (e) => { + e.target.setCustomValidity(''); + }; + }); } From e3eb843cc2fa6188393b5cfd78b54ccf5d91e296 Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Tue, 21 Nov 2023 17:44:49 +0530 Subject: [PATCH 046/250] custom header resolve conflicts --- blocks/header/header.css | 22 ++++++ blocks/header/header.js | 130 +++++++++++++++++++------------- blocks/vin-number/vin-number.js | 2 +- placeholder.json | 2 +- 4 files changed, 103 insertions(+), 53 deletions(-) diff --git a/blocks/header/header.css b/blocks/header/header.css index bfc46641f..eb4131a25 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -1135,3 +1135,25 @@ a.header__link { border-top: solid 1px var(--c-secondary-silver); } } + +/* custom header styles */ +.header__custom .header__actions { + justify-content: flex-end; + margin-left: auto; +} + +.header__custom--black { + background-color: var(--c-primary-black); +} + +.header__custom--black .header__main-nav { + background-color: var(--c-primary-black); +} + +.header__custom--black .header__main-nav-link.header__link { + color: var(--c-primary-white); +} + +.header__custom--black .icon-bulldog svg { + --color-icon: var(--c-primary-white); +} diff --git a/blocks/header/header.js b/blocks/header/header.js index db08e9ced..41b9badea 100644 --- a/blocks/header/header.js +++ b/blocks/header/header.js @@ -1,7 +1,7 @@ import { createElement, generateId, getTextLabel, } from '../../scripts/common.js'; -import { createOptimizedPicture, decorateIcons } from '../../scripts/lib-franklin.js'; +import { createOptimizedPicture, decorateIcons, getMetadata } from '../../scripts/lib-franklin.js'; import { getAllElWithChildren } from '../../scripts/scripts.js'; // domain examples: '.com', 'nicaragua.com', '.com.pa', '.ca' @@ -47,46 +47,48 @@ const createLogo = (logoWrapper) => { const createMainLinks = (mainLinksWrapper) => { const list = mainLinksWrapper.querySelector('ul'); - - list.setAttribute('id', 'header-main-nav'); - list.classList.add(`${blockClass}__main-nav`); - list.querySelectorAll('li').forEach((listItem) => { - const accordionContainer = document.createRange().createContextualFragment(` - `); diff --git a/placeholder.json b/placeholder.json index 4595f01b1..73cec3533 100644 --- a/placeholder.json +++ b/placeholder.json @@ -177,7 +177,7 @@ }, { "Key": "recall_oldest_info", - "Text": "Recall information available since: Jan 15,2008" + "Text": "Recall information available since:" }, { "Key": "All trucks", From 18d185146eb99c92d0a1e876d1be88825e46186e Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Tue, 21 Nov 2023 20:39:23 +0530 Subject: [PATCH 047/250] use icon for laoding arrow-right --- blocks/footer/footer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blocks/footer/footer.js b/blocks/footer/footer.js index 4348b05de..15a00cdc6 100644 --- a/blocks/footer/footer.js +++ b/blocks/footer/footer.js @@ -50,7 +50,7 @@ function addScrollToTopButton(mainEl) { }, }); const svgIcon = document.createRange().createContextualFragment(` - `); + `); scrollToTopButton.append(...svgIcon.children); scrollToTopButton.addEventListener('click', goToTopFunction); @@ -206,6 +206,8 @@ export default async function decorate(block) { block.innerHTML = newFooter.innerHTML; + addScrollToTopButton(block); + await decorateIcons(block); await loadBlocks(block); @@ -280,6 +282,4 @@ export default async function decorate(block) { toggleExpand(e.target); } }); - - addScrollToTopButton(block); } From b21cf385bdc4992c4ab461bb3f20c48ecd27d4c3 Mon Sep 17 00:00:00 2001 From: Lakshmishri Date: Wed, 29 Nov 2023 23:12:53 +0530 Subject: [PATCH 048/250] fix PSI --- blocks/vin-number/vin-number.js | 137 ++++++++++++++++---------------- styles/styles.css | 23 ++++++ 2 files changed, 90 insertions(+), 70 deletions(-) diff --git a/blocks/vin-number/vin-number.js b/blocks/vin-number/vin-number.js index c2e2aa9dd..8bbddef68 100644 --- a/blocks/vin-number/vin-number.js +++ b/blocks/vin-number/vin-number.js @@ -95,19 +95,16 @@ function setStorageItem(key, value) { } async function fetchRefreshDate() { - const refreshDate = getStorageItem('refreshDate-MT'); - if (!refreshDate) { - const { url, key } = getAPIConfig(); - try { - const response = await getJsonFromUrl(`${url}refreshdate?api_key=${key}`); - setStorageItem('refreshDate-MT', response.refresh_date); - return response.refresh_date; - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error fetching refresh date:', error); - } + const { url, key } = getAPIConfig(); + try { + const response = await getJsonFromUrl(`${url}refreshdate?api_key=${key}`); + setStorageItem('refreshDate-MT', response.refresh_date); + return response.refresh_date; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error fetching refresh date:', error); } - return refreshDate; + return null; } function capitalize(text) { @@ -213,66 +210,66 @@ async function fetchRecalls(e) { } export default async function decorate(block) { - fetchRefreshDate().then((response) => { - let refreshDate = response || 'XX-XX-XXXX'; - if (response.refreshDate) { - refreshDate = new Date(response.refreshDate).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric' }); - } - - const refresDateWrapper = createElement('div', { - classes: `${blockName}__refresh-date-wrapper`, - }); - const refreshFragment = docRange.createContextualFragment(` - ${getTextLabel('published_info')}: + const refreshDate = getStorageItem('refreshDate-MT') || ''; + const refresDateWrapper = createElement('div', { + classes: `${blockName}__refresh-date-wrapper`, + }); + const refreshFragment = docRange.createContextualFragment(` + ${getTextLabel('published_info')}: - - ${refreshDate} - `); - - const form = createElement('form', { - classes: [`${blockName}__form`], - }); - const formChildren = document.createRange().createContextualFragment(` -
    - - -
    - - `); + ${refreshDate} + `); - const vinResultsContainer = createElement('div', { classes: `${blockName}__results-container` }); - const innerContent = docRange.createContextualFragment(` - -
    - `); - - vinResultsContainer.append(innerContent); - - form.addEventListener('submit', fetchRecalls, false); - form.append(...formChildren.children); - refresDateWrapper.append(...refreshFragment.children); - block.append(form, refresDateWrapper); - block.append(vinResultsContainer); - - const vinInput = block.querySelector(`.${blockName}__input`); + const form = createElement('form', { + classes: [`${blockName}__form`], + }); + const formChildren = document.createRange().createContextualFragment(` +
    + + +
    + + `); + + const vinResultsContainer = createElement('div', { classes: `${blockName}__results-container` }); + const innerContent = docRange.createContextualFragment(` + +
    + `); + + vinResultsContainer.append(innerContent); + + form.addEventListener('submit', fetchRecalls, false); + form.append(...formChildren.children); + refresDateWrapper.append(...refreshFragment.children); + block.append(form, refresDateWrapper); + block.append(vinResultsContainer); + + const vinInput = block.querySelector(`.${blockName}__input`); + + vinInput.oninvalid = (e) => { + e.target.setCustomValidity(getTextLabel('vinformat')); + }; - vinInput.oninvalid = (e) => { - e.target.setCustomValidity(getTextLabel('vinformat')); - }; + vinInput.oninput = (e) => { + e.target.setCustomValidity(''); + }; - vinInput.oninput = (e) => { - e.target.setCustomValidity(''); - }; - }); + if (!refreshDate) { + fetchRefreshDate().then((response) => { + const refreshEle = block.querySelector(`.${blockName}__refresh-date`); + refreshEle.textContent = response; + }); + } } diff --git a/styles/styles.css b/styles/styles.css index 49058a87f..254cf8012 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -1274,3 +1274,26 @@ main .section.responsive-title h1 { width: 1px; white-space: nowrap; } + +.redesign-v2 .section.recalls-padding { + --text-block-max-width: 506px; + + padding-top: 60px; +} + +.redesign-v2 .recalls-padding .default-content-wrapper { + max-width: var(--text-block-max-width, calc(100vw - 16px)); + text-align: center; +} + +@media (min-width: 744px) { + .redesign-v2 .section.recalls-padding { + --text-block-max-width: 506px; + } +} + +@media (min-width: 1200px) { + .redesign-v2 .section.recalls-padding { + --text-block-max-width: 694px; + } +} \ No newline at end of file From 0b190a400cd1e75648af361a1c7297be03865182 Mon Sep 17 00:00:00 2001 From: SantiagoHomps-NC <103571932+SantiagoHomps-NC@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:55:47 -0300 Subject: [PATCH 049/250] Icon cards block (#181) * #509 Fix displaying header on dealer page * fix done * one trust position ix * Embed block match NZ one * Update feed.xml * Fix createElement() v1-Cards * Update feed.xml * Update release.yml * columns block adapted * icon cards working * css clean * adjustements * last changes * specific class * btn state styling * comments corrected * secondary btn margin corrected * secondary btn class added * cleanup --------- Co-authored-by: Tomasz Dziezyk Co-authored-by: TomaszDziezykNetcentric <125962117+TomaszDziezykNetcentric@users.noreply.github.com> Co-authored-by: Jonatan Lledo <117984204+jonatan-lledo-netcentric@users.noreply.github.com> Co-authored-by: aem-code-sync[bot] Co-authored-by: Syb <133873665+cogniSyb@users.noreply.github.com> --- .github/release.yml | 2 +- blocks/cards/cards.js | 7 +- blocks/dealer-locator/dealer-locator.css | 12 +- blocks/embed/embed.js | 19 +- blocks/v2-columns/v2-columns.css | 67 +----- blocks/v2-columns/v2-columns.js | 35 +--- blocks/v2-icon-cards/v2-icon-cards.css | 250 +++++++++++++++++++++++ blocks/v2-icon-cards/v2-icon-cards.js | 97 +++++++++ mack-news/feed.xml | 10 +- scripts/video-helper.js | 15 +- 10 files changed, 401 insertions(+), 113 deletions(-) create mode 100644 blocks/v2-icon-cards/v2-icon-cards.css create mode 100644 blocks/v2-icon-cards/v2-icon-cards.js diff --git a/.github/release.yml b/.github/release.yml index 6809eb9b5..4b96087a5 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -3,7 +3,7 @@ changelog: exclude: labels: - - ignore-for-release + - Ignore for release categories: - title: Functional requirements labels: diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index d8df8fbb8..f63e105ff 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -16,12 +16,13 @@ const updateListElements = (ul, isDarkVar = false, isCTABlock = false) => { const buttons = li.querySelectorAll('.cards-card-body .button-container'); const { length } = buttons; if (length === 0) return; - const tempLink = [...buttons].at(-1).firstChild; - const newLink = createElement('a', '', { + // Last button is the one we want to use at card level + const tempLink = [...buttons].at(-1).querySelector('a'); + const newLink = createElement('a', { props: { href: tempLink.href, title: tempLink.title }, }); - buttons[length - 1].remove(); + buttons[length - 1].remove(); // remove last button newLink.innerHTML = li.innerHTML; li.textContent = ''; li.appendChild(newLink); diff --git a/blocks/dealer-locator/dealer-locator.css b/blocks/dealer-locator/dealer-locator.css index 7ebbca9ad..011d2b21b 100644 --- a/blocks/dealer-locator/dealer-locator.css +++ b/blocks/dealer-locator/dealer-locator.css @@ -5,6 +5,11 @@ font-family: var(--ff-body); } +/* cookie button position fix */ +#onetrust-consent-sdk #ot-sdk-btn-floating.ot-floating-button { + bottom: 30px; +} + main .section.dealer-locator-container > div { margin: 0; } @@ -711,18 +716,19 @@ main .section.dealer-locator-container > div { width: 100% } +.dealer-locator #map .gmnoprint + .gm-style-cc, .dealer-locator #map .gmnoprint { top: 0; margin-top: 45px } @media (max-width: 992px) { - .dealer-locator #map .gmnoprint { - display:none + .dealer-locator #map .gm-bundled-control { + display: none; } .dealer-locator #map { - height: calc(50% - 39px); + height: calc(50% - 41px) } } diff --git a/blocks/embed/embed.js b/blocks/embed/embed.js index 58d3bd3ae..a04457fc5 100644 --- a/blocks/embed/embed.js +++ b/blocks/embed/embed.js @@ -1,19 +1,32 @@ import { selectVideoLink, addPlayIcon, showVideoModal, isLowResolutionVideoUrl, createIframe, - createLowResolutionBanner, + createLowResolutionBanner, videoTypes, } from '../../scripts/video-helper.js'; export default function decorate(block) { + const { youtube, local, both } = videoTypes; const isAutoplay = block.classList.contains('autoplay'); const isLoopedVideo = block.classList.contains('loop'); const isFullWidth = block.classList.contains('full-width'); + const isYoutubeOnly = block.classList.contains('youtube-only'); + const isLocalOnly = block.classList.contains('local-only'); + const isBothOrDefault = block.classList.contains('both') || (!isYoutubeOnly && !isLocalOnly); const videoWrapper = document.createElement('div'); // removing classes to avoid collision with other css block.classList.remove('loop', 'autoplay', 'full-width'); videoWrapper.classList.add('embed-video'); + let videoType; + if (isBothOrDefault) { + videoType = both; + } else if (isYoutubeOnly) { + videoType = youtube; + } else if (isLocalOnly) { + videoType = local; + } + const links = block.querySelectorAll('a'); - const selectedLink = selectVideoLink(links, isFullWidth ? 'local' : 'auto'); + const selectedLink = selectVideoLink(links, isFullWidth ? 'local' : 'auto', videoType); const video = document.createElement('video'); const source = document.createElement('source'); @@ -66,7 +79,7 @@ export default function decorate(block) { }); } - if (!isFullWidth) { + if (!isFullWidth && !isLocalOnly) { const banner = createLowResolutionBanner(); videoWrapper.prepend(banner); } diff --git a/blocks/v2-columns/v2-columns.css b/blocks/v2-columns/v2-columns.css index d1a7f6318..ab9ddce2a 100644 --- a/blocks/v2-columns/v2-columns.css +++ b/blocks/v2-columns/v2-columns.css @@ -67,43 +67,6 @@ margin: 5px 0 0; } -.v2-columns--info .v2-columns__column { - gap: 8px; -} - -.v2-columns--info .icon { - margin-bottom: 8px; -} - -.v2-columns--info .v2-columns__heading { - margin: 0; -} - -.v2-columns--info .v2-columns__body { - margin: 0 0 22px; -} - -.v2-columns--info .v2-columns__column--info-main { - gap: 24px; -} - -.v2-columns--info .v2-columns__column--info-main .v2-columns__heading { - padding-top: 24px; - margin: 0; -} - -.v2-columns--info .v2-columns__column--with-text { - padding: 0; -} - -.v2-columns--info .v2-columns__row { - gap: 40px; -} - -.v2-columns--info .v2-columns__button { - margin-bottom: 16px; -} - @media (min-width: 744px) { .v2-columns { width: 100%; @@ -123,11 +86,11 @@ order: unset; } - .v2-columns:not(.v2-columns--info) .v2-columns__column--with-text { + .v2-columns .v2-columns__column--with-text { padding: 0 56px; } - .v2-columns:not(.v2-columns--info) .v2-columns__column--with-text .v2-columns__heading { + .v2-columns .v2-columns__column--with-text .v2-columns__heading { margin: 24px 0 12px; font-size: var(--headline-1-font-size); } @@ -135,30 +98,4 @@ .v2-columns__column--with-text .v2-columns__body { margin: 0 0 14px; } - - .v2-columns--info .v2-columns__row { - justify-content: space-between; - align-items: flex-start; - } - - .v2-columns--info .v2-columns__body:last-child { - margin: 0; - } - - .v2-columns--info .v2-columns__column { - max-width: 245px; - } - - .v2-columns--info .v2-columns__column--info-main { - align-self: center; - max-width: 329px; - width: 100%; - } -} - -@media (min-width: 1200px) { - .v2-columns--info { - padding: 20px 40px; - background: var(--c-primary-white); - } } diff --git a/blocks/v2-columns/v2-columns.js b/blocks/v2-columns/v2-columns.js index 86d98c214..32013f401 100644 --- a/blocks/v2-columns/v2-columns.js +++ b/blocks/v2-columns/v2-columns.js @@ -1,9 +1,7 @@ -import { createElement, variantsClassesToBEM } from '../../scripts/common.js'; +import { createElement } from '../../scripts/common.js'; export default async function decorate(block) { const blockName = 'v2-columns'; - const variantClasses = ['info']; - variantsClassesToBEM(block.classList, variantClasses, blockName); const rows = [...block.querySelectorAll(':scope > div')]; const columns = [...block.querySelectorAll(':scope > div > div')]; @@ -64,35 +62,4 @@ export default async function decorate(block) { prevEl.replaceWith(pretitle); } }); - - // logic for info variant - if (block.classList.contains(`${blockName}--info`)) { - const headings = [...block.querySelectorAll('h3, h4, h5, h6')]; - const h2List = [...block.querySelectorAll('h2')]; - - headings.forEach((h) => { - h.classList.add('h5'); - h.classList.remove('h2'); - }); - - h2List.forEach((h) => { - h.classList.add('with-marker', 'h2'); - h.classList.remove('h1'); - h.closest(`.${blockName}__column`)?.classList.add(`${blockName}__column--info-main`); - }); - - // replacing headings (h3, h4, h5, h6) with strong so the block will not break semantic - // (example breaking semantic: col 1 -> h5, col 2 -> h2) - headings.forEach((heading) => { - const newHeadingEl = createElement('strong', { classes: [...heading.classList] }); - newHeadingEl.innerHTML = heading.innerHTML; - heading.replaceWith(newHeadingEl); - }); - - const buttons = [...block.querySelectorAll('.button-container a')]; - buttons.forEach((button) => { - button.classList.add('standalone-link', `${blockName}__button`); - button.classList.remove('button', 'button--primary', 'button--large'); - }); - } } diff --git a/blocks/v2-icon-cards/v2-icon-cards.css b/blocks/v2-icon-cards/v2-icon-cards.css new file mode 100644 index 000000000..526234534 --- /dev/null +++ b/blocks/v2-icon-cards/v2-icon-cards.css @@ -0,0 +1,250 @@ +.redesign-v2 .section .v2-icon-cards-wrapper { + padding: 0; +} + +.v2-icon-cards:not(.v2-icon-cards--4-cols) { + padding: var(--section-div-padding); +} + +.v2-icon-cards__row { + flex-direction: column; + display: flex; + gap: 40px; + width: 100%; + justify-content: center; + align-items: flex-start; + color: var(--c-primary-black); +} + +.v2-icon-cards__column .icon, +.v2-icon-cards__column svg { + width: 32px; + height: 32px; +} + +.v2-icon-cards__column { + max-width: 512px; + order: 1; + display: flex; + flex-direction: column; + gap: 8px; + padding: 0; +} + +.v2-icon-cards__heading { + word-wrap: break-word; + margin: 0; +} + +.v2-icon-cards-container .default-content-wrapper h2, +.v2-icon-cards__heading.h2 { + font-family: var(--ff-headline-medium); +} + +.v2-icon-cards__body { + font-size: var(--body-1-font-size); + margin: 0 0 22px; +} + +.v2-icon-cards__body a { + color: var(--c-primary-black); + border-bottom: 1px solid var(--c-accent-red); + text-decoration: none; +} + +.v2-icon-cards__column a.button { + width: fit-content; + margin-top: 12px; +} + +.v2-icon-cards__column a.button:not(a.button:first-of-type) { + margin: 5px 0 0; +} + +.v2-icon-cards .icon { + margin-bottom: 8px; +} + + .v2-icon-cards__column--main { + gap: 24px; +} + +.v2-icon-cards__column--main .v2-icon-cards__heading { + padding-top: 24px; + margin: 0; +} + +.v2-icon-cards__button { + margin-bottom: 16px; +} + +@media (min-width: 744px) { + .v2-icon-cards { + width: 100%; + } + + .v2-icon-cards__row { + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + gap: 16px; + } + + .v2-icon-cards__column { + flex-direction: column; + width: 100%; + padding: 0; + order: unset; + max-width: 245px; + } + + .v2-icon-cards__body { + margin: 0 0 14px; + } + + .v2-icon-cards__body:last-child { + margin: 0; + } + + .v2-icon-cards__column--main { + align-self: center; + max-width: 329px; + width: 100%; + } +} + +@media (min-width: 1200px) { + .v2-icon-cards { + padding: 20px 40px; + background: var(--c-primary-white); + } +} + +/* 4 COLUMNS VARIANT */ + +/* previous header styling */ + +.redesign-v2 .header-with-mark .default-content-wrapper.v2-icon-cards--4-cols-header { + background-color: var(--c-primary-black); + padding: 52px 16px 24px; +} + +.default-content-wrapper.v2-icon-cards--4-cols-header h2 { + font-size: var(--headline-1-font-size); +} + +@media (min-width: 744px) { + .redesign-v2 .header-with-mark .default-content-wrapper.v2-icon-cards--4-cols-header { + margin: 0; + max-width: unset; + } + + .default-content-wrapper.v2-icon-cards--4-cols-header h2 { + max-width: calc(var(--wrapper-width) - 10px); + padding-bottom: 0; + margin: 0 auto; + } +} + +@media (min-width: 1200px) { + .redesign-v2 .header-with-mark .default-content-wrapper.v2-icon-cards--4-cols-header { + background-color: transparent; + max-width: var(--wrapper-width); + padding: 52px 0 24px; + margin: 0 auto; + } + + .default-content-wrapper.v2-icon-cards--4-cols-header h2 { + max-width: calc(var(--wrapper-width) / 2); + margin: unset; + } +} + +/* block styling */ +.v2-icon-cards--4-cols .v2-icon-cards__column { + max-width: unset; + width: 100%; + gap: 0; + padding: 20px 40px; +} + +.v2-icon-cards--4-cols .v2-icon-cards__row { + gap: 0; +} + +.v2-icon-cards--4-cols .icon { + margin-bottom: 0; +} + +.v2-icon-cards--4-cols .top-icon { + margin-bottom: 16px; +} + +.v2-icon-cards--4-cols .v2-icon-cards__heading { + font-size: var(--body-2-font-size); + line-height: var(--body-2-line-height); + margin-bottom: 8px; +} + +.v2-icon-cards--4-cols .v2-icon-cards__body { + font-size: var(--body-2-font-size); + margin-bottom: 0; +} + +.v2-icon-cards--4-cols .v2-icon-cards__body u { + text-decoration: none; + border-bottom: 1px solid var(--c-accent-red); +} + +.v2-icon-cards__column--extra-col .v2-icon-cards__body { + padding-bottom: 8px; + border-bottom: 1px solid var(--c-accent-copper); + margin-bottom: 16px; +} + +.v2-icon-cards--4-cols .v2-icon-cards__column--extra-col { + background-color: var(--c-primary-gold); +} + +/* stylelint-disable-next-line no-descending-specificity */ +.v2-icon-cards__column--extra-col a.button { + gap: 8px; + min-width: unset; + padding: 0; + margin: 4px 0; +} + +.v2-icon-cards__column--extra-col .button-container { + margin: 0; +} + +.v2-icon-cards__column--extra-col a.button--secondary { + font-style: normal; + border: none; + margin: 4px 0; +} + +/* stylelint-disable-next-line no-descending-specificity */ +.v2-icon-cards__column--extra-col a.button:active, +.v2-icon-cards__column--extra-col a.button:focus, +.v2-icon-cards__column--extra-col a.button:hover, +.v2-icon-cards__column--extra-col a.button--secondary:active, +.v2-icon-cards__column--extra-col a.button--secondary:focus, +.v2-icon-cards__column--extra-col a.button--secondary:hover { + background-color: unset; + text-decoration: none; +} + +@media (min-width: 744px) { + .v2-icon-cards.v2-icon-cards--4-cols { + padding: 0; + } + + .v2-icon-cards--4-cols .v2-icon-cards__row { + gap: 0; + } + + .v2-icon-cards--4-cols .v2-icon-cards__column { + width: 25%; + } +} \ No newline at end of file diff --git a/blocks/v2-icon-cards/v2-icon-cards.js b/blocks/v2-icon-cards/v2-icon-cards.js new file mode 100644 index 000000000..dcb4d956a --- /dev/null +++ b/blocks/v2-icon-cards/v2-icon-cards.js @@ -0,0 +1,97 @@ +import { createElement } from '../../scripts/common.js'; + +export default async function decorate(block) { + const blockName = 'v2-icon-cards'; + + const rows = [...block.querySelectorAll(':scope > div')]; + const columns = [...block.querySelectorAll(':scope > div > div')]; + + rows.forEach((row) => { + row.classList.add(`${blockName}__row`); + }); + + const parentSection = block.parentElement.parentElement; + const hasHeader = parentSection.classList.contains('header-with-mark'); + const hasExtraColumn = columns.length === 4; + + if (hasExtraColumn) block.classList.add(`${blockName}--4-cols`); + if (hasExtraColumn && hasHeader) parentSection.querySelector('.default-content-wrapper').classList.add(`${blockName}--4-cols-header`); + + columns.forEach((col, idx) => { + const isExtraColumn = idx === 3; + col.classList.add(`${blockName}__column`); + + const allTextElmts = col.querySelectorAll('p'); + const bodyElmts = []; + + allTextElmts.forEach((e) => { + const nextElmt = e.nextElementSibling; + + const isButton = [...e.classList].includes('button-container'); + const isPretitle = nextElmt?.tagName.toLowerCase()[0] === 'h'; + + if (!isPretitle && !isButton) bodyElmts.push(e); + }); + bodyElmts.forEach((e) => e.classList.add(`${blockName}__body`)); + + const buttons = [...col.querySelectorAll('.button-container a')]; + buttons.forEach((btn) => { + if (btn.parentElement.classList.contains('button-container')) { + btn.parentElement.replaceWith(btn); + } + }); + + if (isExtraColumn) { + col.classList.add(`${blockName}__column--extra-col`); + col.dataset.theme = 'gold'; + } + + const headings = [...col.querySelectorAll('h1, h2, h3, h4, h5, h6')]; + headings.forEach((heading) => heading.classList.add(`${blockName}__heading`, 'h2')); + + // icons + [...col.querySelectorAll('.icon')].forEach((icon, index) => { + const iconParentEl = icon.parentElement; + if (iconParentEl.children.length === 1 && index === 0) { + icon.classList.add('top-icon'); + iconParentEl.replaceWith(icon); + } + }); + + const prevEl = headings[0]?.previousElementSibling; + const pretitleText = prevEl && !prevEl.classList.contains('icon') && prevEl.textContent; + + if (pretitleText) { + const pretitle = createElement('span', { classes: 'pretitle' }); + pretitle.textContent = pretitleText; + prevEl.replaceWith(pretitle); + } + }); + + const headings = [...block.querySelectorAll('h3, h4, h5, h6')]; + const h2List = [...block.querySelectorAll('h2')]; + + headings.forEach((h) => { + h.classList.add('h5'); + h.classList.remove('h2'); + }); + + h2List.forEach((h) => { + h.classList.add('with-marker', 'h2'); + h.classList.remove('h1'); + h.closest(`.${blockName}__column`)?.classList.add(`${blockName}__column--main`); + }); + + // replacing headings (h3, h4, h5, h6) with strong so the block will not break semantic + // (example breaking semantic: col 1 -> h5, col 2 -> h2) + headings.forEach((heading) => { + const newHeadingEl = createElement('strong', { classes: [...heading.classList] }); + newHeadingEl.innerHTML = heading.innerHTML; + heading.replaceWith(newHeadingEl); + }); + + const buttons = [...block.querySelectorAll('.button-container a')]; + buttons.forEach((button) => { + button.classList.add('standalone-link', `${blockName}__button`); + }); +} diff --git a/mack-news/feed.xml b/mack-news/feed.xml index 92a855290..c2942eb25 100644 --- a/mack-news/feed.xml +++ b/mack-news/feed.xml @@ -2,7 +2,7 @@ https://www.macktrucks.com/mack-news/ Mack News - 2023-10-30T00:00:00.000Z + 2023-11-15T00:00:00.000Z AEM Project Franklin News feed generator (GitHub action) Get the latest news from Mack® Trucks and see how we are taking our Born Ready semi truck line to the next level with new innovations and technology. @@ -2705,4 +2705,12 @@ 2023-10-30T00:00:00.000Z + + <![CDATA[UAW Members Ratify Agreement with Mack Trucks | Mack Trucks]]> + https://www.macktrucks.com/mack-news/2023/uaw-members-ratify-agreement-with-mack-trucks + + 2023-11-15T00:00:00.000Z + + 2023-11-15T00:00:00.000Z + \ No newline at end of file diff --git a/scripts/video-helper.js b/scripts/video-helper.js index daa1e157c..5ca462ec0 100644 --- a/scripts/video-helper.js +++ b/scripts/video-helper.js @@ -1,5 +1,11 @@ import { createElement, getTextLabel } from './common.js'; +export const videoTypes = { + youtube: 'youtube', + local: 'local', + both: 'both', +}; + /* video helpers */ export function isLowResolutionVideoUrl(url) { return url.split('?')[0].endsWith('.mp4'); @@ -12,11 +18,14 @@ export function isVideoLink(link) { && link.closest('.block.embed') === null; } -export function selectVideoLink(links, preferredType) { +export function selectVideoLink(links, preferredType, videoType = videoTypes.both) { + const isDefault = videoType === videoTypes.both; const linksList = [...links]; - const optanonConsentCookieValue = decodeURIComponent(document.cookie.split(';').find((cookie) => cookie.trim().startsWith('OptanonConsent='))); + const optanonConsentCookieValue = decodeURIComponent(document.cookie.split(';') + .find((cookie) => cookie.trim().startsWith('OptanonConsent='))); const cookieConsentForExternalVideos = optanonConsentCookieValue.includes('C0005:1'); - const shouldUseYouTubeLinks = cookieConsentForExternalVideos && preferredType !== 'local'; + const shouldUseYouTubeLinks = (cookieConsentForExternalVideos && preferredType !== 'local') + || (!isDefault && videoType === videoTypes.youtube); const youTubeLink = linksList.find((link) => link.getAttribute('href').includes('youtube.com/embed/')); const localMediaLink = linksList.find((link) => link.getAttribute('href').split('?')[0].endsWith('.mp4')); From 538abb73baf3ba49c6fb1ca7c72a3e57b784ed40 Mon Sep 17 00:00:00 2001 From: synox Date: Tue, 5 Dec 2023 01:39:32 +0000 Subject: [PATCH 050/250] Update feed.xml --- mack-news/feed.xml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mack-news/feed.xml b/mack-news/feed.xml index 799ef8766..83f878180 100644 --- a/mack-news/feed.xml +++ b/mack-news/feed.xml @@ -2,7 +2,7 @@ https://www.macktrucks.com/mack-news/ Mack News - 2023-11-21T00:00:00.000Z + 2023-12-04T00:00:00.000Z AEM Project Franklin News feed generator (GitHub action) Get the latest news from Mack® Trucks and see how we are taking our Born Ready semi truck line to the next level with new innovations and technology. @@ -2721,4 +2721,12 @@ 2023-11-21T00:00:00.000Z + + <![CDATA[Vanguard Truck Centers in Austin, Houston and Phoenix are Now Mack Certified EV Dealers | Mack Trucks]]> + https://www.macktrucks.com/mack-news/2023/vanguard-truck-centers-in-austin-houston-and-phoenix-are-now-mack-certified-ev-dealers + + 2023-12-04T00:00:00.000Z + + 2023-12-04T00:00:00.000Z + \ No newline at end of file From 8cee00613dccd8c0efe1ec024c444195d0356a64 Mon Sep 17 00:00:00 2001 From: Satya Deep Maheshwari Date: Fri, 8 Dec 2023 10:37:57 +0530 Subject: [PATCH 051/250] Add assets sidekick plugin --- tools/sidekick/config.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/sidekick/config.json b/tools/sidekick/config.json index 443aa8215..a0ff8fd8d 100644 --- a/tools/sidekick/config.json +++ b/tools/sidekick/config.json @@ -13,6 +13,19 @@ "title": "Search", "environments": [ "dev", "preview", "live", "prod" ], "url": "/tools/fgrep-attr/fgrep.html" + }, + { + "id": "asset-library", + "title": "My Assets", + "environments": [ + "edit" + ], + "url": "https://experience.adobe.com/solutions/CQ-assets-selectors/static-assets/resources/franklin/asset-selector.html", + "isPalette": true, + "includePaths": [ + "**.docx**" + ], + "paletteRect": "top: 50px; bottom: 10px; right: 10px; left: auto; width:400px; height: calc(100vh - 60px)" } ] } From 75666695da1fb3c0b88fa9da8665ecef62b4854c Mon Sep 17 00:00:00 2001 From: TomaszDziezykNetcentric <125962117+TomaszDziezykNetcentric@users.noreply.github.com> Date: Mon, 11 Dec 2023 10:34:35 +0100 Subject: [PATCH 052/250] The pop-up of the video displayed clicking on the Play video button is smaller and the thumbnail is cutout #182 (#183) --- blocks/v2-images-grid/v2-images-grid.css | 4 ++ common/modal/modal-component.css | 51 ++++++++++++++---------- common/modal/modal-component.js | 7 +--- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/blocks/v2-images-grid/v2-images-grid.css b/blocks/v2-images-grid/v2-images-grid.css index c6b5d2235..c2b03fed1 100644 --- a/blocks/v2-images-grid/v2-images-grid.css +++ b/blocks/v2-images-grid/v2-images-grid.css @@ -233,6 +233,10 @@ --v2-images-grid-padding-space: 40px; } + .v2-images-grid__modal-content { + max-width: 1040px; + } + .v2-images-grid__figcaption { display: block; text-align: center; diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index 82aa0878a..70637d89d 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -48,19 +48,21 @@ } .modal-content { - aspect-ratio: 16/9; width: 100%; + height: auto; background-color: transparent; - position: absolute; opacity: 1; cursor: auto; - height: 100%; + display: flex; + align-items: center; + position: relative; } .modal-content .modal-video { width: 100%; - height: 100%; + height: auto; background-color: rgb(230 230 230 / 72%); + aspect-ratio: 16/9; } .modal-before-banner { @@ -82,33 +84,49 @@ @media (min-width: 768px) { :root:not(.redesign-v2) .modal-content { - width: 726px; + max-width: 726px; + } + + :root:not(.redesign-v2) .modal-background button.modal-close-button { + position: absolute; + right: 0; + top: 0; + transition: unset; + } + + :root:not(.redesign-v2) .modal-background button.modal-close-button:hover, + :root:not(.redesign-v2) .modal-background button.modal-close-button:focus-visible { + --color-icon: var(--c-accent-copper); } } @media (min-width: 992px) { :root:not(.redesign-v2) .modal-content { - width: 930px; + max-width: 930px; } } @media (min-width: 1300px) { :root:not(.redesign-v2) .modal-content { - width: 1170px; + max-width: 1170px; } } -@media (min-width: 768px) { +@media (min-width: 744px) { .redesign-v2 .modal-content { max-width: 1040px; height: auto; - position: relative; - aspect-ratio: unset; + } + + .redesign-v2 .modal-content .modal-video { + max-width: 1040px; } .redesign-v2 .modal-background button.modal-close-button { position: absolute; right: 0; + top: 0; + transition: unset; } .redesign-v2 .modal-background button.modal-close-button:hover, @@ -155,7 +173,7 @@ color: var(--c-primary-white); max-height: 80vh; overflow: auto; - aspect-ratio: unset; + height: auto; } .modal-content.modal-form h2, @@ -224,15 +242,4 @@ .modal-background { background-color: rgb(0 0 0 / 80%); } - - .modal-background button.modal-close-button { - position: relative; - right: calc(-100% + 60px); - top: 0; - transition: unset; - } - - .modal-video { - margin-top: -60px; - } } diff --git a/common/modal/modal-component.js b/common/modal/modal-component.js index fc6ae3704..ff2e1aaf1 100644 --- a/common/modal/modal-component.js +++ b/common/modal/modal-component.js @@ -104,11 +104,8 @@ const createModal = () => { // disable page scrolling document.body.classList.add('disable-scroll'); - // don't close modal when clicking on modal content - [...modalContent.querySelectorAll(':scope > *')].forEach((el) => { - el.addEventListener('click', (event) => { - event.stopPropagation(); - }); + modalContent.addEventListener('click', (event) => { + event.stopPropagation(); }); } From e5dccaf429561ab3d2b53b3499da15b8c6d14ebe Mon Sep 17 00:00:00 2001 From: synox Date: Wed, 13 Dec 2023 01:38:55 +0000 Subject: [PATCH 053/250] Update feed.xml --- mack-news/feed.xml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mack-news/feed.xml b/mack-news/feed.xml index 83f878180..b1d4a00e2 100644 --- a/mack-news/feed.xml +++ b/mack-news/feed.xml @@ -2,7 +2,7 @@ https://www.macktrucks.com/mack-news/ Mack News - 2023-12-04T00:00:00.000Z + 2023-12-12T00:00:00.000Z AEM Project Franklin News feed generator (GitHub action) Get the latest news from Mack® Trucks and see how we are taking our Born Ready semi truck line to the next level with new innovations and technology. @@ -2729,4 +2729,12 @@ 2023-12-04T00:00:00.000Z + + <![CDATA[Mack Defense Chooses BAE Systems for Alternative Propulsion System on U.S. Army Common Tactical Truck Prototypes | Mack Trucks]]> + https://www.macktrucks.com/mack-news/2023/mack-defense-chooses-bae-systems-for-alternative-propulsion-system-on-us-army-common-tactical-truck-prototypes + + 2023-12-12T00:00:00.000Z + + 2023-12-12T00:00:00.000Z + \ No newline at end of file From bec7f0f1b042f2d5505aec040d25e335c17ddcee Mon Sep 17 00:00:00 2001 From: taimurCognizant <150666850+taimurCognizant@users.noreply.github.com> Date: Fri, 15 Dec 2023 14:10:27 +0100 Subject: [PATCH 054/250] iPhone/iPad - Icon Cards - The local video in the video pop-up cannot be played and is incorrectly displayed #184 (#185) * fix #184: use
    ` : ''}
    - ${isCustomHeader ? '' : mobileActions().outerHTML} - ${isCustomHeader ? decorateCTA(actionsContainer).outerHTML : createActions(actionsContainer).outerHTML} + ${isMobileMenuDisabled ? '' : mobileActions().outerHTML} + ${isMobileMenuDisabled ? decorateCTA(actionsContainer).outerHTML : createActions(actionsContainer).outerHTML}
    `); @@ -609,7 +610,7 @@ export default async function decorate(block) { } }; - if (!isCustomHeader) { + if (!isMobileMenuDisabled) { desktopMQ.addEventListener('change', (e) => { const isDesktop = e.matches; diff --git a/blocks/v2-truck-lineup/v2-truck-lineup.css b/blocks/v2-truck-lineup/v2-truck-lineup.css index 857e6b555..cbab43014 100644 --- a/blocks/v2-truck-lineup/v2-truck-lineup.css +++ b/blocks/v2-truck-lineup/v2-truck-lineup.css @@ -281,7 +281,7 @@ ul.v2-truck-lineup__navigation { .v2-truck-lineup__arrow-controls button:hover { background-color: var(--c-primary-gray); - border-color: var(--button-tertiary-white-hover); + border-color: var(--button-tertiary-white-border-hover); } .v2-truck-lineup__arrow-controls button:active { diff --git a/styles/styles.css b/styles/styles.css index ce7000d96..7dcd33aeb 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -38,18 +38,28 @@ --c-accent-copper: #8f654d; /* Applied colors */ + --color-icon: var(--c-primary-black); + --color-icon-accent: var(--c-primary-gold); --background-color: var(--c-primary-white); --text-color: var(--c-primary-black); + --text-subtle: #767676; --link-color: var(--c-accent-red); --link-hover-color: var(--c-primary-black); - --header-color: var(--c-primary-white); + --header-color: var(--c-primary-black); --header-background-color: var(--c-primary-white); --overlay-background-color: var(--c-secondary-silver); --media-background: var(--c-secondary-silver); --section-background: var(--c-primary-white); + --line-subtle: var(--c-secondary-silver); + --navigation-line-default-color: var(--c-secondary-steel); + --navigation-line-active-color: var(--c-accent-red); + --card-background: var(--c-tertiary-light-warm-gray); + --card-text: var(--c-primary-black); /* BUTTONS */ + /* TODO: remove color from button variable names; align with Core Design Kit */ + /* Primary */ --button-primary-red-enabled: var(--c-accent-red); --button-primary-red-hover: #e11d0b; @@ -65,7 +75,8 @@ /* Tertiary */ --button-tertiary-white-enabled: var(--c-primary-white); - --button-tertiary-white-hover: #a7a8a9; + --button-tertiary-white-hover: #f1f1f1; + --button-tertiary-white-border-hover: #a7a8a9; --button-tertiary-white-pressed: #e1dfdd; --button-tertiary-white-disabled: var(--c-primary-white); @@ -803,18 +814,11 @@ main .section.responsive-title h1 { /* REDESIGN GLOBAL STYLES */ .redesign-v2 { --v2-space-small: 16px; - --text-subtle: #767676; - --color-icon: var(--c-primary-black); - --color-icon-accent: var(--c-primary-gold); --section-max-width: 1920px; --section-padding-top: 40px; --section-padding-bottom: 40px; --section-gap: 40px; --section-div-padding: 0 var(--v2-space-small); - --navigation-line-default-color: var(--c-secondary-steel); - --navigation-line-active-color: var(--c-accent-red); - --card-background: var(--c-tertiary-light-warm-gray); - --card-text: var(--c-primary-black); font-size: 16px; @@ -1047,7 +1051,7 @@ main .section.responsive-title h1 { /* stylelint-disable-next-line no-descending-specificity */ .redesign-v2 .button--secondary:hover, .redesign-v2 .button--secondary:focus { - background-color: #f1f1f1; + background-color: var(--button-tertiary-white-hover); border-color: var(--button-tertiary-white-hover); } From 7cc0d080102e00a744cace3de6b706ec84f07828 Mon Sep 17 00:00:00 2001 From: Syb <133873665+cogniSyb@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:46:45 +0200 Subject: [PATCH 127/250] =?UTF-8?q?V2=20Embed=20=E2=80=93=20Configuration?= =?UTF-8?q?=20#650=20(#665)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add configuration and update debug code * fix disable controls * refactor v2-embed, modal, v2-testimonial, video-helpers * add Permissions Policy for autoplay * fix configuration for autoplay and controls * refactor code, add tests --- blocks/v2-embed/v2-embed.js | 118 +++++--- blocks/v2-testimonial/v2-testimonial.js | 69 ++--- common/modal/modal-component.css | 11 +- common/modal/modal-component.js | 29 +- scripts/common.js | 37 ++- scripts/constants.js | 7 + scripts/video-helper.js | 194 +++++++++---- test/scripts/common.test.js | 128 +++++++- test/scripts/video-helper.test.js | 371 ++++++++++++++++++------ 9 files changed, 725 insertions(+), 239 deletions(-) diff --git a/blocks/v2-embed/v2-embed.js b/blocks/v2-embed/v2-embed.js index b6ce654b2..ed6e9e19a 100644 --- a/blocks/v2-embed/v2-embed.js +++ b/blocks/v2-embed/v2-embed.js @@ -1,57 +1,93 @@ -/* eslint-disable no-console */ import { - standardVideoConfig, + addVideoConfig, + createVideo, + handleVideoMessage, } from '../../scripts/video-helper.js'; +import { + AEM_ASSETS, +} from '../../scripts/constants.js'; const blockName = 'v2-embed'; +const extractAspectRatio = (block) => { + const aspectRatioRegex = /aspect-ratio-(\d+)-(\d+)/; + const aspectRatioClass = Array.from(block.classList) + .find((className) => aspectRatioRegex.test(className)); + + if (!aspectRatioClass) { + return null; + } + + const match = aspectRatioClass.match(aspectRatioRegex); + if (match) { + return { + width: parseInt(match[1], 10), + height: parseInt(match[2], 10), + }; + } + + return null; +}; + +const retrieveVideoConfig = (block, aspectRatio) => ({ + ...(aspectRatio ? { aspectRatio: `${aspectRatio.width}:${aspectRatio.height}` } : {}), + ...(block.querySelector('img')?.getAttribute('src') + ? { poster: new URL(block.querySelector('img').getAttribute('src'), window.location.href).href } + : {}), + autoplay: block.classList.contains('autoplay') ? 'any' : false, + muted: block.classList.contains('autoplay'), + loop: block.classList.contains('loop'), + controls: !block.classList.contains('disable-controls'), + disablePictureInPicture: block.classList.contains('disable-picture-in-picture'), + language: document.documentElement.lang, +}); + +const configureVideo = (block, videoId) => { + const config = retrieveVideoConfig(block); + Object.entries(config).forEach(([key, value]) => { + if (value !== undefined) { + addVideoConfig(videoId, { [key]: value }); + } + }); +}; + export default function decorate(block) { - const link = block.querySelector('a').getAttribute('href'); - const title = block.querySelector('a').textContent; + const linkElement = block.querySelector('a'); + const link = linkElement?.getAttribute('href'); + const title = linkElement?.textContent; + const match = link?.match(AEM_ASSETS.videoIdRegex); if (!link) { - /* eslint-disable-next-line no-console */ - console.warn('V2 Video Embed block: There is no video link. Please check if the fallback video link is provided.'); + // eslint-disable-next-line no-console + console.warn(`[${blockName}]: There is no video link`); return; } - window.addEventListener('message', (event) => { - if (event.data.name === 'video-config') { - event.source.postMessage(JSON.stringify(standardVideoConfig), '*'); - } + if (!match) { + // eslint-disable-next-line no-console + console.warn(`[${blockName}]: Video link is incorrect: ${link}`); + return; + } - if (event.data.type === 'embedded-video-player-event') { - switch (event.data.name) { - case 'video-playing': - /* eslint-disable-next-line no-console */ - console.info(`[parent] [playing] ${event.data.name} for ${event.data.videoId} ${Date.now()}`); - break; - case 'video-play': - /* eslint-disable-next-line no-console */ - console.info(`[parent] [play] ${event.data.name} for ${event.data.videoId} ${Date.now()}`); - break; - case 'video-ended': - /* eslint-disable-next-line no-console */ - console.info(`[parent] [ended] ${event.data.name} for ${event.data.videoId} ${Date.now()}`); - break; - case 'video-loadedmetadata': - /* eslint-disable-next-line no-console */ - console.info(`[parent] [loadedmetadata] ${event.data.name} for ${event.data.videoId} ${Date.now()}`); - break; - default: - break; - } - } - }); + const [videoId] = match; + block.videoId = videoId; + + const aspectRatio = extractAspectRatio(block); + if (aspectRatio) { + block.style.setProperty('--video-aspect-ratio', `${aspectRatio.width}/${aspectRatio.height}`); + } + + const videoConfig = retrieveVideoConfig(block, aspectRatio); + const videoProps = { + ...videoConfig, + title, + }; + + const videoElement = createVideo(link, `${blockName}__frame`, videoProps, false, videoId); - const videoFrame = document.createRange().createContextualFragment(` - - `); + configureVideo(block, videoId); + window.addEventListener('message', (event) => handleVideoMessage(event, block.videoId, blockName)); block.innerHTML = ''; - block.append(videoFrame); + block.append(videoElement); } diff --git a/blocks/v2-testimonial/v2-testimonial.js b/blocks/v2-testimonial/v2-testimonial.js index febe77efb..3b339f688 100644 --- a/blocks/v2-testimonial/v2-testimonial.js +++ b/blocks/v2-testimonial/v2-testimonial.js @@ -1,65 +1,62 @@ import { - isVideoLink, selectVideoLink, + isVideoLink, + selectVideoLink, } from '../../scripts/video-helper.js'; import { variantsClassesToBEM } from '../../scripts/common.js'; -const blockClass = 'v2-testimonial'; +const blockName = 'v2-testimonial'; +const variantClasses = ['media-left', 'media-right', 'overlap']; -const handleVideoLinks = (videosLinks) => { - const selectedVideo = selectVideoLink(videosLinks); +const handleVideoLinks = (videoLinks) => { + const selectedVideo = selectVideoLink(videoLinks); - if (!videosLinks.length) { - return; - } - - videosLinks.forEach((link) => { + videoLinks.forEach((link) => { if (link !== selectedVideo) { link.parentElement.remove(); } }); if (selectedVideo) { - selectedVideo.classList.add(`${blockClass}__video-link`); - } else { - // eslint-disable-next-line no-console - console.warn('No proper video link provided for current cookie settings!'); + selectedVideo.classList.add(`${blockName}__video-link`); + return; } + + // eslint-disable-next-line no-console + console.warn(`[${blockName}]: No proper video link provided for current cookie settings!`); }; -const createVideoSection = (col, block) => { - const videosLinks = [...col.querySelectorAll('a')].filter((link) => isVideoLink(link)); +const createVideoSection = (col) => { + const videoLinks = [...col.querySelectorAll('a')].filter(isVideoLink); - if (!videosLinks.length) { + if (videoLinks.length === 0) { return; } - handleVideoLinks(videosLinks, block); - col.classList.add(`${blockClass}__video-section`); + handleVideoLinks(videoLinks); + col.classList.add(`${blockName}__video-section`); col.setAttribute('data-theme', 'gold'); - col.querySelector('p').classList.add(`${blockClass}__author`); - const videoLinkEl = col.querySelector(`.${blockClass}__video-link`); - videoLinkEl.parentElement.classList.add(`${blockClass}__video-link-wrapper`); + col.querySelector('p').classList.add(`${blockName}__author`); + const videoLinkEl = col.querySelector(`.${blockName}__video-link`); + videoLinkEl.parentElement.classList.add(`${blockName}__video-link-wrapper`); }; export default async function decorate(block) { - const variantClasses = ['media-left', 'media-right', 'overlap']; - variantsClassesToBEM(block.classList, variantClasses, blockClass); + variantsClassesToBEM(block.classList, variantClasses, blockName); const columns = block.querySelectorAll(':scope > div > div'); block.parentElement.classList.add('full-width'); columns.forEach((col) => { - col.classList.add(`${blockClass}__column`); + col.classList.add(`${blockName}__column`); const headings = [...col.querySelectorAll('h1, h2, h3, h4, h5, h6')]; - headings.forEach((h) => h.classList.add(`${blockClass}__heading`)); - + headings.forEach((h) => h.classList.add(`${blockName}__heading`)); headings[0]?.classList.add('with-marker'); const images = [...col.querySelectorAll('img')]; images.forEach((img) => { - img.classList.add(`${blockClass}__image`); - col.parentElement.classList.add(`${blockClass}__image-row`); + img.classList.add(`${blockName}__image`); + col.parentElement.classList.add(`${blockName}__image-row`); }); const blockquotes = [...col.querySelectorAll('blockquote')]; @@ -70,19 +67,11 @@ export default async function decorate(block) { em.outerHTML = em.innerHTML; } - bq.classList.add(`${blockClass}__blockquote`); + bq.classList.add(`${blockName}__blockquote`); + bq.closest(`.${blockName}__column`)?.classList.add(`${blockName}__blockquote-column`); + col.parentElement.classList.add(`${blockName}__text-row`); }); - // recognizing the column with blockquotes - blockquotes.forEach((bq) => { - bq.closest(`.${blockClass}__column`)?.classList.add(`${blockClass}__blockquote-column`); - col.parentElement.classList.add(`${blockClass}__text-row`); - }); - - // recognizing the column with video - const hasVideo = [...col.querySelectorAll('a')].some((link) => isVideoLink(link)); - if (hasVideo) { - createVideoSection(col, block); - } + createVideoSection(col, block); }); } diff --git a/common/modal/modal-component.css b/common/modal/modal-component.css index dd8d0db05..38ca7708f 100644 --- a/common/modal/modal-component.css +++ b/common/modal/modal-component.css @@ -6,7 +6,7 @@ height: 100%; background-color: var(--c-secondary-graphite); z-index: 1051; - transition: opacity 1s linear, height 1s linear; + transition: opacity 1s var(--easing-entrance); opacity: 1; display: flex; align-items: center; @@ -63,7 +63,8 @@ width: 100%; height: auto; background-color: rgb(230 230 230 / 72%); - aspect-ratio: 16/9; + aspect-ratio: var(--video-aspect-ratio, 16/9); + border: 0; } .modal-before-banner { @@ -134,6 +135,12 @@ .redesign-v2 .modal-background button.modal-close-button:focus-visible { --color-icon: var(--c-accent-copper); } + + .redesign-v2 .modal-background button.modal-close-button:focus-visible { + outline: 2px solid var(--border-focus); + border-radius: 2px; + outline-offset: 2px; + } } /* adjustments for soundcloud variant of modal, e.g. https://www.volvotrucks.us/trucks/powertrain/i-torque/ */ diff --git a/common/modal/modal-component.js b/common/modal/modal-component.js index 0b47a123e..76342b82a 100644 --- a/common/modal/modal-component.js +++ b/common/modal/modal-component.js @@ -1,7 +1,18 @@ import { loadCSS } from '../../scripts/lib-franklin.js'; // eslint-disable-next-line import/no-cycle -import { createIframe, isLowResolutionVideoUrl } from '../../scripts/video-helper.js'; +import { + createIframe, + createVideo, + handleVideoMessage, + isAEMVideoUrl, + isLowResolutionVideoUrl, +} from '../../scripts/video-helper.js'; import { createElement } from '../../scripts/common.js'; +import { + AEM_ASSETS, +} from '../../scripts/constants.js'; + +const { videoIdRegex } = AEM_ASSETS; const styles$ = new Promise((r) => { loadCSS(`${window.hlx.codeBasePath}/common/modal/modal-component.css`, r); @@ -92,6 +103,22 @@ const createModal = () => { }, }); modalContent.append(videoOrIframe); + } else if (isAEMVideoUrl) { + let videoId; + const match = newContent.match(videoIdRegex); + if (match) { + [videoId] = match; + } + videoOrIframe = createVideo(newContent, 'modal-video', { + autoplay: 'any', + disablePictureInPicture: true, + loop: false, + muted: false, + playsinline: true, + title: 'video', + }, false, videoId); + modalContent.append(videoOrIframe); + window.addEventListener('message', (event) => handleVideoMessage(event, videoId, 'modal')); } else { // otherwise load it as iframe videoOrIframe = createIframe(newContent, { parentEl: modalContent, classes: 'modal-video' }); diff --git a/scripts/common.js b/scripts/common.js index ce7f9d6c2..9d1764e34 100644 --- a/scripts/common.js +++ b/scripts/common.js @@ -39,13 +39,23 @@ export function createElement(tagName, options = {}) { if (props) { Object.keys(props).forEach((propName) => { - const value = propName === props[propName] ? '' : props[propName]; - elem.setAttribute(propName, value); + const isBooleanAttribute = propName === 'allowfullscreen' || propName === 'autoplay' || propName === 'muted' || propName === 'controls'; + + // For boolean attributes, add the attribute without a value if it's truthy + if (isBooleanAttribute) { + if (props[propName]) { + elem.setAttribute(propName, ''); + } + } else { + const value = props[propName]; + elem.setAttribute(propName, value); + } }); } return elem; } + /** * Adds the favicon. * @param {string} href The favicon URL @@ -362,3 +372,26 @@ export function createResponsivePicture(images, eager, alt, imageClass) { return picture; } + +export const deepMerge = (originalTarget, source) => { + let target = originalTarget; + // Initialize target as an empty object if it's undefined or null + if (typeof target !== 'object' || target === null) { + target = {}; + } + + Object.keys(source).forEach((key) => { + const sourceValue = source[key]; + const targetValue = target[key]; + const sourceIsPlainObject = Object.prototype.toString.call(sourceValue) === '[object Object]'; + const targetIsPlainObject = Object.prototype.toString.call(targetValue) === '[object Object]'; + + if (sourceIsPlainObject && targetIsPlainObject) { + target[key] = target[key] || {}; + deepMerge(target[key], sourceValue); + } else { + target[key] = sourceValue; + } + }); + return target; +}; diff --git a/scripts/constants.js b/scripts/constants.js index 4bd0587a6..5c487a2cb 100644 --- a/scripts/constants.js +++ b/scripts/constants.js @@ -25,3 +25,10 @@ export const FORM_MAGAZINE_SUBSCRIBE = { href: 'https://go.pardot.com/l/1038343/2023-12-12/3m4w2c', iframeSize: '900px', }; + +// videoURLRegex: verify if a given string follows a specific pattern indicating it is a video URL +// videoIdRegex: extract the video ID from the URL +export const AEM_ASSETS = { + videoURLRegex: /\/assets\/urn:aaid:aem:[\w-]+\/play/, + videoIdRegex: /urn:aaid:aem:[0-9a-fA-F-]+/, +}; diff --git a/scripts/video-helper.js b/scripts/video-helper.js index 0b5e080af..6c1286c43 100644 --- a/scripts/video-helper.js +++ b/scripts/video-helper.js @@ -1,37 +1,73 @@ -import { createElement, getTextLabel } from './common.js'; +import { + checkOneTrustGroup, + createElement, + deepMerge, + getTextLabel, +} from './common.js'; +import { + AEM_ASSETS, + COOKIE_VALUES, +} from './constants.js'; + +const { videoURLRegex } = AEM_ASSETS; export const videoTypes = { + aem: 'aem', youtube: 'youtube', local: 'local', both: 'both', }; -/* video helpers */ +export const standardVideoConfig = { + autoplay: false, + muted: false, + controls: true, + disablePictureInPicture: false, + currentTime: 0, + playsinline: true, +}; + +export const videoConfigs = {}; + +export const addVideoConfig = (videoId, props = {}) => { + if (!videoConfigs[videoId]) { + videoConfigs[videoId] = deepMerge({}, standardVideoConfig); + } + deepMerge(videoConfigs[videoId], props); +}; + +export const getVideoConfig = (videoId) => videoConfigs[videoId]; + export function isLowResolutionVideoUrl(url) { return url.split('?')[0].endsWith('.mp4'); } +export function isAEMVideoUrl(url) { + return videoURLRegex.test(url); +} + export function isVideoLink(link) { const linkString = link.getAttribute('href'); return (linkString.includes('youtube.com/embed/') + || videoURLRegex.test(linkString) || isLowResolutionVideoUrl(linkString)) && link.closest('.block.embed') === null; } export function selectVideoLink(links, preferredType, videoType = videoTypes.both) { - const isDefault = videoType === videoTypes.both; - const linksList = [...links]; - const optanonConsentCookieValue = decodeURIComponent(document.cookie.split(';') - .find((cookie) => cookie.trim().startsWith('OptanonConsent='))); - const cookieConsentForExternalVideos = optanonConsentCookieValue.includes('C0005:1'); - const shouldUseYouTubeLinks = (cookieConsentForExternalVideos && preferredType !== 'local') - || (!isDefault && videoType === videoTypes.youtube); - const youTubeLink = linksList.find((link) => link.getAttribute('href').includes('youtube.com/embed/')); - const localMediaLink = linksList.find((link) => link.getAttribute('href').split('?')[0].endsWith('.mp4')); - - if (shouldUseYouTubeLinks && youTubeLink) { - return youTubeLink; - } + const hasConsentForSocialVideos = checkOneTrustGroup(COOKIE_VALUES.social); + const isTypeBoth = videoType === videoTypes.both; + const prefersYouTube = (hasConsentForSocialVideos && preferredType !== 'local') + || (!isTypeBoth && videoType === videoTypes.youtube); + + const findLinkByCondition = (conditionFn) => links.find((link) => conditionFn(link.getAttribute('href'))); + + const aemVideoLink = findLinkByCondition((href) => videoURLRegex.test(href)); + const youTubeLink = findLinkByCondition((href) => href.includes('youtube.com/embed/')); + const localMediaLink = findLinkByCondition((href) => href.split('?')[0].endsWith('.mp4')); + + if (aemVideoLink) return aemVideoLink; + if (prefersYouTube && youTubeLink) return youTubeLink; return localMediaLink; } @@ -126,7 +162,7 @@ export function createIframe(url, { parentEl, classes = [] }) { classes: Array.isArray(classes) ? classes : [classes], props: { frameborder: '0', - allowfullscreen: 'allowfullscreen', + allowfullscreen: true, src: url, }, }); @@ -138,51 +174,101 @@ export function createIframe(url, { parentEl, classes = [] }) { return iframe; } -export const createVideo = (src, className = '', props = {}) => { - const video = createElement('video', { - classes: className, - }); - if (props.muted) { - video.muted = props.muted; - } +/** + * Creates a video element or an iframe for a video, depending on whether the video is local + * or not. Configures the element with specified classes, properties, and source. + * + * @param {string} src The source URL of the video. + * @param {string} [className=''] Optional. CSS class names to apply to the video element or iframe. + * @param {Object} [props={}] Optional. Properties and attributes for the video element or iframe, + * including attributes like 'muted', 'autoplay', 'title'. All properties + * are applied as attributes. + * @param {boolean} [localVideo=true] Optional. Indicates if the video is a local file. If true, + * creates a