From 37e89c8d078d544284642ba9b293d54e37c4ddc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Jove=CC=81?= Date: Tue, 19 Sep 2023 17:51:09 +0200 Subject: [PATCH 1/3] add specifications block + accordion block --- blocks/v2-accordion/v2-accordion.css | 95 ++++++++++++ blocks/v2-accordion/v2-accordion.js | 84 +++++++++++ .../v2-specifications/v2-specifications.css | 137 ++++++++++++++++++ blocks/v2-specifications/v2-specifications.js | 112 ++++++++++++++ icons/download.svg | 6 + 5 files changed, 434 insertions(+) create mode 100644 blocks/v2-accordion/v2-accordion.css create mode 100644 blocks/v2-accordion/v2-accordion.js create mode 100644 blocks/v2-specifications/v2-specifications.css create mode 100644 blocks/v2-specifications/v2-specifications.js create mode 100644 icons/download.svg diff --git a/blocks/v2-accordion/v2-accordion.css b/blocks/v2-accordion/v2-accordion.css new file mode 100644 index 000000000..8636c9c3a --- /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; + opacity: 0; + overflow: hidden; + padding: 0; + margin: 0; +} + +.v2-accordion__close svg { + display: flex; + height: 24px; + width: 24px; +} + +.v2-accordion__item { + border-bottom: 3px 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: #09161F; + font-family: var(--ff-subheadings-medium); + font-size: 22px; + letter-spacing: 0.22px; + line-height: 138%; +} + +.v2-accordion__item .v2-accordion__item:not(:last-child) { + 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); + line-height: var(--headline-2-line-height); + letter-spacing: var(--headline-2-letter-spacing); + } + + /* Styles for nested accordion */ + .v2-accordion__content .v2-accordion__title { + font-size: var(--headline-4-font-size); + line-height: var(--headline-4-line-height); + letter-spacing: var(--headline-4-letter-spacing); + } + + /* 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..968d41903 --- /dev/null +++ b/blocks/v2-specifications/v2-specifications.css @@ -0,0 +1,137 @@ +.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 { + display: grid; + gap: 16px; + color: var(--c-primary-black); + font-family: var(--ff-body); + font-size: var(--body-1-font-size); + 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 { + grid-template-columns: 1fr; + gap: 40px; +} + +.v2-specifications__list--with-downloads strong, +.v2-specifications__list--with-pictures strong { + font-family: var(--ff-subheadings-medium); + font-size: var(--headline-5-font-size); + line-height: var(--headline-5-line-height); + display: block; +} + +/* With Text */ +/* stylelint-disable-next-line no-descending-specificity */ +.v2-specifications__list--with-text { + grid-template-columns: 1fr 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 { + width: 16px; + height: 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 { + grid-template-columns: 1fr 1fr; + gap: 40px 16px; + margin-top: 40px; + } +} + +@media (min-width: 1200px) { + .v2-specifications__list--with-text, + .v2-specifications__list--with-downloads { + grid-template-columns: 1fr 1fr 1fr 1fr; + } + + .v2-specifications__list--with-text { + gap: 40px 16px; + } + + .v2-specifications__list--with-text:has(> :nth-child(5)) { + grid-template-columns: 1fr 1fr 1fr 1fr 1fr; + } + + .v2-specifications__list--with-text p { + margin-top: 8px; + } + + .v2-specifications__list--with-pictures { + grid-template-columns: 1fr 1fr 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 { + overflow: auto; + opacity: 1; + height: auto; + margin-top: 40px; + } + + .v2-specifications .v2-accordion__item:not(:last-child) { + 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..e1157d817 --- /dev/null +++ b/blocks/v2-specifications/v2-specifications.js @@ -0,0 +1,112 @@ +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')); + + 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(); + } + + // apply classes to the content based on items inside + if (typePicture) { + item.classList.add(`${blockName}__list--with-pictures`); + } + + if (typeDownloads) { + item.classList.add(`${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) { + item.classList.add(`${blockName}__list--with-text`); + } + + accordionContent.appendChild(item); + + removeEmptyTags(item); + }); + + // close last accordion content + if (accordion) { + accordionBlock.appendChild(accordion); + accordionWrapper.appendChild(accordionContent); + block.appendChild(accordionWrapper); + } + + 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 From 027d902527859efefc53226733403476bfc2d878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Jove=CC=81?= Date: Wed, 20 Sep 2023 19:09:14 +0200 Subject: [PATCH 2/3] fix comments + sort css properties --- blocks/v2-accordion/v2-accordion.css | 12 ++++----- .../v2-specifications/v2-specifications.css | 27 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/blocks/v2-accordion/v2-accordion.css b/blocks/v2-accordion/v2-accordion.css index 8636c9c3a..a513d36f7 100644 --- a/blocks/v2-accordion/v2-accordion.css +++ b/blocks/v2-accordion/v2-accordion.css @@ -36,10 +36,10 @@ .v2-accordion__item-close .v2-accordion__content { height: 0; + margin: 0; opacity: 0; overflow: hidden; padding: 0; - margin: 0; } .v2-accordion__close svg { @@ -49,7 +49,7 @@ } .v2-accordion__item { - border-bottom: 3px solid var(--c-primary-black); + border-bottom: 2px solid var(--c-primary-black); padding: 24px 0; } @@ -60,14 +60,14 @@ /* Styles for nested accordion */ .v2-accordion__content .v2-accordion__title { - color: #09161F; + 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:not(:last-child) { +.v2-accordion__item .v2-accordion__item { border-bottom: 1px solid var(--c-secondary-steel); } @@ -80,15 +80,15 @@ @media (min-width: 1200px) { .v2-accordion__title { font-size: var(--headline-2-font-size); - line-height: var(--headline-2-line-height); 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); - line-height: var(--headline-4-line-height); letter-spacing: var(--headline-4-letter-spacing); + line-height: var(--headline-4-line-height); } /* END Styles for nested accordion */ diff --git a/blocks/v2-specifications/v2-specifications.css b/blocks/v2-specifications/v2-specifications.css index 968d41903..e2b197bb1 100644 --- a/blocks/v2-specifications/v2-specifications.css +++ b/blocks/v2-specifications/v2-specifications.css @@ -13,11 +13,11 @@ .v2-specifications__list--with-text, .v2-specifications__list--with-pictures, .v2-specifications__list--with-downloads { - display: grid; - gap: 16px; 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; } @@ -31,22 +31,23 @@ /* stylelint-disable-next-line no-descending-specificity */ .v2-specifications__list--with-pictures, .v2-specifications__list--with-downloads { - grid-template-columns: 1fr; 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); - display: block; } /* With Text */ /* stylelint-disable-next-line no-descending-specificity */ .v2-specifications__list--with-text { - grid-template-columns: 1fr 1fr; + grid-template-columns: repeat(2, 1fr); } .v2-specifications__list--with-text strong { @@ -69,8 +70,8 @@ /* With downloads */ .v2-specifications__list--with-downloads .icon-download svg { - width: 16px; height: 16px; + width: 16px; } .v2-specifications__list--with-downloads p { @@ -80,8 +81,8 @@ @media (min-width: 744px) { .v2-specifications__list--with-pictures, .v2-specifications__list--with-downloads { - grid-template-columns: 1fr 1fr; gap: 40px 16px; + grid-template-columns: repeat(2, 1fr); margin-top: 40px; } } @@ -89,7 +90,7 @@ @media (min-width: 1200px) { .v2-specifications__list--with-text, .v2-specifications__list--with-downloads { - grid-template-columns: 1fr 1fr 1fr 1fr; + grid-template-columns:repeat(4, 1fr); } .v2-specifications__list--with-text { @@ -97,7 +98,7 @@ } .v2-specifications__list--with-text:has(> :nth-child(5)) { - grid-template-columns: 1fr 1fr 1fr 1fr 1fr; + grid-template-columns: repeat(5, 1fr); } .v2-specifications__list--with-text p { @@ -105,7 +106,7 @@ } .v2-specifications__list--with-pictures { - grid-template-columns: 1fr 1fr 1fr; + grid-template-columns: repeat(3, 1fr); } /* Rempve accordion in Desktop */ @@ -118,13 +119,13 @@ } .v2-specifications .v2-accordion__item-close .v2-accordion__content { - overflow: auto; - opacity: 1; height: auto; margin-top: 40px; + opacity: 1; + overflow: auto; } - .v2-specifications .v2-accordion__item:not(:last-child) { + .v2-specifications .v2-accordion__item { border-bottom-color: var(--c-primary-black); padding: 72px 0; } From ec00514ad44dcb7ab94f7591e846461faa8fc473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Jove=CC=81?= Date: Fri, 22 Sep 2023 08:38:59 +0200 Subject: [PATCH 3/3] fix subtitle inside accordion --- blocks/v2-specifications/v2-specifications.css | 5 +++++ blocks/v2-specifications/v2-specifications.js | 16 +++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/blocks/v2-specifications/v2-specifications.css b/blocks/v2-specifications/v2-specifications.css index e2b197bb1..00bca1e67 100644 --- a/blocks/v2-specifications/v2-specifications.css +++ b/blocks/v2-specifications/v2-specifications.css @@ -44,6 +44,11 @@ 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 { diff --git a/blocks/v2-specifications/v2-specifications.js b/blocks/v2-specifications/v2-specifications.js index e1157d817..cba04a76b 100644 --- a/blocks/v2-specifications/v2-specifications.js +++ b/blocks/v2-specifications/v2-specifications.js @@ -44,6 +44,7 @@ export default async function decorate(block) { 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) { @@ -77,13 +78,16 @@ export default async function decorate(block) { item.remove(); } + const classes = []; + if (subtitleCounter) classes.push(`${blockName}__list--subtitle`); + // apply classes to the content based on items inside if (typePicture) { - item.classList.add(`${blockName}__list--with-pictures`); + classes.push(`${blockName}__list--with-pictures`); } if (typeDownloads) { - item.classList.add(`${blockName}__list--with-downloads`); + classes.push(`${blockName}__list--with-downloads`); const buttons = item.querySelectorAll('.button'); buttons.forEach((bt) => { @@ -93,12 +97,12 @@ export default async function decorate(block) { } if (!typePicture && !typeDownloads && !typeTitle) { - item.classList.add(`${blockName}__list--with-text`); + classes.push(`${blockName}__list--with-text`); } - accordionContent.appendChild(item); + item.classList.add(...classes); - removeEmptyTags(item); + accordionContent.appendChild(item); }); // close last accordion content @@ -108,5 +112,7 @@ export default async function decorate(block) { block.appendChild(accordionWrapper); } + removeEmptyTags(block); + await loadBlock(accordionBlock); }