UNPKG

@uiw/react-native

Version:
172 lines 4.41 kB
import React, { useState, useMemo, useRef, useEffect, useReducer } from 'react'; import { Animated, StyleSheet, Dimensions } from 'react-native'; import MaskLayer from '../MaskLayer'; import { useTheme } from '@shopify/restyle'; const MainWidth = Dimensions.get('window').width; const MainHeight = Dimensions.get('window').height; export const reducer = (state, action) => { return { ...state, ...action }; }; const Modal = (props = {}) => { const { onClosed, visible, children, placement = 'bottom', containerStyle, ...otherProps } = props; const theme = useTheme(); const AnimatedOpacity = useRef(new Animated.Value(0)).current; const [{ layoutHeight, layoutWidth }, dispatch] = useReducer(reducer, { layoutHeight: 0, layoutWidth: 0 }); const [translateValue] = useState(new Animated.Value(0)); const { isVertical, isHorizontal } = useMemo(() => { const isVertical = /^(top|bottom)$/.test(placement); const isHorizontal = /^(left|right)$/.test(placement); return { isVertical, isHorizontal }; }, [placement]); useEffect(() => { function getTransformSize() { if (placement === 'top') { return -layoutHeight; } if (placement === 'bottom') { return layoutHeight; } if (placement === 'left') { return -layoutWidth; } if (placement === 'right') { return layoutWidth; } if (placement === 'middle') { return layoutWidth; } return 0; } const result = getTransformSize(); if (!result) return; if (visible) { translateValue.setValue(result); Animated.parallel([Animated.timing(AnimatedOpacity, { toValue: 1, duration: 0, useNativeDriver: false }), Animated.spring(translateValue, { toValue: 0, overshootClamping: true, useNativeDriver: true })]).start(); } if (!visible) { Animated.parallel([Animated.spring(translateValue, { toValue: result, overshootClamping: true, useNativeDriver: true }), Animated.timing(AnimatedOpacity, { toValue: 0, duration: 0, useNativeDriver: false })]).start(); } }, [visible, layoutHeight, layoutWidth, placement, translateValue, AnimatedOpacity]); const translateStyle = {}; if (isVertical) { translateStyle.translateY = translateValue; } if (isHorizontal) { translateStyle.translateX = translateValue; } if (placement === 'middle') { translateStyle.translateY = translateValue; } const child = useMemo(() => <Animated.View style={[styles.content, placement && styles[placement], placement === 'middle' && styles.middle_warp, { opacity: AnimatedOpacity }, containerStyle]}> <Animated.View onLayout={event => { const { height, width } = event.nativeEvent.layout; if (placement === 'middle') { dispatch({ layoutHeight: height, layoutWidth: width }); } else if (!layoutHeight && isVertical) { dispatch({ layoutHeight: height }); } else if (!layoutWidth && isHorizontal) { dispatch({ layoutWidth: width }); } }} style={[styles.content, placement && styles[placement], { transform: [translateStyle], backgroundColor: theme.colors.mask || '#fff', position: 'relative', zIndex: 10000 }]}> {children} </Animated.View> </Animated.View>, [children, AnimatedOpacity, containerStyle, isHorizontal, isVertical, layoutHeight, layoutWidth, placement, theme.colors.mask, translateStyle]); return <MaskLayer {...otherProps} visible={visible} onDismiss={onClosed}> {child} </MaskLayer>; }; export default Modal; const styles = StyleSheet.create({ content: { position: 'absolute', zIndex: 9999 }, top: { top: 0, width: MainWidth, left: 0, right: 0 }, bottom: { bottom: 0, left: 0, width: MainWidth, right: 0 }, left: { bottom: 0, top: 0, height: MainHeight, left: 0 }, right: { bottom: 0, top: 0, height: MainHeight, right: 0 }, middle: {}, middle_warp: { bottom: 0, top: 0, right: 0, left: 0, justifyContent: 'center', alignItems: 'center' } });