rn-toastify
Version:
A customizable and performant toast notification library for React Native, featuring smooth animations, swipe gestures, and flexible styling options.
107 lines (94 loc) • 3.62 kB
JavaScript
import React, { useEffect, useMemo } from 'react';
import { PanResponder, StyleSheet } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
withSpring,
runOnJS,
Easing,
} from 'react-native-reanimated';
const Toast = ({ visible, duration, position, children, onHide, style }) => {
const opacity = useSharedValue(0);
const translateY = useSharedValue(position === 'top' ? -50 : 50);
const translateX = useSharedValue(0);
const scale = useSharedValue(0.9);
const positionStyle = useMemo(() => (position === 'top' ? styles.top : styles.bottom), [position]);
useEffect(() => {
if (visible) {
opacity.value = withTiming(1, { duration: 300 });
translateY.value = withSpring(0, { damping: 10, stiffness: 120 });
scale.value = withSpring(1, { damping: 10, stiffness: 120 });
if (duration !== Infinity) {
const hideTimeout = setTimeout(() => {
hideToast();
}, duration);
return () => clearTimeout(hideTimeout);
}
} else {
hideToast();
}
}, [visible, duration]);
const hideToast = () => {
opacity.value = withTiming(0, { duration: 300, easing: Easing.ease });
translateY.value = withTiming(position === 'top' ? -50 : 50, { duration: 300, easing: Easing.ease });
scale.value = withTiming(0.9, { duration: 300, easing: Easing.ease });
runOnJS(onHide)();
};
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
transform: [
{ translateY: translateY.value },
{ translateX: translateX.value },
{ scale: scale.value },
],
}));
const panResponder = useMemo(
() =>
PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => {
return Math.abs(gestureState.dx) > 20; // Swipe detection threshold
},
onPanResponderMove: (evt, gestureState) => {
translateX.value = gestureState.dx; // Update translateX as the user swipes
},
onPanResponderRelease: (evt, gestureState) => {
if (Math.abs(gestureState.dx) > 100) { // If the swipe distance is more than 100 pixels, remove the toast
opacity.value = withTiming(0, { duration: 200 });
translateY.value = withTiming(position === 'top' ? -50 : 50, { duration: 200 });
scale.value = withTiming(0.9, { duration: 200 });
runOnJS(onHide)();
} else {
// If the swipe is less than the threshold, reset the position
translateX.value = withSpring(0);
}
},
}),
[]
);
if (!visible) return null;
return (
<Animated.View
{...panResponder.panHandlers}
style={[styles.container, animatedStyle, positionStyle, style]}
>
{children}
</Animated.View>
);
};
const styles = StyleSheet.create({
container: {
position: 'absolute',
left: 0,
right: 0,
alignItems: 'center',
zIndex: 9999,
},
top: {
top: 0,
},
bottom: {
bottom: 20,
},
});
export default React.memo(Toast);