Skip to content

Commit

Permalink
Swap libraries, now it uses embla caroussel
Browse files Browse the repository at this point in the history
  • Loading branch information
sneridagh committed Nov 28, 2023
1 parent e0e6ee4 commit 6be9174
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 149 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on: [push]
env:
ADDON_NAME: "@kitconcept/volto-slider-block"
ADDON_PATH: "volto-slider-block"
VOLTO_VERSION: "17.2.0"
VOLTO_VERSION: "17.6.0"

jobs:

Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@
},
"dependencies": {
"deepmerge": "4.2.2",
"react-slick": "0.29.0",
"slick-carousel": "1.8.1"
"embla-carousel-react": "^8.0.0-rc15"
},
"peerDependencies": {
"@plone/volto": "^17.0.0-alpha.21"
Expand Down
224 changes: 145 additions & 79 deletions src/components/View.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Message } from 'semantic-ui-react';
import Slider from 'react-slick';
import useEmblaCarousel from 'embla-carousel-react';
import cx from 'classnames';
import { defineMessages, useIntl } from 'react-intl';
import Body from './Body';
Expand All @@ -20,6 +20,56 @@ const messages = defineMessages({
},
});

const DotButton = (props) => {
const { children, ...restProps } = props;

return (
<button type="button" {...restProps}>
{children}
</button>
);
};

export const PrevButton = (props) => {
const { children, ...restProps } = props;

return (
<button
className="embla__button embla__button--prev"
type="button"
{...restProps}
>
<svg className="embla__button__svg" viewBox="0 0 532 532">
<path
fill="currentColor"
d="M355.66 11.354c13.793-13.805 36.208-13.805 50.001 0 13.785 13.804 13.785 36.238 0 50.034L201.22 266l204.442 204.61c13.785 13.805 13.785 36.239 0 50.044-13.793 13.796-36.208 13.796-50.002 0a5994246.277 5994246.277 0 0 0-229.332-229.454 35.065 35.065 0 0 1-10.326-25.126c0-9.2 3.393-18.26 10.326-25.2C172.192 194.973 332.731 34.31 355.66 11.354Z"
/>
</svg>
{children}
</button>
);
};

export const NextButton = (props) => {
const { children, ...restProps } = props;

return (
<button
className="embla__button embla__button--next"
type="button"
{...restProps}
>
<svg className="embla__button__svg" viewBox="0 0 532 532">
<path
fill="currentColor"
d="M176.34 520.646c-13.793 13.805-36.208 13.805-50.001 0-13.785-13.804-13.785-36.238 0-50.034L330.78 266 126.34 61.391c-13.785-13.805-13.785-36.239 0-50.044 13.793-13.796 36.208-13.796 50.002 0 22.928 22.947 206.395 206.507 229.332 229.454a35.065 35.065 0 0 1 10.326 25.126c0 9.2-3.393 18.26-10.326 25.2-45.865 45.901-206.404 206.564-229.332 229.52Z"
/>
</svg>
{children}
</button>
);
};

