diff --git a/libs/blocks/notification/notification.css b/libs/blocks/notification/notification.css index 0dff835631..182ca31ced 100644 --- a/libs/blocks/notification/notification.css +++ b/libs/blocks/notification/notification.css @@ -16,6 +16,7 @@ --icon-size: 56px; --icon-size-xl: 64px; --pill-radius: 16px; + --pill-shadow: 0 3px 6px #707070; display: flex; inline-size: 100%; @@ -25,6 +26,7 @@ flex-wrap: wrap; overflow: hidden; min-block-size: var(--min-block-size); + background: var(--color-white); } .notification.ribbon { @@ -151,6 +153,14 @@ inline-size: 100%; } +.notification.ribbon [class*="heading-"]:only-child { + margin-block-end: var(--spacing-s); +} + +.notification.ribbon.space-between [class*="heading-"]:only-child { + margin-block-end: 0; +} + .notification.ribbon.space-between .foreground .text { flex-wrap: nowrap; inline-size: 100%; @@ -158,6 +168,9 @@ .notification.ribbon.space-between .foreground .copy-wrap { margin-inline-end: var(--spacing-s); + display: flex; + flex-direction: column; + justify-content: center; } .notification .foreground .image { @@ -197,6 +210,12 @@ flex-wrap: nowrap; } +.notification .foreground .image :is(picture, video), +.notification .foreground .image picture img { + inline-size: 100%; + display: flex; +} + .notification:is(.ribbon.m-icon, .pill) .icon-area img { max-block-size: var(--icon-size-m); } @@ -250,12 +269,6 @@ margin-block-end: 0; } -.notification .foreground .image :is(picture, video), -.notification .foreground .image picture img { - inline-size: 100%; - display: flex; -} - .notification .foreground .text a:not(.con-button) { inline-size: auto; font-weight: normal; @@ -283,6 +296,7 @@ border-radius: var(--pill-radius); inline-size: calc(100% - var(--spacing-m)); margin-inline: auto; + box-shadow: var(--pill-shadow); } .notification.pill .foreground .action-area { @@ -298,6 +312,11 @@ .notification .flexible-inner { inline-size: 100%; + box-shadow: var(--pill-shadow); +} + +.notification .foreground > :is(.tablet-up, .desktop-up) { + display: none; } .notification.pill .foreground .text > :not(.action-area) { @@ -305,6 +324,10 @@ inline-size: calc(100% - var(--spacing-xxs)); } +.notification.pill .copy-wrap p:first-child:not(:only-child) { + margin-block-end: var(--spacing-xxs); +} + @media screen and (min-width: 600px) { .notification { --max-inline-size-image: 188px; @@ -329,6 +352,14 @@ gap: var(--spacing-s); } + .notification .foreground > :is(.mobile-up, .desktop-up) { + display: none; + } + + .notification .foreground > .tablet-up { + display: flex; + } + .notification:is(.max-width-12-desktop, .ribbon) .foreground { max-inline-size: var(--full-width); margin-inline: var(--grid-margins-width); @@ -418,6 +449,8 @@ .notification.pill.flexible { pointer-events: none; + box-shadow: none; + padding-block-end: var(--spacing-xxs); } .notification .flexible-inner { @@ -444,7 +477,8 @@ --max-inline-size-image-10: 300px; --inline-size-image-full: 33.333%; --max-inline-size-image-full: 400px; - --pill-radius: 36px; + --max-inline-size-pill: 1600px; + --pill-radius: 100px; } .notification:not(.pill, .ribbon) .foreground { @@ -472,6 +506,14 @@ max-inline-size: calc(var(--grid-column-width) * 10); } + .notification .foreground > :is(.mobile-up, .tablet-up) { + display: none; + } + + .notification .foreground > .desktop-up { + display: flex; + } + .notification .foreground > div { object-fit: cover; padding-inline-start: 0; @@ -511,6 +553,7 @@ .notification.pill { min-block-size: var(--min-block-size-pill); inline-size: var(--inline-size-pill); + max-inline-size: var(--max-inline-size-pill); margin-inline: auto; } @@ -525,7 +568,6 @@ } .notification.pill .foreground .text [class*="heading-"] { - margin-inline-end: var(--spacing-xxs); margin-block-end: 0; } @@ -549,7 +591,7 @@ } .notification.pill .foreground .icon-area { - margin-inline-end: var(--spacing-xs); + margin-inline-end: var(--spacing-s); margin-block-end: 0; } @@ -576,6 +618,7 @@ flex-wrap: wrap; align-items: baseline; text-align: start; + gap: var(--spacing-xxs); } .notification.pill .copy-wrap > * { @@ -585,4 +628,8 @@ .notification.ribbon.space-between .foreground .icon-area { margin-inline-end: var(--spacing-s); } + + .notification.pill .copy-wrap p:first-child:not(:only-child) { + margin-block-end: 0; + } } diff --git a/libs/blocks/notification/notification.js b/libs/blocks/notification/notification.js index 52787c2521..4a2cab725d 100644 --- a/libs/blocks/notification/notification.js +++ b/libs/blocks/notification/notification.js @@ -11,10 +11,10 @@ */ /* -* Notification - v1.1 +* Notification - v1.2 */ -import { decorateBlockText, decorateBlockBg, decorateTextOverrides } from '../../utils/decorate.js'; +import { decorateBlockText, decorateBlockBg, decorateTextOverrides, decorateMultiViewport } from '../../utils/decorate.js'; import { createTag, getConfig, loadStyle } from '../../utils/utils.js'; const { miloLibs, codeRoot } = getConfig(); @@ -54,6 +54,8 @@ const closeSvg = ``; +let iconographyLoaded = false; + function getOpts(el) { const optRows = [...el.querySelectorAll(':scope > div:nth-of-type(n+3)')]; if (!optRows.length) return {}; @@ -73,13 +75,15 @@ function getBlockData(el) { } function wrapCopy(foreground) { - const text = foreground.querySelector('.text'); - if (!text) return; - const heading = text?.querySelector('h1, h2, h3, h4, h5, h6, p:not(.icon-area, .action-area)'); - const icon = heading?.previousElementSibling; - const body = heading?.nextElementSibling?.classList.contains('action-area') ? '' : heading?.nextElementSibling; - const copy = createTag('div', { class: 'copy-wrap' }, [heading, body].filter(Boolean)); - text?.insertBefore(copy, icon?.nextSibling || text.children[0]); + const texts = foreground.querySelectorAll('.text'); + if (!texts) return; + texts.forEach((text) => { + const heading = text?.querySelector('h1, h2, h3, h4, h5, h6, p:not(.icon-area, .action-area)'); + const icon = heading?.previousElementSibling; + const body = heading?.nextElementSibling?.classList.contains('action-area') ? '' : heading?.nextElementSibling; + const copy = createTag('div', { class: 'copy-wrap' }, [heading, body].filter(Boolean)); + text?.insertBefore(copy, icon?.nextSibling || text.children[0]); + }); } function decorateClose(el) { @@ -104,10 +108,11 @@ function decorateFlexible(el) { async function loadIconography() { await new Promise((resolve) => { loadStyle(`${base}/styles/iconography.css`, resolve); }); + iconographyLoaded = true; } async function decorateLockup(lockupArea, el) { - await loadIconography(); + if (!iconographyLoaded) await loadIconography(); const icon = lockupArea.querySelector('picture'); const content = icon.nextElementSibling || icon.nextSibling; const label = createTag('span', { class: 'lockup-label' }, content.nodeValue || content); @@ -122,21 +127,29 @@ async function decorateLockup(lockupArea, el) { if (pre && pre[2] === 'icon') el.classList.replace(pre[0], `${pre[1]}-lockup`); } +async function decorateForegroundText(el, container) { + const text = container?.querySelector('h1, h2, h3, h4, h5, h6, p')?.closest('div'); + text?.classList.add('text'); + const iconArea = text?.querySelector('p:has(picture)'); + iconArea?.classList.add('icon-area'); + if (iconArea?.textContent.trim()) await decorateLockup(iconArea, el); +} + async function decorateLayout(el) { const [background, ...rest] = el.querySelectorAll(':scope > div'); const foreground = rest.pop(); if (background) decorateBlockBg(el, background); foreground?.classList.add('foreground', 'container'); - const text = foreground?.querySelector('h1, h2, h3, h4, h5, h6, p')?.closest('div'); - text?.classList.add('text'); - const iconArea = text?.querySelector('p picture')?.closest('p'); - iconArea?.classList.add('icon-area'); - if (iconArea?.textContent.trim()) await decorateLockup(iconArea, el); + if (el.matches(`:is(.${pill}, .${ribbon})`)) { + foreground.querySelectorAll(':scope > div').forEach((div) => decorateForegroundText(el, div)); + } else { + await decorateForegroundText(el, foreground); + } const fgMedia = foreground?.querySelector(':scope > div:not(.text) :is(img, video, a[href*=".mp4"])')?.closest('div'); const bgMedia = el.querySelector(':scope > div:not(.foreground) :is(img, video, a[href*=".mp4"])')?.closest('div'); const media = fgMedia ?? bgMedia; media?.classList.toggle('image', media && !media.classList.contains('text')); - foreground?.classList.toggle('no-image', !media && !iconArea); + foreground?.classList.toggle('no-image', !media && !el.querySelector('.icon-area')); if (el.matches(`:is(.${pill}, .${ribbon}):not(.no-closure)`)) decorateClose(el); if (el.matches(`.${pill}.flexible`)) decorateFlexible(el); return foreground; @@ -152,5 +165,8 @@ export default async function init(el) { } decorateTextOverrides(el); el.querySelectorAll('a:not([class])').forEach((staticLink) => staticLink.classList.add('static')); - if (el.matches(`:is(.${ribbon}, .${pill})`)) wrapCopy(blockText); + if (el.matches(`:is(.${ribbon}, .${pill})`)) { + wrapCopy(blockText); + decorateMultiViewport(el); + } } diff --git a/libs/blocks/section-metadata/section-metadata.css b/libs/blocks/section-metadata/section-metadata.css index 82d18fbb26..d053a42980 100644 --- a/libs/blocks/section-metadata/section-metadata.css +++ b/libs/blocks/section-metadata/section-metadata.css @@ -192,6 +192,10 @@ background-color: var(--color-white); } +.section.sticky-top:has(.notification.pill) { + height: 0; +} + .section.sticky-bottom { position: sticky; bottom: 0; diff --git a/libs/blocks/section-metadata/sticky-section.js b/libs/blocks/section-metadata/sticky-section.js index 7308042421..db2a757554 100644 --- a/libs/blocks/section-metadata/sticky-section.js +++ b/libs/blocks/section-metadata/sticky-section.js @@ -27,10 +27,10 @@ function promoIntersectObserve(el, stickySectionEl, options = {}) { function handleStickyPromobar(section, delay) { const main = document.querySelector('main'); section.classList.add('promo-sticky-section', 'hide-sticky-section'); - if (section.querySelector('.promobar.popup')) section.classList.add('popup'); + if (section.querySelector('.popup:is(.promobar, .notification.pill)')) section.classList.add('popup'); let stickySectionEl = null; let hasScrollControl; - if ((section.querySelector('.promobar').classList.contains('no-delay')) || (delay && section.classList.contains('popup'))) { + if ((section.querySelector(':is(.promobar, .notification.pill)').classList.contains('no-delay')) || (delay && section.classList.contains('popup'))) { hasScrollControl = true; } if (!hasScrollControl && main.children[0] !== section) { @@ -52,7 +52,7 @@ export default async function handleStickySection(sticky, section) { break; } case 'sticky-bottom': { - if (section.querySelector('.promobar')) { + if (section.querySelector(':is(.promobar, .notification.pill.popup)')) { const metadata = getMetadata(section.querySelector('.section-metadata')); const delay = getDelayTime(metadata.delay?.text); if (delay) setTimeout(() => { handleStickyPromobar(section, delay); }, delay); diff --git a/libs/utils/decorate.js b/libs/utils/decorate.js index ab3363489f..25a0e8203c 100644 --- a/libs/utils/decorate.js +++ b/libs/utils/decorate.js @@ -278,3 +278,23 @@ export function applyInViewPortPlay(video) { observer.observe(video); } } + +export function decorateMultiViewport(el) { + const viewports = [ + '(max-width: 599px)', + '(min-width: 600px) and (max-width: 1199px)', + '(min-width: 1200px)', + ]; + const foreground = el.querySelector('.foreground'); + if (foreground.childElementCount === 2 || foreground.childElementCount === 3) { + [...foreground.children].forEach((child, index) => { + const mq = window.matchMedia(viewports[index]); + const setContent = () => { + if (mq.matches) foreground.replaceChildren(child); + }; + setContent(); + mq.addEventListener('change', setContent); + }); + } + return foreground; +} diff --git a/test/blocks/notification/mocks/body.html b/test/blocks/notification/mocks/body.html index 8dedf20078..9a13c85a93 100644 --- a/test/blocks/notification/mocks/body.html +++ b/test/blocks/notification/mocks/body.html @@ -449,4 +449,51 @@