UNPKG

@seirei/carousel

Version:

A reusable, auto-scrolling carousel component for React Native built using **React Native Reanimated v2**, offering customizable pagination indicators, smooth animations, and infinite looping.

158 lines (157 loc) 4.84 kB
"use strict"; import React, { useEffect, useRef, useState } from "react"; import { View, useWindowDimensions } from "react-native"; import Animated, { scrollTo, useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue } from "react-native-reanimated"; /** * InfiniteCarouselWrapper is a horizontally scrollable, auto-playing carousel component * built with Animated.FlatList. It infinitely appends data to create an infinite effect * and supports both manual and automatic scrolling. * * @param {InfiniteCarouselWrapperProps} props - The props for the carousel. * @returns {JSX.Element} The rendered carousel component. */ export const InfiniteCarouselWrapper = ({ children, wrapperStyle, autoSlide = false, snapDuration = 2000, contentPaddingHorizaintal = 0, activeSlideAccentColor = "#00000070", inactiveSlideAccentColor = "#D3D3D350", dotSize = 10, onSlideChange, paginationComponent }) => { const x = useSharedValue(0); const offset = useSharedValue(0); const ref = useAnimatedRef(); const { width } = useWindowDimensions(); const [carouselData, setCarouselData] = useState(children); const [currentIndex, setCurrentIndex] = useState(0); const interval = useRef(null); const onScroll = useAnimatedScrollHandler({ onScroll: e => { x.value = e.contentOffset.x; }, onMomentumEnd: e => { offset.value = e.contentOffset.x; } }); useDerivedValue(() => { scrollTo(ref, offset.value, 0, true); }); const viewabilityConfig = { itemVisiblePercentThreshold: 50 }; const onViewableItemsChanged = ({ viewableItems }) => { if (viewableItems[0]?.index != null) { const newIndex = viewableItems[0].index % children.length; setCurrentIndex(newIndex); onSlideChange?.(newIndex); } }; const viewabilityConfigCallbackPairs = useRef([{ viewabilityConfig, onViewableItemsChanged }]); useEffect(() => { if (autoSlide) { interval.current = setInterval(() => { // Animate the scroll instead of directly updating offset scrollTo(ref, offset.value + width, 0, true); offset.value += width; }, snapDuration); } else { if (interval.current) { clearInterval(interval.current); } } return () => { if (interval.current) { clearInterval(interval.current); } }; }, [autoSlide, snapDuration, width]); const effectiveWidth = width - contentPaddingHorizaintal * 2; return /*#__PURE__*/React.createElement(View, { style: [{ overflow: "hidden" }, wrapperStyle] }, /*#__PURE__*/React.createElement(Animated.FlatList, { ref: ref, horizontal: true, pagingEnabled: true, bounces: false, showsHorizontalScrollIndicator: false, scrollEventThrottle: 16, onScroll: onScroll, onScrollBeginDrag: () => { if (interval.current) { clearInterval(interval.current); } }, onScrollEndDrag: () => { if (autoSlide) { interval.current = setInterval(() => { offset.value += width; }, snapDuration); } }, viewabilityConfigCallbackPairs: viewabilityConfigCallbackPairs.current, onEndReached: () => setCarouselData(prev => [...prev, ...children]), onEndReachedThreshold: 0.5, data: carouselData, keyExtractor: (_, index) => `carousel_item_${index}`, renderItem: ({ item }) => /*#__PURE__*/React.createElement(View, { style: { width: effectiveWidth } }, item) }), paginationComponent ? paginationComponent(currentIndex) : /*#__PURE__*/React.createElement(View, { style: { position: "absolute", bottom: 10, left: 0, right: 0, alignItems: "center", justifyContent: "center", flexDirection: "row" } }, children.length > 1 && /*#__PURE__*/React.createElement(View, { style: { position: "absolute", bottom: 10, left: 0, right: 0, alignItems: "center", justifyContent: "center", flexDirection: "row" } }, [0, 1, 2].map((_, i) => { let isActive = false; if (currentIndex === 0 && i === 0) { isActive = true; // first slide → left dot } else if (currentIndex === children.length - 1 && i === 2) { isActive = true; // last slide → right dot } else if (currentIndex > 0 && currentIndex < children.length - 1 && i === 1) { isActive = true; // middle slides → middle dot } return /*#__PURE__*/React.createElement(View, { key: i, style: { width: dotSize, height: dotSize, borderRadius: dotSize / 2, backgroundColor: isActive ? activeSlideAccentColor : inactiveSlideAccentColor, margin: 4 } }); })))); }; //# sourceMappingURL=InfiniteCarouselWrapper.js.map