Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How To Steps Accordion #1324

Merged
merged 15 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 216 additions & 0 deletions express/blocks/how-to-v2/how-to-v2.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
main .how-to-v2 h2 {
font-size: 22px;
text-align: center;
padding: 0 24px;
}

main .how-to-v2 ol {
list-style-type: none;
font-size: 21px;
font-weight: 900;
}

main .how-to-v2 ol li {
padding: 8px 0;
cursor: pointer;
transition: all 0.21s;
display: flex;
}

main .how-to-v2 ol li .step-indicator {
width: 5px;
min-width: 5px;
background: linear-gradient(-96.68deg, #FF4885 5.24%, #FC7D00 94.76%), #686DF4;
border-radius: 2.5px;
}

main .how-to-v2 ol li .step-content {
padding: 8px 16px;
margin: 0;
}

main .how-to-v2 ol li .step-indicator:has(+ div .closed) {
background: linear-gradient(95.55deg, rgba(255, 255, 255, 0.5) 4.43%, rgba(255, 255, 255, 0.3) 93.65%), #8F8F8F;
}

main .how-to-v2 ol li h3 {
text-align: left;
font-size: 18px;
line-height: 23.4px;
font-weight: 700;
}

main .how-to-v2 ol li .detail-container {
font-size: 16px;
line-height: 20.8px;
font-weight: 400;
}

main .how-to-v2 ol li .detail-container {
max-height: 0;
overflow: hidden;
transition: max-height 0.21s ease-out;
}

main .how-to-v2 ol li .detail-text {
padding-top: 10px;
}

/* so that there is no initial re-paint */
main .how-to-v2 ol li:first-child .detail-container {
max-height: none;
}

main .how-to-v2.video lite-youtube,
main .how-to-v2.image picture {
border-radius: 16px;
overflow: hidden;
}

main .how-to-v2 em {
font-style: normal;
background: linear-gradient(140.08deg, #FF4DD2 67.54%, #FF993B 76.42%);
background-size: 108% 108%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 900;
}

main .how-to-v2 .steps-content {
display: flex;
flex-direction: column;
padding: 0 24px;
position: relative;
}

main .how-to-v2 .steps-content .steps-content-backg {
position: absolute;
width: 100%;
top: -24vw;
z-index: -1;
height: 100%;
transform: scale(0.9);
}

main .how-to-v2 ol.steps {
margin: 6px 0 0;
padding: 0;
}

main .how-to-v2 ol.steps .step .step-content h3:hover {
background-color: var( --color-gray-100);
margin: -8px 0 -8px -16px;
padding: 8px 0 8px 16px;
}

main .how-to-v2.video .video-container {
margin-top: 20px;
}

main .how-to-v2.image .image-container {
margin-top: 20px;
width: 100%;
overflow: hidden;
}

main .how-to-v2.image .image-container img,
main .how-to-v2.video .video-container > * {
border-radius: 16px;
max-width: 400px;
width: 100%;
height: 100%;
object-fit: cover;
}

main .how-to-v2.video .video-container > * {
margin-left: auto;
margin-right: auto;
height: unset;
}

main .how-to-v2.video,
main .how-to-v2.image {
/* note we don't set margin here because background gradient has to go past into this container */
margin: 0;
max-width: unset;
}

@media (min-width: 768px) {

main .how-to-v2 h2 {
font-size: 28px;
}

main .how-to-v2.video,
main .how-to-v2.image {
max-width: 950px;
margin: auto;
}

main .how-to-v2 .steps-content {
display: flex;
flex-direction: row;
padding: 0 24px;
}

main .how-to-v2 .steps-content > * {
flex: 1 1 50%;
box-sizing: border-box;
}

main .how-to-v2.image .image-container,
main .how-to-v2.video .video-container {
align-items: center;
display: inline-grid;
}

main .how-to-v2.image .image-container img {
height: unset;
}

main .how-to-v2 .steps-content .steps {
padding-left: 20px;
}

main .how-to-v2 .steps-content .steps-content-backg {
left: -90px;
width: 67%;
transform: scale(0.9);
top: -68px;
}
}

@media (min-width: 1280px) {

main .how-to-v2 h2 {
font-size: 36px;
}

main .how-to-v2.video,
main .how-to-v2.image {
max-width: 1300px;
margin: auto;
}

main .how-to-v2.image .image-container img,
main .how-to-v2.video .video-container > * {
max-height: 300px;
max-width: 533px;
}

main .how-to-v2.image .image-container {
display: inline-block;
}

main .how-to-v2 .steps-content {
margin-top: 30px;
}

main .how-to-v2 ol li h3 {
text-align: left;
font-size: 22px;
line-height: 28.6px;
font-weight: 700;
}
}
129 changes: 129 additions & 0 deletions express/blocks/how-to-v2/how-to-v2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/* eslint-disable import/named, import/extensions */
import { createTag } from '../../scripts/utils.js';
import { embedYoutube } from '../../scripts/embed-videos.js';

function setStepDetails(block, indexOpenedStep) {
const listItems = block.querySelectorAll(':scope li');

listItems.forEach((item, i) => {
const detail = item.querySelector('.detail-container');

if (i === indexOpenedStep) {
detail.classList.remove('closed');
detail.style.maxHeight = `${detail.scrollHeight}px`;
} else {
detail.classList.add('closed');
detail.style.maxHeight = '0';
}
});
}

function buildAccordion(block, rows, stepsContent) {
let indexOpenedStep = 0;
const list = createTag('OL', { class: 'steps' });

rows.forEach((row, i) => {
const [stepTitle, stepDetail] = row.querySelectorAll(':scope div');

const newStepTitle = createTag('h3');
newStepTitle.replaceChildren(...stepTitle.childNodes);

const listItem = createTag('LI', { class: 'step', tabindex: '0' });
list.append(listItem);

const listItemIndicator = createTag('div', { class: 'step-indicator' });
const listItemContent = createTag('div', { class: 'step-content' });

const detailText = stepDetail;
detailText.classList.add('detail-text');

const detailContainer = createTag('div', { class: 'detail-container' });

if (i !== 0) {
detailContainer.classList.add('closed');
}

detailContainer.append(detailText);

listItem.append(listItemIndicator);
listItem.append(listItemContent);

listItemContent.append(newStepTitle);
listItemContent.append(detailContainer);

const handleOpenDetails = (ev) => {
indexOpenedStep = i;
setStepDetails(block, indexOpenedStep);
ev.preventDefault();
};

newStepTitle.addEventListener('click', handleOpenDetails);
listItem.addEventListener('keyup', (ev) => ev.which === 13 && handleOpenDetails(ev));
});

stepsContent.append(list);

// set this in the next event cycle when scrollHeight has been established
setTimeout(() => {
setStepDetails(block, indexOpenedStep);
}, 0);
}

export default async function decorate(block) {
const isVideoVariant = block.classList.contains('video');
const isImageVariant = block.classList.contains('image');

const section = block.closest('.section');
const rows = Array.from(block.children);

const backgroundRow = block.children[0];
const backgroundImage = backgroundRow.querySelector('img');
const backgroundURL = backgroundImage?.src;
const hasBackground = !!backgroundURL;

if (hasBackground) {
rows.shift();
}

if (isVideoVariant || isImageVariant) {
const stepsContent = createTag('div', { class: 'steps-content' });

if (hasBackground) {
// So that background image goes beyond container
const stepsContentBackground = createTag('div', { class: 'steps-content-backg' });
const stepsContentBackgroundImg = createTag('img', { class: 'steps-content-backg-image' });
stepsContent.append(stepsContentBackground);
stepsContentBackground.append(stepsContentBackgroundImg);
stepsContentBackgroundImg.src = backgroundURL;
}

if (isVideoVariant) {
const videoData = rows.shift();

// remove the added social link from the block DOM
block.removeChild(block.children[0]);

const videoLink = videoData.querySelector('a');
const youtubeURL = videoLink?.href;
const url = new URL(youtubeURL);

const videoContainerEl = createTag('div', { class: 'video-container' });

const videoEl = embedYoutube(url);
videoEl.classList.add('video-how-to-steps-accordion');

videoContainerEl.append(videoEl);
stepsContent.append(videoContainerEl);
} else {
const imageData = rows.shift();
const imageEl = imageData.querySelector('picture');
const imageContainerEl = createTag('div', { class: 'image-container' });
imageContainerEl.append(imageEl);
stepsContent.append(imageContainerEl);
}

const heading = section.querySelector('h2, h3, h4');
block.replaceChildren(heading, stepsContent);
buildAccordion(block, rows, stepsContent);
}
}
Loading