From 0428594d82d4a37c12a4788453fe1413358723de Mon Sep 17 00:00:00 2001 From: Ryan Parrish Date: Tue, 18 Jun 2024 04:37:50 -0600 Subject: [PATCH] =?UTF-8?q?MWPW-144801=20-=20[Hero-Marquee]=20=F0=9F=86=95?= =?UTF-8?q?=20block=20(#2412)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial marquee-hero block * allow key w/ classes structure * button-l default * rename lockup class to flex-row * added icon-list item types * Enable meta rows be authored above main content && authored Icon list support * update block name to just hero and row keys to hero- * added textOverrides * support for 'reverse, reverse-mobile' and remove empty split asset div * fixed padding on split mobile views, linting * multi classes on key rows. Cleaned up some format * decorateBg, order-reverse, con-block dark/light tablet, desktop selectors * default copy margins, extended decorateText for target * updated block name to hero-marquee, per GWP reques, renamed row key to 'milo-row-type` * decorateBgRow fix for selector * Fixed icon-size inheritance across rows and block variant * lockup default * Extending button class in row-text types * min-height on tablet/desk * trim badge titles and set approp margin * Few design feedback items, removed badge * Default spacing on bg-stack * Few design feedback refeinments * added tests - `con-block-row-` naming - Static links w/ dark-tablet etc. - min-height-m (560), min-height-l (700), m-height-l-desktop, etc... - `5 | 7` vs `6 | 6` - Did this w/ 100 margin-end on .copy in desktop. rather then grid 12. Easier w/ split set up - spacing w/ bg-stack-mobile etc - didn't force padding on type. Use authoring instead - bullet icon size margin - OK as is, if bullet and icon are used together the :marker: is default margin * fix align items action area * no default padding on bg-stack * refine naming conventions * Fixed decorate text logic to not rely on buttonClass to continue * distill sup content * bgcolor naming * bgcolor Type * con-vars support for bg-stack-* * - Split image display cover and 0 padding on stacked mobile/tablet. top or bttm 0 depending on stacking order - Split w/ video - treat same as image * milo_blocks list * split variant default padding bottom * added block to adobetv list * updated test row type -row-bgcolor * typo in class * moved bg-stack out of styles.css and scoped to hero, shared bg posltion feature from bricks and made a global export, added bg-stack-bottom-mobile to set order of that feature * logic style for split-asset * refactor asset selector for autoblock content * updated naming convention of fg-media * refactored .split to .fg-media-cover && .foreground-split to .foreground-media * fg-media-top -> fg-media-stack-top. same as bg-stack * fixed fg-media-cover sel to fix poly elem * refactored min-height-l, to l-min-height for naming convention * addressed a few rtl lang displays * updated icons to use margin-inline for better rtl support. Added selector for .image-link as asset * moved breakpoint-thheme into own file and imported based on classFind * decorateBtns by defatult on row-text type * few missing selectors in bg-stack, removed order .action-area defatul * fixed scope of .fg-media .foreground * added -only to tablet/desktop breakpoint-theme * fixed scope of fg-stack-top * distill CLass in -list, fg-media-hidden-mobile w/ foreground-asset * padding fix for media-hidden * lockup transform * padding on bg-stack-bottom * default min-height, helper classes tablet/desktop * -min-height defaults * clean up a few stylss * rtl fix for foreground-media * code coverage * removed blocks/marquee-hero - fragments of the past * Few nit PR formatting feeback issues resolve * Few PR feedback refactors * minor css optimizing * fixed some linting issues and optimized selectors * refined the breakpoint-theme a bit w/ :is scopes * revert style.css * added conditional for distillClasses, removed if w/ switch * removed variant names `fg-` and `stack-` * bg-top, vs bg-stack * linting * removed shorthand `mobile-tablet` vp selectors * check rtl support fix --------- Co-authored-by: milo-pr-merge[bot] <169241390+milo-pr-merge[bot]@users.noreply.github.com> --- libs/blocks/adobetv/adobetv.js | 2 +- libs/blocks/brick/brick.js | 34 +- libs/blocks/hero-marquee/hero-marquee.css | 524 ++++++++++++++++++ libs/blocks/hero-marquee/hero-marquee.js | 227 ++++++++ libs/styles/breakpoint-theme.css | 157 ++++++ libs/utils/decorate.js | 33 +- libs/utils/utils.js | 1 + test/blocks/hero-marquee/hero-marquee.test.js | 38 ++ test/blocks/hero-marquee/mocks/body.html | 119 ++++ 9 files changed, 1104 insertions(+), 31 deletions(-) create mode 100644 libs/blocks/hero-marquee/hero-marquee.css create mode 100644 libs/blocks/hero-marquee/hero-marquee.js create mode 100644 libs/styles/breakpoint-theme.css create mode 100644 test/blocks/hero-marquee/hero-marquee.test.js create mode 100644 test/blocks/hero-marquee/mocks/body.html diff --git a/libs/blocks/adobetv/adobetv.js b/libs/blocks/adobetv/adobetv.js index 9652e06b81..17ba78ef7d 100644 --- a/libs/blocks/adobetv/adobetv.js +++ b/libs/blocks/adobetv/adobetv.js @@ -4,7 +4,7 @@ import { applyHoverPlay, getVideoAttrs } from '../../utils/decorate.js'; const ROOT_MARGIN = 1000; const loadAdobeTv = (a) => { - const bgBlocks = ['aside', 'marquee']; + const bgBlocks = ['aside', 'marquee', 'hero-marquee']; if (a.href.includes('.mp4') && bgBlocks.some((b) => a.closest(`.${b}`))) { a.classList.add('hide'); const { href, hash } = a; diff --git a/libs/blocks/brick/brick.js b/libs/blocks/brick/brick.js index 6260636447..10ba35db93 100644 --- a/libs/blocks/brick/brick.js +++ b/libs/blocks/brick/brick.js @@ -1,11 +1,17 @@ -import { decorateTextOverrides, decorateBlockText, decorateBlockBg, decorateIconStack, decorateButtons } from '../../utils/decorate.js'; +import { + decorateBlockBg, + decorateBlockText, + decorateIconStack, + decorateTextOverrides, + decorateButtons, + handleObjectFit, +} from '../../utils/decorate.js'; import { createTag, getConfig, loadStyle } from '../../utils/utils.js'; const blockTypeSizes = { large: ['xxl', 'm', 'l'], default: ['xl', 'm', 'l'], }; -const objFitOptions = ['fill', 'contain', 'cover', 'none', 'scale-down']; function getBlockSize(el) { const sizes = Object.keys(blockTypeSizes); @@ -21,30 +27,6 @@ function handleSupplementalText(foreground) { if (lastP) lastP.className = 'supplemental-text'; } -function setObjectFitAndPos(text, pic, bgEl) { - const backgroundConfig = text.split(',').map((c) => c.toLowerCase().trim()); - const fitOption = objFitOptions.filter((c) => backgroundConfig.includes(c)); - const focusOption = backgroundConfig.filter((c) => !fitOption.includes(c)); - if (fitOption) [pic.querySelector('img').style.objectFit] = fitOption; - bgEl.innerHTML = ''; - bgEl.append(pic); - bgEl.append(document.createTextNode(focusOption.join(','))); -} - -function handleObjectFit(bgRow) { - const bgConfig = bgRow.querySelectorAll('div'); - [...bgConfig].forEach((r) => { - const pic = r.querySelector('picture'); - if (!pic) return; - let text = ''; - const pchild = [...r.querySelectorAll('p:not(:empty)')].filter((p) => p.innerHTML.trim() !== ''); - if (pchild.length > 2) text = pchild[1]?.textContent.trim(); - if (!text && r.textContent) text = r.textContent; - if (!text) return; - setObjectFitAndPos(text, pic, r); - }); -} - function handleClickableBrick(el, foreground) { if (!el.classList.contains('click')) return; const links = foreground.querySelectorAll('.brick-text a'); diff --git a/libs/blocks/hero-marquee/hero-marquee.css b/libs/blocks/hero-marquee/hero-marquee.css new file mode 100644 index 0000000000..67a3d5eade --- /dev/null +++ b/libs/blocks/hero-marquee/hero-marquee.css @@ -0,0 +1,524 @@ +.hero-marquee { + --s-min-height: 248px; /* 360px */ + --m-min-height: 448px; /* 560px */ + --l-min-height: 588px; /* 700px */ + + /* 112 = 56px default padding */ + + padding: var(--spacing-xl) 0; + position: relative; + display: flex; + flex-direction: column; + gap: var(--spacing-m); + width: 100%; + overflow: hidden; + min-height: var(--m-min-height); + justify-content: center; + align-items: center; +} + +.hero-marquee.s-min-height { min-height: var(--s-min-height); } +.hero-marquee.l-min-height { min-height: var(--l-min-height); } + +.dark .hero-marquee, +.hero-marquee.dark { + color: var(--color-white); +} + +.hero-marquee .background picture { + line-height: 0; + width: 100%; + height: 100%; + display: flex; +} + +/* Alignment */ +.hero-marquee .action-area, +.hero-marquee .lockup-area { + display: flex; +} + +.hero-marquee .action-area { + flex-flow: column wrap; + gap: var(--spacing-s); + width: 100%; +} + +.hero-marquee .background-split picture img { + object-fit: cover; + height: 100%; + width: 100%; +} + +.hero-marquee.media-cover picture { + line-height: 0em; + display: block; +} + +.hero-marquee .main-copy { + display: flex; + flex-direction: column; + gap: var(--spacing-xs); + justify-content: center; +} + +/* Lockup Area */ +.hero-marquee .lockup-area { + font-weight: 700; + display: flex; + align-items: center; + gap: var(--spacing-xs); + margin: 0 0 var(--spacing-xxs); + font-size: var(--type-body-m-size); + line-height: var(--type-body-m-lh); + text-transform: initial; + white-space: nowrap; +} + +.hero-marquee .lockup-area picture { + line-height: 0; + display: block; +} + +.hero-marquee .lockup-area a, +.hero-marquee .lockup-area a:hover { + color: inherit; +} + +.hero-marquee .action-area a:not(.con-button) { + font-weight: 700; +} + +.hero-marquee .foreground-media { + z-index: 1; +} + +.hero-marquee .foreground-media picture img, +.hero-marquee .foreground-media video { + object-fit: cover; + object-position: center top; + width: 100%; + height: 100%; + display: block; +} + +/* Lockup Area sizes - default large */ +.hero-marquee .lockup-area picture img, +.hero-marquee .l-icon .lockup-area, +.hero-marquee.l-icon .lockup-area { + font-size: var(--type-body-m-size); + line-height: var(--type-body-m-lh); +} + +.hero-marquee .lockup-area picture img, +.hero-marquee .l-icon .lockup-area picture img, +.hero-marquee.l-icon .lockup-area picture img { + min-width: var(--icon-size-l); + height: var(--icon-size-l); +} + +.hero-marquee .m-icon .lockup-area, +.hero-marquee.m-icon .lockup-area { + font-size: var(--type-body-s-size); + line-height: var(--type-body-s-lh); +} + +.hero-marquee .m-icon .lockup-area picture img, +.hero-marquee.m-icon .lockup-area picture img { + min-width: var(--icon-size-m); + height: var(--icon-size-m); +} + +.hero-marquee .xl-icon .lockup-area, +.hero-marquee.xl-icon .lockup-area { + font-size: var(--type-body-xl-size); + line-height: var(--type-body-xl-lh); +} + +.hero-marquee .xl-icon .lockup-area picture img, +.hero-marquee.xl-icon .lockup-area picture img { + min-width: var(--icon-size-xl); + height: var(--icon-size-xl); +} + +.hero-marquee.center { + text-align: center; + align-items: center; +} + +.hero-marquee.center .action-area, +.hero-marquee.center .lockup-area { + justify-content: center; +} + +.hero-marquee .norm p { margin: var(--spacing-xs) 0; } + +.hero-marquee .main-copy p, +.hero-marquee .main-copy p:only-child, +.hero-marquee .main-copy [class^="heading"], +.hero-marquee .norm p:only-child { margin: 0; } +.hero-marquee .norm :is(h1, h2, h3, h4, h5, h6) { margin: 0 0 var(--spacing-xs) 0; } +.hero-marquee .norm .action-area { margin-top: var(--spacing-s); } +.hero-marquee .norm div > *:last-child { margin-bottom: 0; } +.hero-marquee .norm div *:first-child { margin-top: 0; } + +.hero-marquee > .foreground { + max-width: var(--grid-container-width); + min-width: var(--grid-container-width); + margin: 0 auto; + display: grid; + gap: var(--spacing-m); +} + +html[dir="rtl"] .hero-marquee .foreground { + flex-direction: row-reverse; +} + +.hero-marquee > .foreground.fw { + width: 100%; +} + +.hero-marquee .foreground .copy { + display: grid; + gap: var(--spacing-xs); +} + +.hero-marquee hr { + width: 100%; + border: none; + height: 1px; + margin: 0; +} + +.hero-marquee .has-divider { + margin: var(--spacing-xxs) 0; +} + +/* content hidden */ +.hero-marquee .hidden { + visibility: hidden; + height: 0; +} + +.hero-marquee .foreground div:empty { + display: none; +} + +/* Row Types */ +.hero-marquee .row-list { + text-align: left; +} + +html[dir="rtl"] .hero-marquee .row-list { + text-align: right; +} + +.hero-marquee .row-list .row-wrapper { + display: table; +} + +.hero-marquee .row-list li { + margin-bottom: var(--spacing-xxs); +} + +/* row-qr */ +.hero-marquee .row-qrcode .row-wrapper { + display: flex; + gap: var(--spacing-s); + margin: 0; +} + +.hero-marquee .row-qrcode .row-wrapper > p { + margin: 0; +} + +.hero-marquee .row-qrcode .qr-code-img { + display: none; +} + +.hero-marquee .row-qrcode .google-play a, +.hero-marquee .row-qrcode .app-store a { + width: 135px; + height: 40px; + display: inline-flex; + color: transparent; +} + +.hero-marquee .row-qrcode .google-play a { + background: url('/libs/img/ui/google-play.svg') no-repeat transparent; +} + +.hero-marquee .row-qrcode .app-store a { + background: url('/libs/img/ui/app-store.svg') no-repeat transparent; +} + +.hero-marquee.center .row-qrcode .row-wrapper { + justify-content: center; +} + +.hero-marquee.center .row-list .row-wrapper { + margin: 0 auto; +} + +.hero-marquee .row-list .icon-list { + padding-inline-start: var(--spacing-m); + margin: 0; +} + +.hero-marquee li.icon-item { + position: relative; + list-style: none; +} + +.hero-marquee li::marker { + padding-inline-start: 40px; +} + +.hero-marquee li.icon-item::marker { + color: transparent; +} + +.hero-marquee li.icon-item span.icon { + position: absolute; + left: -28px; +} + +html[dir="rtl"] .hero-marquee li.icon-item span.icon { + left: unset; + right: -28px; +} + +/* row-supplemental */ +.hero-marquee .row-supplemental.bold { + font-weight: 700; +} + +.hero-marquee.media-cover.has-bg .asset { + display: none; +} + +.hero-marquee.asset-left > .foreground.cols-2 > .asset { + order: 2; +} + +/* mobile ONLY */ +@media (max-width: 600px) { + .hero-marquee .con-button { + display: block; + text-align: center; + } + + .hero-marquee.media-top-mobile { + flex-direction: column-reverse; + } + + .hero-marquee.media-top-mobile .foreground .copy { + order: 2; + } + + .hero-marquee.media-cover:not(.bg-bottom-mobile), + .hero-marquee.media-cover:not(.media-hidden-mobile) { + padding-bottom: 0; + } + + /* con-vars support */ + .hero-marquee:is(.bg-top-mobile, .bg-top-mobile, .bg-bottom-mobile) .background { + position: relative; + } + + .hero-marquee.bg-top-mobile { + padding-top: unset; + } + + .hero-marquee.bg-bottom-mobile { + padding-bottom: unset; + } + + .hero-marquee.media-cover.media-top-mobile, + .hero-marquee.media-cover.media-hidden-mobile:not(.bg-bottom-mobile) { + padding-top: 0; + padding-bottom: var(--spacing-xl); + } + + .hero-marquee.bg-top-mobile:not(.bg-bottom-mobile), + .hero-marquee.bg-top-mobile.media-top-mobile { + padding-top: 0; + } + + .hero-marquee.bg-bottom-mobile .background { + order: 2; + } + + .hero-marquee.media-hidden-mobile .foreground .asset, + .hero-marquee.media-hidden-mobile .foreground-media { display: none; } +} + +/* Tablet ONLY */ +@media screen and (min-width: 600px) and (max-width: 1199px) { + .hero-marquee.media-cover:not(.bg-bottom-mobile) { + padding-bottom: 0; + } + + .hero-marquee.media-top-tablet, + .hero-marquee.media-cover.media-top-tablet { + flex-direction: column-reverse; + } + + .hero-marquee.media-top-tablet > .foreground .copy { + order: 2; + } + + .hero-marquee.media-cover.media-top-tablet, + .hero-marquee.media-cover.media-hidden-tablet { + padding-top: 0; + padding-bottom: var(--spacing-xl); + } + + /* con-vars support */ + .hero-marquee.bg-top-tablet .background, + .hero-marquee.bg-bottom-tablet .background { + position: relative; + } + + .hero-marquee.bg-top-tablet { + padding-top: 0; + } + + .hero-marquee.bg-bottom-tablet .background { + order: 2; + } + + /* helper classes */ + .hero-marquee.media-hidden-tablet .foreground .asset, + .hero-marquee.media-hidden-tablet .foreground-media, + .hero-marquee.media-hidden-tablet-tablet .foreground-media { display: none; } +} + +/* Tablet UP */ +@media screen and (min-width: 600px) { + .hero-marquee, + .hero-marquee .action-area { + flex-direction: row; + } + + .hero-marquee .action-area { + align-items: center; + } + + .hero-marquee.media-cover { + flex-direction: column; + } + + .hero-marquee.center .action-area { + justify-content: center; + } + + .hero-marquee.bg-top-tablet { + flex-direction: column; + } + + /* min height */ + .hero-marquee.s-min-height-tablet { min-height: var(--s-min-height);} + .hero-marquee.l-min-height-tablet { min-height: var(--l-min-height);} + + /* helper classes */ + .hero-marquee .order-0-tablet { order: 0; } + .hero-marquee .order-1-tablet { order: 1; } + .hero-marquee .order-2-tablet { order: 2; } + .hero-marquee .order-3-tablet { order: 3; } +} + +@media screen and (min-width: 920px) { + .hero-marquee .foreground.cols-1 { + max-width: 800px; + min-width: unset; + } +} + +/* desktop up */ +@media screen and (min-width: 1200px) { + .hero-marquee { + display: flex; + flex-direction: row; + } + + .hero-marquee.media-cover { + flex-direction: row; + } + + .hero-marquee.media-cover picture { + display: unset; + } + + .hero-marquee.media-cover.has-bg .asset { + display: initial; + } + + .hero-marquee > .foreground.cols-2 { + grid-template-columns: minmax(50%, 1fr) minmax(50%, 1fr); + align-items: center; + gap: 0; + } + + .hero-marquee > .foreground.cols-2 .copy { + padding-inline-end: 100px; + } + + .hero-marquee.asset-left > .foreground.cols-2 .copy { + padding-inline-start: 100px; + padding-inline-end: unset; + } + + + .hero-marquee .foreground-media { + position: absolute; + width: 50vw; + height: 100%; + right: 0; + top: 0; + } + + .hero-marquee.asset-left .foreground-media, + html[dir="rtl"] .hero-marquee .foreground-media { + right: unset; + left: 0; + } + + html[dir="rtl"] .hero-marquee.asset-left .foreground-media { + right: 0; + left: unset; + } + + .hero-marquee.asset-left > .foreground.cols-2 > .asset { + order: unset; + } + + /* Action area order */ + .hero-marquee .main-copy .action-area, + .hero-marquee .main-copy [class*='heading-'] + [class*='body-'] { + order: unset; + } + + /* meta blocks */ + .hero-marquee .row-qrcode .qr-code-img { + display: inline-block; + max-width: 140px; + max-height: 140px; + margin: 0; + } + + .hero-marquee .row-qrcode .google-play, + .hero-marquee .row-qrcode .app-store { + display: none; + } + + /* min height */ + .hero-marquee.s-min-height-desktop { min-height: var(--s-min-height);} + .hero-marquee.l-min-height-desktop { min-height: var(--l-min-height);} + + /* helper classes */ + .hero-marquee .order-0-desktop { order: 0; } + .hero-marquee .order-1-desktop { order: 1; } + .hero-marquee .order-2-desktop { order: 2; } + .hero-marquee .order-3-desktop { order: 3; } +} diff --git a/libs/blocks/hero-marquee/hero-marquee.js b/libs/blocks/hero-marquee/hero-marquee.js new file mode 100644 index 0000000000..230742aa5a --- /dev/null +++ b/libs/blocks/hero-marquee/hero-marquee.js @@ -0,0 +1,227 @@ +import { + decorateBlockBg, + decorateBlockHrs, + decorateBlockText, + decorateTextOverrides, + decorateButtons, + handleObjectFit, +} from '../../utils/decorate.js'; +import { createTag, loadStyle, getConfig } from '../../utils/utils.js'; + +const contentTypes = ['list', 'qrcode', 'lockup', 'text', 'bgcolor', 'supplemental']; +const rowTypeKeyword = 'con-block-row-'; +const breakpointThemeClasses = ['dark-mobile', 'light-mobile', 'dark-tablet', 'light-tablet', 'dark-desktop', 'light-desktop']; + +function distillClasses(el, classes) { + const taps = ['-heading', '-body', '-detail']; + classes?.forEach((elClass) => { + const elTaps = taps.filter((tap) => elClass.endsWith(tap)); + if (!elTaps.length) return; + const parts = elClass.split('-'); + el.classList.add(`${parts[1]}-${parts[0]}`); + el.classList.remove(elClass); + }); +} + +function decorateList(el, classes) { + el.classList.add('body-l'); + const listItems = el.querySelectorAll('li'); + if (listItems.length) { + const firstDiv = el.querySelector(':scope > div'); + firstDiv.classList.add('foreground'); + [...listItems].forEach((item) => { + const firstElemIsIcon = item.children[0]?.classList.contains('icon'); + if (firstElemIsIcon) item.classList.add('icon-item'); + if (!item.parentElement.classList.contains('icon-list')) item.parentElement.classList.add('icon-list'); + }); + } + distillClasses(el, classes); +} + +function decorateQr(el) { + const text = el.querySelector(':scope > div'); + /* c8 ignore next */ + if (!text) return; + const classes = ['qr-code-img', 'google-play', 'app-store']; + [...text.children].forEach((e, i) => { + e.classList.add(classes[i]); + }); +} + +function decorateLockupFromContent(el) { + const rows = el.querySelectorAll(':scope > p'); + const firstRowImg = rows[0]?.querySelector('img'); + if (firstRowImg) rows[0].classList.add('lockup-area'); +} + +function decorateBg(el) { + const block = el.closest('.hero-marquee'); + block.style.background = el.textContent.trim(); + el.remove(); +} + +function decorateText(el, classes) { + el.classList.add('norm'); + const btnClass = classes?.find((c) => c.endsWith('-button')); + if (btnClass) { + const [theme, size] = btnClass.split('-').reverse(); + el.classList.remove(btnClass); + decorateButtons(el, `${size}-${theme}`); + } else { + decorateButtons(el, 'button-xl'); + } + distillClasses(el, classes); +} + +function decorateLockupRow(el) { + const child = el.querySelector(':scope > div'); + if (child) child.classList.add('lockup-area'); +} + +function decorateSup(el, classes) { + el.classList.add('norm'); + distillClasses(el, classes); +} + +function extendButtonsClass(copy) { + const buttons = copy.querySelectorAll('.con-button'); + if (buttons.length === 0) return; + buttons.forEach((button) => { + button.classList.add('button-xl', 'button-justified-mobile'); + }); +} +function parseKeyString(str) { + const regex = /^(\w+)\s*\((.*)\)$/; + const match = str.match(regex); + if (!match) return { key: str }; + const key = match[1]; + const classes = match[2].split(',').map((c) => c.trim()); + const result = { key, classes }; + return result; +} + +function loadContentType(el, key, classes) { + if (classes !== undefined && classes.length) el.classList.add(...classes); + switch (key) { + case 'bgcolor': + decorateBg(el); + break; + case 'lockup': + decorateLockupRow(el); + break; + case 'qrcode': + decorateQr(el); + break; + case 'list': + decorateList(el, classes); + break; + case 'supplemental': + decorateSup(el, classes); + break; + case 'text': + decorateText(el, classes); + break; + default: + } +} + +function loadBreakpointThemes() { + const { miloLibs, codeRoot } = getConfig(); + loadStyle(`${miloLibs || codeRoot}/styles/breakpoint-theme.css`); +} + +export default async function init(el) { + el.classList.add('con-block'); + let rows = el.querySelectorAll(':scope > div'); + if (rows.length > 1 && rows[0].textContent !== '') { + el.classList.add('has-bg'); + const [head, ...tail] = rows; + handleObjectFit(head); + decorateBlockBg(el, head, { useHandleFocalpoint: true }); + rows = tail; + } + + // get first row that's not a keyword key/value row + const mainRowIndex = rows.findIndex((row) => { + const firstColText = row.children[0].textContent.toLowerCase().trim(); + return !firstColText.includes(rowTypeKeyword); + }); + const foreground = rows[mainRowIndex]; + const fRows = foreground.querySelectorAll(':scope > div'); + foreground.classList.add('foreground', `cols-${fRows.length}`); + let copy = fRows[0]; + const anyTag = foreground.querySelector('p, h1, h2, h3, h4, h5, h6'); + const asset = foreground.querySelector('div > picture, div > video, div > a[href*=".mp4"], div > a.image-link'); + const allRows = foreground.querySelectorAll('div > div'); + copy = anyTag.closest('div'); + copy.classList.add('copy'); + + if (asset) { + asset.parentElement.classList.add('asset'); + if (el.classList.contains('media-cover')) { + el.appendChild(createTag('div', { class: 'foreground-media' }, asset)); + } + } else { + [...fRows].forEach((row) => { + if (row.childElementCount === 0) { + row.classList.add('empty-asset'); + } + }); + } + + const assetUnknown = (allRows.length === 2 + && allRows[1].classList.length === 0) + ? allRows[1] + : null; + if (assetUnknown) assetUnknown.classList.add('asset-unknown'); + + decorateBlockText(copy, ['xxl', 'm', 'l']); // heading, body, detail + decorateLockupFromContent(copy); + extendButtonsClass(copy); + + /* c8 ignore next 2 */ + const containsClassFromArray = () => breakpointThemeClasses.some( + (className) => el.classList.contains(className), + ); + if (containsClassFromArray) loadBreakpointThemes(); + + const assetRow = allRows[0].classList.contains('asset'); + if (assetRow) el.classList.add('asset-left'); + const mainCopy = createTag('div', { class: 'main-copy' }, copy.innerHTML); + rows.splice(mainRowIndex, 1); + if (mainRowIndex > 0) { + for (let i = 0; i < mainRowIndex; i += 1) { + rows[i].classList.add('prepend'); + } + } + + copy.innerHTML = ''; + copy.append(mainCopy); + [...rows].forEach((row) => { + if (row.classList.contains('prepend')) { + mainCopy.before(row); + } else { + copy.append(row); + } + }); + + [...rows].forEach(async (row) => { + const cols = row.querySelectorAll(':scope > div'); + const firstCol = cols[0]; + const firstColText = firstCol.textContent.toLowerCase().trim(); + const isKeywordRow = firstColText.includes(rowTypeKeyword); + if (isKeywordRow) { + const keyValue = firstColText.replace(rowTypeKeyword, '').trim(); + const parsed = parseKeyString(keyValue); + firstCol.parentElement.classList.add(`row-${parsed.key}`, 'con-block'); + firstCol.remove(); + cols[1].classList.add('row-wrapper'); + if (contentTypes.includes(parsed.key)) loadContentType(row, parsed.key, parsed.classes); + } else { + row.classList.add('norm'); + decorateBlockHrs(row); + decorateButtons(row, 'button-xl'); + } + }); + decorateTextOverrides(el, ['-heading', '-body', '-detail'], mainCopy); +} diff --git a/libs/styles/breakpoint-theme.css b/libs/styles/breakpoint-theme.css new file mode 100644 index 0000000000..2d5ae14c02 --- /dev/null +++ b/libs/styles/breakpoint-theme.css @@ -0,0 +1,157 @@ + + +/* Static Links */ +.con-block.static-links[class*="dark-"] a:not(.con-button), +.static-links .con-block[class*="dark-"] a:not(.con-button), +.con-block.static-links[class*="light-"] a:not(.con-button), +.static-links .con-block[class*="light-"] a:not(.con-button), +.con-block.static-links[class*="dark-"] a:not(.con-button):hover, +.static-links .con-block[class*="dark-"] a:not(.con-button):hover, +.con-block.static-links[class*="light-"] a:not(.con-button):hover, +.static-links .con-block[class*="light-"] a:not(.con-button):hover { + color: inherit; +} + +/* mobile ONLY */ +@media (max-width: 600px) { + .con-block.light-mobile-only { color: var(--text-color); } + .con-block.dark-mobile-only { color: var(--color-white); } + + .con-block.light-mobile-only a:not(.con-button) { color: var(--link-color); } + .con-block.dark-mobile-only a:not(.con-button) { color: var(--link-color-dark); } + + .con-block.light-mobile-only a:not(.con-button):is(:hover, :focus, :active) { color: var(--link-hover-color); } + .con-block.dark-mobile-only a:not(.con-button):is(:hover, :focus, :active) { color: var(--link-hover-color-dark); } + + .con-block.light-mobile-only a.con-button.outline { + border-color: var(--text-color); + color: var(--text-color); + } + + .con-block.dark-mobile-only a.con-button.outline { + border-color: var(--color-white); + color: var(--color-white); + } + + .con-block.light-mobile-only a.con-button.outline:is(:hover, :focus, :active) { + background-color: var(--color-black); + border-color: var(--color-black); + color: var(--color-white); + } + + .con-block.dark-mobile-only a.con-button.outline:is(:hover, :focus, :active) { + background-color: var(--color-white); + border-color: var(--color-white); + color: var(--color-black); + text-decoration: none; + } +} + +/* Tablet ONLY */ +@media screen and (min-width: 600px) and (max-width: 1199px) { + /* tablet theme */ + .con-block.light-tablet-only { color: var(--text-color); } + .con-block.dark-tablet-only { color: var(--color-white); } + + .con-block.light-tablet-only a:not(.con-button) { color: var(--link-color); } + .con-block.dark-tablet-only a:not(.con-button) { color: var(--link-color-dark); } + + .con-block.light-tablet-only a:not(.con-button):is(:hover, :focus, :active) { color: var(--link-hover-color); } + .con-block.dark-tablet-only a:not(.con-button):is(:hover, :focus, :active) { color: var(--link-hover-color-dark); } + + .con-block.light-tablet-only a.con-button.outline { + border-color: var(--text-color); + color: var(--text-color); + } + + .con-block.dark-tablet-only a.con-button.outline { + border-color: var(--color-white); + color: var(--color-white); + } + + .con-block.light-tablet-only a.con-button.outline:is(:hover, :focus, :active) { + background-color: var(--color-black); + border-color: var(--color-black); + color: var(--color-white); + } + + .con-block.dark-tablet-only a.con-button.outline:is(:hover, :focus, :active) { + background-color: var(--color-white); + border-color: var(--color-white); + color: var(--color-black); + text-decoration: none; + } +} + +/* Tablet UP */ +@media screen and (min-width: 600px) { + /* tablet theme */ + .con-block.light-tablet { color: var(--text-color); } + .con-block.dark-tablet { color: var(--color-white); } + + .con-block.light-tablet a:not(.con-button) { color: var(--link-color); } + .con-block.dark-tablet a:not(.con-button) { color: var(--link-color-dark); } + + .con-block.light-tablet a:not(.con-button):is(:hover, :focus, :active) { color: var(--link-hover-color); } + .con-block.dark-tablet a:not(.con-button):is(:hover, :focus, :active) { color: var(--link-hover-color-dark); } + + .con-block.light-tablet a.con-button.outline { + border-color: var(--text-color); + color: var(--text-color); + } + + .con-block.dark-tablet a.con-button.outline { + border-color: var(--color-white); + color: var(--color-white); + } + + .con-block.light-tablet a.con-button.outline:is(:hover, :focus, :active) { + background-color: var(--color-black); + border-color: var(--color-black); + color: var(--color-white); + } + + .con-block.dark-tablet a.con-button.outline:is(:hover, :focus, :active) { + background-color: var(--color-white); + border-color: var(--color-white); + color: var(--color-black); + text-decoration: none; + } +} + +/* desktop up */ +@media screen and (min-width: 1200px) { + /* desktop theme */ + .con-block.light-desktop { color: var(--text-color); } + .con-block.dark-desktop { color: var(--color-white); } + + .con-block.light-desktop a:not(.con-button) { color: var(--link-color); } + .con-block.dark-desktop a:not(.con-button) { color: var(--link-color-dark); } + + .con-block.light-desktop a:not(.con-button):is(:hover, :focus, :active) { color: var(--link-hover-color); } + + .con-block.dark-desktop a:not(.con-button):is(:hover, :focus, :active) { color: var(--link-hover-color-dark); } + + .con-block.light-desktop a.con-button.outline { + border-color: var(--text-color); + color: var(--text-color); + } + + .con-block.dark-desktop a.con-button.outline { + border-color: var(--color-white); + color: var(--color-white); + } + + .con-block.light-desktop a.con-button.outline:is(:hover, :focus, :active) { + background-color: var(--color-black); + border-color: var(--color-black); + color: var(--color-white); + } + + .con-block.dark-desktop a.con-button.outline:is(:hover, :focus, :active) { + background-color: var(--color-white); + border-color: var(--color-white); + color: var(--color-black); + text-decoration: none; + } +} diff --git a/libs/utils/decorate.js b/libs/utils/decorate.js index eec2e1770e..7621c3b7ba 100644 --- a/libs/utils/decorate.js +++ b/libs/utils/decorate.js @@ -148,10 +148,11 @@ export const decorateBlockHrs = (el) => { if (hasHr) el.classList.add('has-divider'); }; -function applyTextOverrides(el, override) { +function applyTextOverrides(el, override, targetEl) { const parts = override.split('-'); const type = parts[1]; - const els = el.querySelectorAll(`[class^="${type}"]`); + const scopeEl = (targetEl !== false) ? targetEl : el; + const els = scopeEl.querySelectorAll(`[class^="${type}"]`); if (!els.length) return; els.forEach((elem) => { const replace = [...elem.classList].find((i) => i.startsWith(type)); @@ -159,12 +160,12 @@ function applyTextOverrides(el, override) { }); } -export function decorateTextOverrides(el, options = ['-heading', '-body', '-detail']) { +export function decorateTextOverrides(el, options = ['-heading', '-body', '-detail'], target = false) { const overrides = [...el.classList] .filter((elClass) => options.findIndex((ovClass) => elClass.endsWith(ovClass)) >= 0); if (!overrides.length) return; overrides.forEach((override) => { - applyTextOverrides(el, override); + applyTextOverrides(el, override, target); el.classList.remove(override); }); } @@ -200,3 +201,27 @@ export function applyHoverPlay(video) { video.setAttribute('data-mouseevent', true); } } + +function setObjectFitAndPos(text, pic, bgEl, objFitOptions) { + const backgroundConfig = text.split(',').map((c) => c.toLowerCase().trim()); + const fitOption = objFitOptions.filter((c) => backgroundConfig.includes(c)); + const focusOption = backgroundConfig.filter((c) => !fitOption.includes(c)); + if (fitOption) [pic.querySelector('img').style.objectFit] = fitOption; + bgEl.innerHTML = ''; + bgEl.append(pic); + bgEl.append(document.createTextNode(focusOption.join(','))); +} + +export function handleObjectFit(bgRow) { + const bgConfig = bgRow.querySelectorAll('div'); + [...bgConfig].forEach((r) => { + const pic = r.querySelector('picture'); + if (!pic) return; + let text = ''; + const pchild = [...r.querySelectorAll('p:not(:empty)')].filter((p) => p.innerHTML.trim() !== ''); + if (pchild.length > 2) text = pchild[1]?.textContent.trim(); + if (!text && r.textContent) text = r.textContent; + if (!text) return; + setObjectFitAndPos(text, pic, r, ['fill', 'contain', 'cover', 'none', 'scale-down']); + }); +} diff --git a/libs/utils/utils.js b/libs/utils/utils.js index eaf8a47b98..babe989979 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -37,6 +37,7 @@ const MILO_BLOCKS = [ 'graybox', 'footer', 'gnav', + 'hero-marquee', 'how-to', 'icon-block', 'iframe', diff --git a/test/blocks/hero-marquee/hero-marquee.test.js b/test/blocks/hero-marquee/hero-marquee.test.js new file mode 100644 index 0000000000..063f9b7631 --- /dev/null +++ b/test/blocks/hero-marquee/hero-marquee.test.js @@ -0,0 +1,38 @@ +import { readFile } from '@web/test-runner-commands'; +import { expect } from '@esm-bundle/chai'; +import { stub } from 'sinon'; +import { waitForElement } from '../../helpers/waitfor.js'; +import { setConfig } from '../../../libs/utils/utils.js'; + +window.lana = { log: stub() }; + +const locales = { '': { ietf: 'en-US', tk: 'hah7vzn.css' } }; +const conf = { locales }; +setConfig(conf); + +describe('Hero Marquee', () => { + before(async () => { + document.body.innerHTML = await readFile({ path: './mocks/body.html' }); + const { default: init } = await import('../../../libs/blocks/hero-marquee/hero-marquee.js'); + const marquees = document.querySelectorAll('.hero-marquee'); + marquees.forEach(async (marquee) => { + await init(marquee); + }); + }); + it('supports main copy and additinoal block-rows (list, qrcode, lockup, text)', async () => { + const copy = await waitForElement('.main-copy'); + const rowList = await waitForElement('.row-list'); + const rowQrCode = await waitForElement('.row-qrcode'); + const rowLockup = await waitForElement('.row-lockup'); + const rowText = await waitForElement('.row-text'); + expect(copy).to.exist; + expect(rowList).to.exist; + expect(rowQrCode).to.exist; + expect(rowLockup).to.exist; + expect(rowText).to.exist; + }); + it('supports authorable horizontal rules', async () => { + const hr = await waitForElement('.has-divider'); + expect(hr).to.exist; + }); +}); diff --git a/test/blocks/hero-marquee/mocks/body.html b/test/blocks/hero-marquee/mocks/body.html new file mode 100644 index 0000000000..72166c8f81 --- /dev/null +++ b/test/blocks/hero-marquee/mocks/body.html @@ -0,0 +1,119 @@ +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
con-block-row-lockup (xl-icon-size)
+
XL Icon Size
+
+
+
+

After Effects

+

DETAIL TEXT

+

This Hero has all row types

+

lockup, list, qrcode, text, background

+

See more Other options you say?

+
+
+
+
con-block-row-list (max-width-6-tablet)
+
+
    +
  • Small
  • +
  • Medium length text
  • +
  • Long length text that may break onto a new line, what will happen, keep it going so this is even longer and really wraps?
  • +
  • Another list
  • +
+
+
+
+
--- white
+
+
+
con-block-row-qrcode
+
+

https://main--milo--adobecom.hlx.page/drafts/rparrish/assets/qr-adobe-com.svg

+

Google play

+

Apple store

+
+
+
+
con-block-row-qrcode
+ +
+
+
+
con-block-row-text (xs-body, m-button)
+
See plans for students and teachers or small and medium business.
+
+
+
con-block-row-text (body-m)
+
Text with no button class
+
+
+
con-block-row-bgcolor
+
#fafafa
+
+
+
con-block-row-supplemental
+
sup text
+
+
+ +
+
+
#FDD590
+
+
+
+ + + +
+
+

FOREGROUND ASSET (order-reverse-mobile, media-cover)

+

2-Row Cell - text right

+
+
+
+ +
+
+
#FDD590
+
#333333
+
#333333
+
+
+
+

Empty FOREGROUND ASSET (l-min-height, dark-tablet)

+

2-Row Cell - text right

+
+
+
+
+ +
+
+
#ffffff
+
+
+
+

Hero w/ Adobe.tv link

+
+
https://video.tv.adobe.com/v/3427744
+
+