Skip to content

Commit

Permalink
Merge pull request #187 from hlxsites/93-hero-slides
Browse files Browse the repository at this point in the history
Luxury Collection Hero Listing Slider
  • Loading branch information
rrusher authored Jan 18, 2024
2 parents b783381 + 9ce93bb commit 6d8ed43
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 0 deletions.
4 changes: 4 additions & 0 deletions blocks/header/luxury-collection-template.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
color: initial;
}

.light-nav.luxury-collection header * {
background: transparent;
}

.luxury-collection .header.block nav .nav-sections {
background-color: var(--primary-color);
}
Expand Down
188 changes: 188 additions & 0 deletions blocks/hero-slides/hero-slides.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
.section.hero-slides-container {
padding: 0;
max-width: var(--wide-page-width);
}

.section > .hero-slides-wrapper {
max-width: unset;
margin: unset;
}

.hero-slides {
width: 100%;
position: relative;
height: 600px;
}

.hero-slides.block.under-nav {
margin-top: calc(var(--nav-height) * -1);
background: linear-gradient(180deg, rgba(0 0 0 / 50%) 0, rgba(0 0 0 / 15%) 50px, transparent 100px, transparent 100%);
}

.hero-slides.block.has-content.under-nav {
background: linear-gradient(180deg, rgba(0 0 0 / 50%) 0, rgba(0 0 0 / 15%) 100px, rgba(0 0 0 / 15%) 100%);
}

.hero-slides .slide {
display: flex;
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
flex-direction: column;
opacity: 0;
transition: opacity 0.9s ease;
}

.hero-slides .slide.active {
opacity: 1;
z-index: 2;
}

.hero-slides .slide .image {
height: 100%;
width: 100%;
}

.hero-slides .slideshow-buttons div {
display: none;
}

.hero-slides .slideshow-buttons img {
cursor: pointer;
}

.hero-slides .slide .image img {
object-fit: cover;
width: 100%;
height: 100%;
}

.hero-slides .slide .row {
width: 100%;
height: 120px;
flex-flow: column wrap;
align-content: space-between;
justify-content: flex-start;
align-items: stretch;
padding-left: 0;
padding-right: 0;
display: flex;
position: absolute;
top: auto;
bottom: 0%;
left: 0%;
right: 0%;
background: transparent;
}

.hero-slides .slide .row .logo {
width: 33.33%;
height: inherit;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
background: transparent;
}

.hero-slides .slide .row .logo img {
filter: invert();
height: 100%;
background: transparent;
}

.hero-slides .slide .text {
color: var(--white);
align-self: center;
padding: 30px;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
background-color: black;
width: 66.67%;

/* font size is relative to screen width, but limited in min and max size.
the actual texts then use `em` to scale with this font size.
*/
font-size: clamp(11px, 1.5vw, 16px);
}

.hero-slides .slide .text * {
margin: 0;
font-size: var(--body-font-size-m);
line-height: var(--line-height-m);
color: var(--white);
}

.hero-slides .slide .text .price {
font-weight: var(--font-weight-bold);
}

.hero-slides .slide .text .link {
font-size: var(--body-font-size-s);
}

.hero-slides .slideshow-buttons {
position: absolute;
bottom: 4rem;
right: 7%;
height: 20px;
display: flex;
justify-content: center;
gap: 1.5vw;
z-index: 3;
}

.hero-slides .slideshow-buttons .prev {
transform: rotateY(180deg);
}

@media (min-width: 992px) {
.hero-slides .slide {
flex-direction: row;
}

.hero-slides .slide .row {
width: 33.33%;
height: 340px;
flex: 0 auto;
justify-content: center;
display: flex;
position: absolute;
bottom: 0%;
left: auto;
right: 0%;
flex-direction: column;
}

.hero-slides .slide .row .logo {
height: 200px;
padding: 0;
width: 100%;
}

.hero-slides .slide .row .logo img {
height: 60%;
}

.hero-slides .slide .text {
height: 140px;
padding: 0 30px;
width: 100%;
}
}

@media screen and (min-width: 1200px) {
.hero.block.under-nav {
margin-top: -100px;
}

.hero-slides .slideshow-buttons div {
display: block;
padding-top: 5px;
font-size: var(--body-font-size-xs);
}
}
136 changes: 136 additions & 0 deletions blocks/hero-slides/hero-slides.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { createOptimizedPicture, readBlockConfig } from '../../scripts/aem.js';

