design-comuni-plone-theme
Version:
Volto Theme for Italia design guidelines
273 lines (259 loc) • 7.8 kB
JSX
/*
* Slider
*/
import 'slick-carousel/slick/slick.css';
import 'design-comuni-plone-theme/components/slick-carousel/slick/slick-theme.css';
import { Col, Container, Row } from 'design-react-kit';
import {
ListingImage,
ListingLinkMore,
SingleSlideWrapper,
CarouselWrapper,
ButtonPlayPause,
} from 'design-comuni-plone-theme/components/ItaliaTheme';
import { useSlider } from 'design-comuni-plone-theme/components/ItaliaTheme/Slider/slider';
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
import config from '@plone/volto/registry';
const messages = defineMessages({
carouselItemAriaLabel: {
id: 'carousel-item-aria-label',
defaultMessage:
'Sei attualmente in un carosello, per navigare usa le frecce sinistra e destra',
},
dots: {
id: 'dots',
defaultMessage: 'Navigazione elementi slider',
},
slideDot: {
id: 'slideDot',
defaultMessage: 'Vai alla slide {index}',
},
openLink: {
id: 'openLink',
defaultMessage: 'Apri il link',
},
});
const Slide = (props) => {
const intl = useIntl();
const { index, appearance, appearanceProp, onKeyDown } = props;
const appearances = config.blocks.blocksConfig.listing.variations.filter(
(v) => v.id === 'slider',
)[0]?.appearance;
const SlideItemAppearance = appearances[appearance] ?? appearances['default'];
return (
<SingleSlideWrapper
index={index}
onKeyDown={onKeyDown}
aria-label={
(props['aria-label'] ? props['aria-label'] + ' - ' : '') +
intl.formatMessage(messages.carouselItemAriaLabel)
}
>
<div className={'slide-wrapper'} role="presentation">
<SlideItemAppearance {...props} {...appearanceProp} intl={intl} />
</div>
</SingleSlideWrapper>
);
};
const SliderTemplate = ({
items,
title,
isEditMode,
show_block_bg,
linkTitle,
linkHref,
slidesToShow = '1',
full_width = false,
show_image_title = true,
show_dots = true,
autoplay = false,
autoplay_speed = 2, //seconds
slide_appearance = 'default',
reactSlick,
block,
...otherProps
}) => {
const block_id = block;
const intl = useIntl();
const [userAutoplay, setUserAutoplay] = useState(autoplay);
const nSlidesToShow =
items.length < parseInt(slidesToShow)
? items.length
: parseInt(slidesToShow);
const Slider = reactSlick.default;
const {
slider,
focusSlide,
SliderNextArrow,
SliderPrevArrow,
handleSlideKeydown,
} = useSlider(userAutoplay, setUserAutoplay, block_id);
const toggleAutoplay = () => {
if (!slider?.current) return;
if (userAutoplay) {
setUserAutoplay(false);
slider.current.slickPause();
} else {
setUserAutoplay(true);
slider.current.slickPlay();
}
};
const renderCustomDots = (props) => {
// Custom handling of focus for a11y
return (
<ul
className="slick-dots"
aria-label={intl.formatMessage(messages.dots)}
title={intl.formatMessage(messages.dots)}
>
{props.map((item, index) => {
const El = item.type;
const children = item.props.children;
// Justified assumption: children is an Object and not an Array here
const Child =
children.type ||
function () {
return null;
};
return (
<El
key={index}
className={`${item.props.className} slick-dot`}
tabIndex={-1}
title={intl.formatMessage(messages.slideDot, {
index: index + 1,
})}
aria-hidden={true}
>
<Child
{...children.props}
tabIndex={-1}
style={{ padding: 0 }}
title={intl.formatMessage(messages.slideDot, {
index: index + 1,
})}
aria-label={intl.formatMessage(messages.slideDot, {
index: index + 1,
})}
/>
</El>
);
})}
</ul>
);
};
const settings = {
dots: show_dots,
infinite: true,
autoplay: autoplay,
speed: 500,
lazyLoad: true,
slidesToShow: nSlidesToShow,
slidesToScroll: nSlidesToShow,
autoplaySpeed: autoplay_speed * 1000,
pauseOnHover: true,
pauseOnFocus: true,
pauseOnDotsHover: true,
swipe: true,
swipeToSlide: true,
focusOnSelect: false,
draggable: true,
accessibility: true,
nextArrow: <SliderNextArrow intl={intl} />,
prevArrow: <SliderPrevArrow intl={intl} />,
appendDots: renderCustomDots,
// Custom handling of focus for a11y
afterChange: focusSlide,
responsive: [
{
breakpoint: 980,
settings: {
slidesToShow: 1,
slidesToScroll: 1,
},
},
],
};
return (
<div
className={cx(`sliderTemplate slidesToShow-${nSlidesToShow || 1}`, {
'no-margin': full_width,
['appearance_' + slide_appearance]: slide_appearance,
})}
id={'slider_' + block_id}
>
<Container className="px-4">
{title && (
<Row>
<Col>
<h2 className="mb-4">{title}</h2>
</Col>
</Row>
)}
<div
className={cx('slider-container', {
'px-4 px-md-0': !full_width,
'full-width': full_width,
})}
>
<CarouselWrapper className="it-card-bg">
{items?.length > nSlidesToShow && (
<ButtonPlayPause onClick={toggleAutoplay} autoplay={userAutoplay}>
<span>{userAutoplay ? 'pause' : 'play'}</span>
</ButtonPlayPause>
)}
<Slider {...settings} ref={slider}>
{items.map((item, index) => {
const image = (
<ListingImage
item={item}
loading={index === 0 ? 'eager' : 'lazy'}
sizes={`max-width(991px) 620px, ${1300 / nSlidesToShow}px`}
critical
showDefault
/>
);
const nextIndex = index < items.length - 1 ? index + 1 : null;
const prevIndex = index > 0 ? index - 1 : null;
return (
<Slide
key={item['@id'] + index}
image={image}
index={index}
full_width={full_width}
item={item}
show_image_title={show_image_title}
userAutoplay={userAutoplay}
slider={slider}
appearance={slide_appearance}
appearanceProp={otherProps}
block_id={block_id}
aria-label={
!show_image_title && slide_appearance === 'default'
? item.title
: null
}
onKeyDown={handleSlideKeydown(index, prevIndex, nextIndex)}
/>
);
})}
</Slider>
</CarouselWrapper>
</div>
<ListingLinkMore title={linkTitle} href={linkHref} className="my-4" />
</Container>
</div>
);
};
SliderTemplate.propTypes = {
items: PropTypes.arrayOf(PropTypes.any).isRequired,
linkTitle: PropTypes.any,
linkHref: PropTypes.any,
isEditMode: PropTypes.bool,
title: PropTypes.string,
};
export default injectLazyLibs(['reactSlick'])(SliderTemplate);