import React, { forwardRef, useMemo, useState } from 'react'; import { Animated, Modal as RNModal, RefreshControl, StatusBar, StyleSheet, TouchableOpacity, } from 'react-native'; import useTheme from '../../context/theme/useTheme'; import { useStatusBarHeight } from '../../hooks/useHeaderHeight'; import useModal, { UseModalType } from '../../hooks/useModal'; import type { ModalProps, PositionType } from './types'; import { Box } from '../Box'; import createSxStyle from '../../lib/sx'; import { SxProps } from '../../lib/styleDictionary'; import { Header } from './Header'; import { Content } from './Content'; import { Footer } from './Footer'; import { Button } from '../Button'; import { CloseIcon } from '../../icons'; import { ScrollView } from '../ScrollView'; type ExportComponent = { useModal: (initial?: boolean) => UseModalType; Header: typeof Header; Content: typeof Content; Footer: typeof Footer; }; const DEFAULT_ANIMATIONS = { fade: 0, fadeMask: 0, translate: 250, }; const RenderModal = forwardRef( ( { children, hiddenBar, visible: isOpen, onClose, fullScreen, styles, sx, extra, closeIcon, header, scrollProps, width, removePaddingRoot, closable = true, scrollable = false, maskClosable = true, statusHeight: _statusHeight, maskComponent: MaskComponent = TouchableOpacity, position = 'center', animationType = position !== 'bottom' ? 'fade' : 'slide', ...rest }, ref ) => { const [visible, setVisible] = useState(false); const translate = React.useRef( new Animated.Value(DEFAULT_ANIMATIONS.translate) ).current; const fadeMask = React.useRef( new Animated.Value(DEFAULT_ANIMATIONS.fadeMask) ).current; const fade = React.useRef( new Animated.Value(DEFAULT_ANIMATIONS.fade) ).current; const theme = useTheme(); const statusBarHeightCalc = useStatusBarHeight(); const statusBarHeight = _statusHeight ?? statusBarHeightCalc; const isTop = useMemo(() => position === 'top', [position]); const isBottom = useMemo(() => position === 'bottom', [position]); const isEnabledAnimation = useMemo( () => animationType !== 'none', [animationType] ); React.useEffect(() => { if (isOpen) { setVisible(true); if (isEnabledAnimation) { if (isBottom) { Animated.parallel([ Animated.timing(fadeMask, { toValue: 1, duration: 200, useNativeDriver: true, }), Animated.timing(translate, { toValue: 0, duration: 200, useNativeDriver: false, }), Animated.timing(fade, { toValue: 1, duration: 100, delay: 150, useNativeDriver: true, }), ]).start(); } else { Animated.parallel([ Animated.timing(fadeMask, { toValue: 1, duration: 250, useNativeDriver: true, }), Animated.timing(fade, { toValue: 1, duration: 100, // delay: 200, useNativeDriver: true, }), ]).start(); } } } else if (!isOpen) { if (isEnabledAnimation) { if (isBottom) { Animated.parallel([ Animated.timing(fadeMask, { toValue: DEFAULT_ANIMATIONS.fadeMask, duration: 150, useNativeDriver: true, }), Animated.timing(translate, { toValue: DEFAULT_ANIMATIONS.translate, duration: 200, useNativeDriver: false, }), Animated.timing(fade, { toValue: DEFAULT_ANIMATIONS.fade, duration: 100, // delay: 200, useNativeDriver: true, }), ]).start(({ finished }) => { if (finished) { setVisible(false); } }); } else { Animated.parallel([ Animated.timing(fadeMask, { toValue: DEFAULT_ANIMATIONS.fadeMask, isInteraction: false, duration: 250, useNativeDriver: true, }), Animated.timing(fade, { toValue: DEFAULT_ANIMATIONS.fade, isInteraction: false, duration: 100, // delay: 200, useNativeDriver: true, }), ]).start(({ finished }) => { if (finished) { setVisible(false); } }); } } else { setVisible(false); } } }, [fade, fadeMask, isBottom, isEnabledAnimation, isOpen, translate]); const icon = closeIcon || ( ); const rounded = fullScreen ? 0 : ('modal' as const); const paddingTopWithStatusBar = useMemo(() => { return Number( ((statusBarHeight || theme.spacing) / theme.spacing).toFixed(2) ); }, [statusBarHeight, theme.spacing]); return ( {hiddenBar && ); } ); const positionSx: Record = { top: { justifyContent: 'flex-start', alignItems: 'center', }, center: { justifyContent: 'center', alignItems: 'center', }, bottom: { justifyContent: 'flex-end', alignItems: 'center', }, }; export const Modal = RenderModal as unknown as React.ForwardRefExoticComponent< ModalProps & React.RefAttributes > & ExportComponent; Modal.useModal = useModal; Modal.Header = Header; Modal.Content = Content; Modal.Footer = Footer;