import React, { ReactNode, useCallback, useEffect, useRef, useState, } from "react"; import { Keyboard, LayoutChangeEvent, StyleSheet, TouchableOpacity, View, } from "react-native"; import { PanGestureHandler } from "react-native-gesture-handler"; import Animated, { runOnJS, withTiming } from "react-native-reanimated"; import { Platform_iOS, useReanimatedValue } from "../../commons"; import { DEFAULT_ANIMATION_TYPE, DEFAULT_MODAL_TYPE, DEFAULT_OVERLAY_COLOR, DURATION, emptyFunc, } from "./configs"; import { useBack, useGesture, useScroll, useStyle, useTimeout } from "./hooks"; import { LayoutType, ModalProps } from "./types"; const styles = StyleSheet.create({ container: { flex: 1, }, fullWidth: { width: "100%", }, fullHeight: { height: "100%", }, }); export default (props: ModalProps) => { const { offset = 0, visible = false, fullsize = false, disableGesture = false, disableSwipeDismiss = false, disableBackdropDismiss = false, children = undefined, childrenScroll = false, overlayColor = DEFAULT_OVERLAY_COLOR, modalType = DEFAULT_MODAL_TYPE, animationType = DEFAULT_ANIMATION_TYPE, onBack = undefined, onOpen = emptyFunc, onOpened = emptyFunc, onClose = emptyFunc, onClosed = emptyFunc, renderOverlay, } = props; const enableFullGesture = Platform_iOS || !childrenScroll; const center = modalType === "center"; const horizontal = modalType === "left" || modalType === "right"; const visibleRef = useRef(false); const [layout, setLayout] = useState({ x: 0, y: 0, width: 0, height: 0, }); const gestureX = useReanimatedValue(0); const gestureY = useReanimatedValue(0); const visibleValue = useReanimatedValue(0); const { overlayStyle, containerStyle, contentStyle } = useStyle( layout, modalType, animationType, visibleValue, gestureX, gestureY ); const [startHandle, stopHandle] = useTimeout(DURATION * 2); const onLayout = useCallback((ev: LayoutChangeEvent) => { setLayout(ev.nativeEvent.layout); }, []); const onHandle = useCallback(() => { stopHandle(); (visibleRef.current ? onOpened : onClosed)(); }, [onOpened, onClosed]); const visibleHandler = useCallback( (val: boolean) => { Keyboard.dismiss(); visibleRef.current = val; val ? onOpen() : onClose(); startHandle(() => onHandle()); visibleValue.value = withTiming( val ? 1 : 0, { duration: DURATION }, (finished) => { finished && runOnJS(onHandle)(); } ); }, [onOpen, onClose] ); const gestureHandler = useGesture( layout, modalType, gestureX, gestureY, disableSwipeDismiss ? undefined : visibleHandler ); const scrollHandler = useScroll( layout, modalType, gestureX, gestureY, disableSwipeDismiss ? undefined : visibleHandler ); const renderGesture = useCallback( (content: ReactNode) => { return ( {content} ); }, [disableGesture] ); const renderDismiss = useCallback(() => { return ( visibleHandler(false)} /> ); }, [center, offset, fullsize, horizontal, disableBackdropDismiss]); const renderHandle = useCallback( (content: ReactNode) => { return enableFullGesture ? content : renderGesture({content}); }, [enableFullGesture] ); const renderContent = () => { return ( {/* backdrop */} {renderOverlay?.()} {/* container */} {/* dismiss */} {(modalType === "bottom" || modalType === "right") && (enableFullGesture ? renderDismiss() : renderGesture(renderDismiss()))} {center && ( {renderDismiss()} )} {/* content */} {typeof children === "function" ? children(scrollHandler, renderHandle) : children} {/* dismiss */} {(modalType === "top" || modalType === "left") && (enableFullGesture ? renderDismiss() : renderGesture(renderDismiss()))} ); }; useBack(() => { onBack ? onBack() : visibleHandler(false); }); useEffect(() => { visibleHandler(visible); }, [visible]); return enableFullGesture ? renderGesture(renderContent()) : renderContent(); };