import React, { useCallback, useEffect, useRef } from "react"; import { Animated, Easing, Modal as ReactNativeModal, TouchableWithoutFeedback, useWindowDimensions, } from "react-native"; import { styles } from "./styles"; import type { ModalProps } from "./types"; export const Modal = (props: ModalProps) => { const { animationDuration = 300, children, contentStyle, isVisible = false, modalProps, onHide, onOverlayPress, overlayOpacity = 0.4, overlayStyle, testID = "modal", } = props; const { height: screenHeight } = useWindowDimensions(); const isMounted = useRef(false); const animatedOpacity = useRef(new Animated.Value(0)); useEffect(() => { isMounted.current = true; if (isVisible) { show(); } return () => { isMounted.current = false; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const backdropAnimatedStyle = { opacity: animatedOpacity.current.interpolate({ inputRange: [0, 1], outputRange: [0, overlayOpacity], }), }; const contentAnimatedStyle = { transform: [ { translateY: animatedOpacity.current.interpolate({ extrapolate: "clamp", inputRange: [0, 1], outputRange: [screenHeight, 0], }), }, ], }; const show = useCallback(() => { Animated.timing(animatedOpacity.current, { duration: animationDuration, easing: Easing.inOut(Easing.quad), toValue: 1, useNativeDriver: true, }).start(); }, [animationDuration]); const hide = useCallback(() => { Animated.timing(animatedOpacity.current, { duration: animationDuration, easing: Easing.inOut(Easing.quad), toValue: 0, useNativeDriver: true, }).start(() => { if (isMounted.current) { onHide?.(); } }); }, [animationDuration, onHide]); useEffect(() => { if (isVisible) { show(); } else { hide(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isVisible]); return ( {children} ); }; export default React.memo(Modal);