diff --git a/blocks/carousel/card.css b/blocks/carousel/card.css new file mode 100644 index 0000000..d1ce8ca --- /dev/null +++ b/blocks/carousel/card.css @@ -0,0 +1,148 @@ +main .card { + height: 100%; + background: var(--background-color); + margin: 0; + position: relative; + box-shadow: 0 1px 15px rgba(0 0 0 / 40%); + transition: all .3s; + display: flex; + align-items: stretch; + align-content: stretch; + width: 100%; + flex-direction: column; + justify-content: space-between; + margin-bottom: 30px; + } + + main .carousel .card { + margin: 0; + } + + main .card:hover { + z-index: 1; + transform: scale(1.07); + transition: all .3s; + } + + main .card-thumb { + height: 200px; + min-height: 200px; + overflow: hidden; + } + + main .card-thumb a { + display: block; + } + + main .card-thumb img { + height: 200px; + width: 100%; + min-width: auto; + object-fit: cover; + transition: all .5s ease-in-out; + } + + main .card-caption { + display: flex; + flex-direction: column; + padding: 10px 20px 20px; + color: #12141f; + height: 100%; + min-height: auto; + } + + main .card-caption h3 { + font-size: 22px; + font-weight: normal; + text-align: left; + min-height: 49px; + margin-top: 14px; + margin-bottom: 15px; + } + + main .card-caption h3 a { + color: inherit; + } + + main .card-caption .card-description { + font-size: var(--body-font-size-s); + text-align: left; + margin-bottom: 10px; + margin-top: 0; + } + + main .card-caption .c2a { + text-align: center; + margin-top: auto; + } + + main .card-caption .button.primary { + border: 1px solid var(--background-color-green); + color: var(--text-color); + padding-bottom: 10px; + padding-top: 11px; + background: none; + border-radius: 0; + font-size: 18px; + max-width: unset; + width: unset; + white-space: break-spaces; + word-break: break-word; + } + + main .card-caption .c2a .compare-button { + color: var(--text-light-gray); + letter-spacing: .5px; + align-items: center; + float: right; + display: none; + } + + .product-finder .card-caption .c2a .compare-button, + .product-compare main .card-caption .c2a .compare-button { + display: flex; + } + + main .card-caption .c2a .compare-checkbox { + width: 18px; + height: 18px; + margin-left: 10px; + vertical-align: middle; + border: 2px solid var(--dark-border-color-gray); + cursor: pointer; + } + + main .card-caption .c2a .compare-checkbox.selected { + background: var(--background-color-green); + border-color: var(--background-color-green); + } + + main .card-type { + display: none; + } + + main .card a .icon.icon-chevron-right-outline { + margin-left: 5px; + vertical-align: text-top; + } + + main .card a .icon.icon-chevron-right-outline svg { + height: 18px; + width: 18px; + } + + main .card a .icon.icon-chevron-right-outline svg path { + stroke-width: 15; + } + + @media only screen and (max-width: 767px) { + main .card-thumb { + height: 150px; + min-height: 150px; + } + + main .card-thumb img { + height: 150px; + } + } + \ No newline at end of file diff --git a/blocks/carousel/card.js b/blocks/carousel/card.js new file mode 100644 index 0000000..e15b642 --- /dev/null +++ b/blocks/carousel/card.js @@ -0,0 +1,46 @@ +import { createOptimizedPicture } from '../../scripts/lib-franklin.js'; +import { a, li as liHelper, div as divHelper } from '../../scripts/dom-helpers.js'; + +// prettier-ignore +export default function decorate(block) { + /* change to ul, li */ + const ul = document.createElement('ul'); + [...block.children].forEach((row) => { + const wrappingDiv = divHelper({ class: 'cards-card-wrapper' }, ...row.children); + [...wrappingDiv.children].forEach((div) => { + if (div.children.length === 1 && div.querySelector('picture')) { + div.className = 'cards-card-image'; + } else { + div.className = 'cards-card-body'; + } + }); + + const li = liHelper(wrappingDiv); + + ul.append(li); + }); + ul.querySelectorAll('img').forEach((img) => img.closest('picture').replaceWith(createOptimizedPicture(img.src, img.alt, false, [{ width: '750' }]))); + block.textContent = ''; + block.append(ul); + + if (block.classList.contains('image-link') || block.classList.contains('who-we-are')) { + block.querySelectorAll('li').forEach((li) => { + const link = li.querySelector('a'); + li.querySelectorAll('picture').forEach((picture) => { + const pictureClone = picture.cloneNode(true); + const newLink = a({ href: link.href }, pictureClone); + picture.parentNode.replaceChild(newLink, picture); + }); + }); + } else if (block.classList.contains('image-only')) { + block.querySelectorAll('li').forEach((li) => { + const link = li.querySelector('a'); + const picture = li.querySelector('picture'); + const pictureClone = picture.cloneNode(true); + const newLink = a({ href: link.href }, pictureClone); + picture.parentNode.replaceChild(newLink, picture); + const cardBody = li.querySelector('.cards-card-body'); + cardBody.parentNode.removeChild(cardBody); + }); + } +} diff --git a/blocks/carousel/carousel.js b/blocks/carousel/carousel.js index 127afdc..d272733 100644 --- a/blocks/carousel/carousel.js +++ b/blocks/carousel/carousel.js @@ -1,7 +1,7 @@ /* eslint-disable no-unused-expressions */ import { decorateIcons, loadCSS } from '../../scripts/lib-franklin.js'; import { div, p, span } from '../../scripts/dom-helpers.js'; -import { handleCompareProducts } from '../card/card.js'; +import { handleCompareProducts } from '../carousel/card.js'; const AUTOSCROLL_INTERVAL = 7000; @@ -310,8 +310,10 @@ class Carousel { } createCounter() { - const counter = div({ class: 'carousel-counter' }, - div({ class: 'carousel-counter-text' }, + const counter = div( + { class: 'carousel-counter' }, + div( + { class: 'carousel-counter-text' }, p(''), ), ); diff --git a/blocks/tabs/tabs.css b/blocks/tabs/tabs.css new file mode 100644 index 0000000..f59e83c --- /dev/null +++ b/blocks/tabs/tabs.css @@ -0,0 +1,79 @@ +.tabs { + margin-top: 20px; + } + + .tabs .section > div { + width: 100%; + } + + .tabs h2 { + font-size: 42px; + color: #5f696b; + font-family: var(--ff-proxima-regular); + } + + .tabs h3 { + font-family: var(--ff-proxima-xbold); + font-weight: 700; + } + + .tabs .tab-pane { + display: none; + border: 1px solid var(--border-color-gray); + background-color: var(--background-color); + } + + .tabs .tab-pane.active { + display: block; + } + + .tabs .tab-pane > div { + padding-left: 50px; + padding-right: 50px; + } + + .tabs .tab-pane .columns { + width: 100%; + } + + .tabs ul.tabs-nav { + display: flex; + flex-wrap: wrap; + padding: 0; + margin: 0; + } + + .tabs li.tabs-nav-item { + display: flex; + list-style: none; + flex-grow: 1; + padding: 10px 20px; + text-align: center; + align-items: center; + justify-content: center; + color: var(--text-white); + border: 1px solid var(--text-white); + border-bottom-color: var(--dark-gray-background-color); + background-color: var(--border-color-gray); + cursor: pointer; + } + + .tabs li.tabs-nav-item.active { + background-color: var(--dark-gray-background-color); + } + + @media only screen and (max-width: 767px) { + .tabs h2 { + font-size: 36px; + } + + .tabs .tab-pane > div { + padding-left: 20px; + padding-right: 20px; + } + + .tabs li.tabs-nav-item { + width: 100% !important; + } + } + \ No newline at end of file diff --git a/blocks/tabs/tabs.js b/blocks/tabs/tabs.js new file mode 100644 index 0000000..9c41745 --- /dev/null +++ b/blocks/tabs/tabs.js @@ -0,0 +1,61 @@ +import { + div, li, ul, + } from '../../scripts/dom-helpers.js'; + import { processEmbedFragment } from '../../scripts/scripts.js'; + + const classActive = 'active'; + + function handleTabClick(e, idx) { + e.preventDefault(); + const { target } = e; + [...target.closest('.tabs-nav').children].forEach((nav) => nav.classList.remove(classActive)); + target.closest('.tabs-nav-item').classList.add(classActive); + const panes = target.closest('.tabs').querySelectorAll('.tab-pane'); + [...panes].forEach((pane) => pane.classList.remove(classActive)); + panes[idx].classList.add(classActive); + } + + function buildNav(block) { + const titles = block.querySelectorAll('.tabs > div > div:first-child'); + const elemWidth = Math.floor(100 / titles.length); + const navList = ul({ class: 'tabs-nav' }); + [...titles].forEach((title, idx) => { + const tabTitle = title.textContent; + const listItem = li( + { + class: 'tabs-nav-item', + style: `width: ${elemWidth}%;`, + onclick: (e) => { handleTabClick(e, idx); }, + 'aria-label': tabTitle, + }, + div(tabTitle), + ); + navList.append(listItem); + }); + navList.querySelector('li').classList.add(classActive); + return navList; + } + + async function buildTabs(block) { + const tabPanes = block.querySelectorAll('.tabs > div > div:last-child'); + const tabList = div({ class: 'tabs-list' }); + const decoratedPanes = await Promise.all([...tabPanes].map(async (pane) => { + pane.classList.add('tab-pane'); + const decoratedPane = await processEmbedFragment(pane); + return decoratedPane; + })); + decoratedPanes.forEach((pane) => { tabList.append(pane); }); + tabList.querySelector('.tab-pane').classList.add(classActive); + return tabList; + } + + export default async function decorate(block) { + const nav = buildNav(block); + const tabs = await buildTabs(block); + block.innerHTML = ''; + + block.append(nav); + block.append(tabs); + + return block; + } \ No newline at end of file diff --git a/scripts/scripts.js b/scripts/scripts.js index f6d8498..71ba69b 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -12,6 +12,9 @@ import { loadBlocks, loadCSS, } from './lib-franklin.js'; +import { + a, div, p, +} from './dom-helpers.js'; const LCP_BLOCKS = []; // add your LCP blocks to the list @@ -67,6 +70,8 @@ export function decorateMain(main) { buildAutoBlocks(main); decorateSections(main); decorateBlocks(main); + decorateLinks(main); + decorateParagraphs(main); } /** @@ -93,6 +98,75 @@ async function loadEager(doc) { } } +/** + * Decorate blocks in an embed fragment. + */ +function decorateEmbeddedBlocks(container) { + container + .querySelectorAll('div.section > div') + .forEach(decorateBlock); +} + +export async function fetchFragment(path, plain = true) { + const response = await fetch(path + (plain ? '.plain.html' : '')); + if (!response.ok) { + // eslint-disable-next-line no-console + console.error('error loading fragment details', response); + return null; + } + const text = await response.text(); + if (!text) { + // eslint-disable-next-line no-console + console.error('fragment details empty', path); + return null; + } + return text; +} + +export function decorateLinks(main) { + main.querySelectorAll('a').forEach((link) => { + const url = new URL(link.href); + // decorate video links + if (isVideo(url) && !link.closest('.block.hero-advanced') && !link.closest('.block.hero')) { + const closestButtonContainer = link.closest('.button-container'); + if (link.closest('.block.cards') || (closestButtonContainer && closestButtonContainer.querySelector('strong,em'))) { + videoButton(link.closest('div'), link, url); + } else { + const up = link.parentElement; + const isInlineBlock = (link.closest('.block.vidyard') && !link.closest('.block.vidyard').classList.contains('lightbox')); + const type = (up.tagName === 'EM' || isInlineBlock) ? 'inline' : 'lightbox'; + const wrapper = div({ class: 'video-wrapper' }, div({ class: 'video-container' }, a({ href: link.href }, link.textContent))); + if (link.href !== link.textContent) wrapper.append(p({ class: 'video-title' }, link.textContent)); + up.innerHTML = wrapper.outerHTML; + embedVideo(up.querySelector('a'), url, type); + } + } + + // decorate RFQ page links with pid parameter + if (url.pathname.startsWith('/quote-request') && !url.searchParams.has('pid') && getMetadata('family-id')) { + url.searchParams.append('pid', getMetadata('family-id')); + link.href = url.toString(); + } + + if (url.pathname.endsWith('.pdf')) { + link.setAttribute('target', '_blank'); + link.setAttribute('rel', 'noopener noreferrer'); + } + + // decorate external links + decorateExternalLink(link); + }); +} + +function decorateParagraphs(main) { + [...main.querySelectorAll('p > picture')].forEach((picturePar) => { + picturePar.parentElement.classList.add('picture'); + }); + [...main.querySelectorAll('ol > li > em:only-child')].forEach((captionList) => { + captionList.parentElement.parentElement.classList.add('text-caption'); + }); +} + /** * Loads everything that doesn't need to be delayed. * @param {Element} doc The container element @@ -132,4 +206,49 @@ async function loadPage() { loadDelayed(); } +export async function processEmbedFragment(element) { + const block = div({ class: 'embed-fragment' }); + [...element.classList].forEach((className) => { block.classList.add(className); }); + let found = false; + const link = element.querySelector('a'); + if (link) { + const linkUrl = new URL(link.href); + let linkTextUrl; + try { + linkTextUrl = new URL(link.textContent); + } catch { + // not a url, ignore + } + if (linkTextUrl && linkTextUrl.pathname === linkUrl.pathname) { + const fragmentDomains = ['localhost', 'moleculardevices.com', 'moleculardevices--hlxsites.hlx.page', 'moleculardevices--hlxsites.hlx.live']; + found = fragmentDomains.find((domain) => linkUrl.hostname.endsWith(domain)); + if (found) { + block.classList.remove('button-container'); + const fragment = await fetchFragment(linkUrl); + block.innerHTML = fragment; + const sections = block.querySelectorAll('.embed-fragment > div'); + [...sections].forEach((section) => { + section.classList.add('section'); + }); + decorateEmbeddedBlocks(block); + decorateSections(block); + loadBlocks(block); + } + } + } + + if (!found) { + const elementInner = element.innerHTML; + block.append(div({ class: 'section' })); + block.querySelector('.section').innerHTML = elementInner; + } + + decorateButtons(block); + decorateIcons(block); + decorateLinks(block); + decorateParagraphs(block); + + return block; +} + loadPage();