import * as React from 'react'; import { Dimensions, type ViewStyle } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { Easing, LinearTransition, interpolate, runOnJS, useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated'; import { easeInOutCircFn } from './easings'; import { useToastContext } from './context'; const { width: WINDOW_WIDTH } = Dimensions.get('window'); type ToastSwipeHandlerProps = { onRemove: () => void; style?: ViewStyle | (ViewStyle | undefined)[]; className?: string; onBegin: () => void; onFinalize: () => void; enabled?: boolean; unstyled?: boolean; }; export const ToastSwipeHandler: React.FC< React.PropsWithChildren > = ({ children, onRemove, className, style, onBegin, onFinalize, enabled, unstyled, }) => { const translate = useSharedValue(0); const { swipToDismissDirection: direction } = useToastContext(); const pan = Gesture.Pan() .onBegin(() => { if (!enabled) { return; } runOnJS(onBegin)(); }) .onChange((event) => { if (!enabled) { return; } if (direction === 'left' && event.translationX < 0) { translate.value = event.translationX; } else if (direction === 'up') { translate.value = event.translationY; } }) .onFinalize(() => { if (!enabled) { return; } const threshold = direction === 'left' ? -WINDOW_WIDTH * 0.25 : -16; const shouldDismiss = translate.value < threshold; if (shouldDismiss) { translate.value = withTiming( -WINDOW_WIDTH, { easing: Easing.inOut(Easing.ease), }, (isDone) => { if (isDone) { runOnJS(onRemove)(); } } ); } else { translate.value = withTiming(0, { easing: Easing.elastic(0.8), }); } runOnJS(onFinalize)(); }); const animatedStyle = useAnimatedStyle(() => { return { transform: [ direction === 'left' ? { translateX: translate.value } : { translateY: translate.value }, ], opacity: interpolate( translate.value, [0, direction === 'left' ? -WINDOW_WIDTH : -60], [1, 0] ), }; }); return ( {children} ); };