import TitleCard from '@app/components/TitleCard'; import globalMessages from '@app/i18n/globalMessages'; import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/outline'; import { debounce } from 'lodash'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; import { useSpring } from 'react-spring'; interface SliderProps { sliderKey: string; items?: JSX.Element[]; isLoading: boolean; isEmpty?: boolean; emptyMessage?: string; placeholder?: React.ReactNode; } enum Direction { RIGHT, LEFT, } const Slider = ({ sliderKey, items, isLoading, isEmpty = false, emptyMessage, placeholder = , }: SliderProps) => { const intl = useIntl(); const containerRef = useRef(null); const [scrollPos, setScrollPos] = useState({ isStart: true, isEnd: false }); const handleScroll = useCallback(() => { const scrollWidth = containerRef.current?.scrollWidth ?? 0; const clientWidth = containerRef.current?.getBoundingClientRect().width ?? 0; const scrollPosition = containerRef.current?.scrollLeft ?? 0; if (!items || items?.length === 0) { setScrollPos({ isStart: true, isEnd: true }); } else if (clientWidth >= scrollWidth) { setScrollPos({ isStart: true, isEnd: true }); } else if ( scrollPosition >= (containerRef.current?.scrollWidth ?? 0) - clientWidth ) { setScrollPos({ isStart: false, isEnd: true }); } else if (scrollPosition > 0) { setScrollPos({ isStart: false, isEnd: false }); } else { setScrollPos({ isStart: true, isEnd: false }); } }, [items]); // eslint-disable-next-line react-hooks/exhaustive-deps const debouncedScroll = useCallback( debounce(() => handleScroll(), 50), [handleScroll] ); useEffect(() => { const handleResize = () => { debouncedScroll(); }; window.addEventListener('resize', handleResize, { passive: true }); return () => { window.removeEventListener('resize', handleResize); }; }, [debouncedScroll]); useEffect(() => { handleScroll(); }, [items, handleScroll]); const onScroll = () => { debouncedScroll(); }; const [, setX] = useSpring(() => ({ from: { x: 0 }, to: { x: 0 }, onChange: (results) => { if (containerRef.current) { containerRef.current.scrollLeft = results.value.x; } }, })); const slide = (direction: Direction) => { const clientWidth = containerRef.current?.getBoundingClientRect().width ?? 0; const cardWidth = containerRef.current?.firstElementChild?.getBoundingClientRect().width ?? 0; const scrollPosition = containerRef.current?.scrollLeft ?? 0; const visibleItems = Math.floor(clientWidth / cardWidth); const scrollOffset = scrollPosition % cardWidth; if (direction === Direction.LEFT) { const newX = Math.max( scrollPosition - scrollOffset - visibleItems * cardWidth, 0 ); setX.start({ from: { x: scrollPosition }, to: { x: newX }, onChange: (results) => { if (containerRef.current) { containerRef.current.scrollLeft = results.value.x; } }, reset: true, config: { friction: 60, tension: 500, velocity: 20 }, }); if (newX === 0) { setScrollPos({ isStart: true, isEnd: false }); } else { setScrollPos({ isStart: false, isEnd: false }); } } else if (direction === Direction.RIGHT) { const newX = Math.min( scrollPosition - scrollOffset + visibleItems * cardWidth, containerRef.current?.scrollWidth ?? 0 - clientWidth ); setX.start({ from: { x: scrollPosition }, to: { x: newX }, onChange: (results) => { if (containerRef.current) { containerRef.current.scrollLeft = results.value.x; } }, reset: true, config: { friction: 60, tension: 500, velocity: 20 }, }); if (newX >= (containerRef.current?.scrollWidth ?? 0) - clientWidth) { setScrollPos({ isStart: false, isEnd: true }); } else { setScrollPos({ isStart: false, isEnd: false }); } } }; return (
{items?.map((item, index) => (
{item}
))} {isLoading && [...Array(10)].map((_item, i) => (
{placeholder}
))} {isEmpty && (
{emptyMessage ? emptyMessage : intl.formatMessage(globalMessages.noresults)}
)}
); }; export default Slider;