react-native-toast-message
Version:
Toast message component for React Native
96 lines (95 loc) • 3.69 kB
JavaScript
import React from 'react';
import { Animated, Dimensions } from 'react-native';
import { useLogger, useGesture } from '../contexts';
import { usePanResponder, useSlideAnimation, useViewDimensions } from '../hooks';
import { noop } from '../utils/func';
import { bound } from '../utils/number';
import { getTestId } from '../utils/test-id';
import { styles } from './AnimatedContainer.styles';
/**
* Produces a positive damping value.
*
* To note: `moveY` becomes negative when going off-screen. By making sure the value
* produced is always positive, we avoid issues like: https://github.com/calintamas/react-native-toast-message/issues/280
*/
export function dampingFor(gesture, position) {
const { moveY } = gesture;
switch (position) {
case 'bottom': {
const { height: screenHeight } = Dimensions.get('screen');
return Math.abs(screenHeight - moveY);
}
case 'top':
return Math.abs(moveY);
default:
throw new Error(`Toast position: ${position} not implemented`);
}
}
export function animatedValueFor(gesture, position, damping) {
const boundValue = (val) => bound(val, 0, 2);
const { dy } = gesture;
switch (position) {
case 'bottom':
return boundValue(1 - dy / damping);
case 'top':
return boundValue(1 + dy / damping);
default:
throw new Error(`Toast position: ${position} not implemented`);
}
}
export function AnimatedContainer({ children, isVisible, position, topOffset, bottomOffset, keyboardOffset, avoidKeyboard, onHide, onRestorePosition = noop, swipeable }) {
const { log } = useLogger();
const { panning } = useGesture();
const { computeViewDimensions, height } = useViewDimensions();
const { animatedValue, animate, animationStyles } = useSlideAnimation({
position,
height,
topOffset,
bottomOffset,
keyboardOffset,
avoidKeyboard
});
const disable = !swipeable || !isVisible;
const onStart = React.useCallback(() => {
log('Swipe, pan start');
panning.current = true;
}, [log, panning]);
const onEnd = React.useCallback(() => {
log('Swipe, pan end');
panning.current = false;
}, [log, panning]);
const onDismiss = React.useCallback(() => {
log('Swipe, dismissing');
animate(0);
onHide();
}, [animate, log, onHide]);
const onRestore = React.useCallback(() => {
log('Swipe, restoring to original position');
animate(1);
onRestorePosition();
}, [animate, log, onRestorePosition]);
const computeNewAnimatedValueForGesture = React.useCallback((gesture) => {
const damping = dampingFor(gesture, position);
const newAnimatedValue = animatedValueFor(gesture, position, damping);
return newAnimatedValue;
}, [position]);
const { panResponder } = usePanResponder({
animatedValue,
computeNewAnimatedValueForGesture,
onDismiss,
onRestore,
onStart,
onEnd,
disable,
});
React.useLayoutEffect(() => {
const newAnimationValue = isVisible ? 1 : 0;
animate(newAnimationValue);
}, [animate, isVisible]);
return (<Animated.View testID={getTestId('AnimatedContainer')} onLayout={computeViewDimensions} style={[styles.base, styles[position], animationStyles]}
// This container View is never the target of touch events but its subviews can be.
// By doing this, tapping buttons behind the Toast is allowed
pointerEvents='box-none' {...panResponder.panHandlers}>
{children}
</Animated.View>);
}