-
Notifications
You must be signed in to change notification settings - Fork 34
/
carousel-list.tsx
102 lines (90 loc) · 2.98 KB
/
carousel-list.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import React, {
FC, ReactNode,
Children, useEffect,
useRef, RefObject, useCallback, useState
} from 'react'
import classNames from 'classnames'
import { isNativeScrolling, alterChildren, getClosestIndex } from './helpers'
import { scrollTransition } from './scroll-to-transition'
import { ListItemRef } from './types'
type CarouselListProps = {
gap?: number;
itemsPerSlide: number;
slideWidth: number;
offset: number;
activeIndex: number;
nextControlDisabled?: boolean;
className?: string;
itemsRef?: RefObject<Array<ListItemRef | null>>;
children: ReactNode;
onSetActiveIndex: (index: number) => void
onScroll?: ({ index }) => void;
};
const CarouselList: FC<CarouselListProps> = ({
gap = 16,
itemsPerSlide,
slideWidth,
offset,
activeIndex,
nextControlDisabled,
className,
itemsRef,
children,
onSetActiveIndex,
onScroll
}) => {
const [translateLeft, setTranslateLeft] = useState(0)
const [skipScrolling, setSkipScrolling] = useState(false)
const [scrollTransitioning, setScrollTransitioning] = useState(false)
const listRef = useRef<HTMLUListElement | null>(null)
const scrollTimeout = useRef(null)
useEffect(() => {
const size = Children.count(children)
if (!size || !listRef.current) return
if (skipScrolling) {
setSkipScrolling(false)
return
}
const list = listRef.current
const isNativeScroll = isNativeScrolling(list)
if (isNativeScroll) {
setScrollTransitioning(true)
scrollTransition(list, offset, () => setScrollTransitioning(false))
} else {
setTranslateLeft(offset)
}
}, [offset])
const handleFinishScrolling = useCallback(() => {
const scrollLeft = listRef.current.scrollLeft
const closest = getClosestIndex(scrollLeft, itemsRef.current, slideWidth, itemsPerSlide, gap)
if (closest !== activeIndex) {
setSkipScrolling(true)
onSetActiveIndex(closest)
onScroll({ index: closest })
}
}, [listRef.current, itemsRef.current, slideWidth, activeIndex, onSetActiveIndex])
const handleScroll = () => {
if (scrollTimeout.current) {
clearTimeout(scrollTimeout.current)
}
scrollTimeout.current = setTimeout(() => {
if (!scrollTransitioning) {
handleFinishScrolling()
}
}, 640)
}
return (
<div className={classNames('carousel__viewport', {
'carousel__viewport--mask': !nextControlDisabled
}, className)}>
<ul
className="carousel__list"
ref={listRef}
onScroll={handleScroll}
style={{ transform: `translate3d(${-translateLeft}px, 0, 0)` }}>
{alterChildren(children, itemsRef, itemsPerSlide, slideWidth, offset, gap)}
</ul>
</div>
)
}
export default CarouselList