From cf58c93f1ce1211372c73c897120b5149ade5526 Mon Sep 17 00:00:00 2001 From: Taimur Date: Thu, 14 Dec 2023 11:54:26 +0100 Subject: [PATCH] Refactor: new classes names for consistent content and developer experience, new reusable JS functions and code cleanup --- blocks/v2-content-card/v2-content-card.js | 3 +- .../v2-media-with-text/v2-media-with-text.css | 277 ++++++++++-------- .../v2-media-with-text/v2-media-with-text.js | 130 +++----- common/modal/modal.js | 2 +- scripts/common.js | 40 +++ scripts/video-helper.js | 21 ++ 6 files changed, 267 insertions(+), 206 deletions(-) diff --git a/blocks/v2-content-card/v2-content-card.js b/blocks/v2-content-card/v2-content-card.js index db285a37..d4e9d9b0 100644 --- a/blocks/v2-content-card/v2-content-card.js +++ b/blocks/v2-content-card/v2-content-card.js @@ -2,8 +2,7 @@ import { createOptimizedPicture } from '../../scripts/lib-franklin.js'; import { adjustPretitle, createElement, removeEmptyTags, variantsClassesToBEM, } from '../../scripts/common.js'; -import { createVideo, isVideoLink } from '../../scripts/video-helper.js'; -import { getDynamicVideoHeight } from '../v2-media-with-text/v2-media-with-text.js'; +import { createVideo, getDynamicVideoHeight, isVideoLink } from '../../scripts/video-helper.js'; const variantClasses = ['images-grid', 'images-grid-masonry', 'editorial']; diff --git a/blocks/v2-media-with-text/v2-media-with-text.css b/blocks/v2-media-with-text/v2-media-with-text.css index 0774e2f2..83905a83 100644 --- a/blocks/v2-media-with-text/v2-media-with-text.css +++ b/blocks/v2-media-with-text/v2-media-with-text.css @@ -1,213 +1,254 @@ +/* Variant: default */ + .v2-media-with-text { - position: relative; display: flex; flex-direction: column; - gap: 24px; + gap: 40px; } -.v2-media-with-text video { - width: 100%; +.v2-media-with-text__container { + display: flex; + flex-direction: column; + gap: 32px; } -h4.v2-media-with-text__heading { - font-size: var(--f-heading-5-font-size); - text-transform: unset; - margin-bottom: 0; +.v2-media-with-text__media-section { + position: relative; } -.v2-media-with-text__picture img { - object-fit: cover; +.v2-media-with-text__media-section video { border-radius: var(--border-radius); - height: 100%; width: 100%; } -.v2-media-with-text__paragraph { - text-align: center; +.v2-media-with-text__media-section img { + border-radius: var(--border-radius); + object-fit: cover; + aspect-ratio: 7/5; } -.v2-media-with-text--single-image-1 .v2-media-with-text__picture { - aspect-ratio: 16 / 10; +.v2-media-with-text__content-section { + width: 100%; } -.v2-media-with-text--single-image-1 .v2-media-with-text__paragraph { - text-align: left; - margin: -8px 0 0; +.v2-media-with-text__content-section p { + margin-bottom: 0; } -/* Single video modal 1 */ -.v2-media-with-text--single-video-modal-1 .icon-play-video { - position: absolute; - top: 50%; - right: 50%; - transform: translate(50%, -50%); - width: 48px; - height: 48px; +.v2-media-with-text__heading { + font: 400 var(--f-heading-5-font-size) / var(--f-heading-5-line-height) var(--font-family-heading); + letter-spacing: var(--f-heading-5-letter-spacing); } -/* Single image 2 left */ -.v2-media-with-text--single-image-2-left > div { - display: flex; - gap: 24px; - flex-direction: column; +.v2-media-with-text__sub-text-section { + color: var(--c-grey-4); } -.v2-media-with-text--single-image-2-left .v2-media-with-text__paragraph { - text-align: left; +/* Variant: text-centered */ + +.v2-media-with-text--text-centered .v2-media-with-text__content-section { + text-align: center; + margin: auto; +} + +/* Variant: full-width */ + +.v2-media-with-text--full-width .v2-media-with-text__media-section img { + width: 100%; + height: 210px; + border-radius: 0; } -p:has(picture) { - margin: 0; +.v2-media-with-text--full-width .v2-media-with-text__media-section video { + border-radius: 0; } -.v2-media-with-text--single-image-2-right > div { - display: flex; - flex-direction: column; +.v2-media-with-text--full-width .v2-media-with-text__content-section { + padding: 0 16px; +} + +/* Variant: media-right, media-left */ +.v2-media-with-text--media-left, .v2-media-with-text--media-right { gap: 24px; } -.v2-media-with-text--single-image-2-right > div > div:first-child { - order: 2; +.v2-media-with-text--media-left .v2-media-with-text__media-section img, .v2-media-with-text--media-right .v2-media-with-text__media-section img { + aspect-ratio: 7/5; + height: 100%; +} + +/* Variant: media-vertical */ + +.v2-media-with-text--media-vertical { + grid-gap: 48px; } -.v2-media-with-text--single-image-2-right .v2-media-with-text__paragraph { +.v2-media-with-text--media-vertical .v2-media-with-text__content-section { text-align: left; + max-width: 100%; } -.v2-media-with-text--double-image-1 { - border: 1px seagreen solid; - gap: 32px; +.v2-media-with-text--media-vertical .v2-media-with-text__media-section img { + aspect-ratio: 4/3; } -.v2-media-with-text--double-image-1 .v2-media-with-text__paragraph { - margin-top: -16px; - text-align: left; +/* Variant: media-gallery */ + + +.v2-media-with-text--media-gallery .v2-media-with-text__media-section { + display: flex; + gap: 20px; + scroll-snap-type: x mandatory; + overflow: auto; + flex-flow: row nowrap; +} + +.v2-media-with-text--media-gallery .v2-media-with-text__media-section picture { + scroll-snap-align: center; + width: 335px; + flex-shrink: 0; +} + +.v2-media-with-text--media-gallery .v2-media-with-text__media-section img { + aspect-ratio: 1; } + @media (min-width: 744px) { - .v2-media-with-text { - gap: 32px; + /* Variant: default */ + .v2-media-with-text__media-section img { + aspect-ratio: 4/3; } - .v2-media-with-text__paragraph { - width: 80%; - margin-left: auto; - margin-right: auto; + .v2-media-with-text__content-section, .v2-media-with-text__sub-text-section { + max-width: 506px; } - .v2-media-with-text--single-image-1 .v2-media-with-text__paragraph { - margin: -16px 0 0; - } - .v2-media-with-text--single-video-autoplay-1 .v2-media-with-text__paragraph { - margin: auto; - width: 80%; + /* Variant: full-width */ + .v2-media-with-text--full-width .v2-media-with-text__media-section img { + height: 418px; } - .v2-media-with-text--single-image-2-left > div { + + /* Variant: media-left, media-right */ + .v2-media-with-text--media-left, .v2-media-with-text--media-right { gap: 32px; } - .v2-media-with-text--single-image-2-left .v2-media-with-text__paragraph { - text-align: left; - margin-left: 0; - width: 50%; + .v2-media-with-text--media-left .v2-media-with-text__content-section, .v2-media-with-text--media-right .v2-media-with-text__content-section { + max-width: 332px; } - .v2-media-with-text--single-image-2-right .v2-media-with-text__paragraph { - margin-left: 0; + .v2-media-with-text--media-left .v2-media-with-text__media-section img, .v2-media-with-text--media-right .v2-media-with-text__media-section img { + aspect-ratio: 4 / 3; + } + + + /* Variant: media-vertical */ + .v2-media-with-text--media-vertical .v2-media-with-text__content-section { + max-width: 332px; } - .v2-media-with-text--double-image-1 .v2-media-with-text__paragraph { - margin-left: 0; + /* Variant: media-gallery */ + .v2-media-with-text--media-gallery .v2-media-with-text__media-section picture { + width: 506px; } + } @media (min-width: 1200px) { + /* Variant: default */ .v2-media-with-text { gap: 48px; } - .v2-media-with-text__picture img { + + .v2-media-with-text__media-section img { aspect-ratio: 16 / 9; } - h4.v2-media-with-text__heading { + .v2-media-with-text__content-section { + max-width: 694px; text-align: center; - font-size: var(--f-heading-4-font-size); + margin: auto; } - p.v2-media-with-text__paragraph { - text-align: center; - width: 61.8%; - margin: 24px auto 0; - text-wrap: balance; + .v2-media-with-text__content-section p { font-size: var(--f-body-2-font-size); - line-height: var(--f-body-2-font-size); + letter-spacing: var(--f-body-2-letter-spacing); + line-height: var(--f-body-2-line-height); + margin-bottom: 0; } - .v2-media-with-text--single-image-1 .v2-media-with-text__paragraph { + .v2-media-with-text__sub-text-section { text-align: center; - margin: -24px auto 0; + max-width: 694px; } - .v2-media-with-text--single-video-autoplay-1 .v2-media-with-text__paragraph { - width: 61.8%; - } - .v2-media-with-text--single-video-modal-1 .v2-media-with-text__paragraph { - margin-top: 0; + .v2-media-with-text__heading { + font: 400 var(--f-heading-4-font-size) / var(--f-heading-4-line-height) var(--font-family-heading); + letter-spacing: var(--f-heading-4-letter-spacing); } - /* Single image 2 left / Single image 2 right */ - .v2-media-with-text--single-image-2-left .v2-media-with-text__picture, - .v2-media-with-text--single-image-2-right .v2-media-with-text__picture { - aspect-ratio: 23 / 20; + /* Variant: full-width */ + .v2-media-with-text--full-width .v2-media-with-text__content-section { + padding: 0; } - .v2-media-with-text--single-image-2-left .v2-media-with-text__paragraph, - .v2-media-with-text--single-image-2-right .v2-media-with-text__paragraph { - width: auto; - margin: 0; - line-height: var(--f-body-2-line-height); - text-align: left; + .v2-media-with-text--full-width .v2-media-with-text__media-section img { + height: 810px; } - .v2-media-with-text--single-image-2-left h4.v2-media-with-text__heading, - .v2-media-with-text--single-image-2-right h4.v2-media-with-text__heading { - text-align: left; - margin-bottom: 24px; + + /* Variant: media-left, media-right */ + .v2-media-with-text--media-left, .v2-media-with-text--media-right { + align-items: center; } - .v2-media-with-text--single-image-2-left > div, - .v2-media-with-text--single-image-2-right > div { - display: flex; + .v2-media-with-text--media-left { flex-direction: row; - justify-content: center; - height: 100%; } - .v2-media-with-text--single-image-2-left > div > div:nth-child(2), - .v2-media-with-text--single-image-2-right > div > div:nth-child(1) { - display: flex; - flex-direction: column; - justify-content: center; - flex-basis: 30%; + .v2-media-with-text--media-right { + flex-direction: row-reverse } - .v2-media-with-text--single-image-2-left .v2-media-with-text__picture img, - .v2-media-with-text--single-image-2-right .v2-media-with-text__picture img { - aspect-ratio: 23 / 20; + .v2-media-with-text--media-left .v2-media-with-text__content-section, .v2-media-with-text--media-right .v2-media-with-text__content-section { + text-align: left; + max-width: 309px; } - /* Single image 2 right */ - .v2-media-with-text--single-image-2-right > div > div:first-child { - order: unset; + .v2-media-with-text--media-left .v2-media-with-text__media-section img, .v2-media-with-text--media-right .v2-media-with-text__media-section img { + aspect-ratio: 7 / 6; } - /* Double image 1 */ - .v2-media-with-text--double-image-1, .v2-media-with-text--double-image-2 { - flex-direction: row; + /* Variant: media-vertical */ + .v2-media-with-text--media-vertical { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto; + grid-gap: 64px 32px; } + + .v2-media-with-text--media-vertical .v2-media-with-text__content-section { + max-width: 100%; + } + + .v2-media-with-text--media-vertical .v2-media-with-text__sub-text-section { + grid-column: 1 / -1; + max-width: 694px; + margin: auto; + } + + /* Variant: media-gallery */ + .v2-media-with-text--media-gallery .v2-media-with-text__media-section picture { + width: 575px; + } } + + + + diff --git a/blocks/v2-media-with-text/v2-media-with-text.js b/blocks/v2-media-with-text/v2-media-with-text.js index 32779b41..9842ed32 100644 --- a/blocks/v2-media-with-text/v2-media-with-text.js +++ b/blocks/v2-media-with-text/v2-media-with-text.js @@ -1,95 +1,55 @@ -import { variantsClassesToBEM, removeEmptyTags, unwrapDivs } from '../../scripts/common.js'; import { - isVideoLink, createVideo, setPlaybackControls, wrapImageWithVideoLink, - selectVideoLink, -} from '../../scripts/video-helper.js'; + addVideoToSection, + createElement, + createNewSection, + unwrapDivs, + variantsClassesToBEM, +} from '../../scripts/common.js'; const blockName = 'v2-media-with-text'; - -export function getDynamicVideoHeight(video, playbackControls) { - // Get the element's height(use requestAnimationFrame to get actual height instead of 0) - requestAnimationFrame(() => { - const height = video.offsetHeight - 60; - playbackControls.style.top = `${height.toString()}px`; - }); - - // Get the element's height on resize - const getVideoHeight = (entries) => { - // eslint-disable-next-line no-restricted-syntax - for (const entry of entries) { - const height = entry.target.offsetHeight - 60; - playbackControls.style.top = `${height.toString()}px`; - } - }; - - const resizeObserver = new ResizeObserver(getVideoHeight); - resizeObserver.observe(video); -} - -const variantClasses = ['single-image-1', 'single-video-autoplay-1', 'single-video-modal-1', 'single-image-2-left', 'single-image-2-right', 'double-image-1', 'double-image-2']; - -const blockClasses = { - singleImage1: `.${blockName}--single-image-1`, - singleVideoAutoplay: `.${blockName}--single-video-autoplay-1`, - singleVideoModal: `.${blockName}--single-video-modal-1`, - singleImage2Left: `.${blockName}--single-image-2-left`, - singleImage2Right: `.${blockName}--single-image-2-right`, - doubleImage1: `.${blockName}--double-image-1`, - doubleImage2: `.${blockName}--double-image-2`, -}; +const variantClasses = ['text-centered', 'full-width', 'media-left', 'media-left', 'media-right', 'media-vertical', 'media-gallery']; export default async function decorate(block) { variantsClassesToBEM(block.classList, variantClasses, blockName); - const pictures = block.querySelectorAll('picture'); - pictures.forEach((picture) => picture.classList.add(`${blockName}__picture`)); - - const headings = block.querySelectorAll('h4'); - headings.forEach((heading) => heading.classList.add(`${blockName}__heading`)); - - const paragraphs = block.querySelectorAll('p:not(:has(picture))'); - paragraphs.forEach((paragraph) => paragraph.classList.add(`${blockName}__paragraph`)); - - const picture = block.querySelector('picture'); - const link = block.querySelector('a'); - const isVideo = link ? isVideoLink(link) : false; - if (isVideo && block.classList.contains(`${blockName}--single-video-autoplay-1`)) { - const video = createVideo(link.getAttribute('href'), `${blockName}__video`, { - muted: true, - autoplay: true, - loop: true, - playsinline: true, - }); - block.prepend(video); - - const playbackControls = video.querySelector('button'); - getDynamicVideoHeight(video, playbackControls); - setPlaybackControls(); - - link.remove(); - } - - const links = block.querySelectorAll('a'); - const videos = [...links].filter((singleLink) => isVideoLink(singleLink)); - - if (videos.length) { - // display image as link with play icon - const selectedLink = selectVideoLink(videos); - - if (selectedLink) { - picture.after(selectedLink); - wrapImageWithVideoLink(selectedLink, picture); + const isFullWidthBlock = block.className.includes('full-width'); + if (isFullWidthBlock) block.parentElement.classList.add('full-width'); + + const cells = block.querySelectorAll(':scope > div > div'); + let contentSection; let mediaSection; let subTextSection; let + containerSection; + + cells.forEach((cell, index) => { + // First cell for content, second for media and last for the subtext + const isLastCell = index % 2 === 0 && index === cells.length - 1; + const isCellNumberEven = index % 2 === 0; + const isTotalCellsEven = cells.length % 2 === 0; + + if (isLastCell) subTextSection = createNewSection(blockName, 'sub-text', cell); else if (isCellNumberEven) { + contentSection = createNewSection(blockName, 'content', cell); + const headings = [...cell.querySelectorAll(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])]; + headings.forEach((heading) => heading.classList.add(`${blockName}__heading`)); + } else { + mediaSection = createNewSection(blockName, 'media', cell); + const link = mediaSection.querySelector('a'); + mediaSection = addVideoToSection(blockName, mediaSection, link); } - // removing all the videos links excluding the selected one - // eslint-disable-next-line max-len - videos.forEach((singleLink) => singleLink !== selectedLink && singleLink.parentElement.remove()); - } - - removeEmptyTags(block); - const blocksCollection = document.querySelectorAll(`${blockClasses.singleImage1}, - ${blockClasses.singleVideoAutoplay}, ${blockClasses.singleVideoModal}, ${blockClasses.doubleImage2}`); - - blocksCollection.forEach((element) => { - unwrapDivs(element); + // If cell number is odd(i.e. a 'media' cell) and not the last cell + if (!isLastCell && !isCellNumberEven) { + // Wrap with a row if the number of cells is more than 2. + if (cells.length > 2) { + containerSection = createElement('div', { classes: `${blockName}__container` }); + containerSection.append(mediaSection, contentSection); + block.append(containerSection); + } else { + // Append the media and content sections directly to the block. + block.append(mediaSection, contentSection); + } + } + // For an odd number of cells, append the remaining subTextSection. + if (!isTotalCellsEven) { + if (subTextSection) block.append(subTextSection); + } }); + unwrapDivs(block); } diff --git a/common/modal/modal.js b/common/modal/modal.js index cb4e8efc..a4d0a4cb 100644 --- a/common/modal/modal.js +++ b/common/modal/modal.js @@ -1,6 +1,6 @@ +// eslint-disable-next-line import/no-cycle import { createElement, getTextLabel } from '../../scripts/common.js'; import { decorateIcons, loadCSS } from '../../scripts/lib-franklin.js'; -// eslint-disable-next-line import/no-cycle import { createIframe, isLowResolutionVideoUrl } from '../../scripts/video-helper.js'; const styles$ = new Promise((r) => { diff --git a/scripts/common.js b/scripts/common.js index 93b0013d..763dc80f 100644 --- a/scripts/common.js +++ b/scripts/common.js @@ -7,6 +7,8 @@ import { buildBlock, decorateBlock, } from './lib-franklin.js'; +// eslint-disable-next-line import/no-cycle +import { createVideo, isVideoLink } from './video-helper.js'; let placeholders = null; @@ -62,6 +64,44 @@ export function createElement(tagName, options = {}) { return elem; } + +/** + * Create a new section with the specific name. + * Append the given node to this section. + * @param {string} blockName - Block name. + * @param {string} sectionName - Name of the section + * (content-section, media-section etc...). + * @param {HTMLElement} node - HTML element that represents + * the node to append to the section. + * @returns {HTMLElement} - Returns an HTML element representing + * the new section with appended cell. + */ +export function createNewSection(blockName, sectionName, node) { + const section = createElement('div', { classes: `${blockName}__${sectionName}-section` }); + section.append(node); + return section; +} + +/** + * Provides the functionality to add a video + * to the provided section if the link corresponds to a video. + * @param {string} blockName - Name of the block. + * @param {HTMLElement} section - Represents the section to which the video should be added. + * @param {HTMLAnchorElement} link - Anchor link + * @returns {HTMLElement} - Section with added video + */ +export function addVideoToSection(blockName, section, link) { + const isVideo = link ? isVideoLink(link) : false; + if (isVideo) { + const video = createVideo(link.getAttribute('href'), `${blockName}__video`, { + muted: true, autoplay: true, loop: true, playsinline: true, + }); + const playbackControls = video.querySelector('button'); + link.remove(); + section.append(video, playbackControls); + } + return section; +} /** * Adds the favicon. * @param {string} href The favicon URL diff --git a/scripts/video-helper.js b/scripts/video-helper.js index 425e74aa..33acbcf9 100644 --- a/scripts/video-helper.js +++ b/scripts/video-helper.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line import/no-cycle import { createElement, getTextLabel } from './common.js'; /* video helpers */ @@ -218,3 +219,23 @@ export const createVideo = (src, className = '', props = {}) => { return video; }; + +export function getDynamicVideoHeight(video, playbackControls) { + // Get the element's height(use requestAnimationFrame to get actual height instead of 0) + requestAnimationFrame(() => { + const height = video.offsetHeight - 60; + playbackControls.style.top = `${height.toString()}px`; + }); + + // Get the element's height on resize + const getVideoHeight = (entries) => { + // eslint-disable-next-line no-restricted-syntax + for (const entry of entries) { + const height = entry.target.offsetHeight - 60; + playbackControls.style.top = `${height.toString()}px`; + } + }; + + const resizeObserver = new ResizeObserver(getVideoHeight); + resizeObserver.observe(video); +}