Skip to content

Instantly share code, notes, and snippets.

@juanchoperezj
Created August 11, 2023 20:10
Show Gist options
  • Save juanchoperezj/f2b4e9c56178c4251697ed9398ad2077 to your computer and use it in GitHub Desktop.
Save juanchoperezj/f2b4e9c56178c4251697ed9398ad2077 to your computer and use it in GitHub Desktop.
Custom Toast implemented with Reanimated and Gesture Handler
import React, { forwardRef, useCallback, useImperativeHandle } from 'react';
import { PanGestureHandler } from 'react-native-gesture-handler';
import Animated, {
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withDelay,
withSequence,
withSpring,
withTiming,
} from 'react-native-reanimated';
import { styles } from './toast.styles';
import type { ToastProps, ToastRef, ToastShowConfig } from './toast.type';
const SPRING_CONFIG = {
damping: 100,
mass: 0.3,
stiffness: 120,
overshootClamping: true,
restSpeedThreshold: 0.3,
restDisplacementThreshold: 0.3,
};
const CLOSE_TOAST_VALUE = -100;
export const Toast = forwardRef<ToastRef, ToastProps>(({ onUndo: onPress, children }, ref) => {
const toastTopAnimation = useSharedValue(-100);
const show = useCallback(
({ duration, to }: ToastShowConfig) => {
toastTopAnimation.value = withSequence(
withTiming(to),
withDelay(duration, withSpring(CLOSE_TOAST_VALUE, SPRING_CONFIG))
);
},
[toastTopAnimation]
);
useImperativeHandle(
ref,
() => ({
show,
}),
[show]
);
const animatedTopStyles = useAnimatedStyle(() => {
return {
bottom: toastTopAnimation.value,
};
});
const gestureHandler = useAnimatedGestureHandler({
onStart: (_, ctx: { startY: number }) => {
ctx.startY = toastTopAnimation.value;
},
onActive: (event, ctx) => {
if (event.translationY > -20) {
toastTopAnimation.value = withSpring(ctx.startY - event.translationY, SPRING_CONFIG);
}
if (event.translationY > 1) {
toastTopAnimation.value = withSpring(CLOSE_TOAST_VALUE, SPRING_CONFIG);
}
},
onEnd: (event) => {
if (event.translationY < 0) {
toastTopAnimation.value = withSpring(CLOSE_TOAST_VALUE, SPRING_CONFIG);
}
},
});
const onUndo = useCallback(() => {
toastTopAnimation.value = withSpring(CLOSE_TOAST_VALUE, SPRING_CONFIG, (finish) => {
if (finish) {
runOnJS(onPress)();
}
});
}, [toastTopAnimation, onPress]);
const ChildrenWithProps = React.cloneElement(children as React.ReactElement, { onUndo });
return (
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View style={[styles.toastContainer, animatedTopStyles]}>
{ChildrenWithProps}
</Animated.View>
</PanGestureHandler>
);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment