UNPKG

@ahsankk/react-native-animated-components

Version:

A set of reusable animated components for React Native using Reanimated

124 lines (122 loc) 4.66 kB
import React, { useState, useEffect } from 'react'; import { View, StyleSheet, Dimensions, UIManager, LayoutAnimation, Platform, } from 'react-native'; import Animated, { useSharedValue, useAnimatedStyle, useAnimatedGestureHandler, withTiming, runOnJS, } from 'react-native-reanimated'; import { PanGestureHandler, } from 'react-native-gesture-handler'; const { width: SCREEN_WIDTH } = Dimensions.get('window'); function Carousel({ data, CARD_WIDTH, CARD_HEIGHT, SWIPE_THRESHOLD, renderCard, onIndexChange }) { const [cards, setCards] = useState(data); const translateX = useSharedValue(0); const [currentIndex, setCurrentIndex] = useState(0); useEffect(() => { if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) { UIManager.setLayoutAnimationEnabledExperimental(true); } }, []); const reorderNext = () => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); setCards(prev => { const updated = [...prev]; const first = updated.shift(); if (first) updated.push(first); return updated; }); setCurrentIndex(prev => { const newIndex = (prev + 1) % data.length; onIndexChange === null || onIndexChange === void 0 ? void 0 : onIndexChange(newIndex); // ✅ Notify parent return newIndex; }); translateX.value = 0; }; const reorderPrevious = () => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); setCards(prev => { const updated = [...prev]; const last = updated.pop(); if (last) updated.unshift(last); return updated; }); setCurrentIndex(prev => { const newIndex = (prev - 1 + data.length) % data.length; onIndexChange === null || onIndexChange === void 0 ? void 0 : onIndexChange(newIndex); // ✅ Notify parent return newIndex; }); translateX.value = 0; }; const gestureHandler = useAnimatedGestureHandler({ onStart: (_, ctx) => { ctx.startX = translateX.value; }, onActive: (event, ctx) => { translateX.value = ctx.startX + event.translationX; }, onEnd: (event) => { if (event.translationX < -SWIPE_THRESHOLD) { translateX.value = withTiming(-SCREEN_WIDTH, {}, (finished) => { if (finished) runOnJS(reorderNext)(); }); } else if (event.translationX > SWIPE_THRESHOLD) { translateX.value = withTiming(SCREEN_WIDTH, {}, (finished) => { if (finished) runOnJS(reorderPrevious)(); }); } else { translateX.value = withTiming(0); } }, }); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ translateX: translateX.value }], })); return (<View style={[styles.container, { height: CARD_HEIGHT + 60 }]}> {/* Background Cards */} {cards.slice(1, 3).reverse().map((item, index) => { const scale = 1 - (index + 1) * 0.05; const translateY = (index + 1) * 18; const zIndex = index; return (<Animated.View key={index} style={[ styles.card, { width: CARD_WIDTH * scale, height: CARD_HEIGHT * scale, transform: [{ translateY }], zIndex, opacity: (100 - (index + 50)) / 200, }, ]}> {renderCard(item, index + 1)} </Animated.View>); })} {/* Top card with gesture */} <PanGestureHandler onGestureEvent={gestureHandler}> <Animated.View style={[ styles.card, { width: CARD_WIDTH, height: CARD_HEIGHT, zIndex: 99, }, animatedStyle, ]}> {renderCard(cards[0], 0)} </Animated.View> </PanGestureHandler> </View>); } const styles = StyleSheet.create({ container: { alignItems: 'center', justifyContent: 'center', }, card: { position: 'absolute', borderRadius: 16, overflow: 'hidden', }, }); export default Carousel;