// eslint-disable-next-line no-unused-vars
async function fetchListings(config) {
const resp = await fetch(`${window.hlx.codeBasePath}/drafts/rrusher/listings.json`);
// eslint-disable-next-line no-return-await
return (await resp.json()).data;
}

function setupSlideControls(block) {
function goToSlide(index) {
block.querySelector('.slide.active').classList.remove('active');
[...block.querySelectorAll('.slide')].at(index).classList.add('active');
const paging = block.querySelector('.slideshow-buttons div');
paging.innerText = paging.innerText.replace(/\d+/, index + 1);
// automatically advance slides. Reset timer when user interacts with the slideshow
// eslint-disable-next-line no-use-before-define
autoplaySlides();
}

let autoSlideInterval = null;
function autoplaySlides() {
clearInterval(autoSlideInterval);
// eslint-disable-next-line no-use-before-define
autoSlideInterval = setInterval(() => advanceSlides(+1), 3000);
}

function advanceSlides(diff) {
const allSlides = [...block.querySelectorAll('.slide')];
const activeSlide = block.querySelector('.slide.active');
const currentIndex = allSlides.indexOf(activeSlide);

const newSlideIndex = (allSlides.length + currentIndex + diff) % allSlides.length;
goToSlide(newSlideIndex);
}

/** detect swipe gestures on touch screens to advance slides */
function gestureStart(event) {
const touchStartX = event.changedTouches[0].screenX;

function gestureEnd(endEvent) {
const touchEndX = endEvent.changedTouches[0].screenX;
const delta = touchEndX - touchStartX;
if (delta < -5) {
advanceSlides(+1);
} else if (delta > 5) {
advanceSlides(-1);
} else {
// finger not moved enough, do nothing
}
}

block.addEventListener('touchend', gestureEnd, { once: true });
}

block.addEventListener('touchstart', gestureStart, { passive: true });

autoplaySlides();
return { advanceSlides };
}

/**
* make text safe to use in innerHTML
* @param text any string
* @return {string} sanitized html string
*/
function plainText(text) {
const fragment = document.createElement('div');
fragment.append(text);
return fragment.innerHTML;
}

/**
* Slideshow with luxury listings. Supports swiping on touch screens.
* Also supports manually adding content into the block.
* @param block
*/
export default async function decorate(block) {
const config = readBlockConfig(block);
const listings = await fetchListings(config);
block.textContent = '';
const { advanceSlides } = setupSlideControls(block);

const count = document.createElement('div');
count.innerText = `1 of ${listings.length}`;
const slideshowButtons = document.createElement('div');
slideshowButtons.classList.add('slideshow-buttons');
const prevBtn = document.createElement('img');
prevBtn.classList.add('prev');
prevBtn.src = '/icons/chevron-right-white.svg';
prevBtn.addEventListener('click', () => advanceSlides(1));
const nextBtn = document.createElement('img');
nextBtn.classList.add('next');
nextBtn.src = '/icons/chevron-right-white.svg';
nextBtn.addEventListener('click', () => advanceSlides(-1));

slideshowButtons.append(count, prevBtn, nextBtn);

listings.forEach((listing, index) => {
const slide = document.createElement('div');
slide.classList.add('slide');

const imageSizes = [
// desktop
{ media: '(min-width: 600px)', height: '600' },
// tablet and mobile sizes:
{ media: '(min-width: 400px)', height: '600' },
{ width: '400' },
];
const picture = listing.picture || createOptimizedPicture(
config[listing.ListingId],
listing.City,
index === 0,
imageSizes,
);
slide.innerHTML = `
<div class="image">${picture.outerHTML}</div>
<div class="row">
<div class="logo">
<img src="/icons/lux_mark_classic_blk.svg">
</div>
<div class="text">
<p class="city">${plainText(listing.City)}, ${plainText(listing.StateOrProvince)}</p>
<p class="price">${plainText(listing.ListPriceUS)}</p>
<a class="link" href='${listing.PdpPath}'>LEARN MORE</a>
</div>
</div> `;
block.append(slide);

if (index === 0) {
slide.classList.add('active');
}
});

block.append(slideshowButtons);
}
1 change: 1 addition & 0 deletions icons/lux_mark_classic_blk.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 6d8ed43

Please sign in to comment.