const PrevArrow = ({ className, style, onClick }) => (
<button
className={className}
Expand Down Expand Up @@ -55,54 +105,66 @@ const SliderView = (props) => {
} = props;
const intl = useIntl();

// These are the local state in case of view mode
// The ones that control the edit need to be above since they have
// to be drilled down to here AND to the sidebar
const [slideViewIndex, setSlideViewIndex] = React.useState(0);
const [prevBtnDisabled, setPrevBtnDisabled] = useState(true);
const [nextBtnDisabled, setNextBtnDisabled] = useState(true);
const [selectedIndex, setSelectedIndex] = useState(0);
const [scrollSnaps, setScrollSnaps] = useState([]);

const [emblaRef, emblaApi] = useEmblaCarousel();

const sliderRef = React.useRef();
const scrollPrev = useCallback(() => {
if (emblaApi) {
emblaApi.scrollPrev();
setSlideIndex && setSlideIndex(selectedIndex - 1);
}
}, [emblaApi, selectedIndex, setSlideIndex]);

const scrollNext = useCallback(() => {
if (emblaApi) {
emblaApi.scrollNext();
setSlideIndex && setSlideIndex(selectedIndex + 1);
}
}, [emblaApi, selectedIndex, setSlideIndex]);

const scrollTo = useCallback(
(index) => {
if (emblaApi) {
emblaApi.scrollTo(index);
setSlideIndex && setSlideIndex(index);
}
},
[emblaApi, setSlideIndex],
);

const onInit = useCallback((emblaApi) => {
setScrollSnaps(emblaApi.scrollSnapList());
}, []);

const onSelect = useCallback((emblaApi) => {
setSelectedIndex(emblaApi.selectedScrollSnap());
setPrevBtnDisabled(!emblaApi.canScrollPrev());
setNextBtnDisabled(!emblaApi.canScrollNext());
}, []);

useEffect(() => {
if (!emblaApi) return;

if (sliderRef.current && isEditMode) {
onInit(emblaApi);
onSelect(emblaApi);
emblaApi.on('reInit', onInit);
emblaApi.on('reInit', onSelect);
emblaApi.on('select', onSelect);
}, [emblaApi, onInit, onSelect]);

useEffect(() => {
// This syncs the current slide with the objectwidget (or other sources
// able to access the slider context)
// that can modify the SliderContext (and come here via props slideIndex)
sliderRef.current.slickGoTo(slideIndex);
}

const [headerNode, setHeaderNode] = React.useState(null);

React.useEffect(() => {
// Unfortunately, we need to go with this ugly hack above the
// dimensions hack for make the slide width work in edit mode as
// we want it.
// The reason is behind how React Portals work and the timing
// around when they are updated.
// What happens is that when the edit route kicks in, this
// component renders and checks for the size of the element slightly
// before the Portal kicks in and renders itself, then the sidebar is
// rendered with its dimensions and pushes the rest to its right position.
// When this happens is late, and the dimensions have been calculated already
// thus the dimensions are wrong (they are the ones before the portal kicks
// is, so they are wider than expected).
if (isEditMode) {
setTimeout(() => {
window.scroll(0, 1);
}, 100);
}
}, [isEditMode]);

React.useEffect(() => {
setHeaderNode(
document.querySelector(
config.blocks.blocksConfig.slider.referenceContainerQuery,
),
);
}, []);
const { width } = useNodeDimensions(headerNode);
scrollTo(slideIndex);
}, [slideIndex, scrollTo]);

return (
<>
<SlidesWidthFix width={width} />
<div className={cx('block slider', className)}>
{(data.slides?.length === 0 || !data.slides) && isEditMode && (
<Message>
Expand All @@ -113,43 +175,47 @@ const SliderView = (props) => {
</Message>
)}
{data.slides?.length > 0 && (
<Slider
ref={sliderRef}
dots
infinite
speed={500}
slidesToShow={1}
slidesToScroll={1}
draggable={false}
nextArrow={<NextArrow />}
prevArrow={<PrevArrow />}
slideWidth="1200px"
// This syncs the current slide with the SliderContext state
// responding to the slide change event from the slider itself
// (the dots or the arrows)
afterChange={(current) => isEditMode && setSlideIndex(current)}
beforeChange={(current) => setSlideViewIndex(current)}
>
{data.slides &&
data.slides.map((item, index) => {
return (
<div key={item['@id']}>
<Body
{...props}
key={item['@id']}
data={item}
isEditMode={isEditMode}
dataBlock={data}
index={index}
block={block}
openObjectBrowser={openObjectBrowser}
onChangeBlock={onChangeBlock}
isActive={slideViewIndex === index}
/>
</div>
);
})}
</Slider>
<div className="embla">
<div className="embla__viewport" ref={emblaRef}>
<div className="embla__container">
{data.slides &&
data.slides.map((item, index) => {
return (
<div key={item['@id']} className="embla__slide">
<Body
{...props}
key={item['@id']}
data={item}
isEditMode={isEditMode}
dataBlock={data}
index={index}
block={block}
openObjectBrowser={openObjectBrowser}
onChangeBlock={onChangeBlock}
isActive={selectedIndex === index}
/>
</div>
);
})}
</div>
<div className="embla__buttons">
<PrevButton onClick={scrollPrev} disabled={prevBtnDisabled} />
<NextButton onClick={scrollNext} disabled={nextBtnDisabled} />
</div>
</div>

<div className="embla__dots">
{scrollSnaps.map((_, index) => (
<DotButton
key={index}
onClick={() => scrollTo(index)}
className={'embla__dot'.concat(
index === selectedIndex ? ' embla__dot--selected' : '',
)}
/>
))}
</div>
</div>
)}
</div>
</>
Expand Down
110 changes: 107 additions & 3 deletions src/theme/main.less
Original file line number Diff line number Diff line change
@@ -1,13 +1,117 @@
@import (less) '~slick-carousel/slick/slick.css';
@import (less) '~slick-carousel/slick/slick-theme.css';

@toolbarWidth: 80px;
@sidebarWidth: 375px;
@collapsedWidth: 20px;

@slider-images-aspect-ratio: var(--slider-images-aspect-ratio, 16/9);
@slider-images-object-position: var(--slider-images-object-position, top left);

:root {
--brand-primary: rgb(47, 112, 193);
--brand-secondary: rgb(116, 97, 195);
}

.embla {
overflow: hidden;
}

.embla__container {
display: flex;
}

.embla__slide {
min-width: 0;
flex: 0 0 100%;
}

.embla__button {
display: inline-flex;
padding: 0;
border: 0;
margin: 0;
-webkit-appearance: none;
background-color: transparent;
cursor: pointer;
text-decoration: none;
touch-action: manipulation;
}

.embla__buttons {
position: absolute;
top: 50%;
left: 1.6rem;
display: flex;
align-items: center;
transform: translateY(-50%);
}

.embla__button {
z-index: 1;
display: flex;
width: 4rem;
height: 4rem;
align-items: center;
justify-content: center;
color: var(--background-site);
cursor: pointer;
}

.embla__button:disabled {
opacity: 0.3;
}

.embla__button__svg {
width: 65%;
height: 65%;
}

.embla__dot {
display: inline-flex;
padding: 0;
border: 0;
margin: 0;
-webkit-appearance: none;
background-color: transparent;
cursor: pointer;
text-decoration: none;
touch-action: manipulation;
}

.embla__dots {
position: absolute;
z-index: 1;
right: 0;
bottom: 1.6rem;
left: 0;
display: flex;
align-items: center;
justify-content: center;
}

.embla__dot {
display: flex;
width: 2.4rem;
height: 2.4rem;
align-items: center;
margin-right: 0.75rem;
margin-left: 0.75rem;
}

.embla__dot:after {
width: 100%;
height: 0.3rem;
border-radius: 0.2rem;
background: var(--background-site);
content: '';
}

.embla__dot--selected:after {
background: linear-gradient(
45deg,
var(--brand-primary),
var(--brand-secondary)
);
}

.block.slider {
&:not(.inner):not([role='presentation']) {
padding-bottom: 4em;
Expand Down
Loading

0 comments on commit 6be9174

Please sign in to comment.