UNPKG

react-smooth-carousel-dots

Version:

Custom Carousel Dots component to plug in to any your Carousels.

216 lines (186 loc) 5.32 kB
import { useState, useEffect } from "react" import usePrevious from "./use-previous" const range = (start, end) => { if (start === 0 && end === 0) return [0] return Array(end - start + 1) .fill() .map((_, idx) => start + idx) } const defaultHolderStyle = { display: "flex", alignItems: "center", overflow: "hidden", transition: "all 0.5s ease", } const defaultDotsStyle = { flexShrink: 0, transition: "all 0.5s ease", } const defaultDotStyle = { width: "100%", height: "100%", borderRadius: "50%", backgroundColor: "rgba(255, 255, 255, 0.5)", flexShrink: 0, transition: "transform 0.5s ease, background 0.3s ease-in-out, opacity 0.3s ease-in-out", } const defaultActiveDotStyle = { backgroundColor: "white", } const defaultVisibleDotStyle = { opacity: 1, } const defaultSmallDotStyle = { transform: "scale(0.6667)", } const defaultMediumDotStyle = { transform: "scale(0.8333)", } const defaultBigDotStyle = { transform: "scale(1)", } const useCarouselDots = props => { const { total, current, size = 12, margin = 3, padding = 16, visible = 3, } = props if (total == null) { throw new Error("'total' cannot be null/undefined") } if (total <= 0) { throw new Error("'total' cannot be less than or equal to 0") } if (visible < 3) { throw new Error("'visible' cannot be less than 3") } let _visible = visible if (total <= 3) { _visible = total } if (_visible > 3 && _visible !== total && visible % 2 === 0) { throw new Error("'visible' cannot be an even number") } if (_visible > total) { throw new Error("'visible' cannot be more than 'total'") } const prevCurrent = usePrevious(current) const dotFullWidth = size + margin * 2 const center = Math.round(_visible / 2) const centerIndex = Math.floor(_visible / 2) const [translate, setTranslate] = useState(0) const [visibleDots, setVisibleDots] = useState(range(0, _visible - 1)) const [mediumDots, setMediumDots] = useState( range(0, centerIndex + centerIndex - 1), ) const [bigDots, setBigDots] = useState(range(0, centerIndex)) const [isLooping, setIsLooping] = useState(false) useEffect(() => { if (current < centerIndex) { setTranslate(0) setVisibleDots(range(0, _visible - 1)) } else if (total - center < current) { setTranslate((total - _visible) * dotFullWidth) setVisibleDots(range(total - _visible, total - 1)) } else { setTranslate((current - centerIndex) * dotFullWidth) setVisibleDots(range(current - centerIndex, current + centerIndex)) } }, [current, center, centerIndex, dotFullWidth, total, _visible]) useEffect(() => { if (current < center) { if (_visible === 3) { setMediumDots(range(0, _visible)) } else { setMediumDots(range(0, centerIndex + centerIndex - 1)) } setBigDots(range(0, centerIndex)) } else if (total - center - 1 < current) { if (_visible === 3) { setMediumDots(range(total - _visible, total - 1)) } else { setMediumDots(range(total - centerIndex - centerIndex, total - 1)) } setBigDots(range(total - center, total - 1)) } else { if (_visible === 3) { setMediumDots(range(current - 1, current + 1)) setBigDots([current]) } else { setMediumDots(range(current - 2, current + 2)) setBigDots(range(current - 1, current + 1)) } } }, [current, center, centerIndex, total, _visible]) useEffect(() => { if ( (prevCurrent === 0 && current === total - 1) || (prevCurrent === total - 1 && current === 0) ) { setIsLooping(true) setTimeout(() => setIsLooping(false), 300) } }, [current, prevCurrent, total]) const getHolderStyle = { height: size, paddingLeft: padding, paddingRight: padding, width: _visible * dotFullWidth + padding * 2, } const getDotsStyle = { height: size, width: size, marginRight: margin, marginLeft: margin, transform: `translateX(-${translate}px)`, } const getDotStyle = { opacity: 0, ...(isLooping && { opacity: 1, transform: "scale(1)" }), } if (total === visible || total <= 3) { return { defaultHolderStyle, getHolderStyle, dots: range(0, total - 1), defaultDotsStyle, getDotsStyle, defaultDotStyle, getDotStyle, defaultVisibleDotStyle, defaultActiveDotStyle, defaultSmallDotStyle, defaultMediumDotStyle, defaultBigDotStyle, isSmall: () => false, isMedium: () => false, isBig: () => true, isActive: dot => current === dot, isVisible: dot => visibleDots.includes(dot), } } return { defaultHolderStyle, getHolderStyle, dots: range(0, total - 1), defaultDotsStyle, getDotsStyle, defaultDotStyle, getDotStyle, defaultVisibleDotStyle, defaultActiveDotStyle, defaultSmallDotStyle, defaultMediumDotStyle, defaultBigDotStyle, isSmall: dot => !mediumDots.includes(dot), isMedium: dot => mediumDots.includes(dot), isBig: dot => bigDots.includes(dot), isActive: dot => current === dot, isVisible: dot => visibleDots.includes(dot), } } export default useCarouselDots