diff --git a/blocks/v2-accordion/v2-accordion.css b/blocks/v2-accordion/v2-accordion.css new file mode 100644 index 000000000..a513d36f7 --- /dev/null +++ b/blocks/v2-accordion/v2-accordion.css @@ -0,0 +1,95 @@ +.v2-accordion__button { + align-items: center; + background: transparent; + border: 0; + color: var(--c-primary-black); + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + margin: 0; + padding: 0; + text-align: left; + width: 100%; +} + +.v2-accordion__button:hover, +.v2-accordion__button:focus { + background: transparent; +} + +.v2-accordion__title { + font-family: var(--ff-headline-medium); + font-size: var(--headline-1-font-size); + letter-spacing: var(--headline-1-letter-spacing); + line-height: var(--headline-1-line-height); + margin: 0; +} + +.v2-accordion__close { + transform: rotate(180deg); + transition: transform var(--duration-small) var(--easing-standard); +} + +.v2-accordion__item-close .v2-accordion__close { + transform: rotate(0); +} + +.v2-accordion__item-close .v2-accordion__content { + height: 0; + margin: 0; + opacity: 0; + overflow: hidden; + padding: 0; +} + +.v2-accordion__close svg { + display: flex; + height: 24px; + width: 24px; +} + +.v2-accordion__item { + border-bottom: 2px solid var(--c-primary-black); + padding: 24px 0; +} + +/* stylelint-disable-next-line no-descending-specificity */ +.v2-accordion__content { + margin-top: 40px; +} + +/* Styles for nested accordion */ +.v2-accordion__content .v2-accordion__title { + color: var(--c-secondary-graphite); + font-family: var(--ff-subheadings-medium); + font-size: 22px; + letter-spacing: 0.22px; + line-height: 138%; +} + +.v2-accordion__item .v2-accordion__item { + border-bottom: 1px solid var(--c-secondary-steel); +} + +.v2-accordion__item .v2-accordion__item:last-child { + border-bottom: 0; +} + +/* END Styles for nested accordion */ + +@media (min-width: 1200px) { + .v2-accordion__title { + font-size: var(--headline-2-font-size); + letter-spacing: var(--headline-2-letter-spacing); + line-height: var(--headline-2-line-height); + } + + /* Styles for nested accordion */ + .v2-accordion__content .v2-accordion__title { + font-size: var(--headline-4-font-size); + letter-spacing: var(--headline-4-letter-spacing); + line-height: var(--headline-4-line-height); + } + + /* END Styles for nested accordion */ +} \ No newline at end of file diff --git a/blocks/v2-accordion/v2-accordion.js b/blocks/v2-accordion/v2-accordion.js new file mode 100644 index 000000000..25194b44d --- /dev/null +++ b/blocks/v2-accordion/v2-accordion.js @@ -0,0 +1,84 @@ +import { createElement } from '../../scripts/common.js'; +import fragmentBlock from '../fragment/fragment.js'; + +const blockName = 'v2-accordion'; + +/* Function checks if the content of the provied element is just a link to other doc */ +function isContentLink(el) { + // The assumpions: + // 1. The content is just plain text - no HTML inside + // 2. The link starts from '/' and doesn't contain any white space character + return el.innerHTML === el.textContent && /^\/(\S+)$/g.test(el.innerHTML); +} + +function loaded(element, pointedContent, display) { + element.innerHTML = ''; + element.append(pointedContent.parentElement); + pointedContent.parentElement.style.display = display; +} + +export default async function decorate(block) { + const rows = [...block.querySelectorAll(':scope > div')]; + const accordionsPromises = rows.map(async (row) => { + const accordionHeader = row.querySelector( + ':scope > div > h1, :scope > div > h2, :scope > div > h3, :scope > div > h4, :scope > div > h5, :scope > div > h6', + ); + accordionHeader?.classList.add(`${blockName}__title`); + const accordionContent = row.querySelector( + ':scope > div:nth-child(2)', + ); + + const headerButton = createElement('button', { classes: `${blockName}__button` }); + const arrowEl = createElement('div', { classes: `${blockName}__close` }); + + const dropdownArrowIcon = document.createRange().createContextualFragment(` + `); + arrowEl.appendChild(...dropdownArrowIcon.children); + headerButton.append(accordionHeader, arrowEl); + + const contentEl = createElement('div', { classes: [`${blockName}__content`, `${blockName}__content-close`] }); + + if (isContentLink(accordionContent)) { + await fragmentBlock(accordionContent); + } + + contentEl.innerHTML = accordionContent.innerHTML; + + if (accordionContent.textContent.startsWith('#id-') && accordionContent.innerHTML === accordionContent.textContent) { + const pointedContent = document.querySelector(`.${accordionContent.textContent.substring(1)}`); + if (pointedContent) { + const prevDisplay = pointedContent.parentElement.style.display; + pointedContent.parentElement.style.display = 'none'; + + if (pointedContent.dataset.blockStatus === 'loaded') { + loaded(contentEl, pointedContent, prevDisplay); + } else { + // lets wait for loading of the content that we want to put inside the accordion + new MutationObserver((_, observer) => { + if (pointedContent.dataset.blockStatus === 'loaded') { + observer.disconnect(); + loaded(contentEl, pointedContent, prevDisplay); + } + }).observe(pointedContent, { attributes: true }); + } + } + } + + const accordionEl = createElement('div', { classes: [`${blockName}__item`, `${blockName}__item-close`] }); + accordionEl.append(headerButton); + accordionEl.append(contentEl); + + headerButton.addEventListener('click', () => { + accordionEl.classList.toggle(`${blockName}__item-close`); + }); + + return accordionEl; + }); + + block.innerHTML = ''; + await Promise.allSettled(accordionsPromises); + accordionsPromises.forEach(async (acc) => { + const result = await acc; + block.append(result); + }); +} diff --git a/blocks/v2-specifications/v2-specifications.css b/blocks/v2-specifications/v2-specifications.css new file mode 100644 index 000000000..00bca1e67 --- /dev/null +++ b/blocks/v2-specifications/v2-specifications.css @@ -0,0 +1,143 @@ +.v2-specifications__list--with-text > div > *:first-child, +.v2-specifications__list--with-pictures > div > *:first-child, +.v2-specifications__list--with-downloads > div > *:first-child { + margin-top: 0; +} + +.v2-specifications__list--with-text > div > *:last-child, +.v2-specifications__list--with-pictures > div > *:last-child, +.v2-specifications__list--with-downloads > div > *:last-child { + margin-bottom: 0; +} + +.v2-specifications__list--with-text, +.v2-specifications__list--with-pictures, +.v2-specifications__list--with-downloads { + color: var(--c-primary-black); + display: grid; + font-family: var(--ff-body); + font-size: var(--body-1-font-size); + gap: 16px; + line-height: var(--body-1-line-height); + margin-top: 16px; +} + +.v2-specifications__list--with-text:first-child, +.v2-specifications__list--with-pictures:first-child, +.v2-specifications__list--with-downloads:first-child { + margin-top: 0; +} + +/* stylelint-disable-next-line no-descending-specificity */ +.v2-specifications__list--with-pictures, +.v2-specifications__list--with-downloads { + gap: 40px; + grid-template-columns: 1fr; +} + +.v2-specifications__list--with-downloads strong, +.v2-specifications__list--with-pictures strong { + display: block; + font-family: var(--ff-subheadings-medium); + font-size: var(--headline-5-font-size); + font-weight: normal; + line-height: var(--headline-5-line-height); +} + +.v2-specifications__list--subtitle { + margin-bottom: 24px; + margin-top: 24px; +} + +/* With Text */ +/* stylelint-disable-next-line no-descending-specificity */ +.v2-specifications__list--with-text { + grid-template-columns: repeat(2, 1fr); +} + +.v2-specifications__list--with-text strong { + font-family: var(--ff-body-bold); + font-size: 12px; + letter-spacing: 1.92px; + line-height: 16px; + text-transform: uppercase; +} + +.v2-specifications__list--with-text p { + margin: 0; +} + +/* With images */ +.v2-specifications__list--with-pictures img { + display: block; + margin-bottom: 24px; +} + +/* With downloads */ +.v2-specifications__list--with-downloads .icon-download svg { + height: 16px; + width: 16px; +} + +.v2-specifications__list--with-downloads p { + margin: 8px 0 0; +} + +@media (min-width: 744px) { + .v2-specifications__list--with-pictures, + .v2-specifications__list--with-downloads { + gap: 40px 16px; + grid-template-columns: repeat(2, 1fr); + margin-top: 40px; + } +} + +@media (min-width: 1200px) { + .v2-specifications__list--with-text, + .v2-specifications__list--with-downloads { + grid-template-columns:repeat(4, 1fr); + } + + .v2-specifications__list--with-text { + gap: 40px 16px; + } + + .v2-specifications__list--with-text:has(> :nth-child(5)) { + grid-template-columns: repeat(5, 1fr); + } + + .v2-specifications__list--with-text p { + margin-top: 8px; + } + + .v2-specifications__list--with-pictures { + grid-template-columns: repeat(3, 1fr); + } + + /* Rempve accordion in Desktop */ + .v2-specifications .v2-accordion__button { + pointer-events: none; + } + + .v2-specifications .v2-accordion__button .v2-accordion__close { + display: none; + } + + .v2-specifications .v2-accordion__item-close .v2-accordion__content { + height: auto; + margin-top: 40px; + opacity: 1; + overflow: auto; + } + + .v2-specifications .v2-accordion__item { + border-bottom-color: var(--c-primary-black); + padding: 72px 0; + } + + .v2-specifications .v2-accordion__item:first-child { + padding-top: 10px; + } + + /* END Rempve accordion in Desktop */ +} diff --git a/blocks/v2-specifications/v2-specifications.js b/blocks/v2-specifications/v2-specifications.js new file mode 100644 index 000000000..cba04a76b --- /dev/null +++ b/blocks/v2-specifications/v2-specifications.js @@ -0,0 +1,118 @@ +import { + loadBlock, +} from '../../scripts/lib-franklin.js'; +import { + createElement, + slugify, + removeEmptyTags, +} from '../../scripts/common.js'; + +const blockName = 'v2-specifications'; + +export default async function decorate(block) { + const accordionId = [...block.classList].find((className) => className.startsWith('id-')); + + const items = block.querySelectorAll(':scope > div'); + + let accordion; + let accordionContent; + let accordionWrapper; + + const accordionBlock = createElement('div', { + classes: ['block', 'v2-accordion', accordionId], + props: { 'data-block-name': 'v2-accordion' }, + }); + + const accordionBlockWrapper = createElement('div', { + classes: ['v2-accordion-wrapper'], + }); + accordionBlockWrapper.appendChild(accordionBlock); + block.appendChild(accordionBlockWrapper); + + // 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 headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].slice(headerTag); + + [...items].forEach((item) => { + const typeTitle = item.querySelector(`h${headerTag}`); // header of the accordion + const typePicture = item.querySelector('picture'); // with image + const typeDownloads = item.querySelector('.button'); // with downloads + + // Add title styles to the headings that are not as the accordion button + const headingString = headings.join(','); + const headingsList = [...block.querySelectorAll(headingString)]; + headingsList.forEach((heading) => heading.classList.add(`${blockName}__subtitle`, 'h5')); + const subtitleCounter = item.querySelectorAll(`.${blockName}__subtitle`).length; + + if (typeTitle) { + if (accordion) { + // close old Accordion content + accordionBlock.appendChild(accordion); + accordionWrapper.appendChild(accordionContent); + block.appendChild(accordionWrapper); + } + + // create slug based on title name + const name = `id-${slugify(item.textContent.trim())}`; + + // new Accordion content + // (accordionWrapper + accordionContent + accordion are needed) + accordionWrapper = createElement('div', { classes: [`${blockName}__accordion-wrapper`] }); + accordionContent = createElement('div', { + classes: [`${blockName}__accordion-content`, name], + props: { 'data-block-status': 'loaded' }, + }); + accordion = createElement('div'); + + // Title of the accordion + const titleDiv = item.querySelector(':scope > div'); + titleDiv.classList.add(`${blockName}__title`); + accordion.appendChild(titleDiv); + + // Id to be updated in the accordion content + const divId = createElement('div'); + divId.textContent = `#${name}`; + accordion.appendChild(divId); + item.remove(); + } + + const classes = []; + if (subtitleCounter) classes.push(`${blockName}__list--subtitle`); + + // apply classes to the content based on items inside + if (typePicture) { + classes.push(`${blockName}__list--with-pictures`); + } + + if (typeDownloads) { + classes.push(`${blockName}__list--with-downloads`); + + const buttons = item.querySelectorAll('.button'); + buttons.forEach((bt) => { + bt.classList.remove('button--primary', 'button'); + bt.classList.add('standalone-link'); + }); + } + + if (!typePicture && !typeDownloads && !typeTitle) { + classes.push(`${blockName}__list--with-text`); + } + + item.classList.add(...classes); + + accordionContent.appendChild(item); + }); + + // close last accordion content + if (accordion) { + accordionBlock.appendChild(accordion); + accordionWrapper.appendChild(accordionContent); + block.appendChild(accordionWrapper); + } + + removeEmptyTags(block); + + await loadBlock(accordionBlock); +} diff --git a/icons/download.svg b/icons/download.svg new file mode 100644 index 000000000..2e032ecc2 --- /dev/null +++ b/icons/download.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file