import React, { useState, FunctionComponent, KeyboardEvent, useEffect, useRef, } from 'react'; import { Arrow } from '../arrow'; import { ItemProvider } from '../item'; import { rotateItems, getTransformAmount, getCurrent, initItems, getShowArrow, cleanItems, updateNodes, } from '../../helpers'; import { SlideDirection, Item, ArrowKeys } from '../../types/carousel'; import { defaultProps } from './defaultProps'; import styles from '../../styles/styles.module.css'; import { usePrevious } from '../../hooks'; export const Carousel: FunctionComponent = (userProps: CarouselProps) => { const props: Required = { ...defaultProps, ...userProps }; const [items, setItems] = useState( initItems(props.children, props.slide, props.infinite), ); const [width, setWidth] = useState(0); const [animation, setAnimation] = useState({ transform: 0, transition: 0, isSliding: false, }); const [current, setCurrent] = useState(0); const [showArrow, setShowArrow] = useState( getShowArrow(props.children.length, props.show, props.infinite, current), ); const prevChildren = usePrevious(userProps.children); const [page, setPage] = useState(0); const itemsRef = useRef(initItems(props.children, props.slide, props.infinite)); const isPaginating = useRef(false); if (props.dynamic) { useEffect(() => { const newItems = updateNodes( itemsRef.current, props.children, prevChildren, props.slide, props.infinite, ); setItems(newItems); itemsRef.current = newItems; if ( page < props.pageCount && prevChildren && prevChildren?.length < props.children.length ) { slide(SlideDirection.Right); setPage(page + 1); } }, [props.children]); } const slide = (direction: SlideDirection) => { if ( animation.isSliding || (direction === SlideDirection.Right && !showArrow.right) || (direction === SlideDirection.Left && !showArrow.left) ) { return; } if ( props.paginationCallback && direction === SlideDirection.Right && page < props.pageCount - 1 && !isPaginating.current ) { isPaginating.current = true; props.paginationCallback(direction); return; } const elements = props.children; const next = getCurrent(current, props.slide, elements.length, direction); const rotated = props.infinite ? rotateItems(elements, items, next, props.show, props.slide, direction) : items; if (props.infinite && direction === SlideDirection.Right) { setItems(rotated); itemsRef.current = rotated; } setAnimation({ transform: animation.transform + getTransformAmount(width, props.slide, direction), transition: props.transition, isSliding: true, }); setCurrent(next); setShowArrow(getShowArrow(elements.length, props.show, props.infinite, next)); setTimeout(() => { if (props.infinite) { const cleanedItems = cleanItems( direction === SlideDirection.Right ? itemsRef.current : rotated, props.slide, direction, ); setItems(cleanedItems); itemsRef.current = cleanedItems; } setAnimation({ transform: props.infinite ? getTransformAmount(width, props.slide, SlideDirection.Right) : animation.transform + getTransformAmount(width, props.slide, direction), transition: 0, isSliding: false, }); }, props.transition * 1_0_0_0); isPaginating.current = false; }; const widthCallBack = (calculatedWidth: number) => { setWidth(calculatedWidth); setAnimation({ transform: props.infinite ? getTransformAmount(calculatedWidth, props.slide, SlideDirection.Right) : 0, transition: 0, isSliding: false, }); }; const dragCallback = (translateX: number) => { setAnimation({ transform: translateX, transition: props.transition, isSliding: false, }); setTimeout( () => setAnimation({ ...animation, transition: 0 }), props.transition * 1_0_0_0, ); }; const slideCallback = (direction: SlideDirection) => { slide(direction); }; const handleOnKeyDown = (e: KeyboardEvent) => { if (e.keyCode === ArrowKeys.Left) { slide(SlideDirection.Left); } else if (e.keyCode === ArrowKeys.Right) { slide(SlideDirection.Right); } }; return (
{showArrow.left && ( slide(SlideDirection.Left)} /> )} {showArrow.right && ( slide(SlideDirection.Right)} /> )}
); }; export interface CarouselProps { children: Item[]; show: number; slide: number; transition?: number; swiping?: boolean; swipeOn?: number; responsive?: boolean; infinite?: boolean; className?: string; useArrowKeys?: boolean; a11y?: { [key: string]: string }; dynamic?: boolean; paginationCallback?: ((direction: SlideDirection) => any) | null; pageCount?: number; } export interface CarouselState { items: Item[]; width: number; transform: number; transition: number; isSliding: boolean; current: number; }