UNPKG

@sendbird/uikit-react-native-foundation

Version:

A foundational UI kit for building chat-enabled React Native apps.

179 lines (178 loc) 6.21 kB
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } import React, { useEffect, useRef, useState } from 'react'; import { Animated, Dimensions, KeyboardAvoidingView, PanResponder, Platform, Pressable, Modal as RNModal, StyleSheet, TouchableWithoutFeedback, View, useWindowDimensions } from 'react-native'; import createStyleSheet from '../../styles/createStyleSheet'; import useHeaderStyle from '../../styles/useHeaderStyle'; import useUIKitTheme from '../../theme/useUIKitTheme'; /** * Modal Open: Triggered by Modal.props.visible state changed to true * - visible true -> modalVisible true -> animation start * * Modal Close: Triggered by Modal.props.onClose() call * - Modal.props.onClose() -> visible false -> animation start -> modalVisible false * */ const Modal = ({ children, onClose, backgroundStyle, onDismiss, type = 'fade', visible = false, disableBackgroundClose = false, enableKeyboardAvoid = false, statusBarTranslucent, ...props }) => { const { palette } = useUIKitTheme(); const { content, backdrop, showTransition, hideTransition } = useModalAnimation(type); const panResponder = useModalPanResponder(type, content.translateY, showTransition, onClose); const { topInset } = useHeaderStyle(); const [modalVisible, setModalVisible] = useState(false); const showAction = () => setModalVisible(true); const hideAction = () => hideTransition(() => setModalVisible(false)); const { width, height } = useWindowDimensions(); useEffect(() => { if (visible) showAction();else hideAction(); }, [visible]); useOnDismiss(modalVisible, onDismiss); return /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(RNModal, _extends({ statusBarTranslucent: statusBarTranslucent, transparent: true, hardwareAccelerated: true, visible: modalVisible, onRequestClose: onClose, onShow: () => showTransition(), onDismiss: onDismiss, supportedOrientations: ['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right'], animationType: 'none' }, props), /*#__PURE__*/React.createElement(TouchableWithoutFeedback, { onPress: disableBackgroundClose ? undefined : onClose }, /*#__PURE__*/React.createElement(Animated.View, { style: [StyleSheet.absoluteFill, { opacity: backdrop.opacity, backgroundColor: palette.onBackgroundLight03 }] })), /*#__PURE__*/React.createElement(KeyboardAvoidingView // NOTE: This is trick for Android. // When orientation is changed on Android, the offset that to avoid soft-keyboard is not updated normally. , { key: Platform.OS === 'android' && enableKeyboardAvoid ? `${width}-${height}` : undefined, enabled: enableKeyboardAvoid, style: styles.background, behavior: Platform.select({ ios: 'padding', default: 'height' }), pointerEvents: 'box-none', keyboardVerticalOffset: enableKeyboardAvoid && statusBarTranslucent ? -topInset : 0 }, /*#__PURE__*/React.createElement(Animated.View, _extends({ style: [styles.background, backgroundStyle, { opacity: content.opacity, transform: [{ translateY: content.translateY }] }], pointerEvents: 'box-none' }, panResponder.panHandlers), /*#__PURE__*/React.createElement(Pressable // NOTE: https://github.com/facebook/react-native/issues/14295 // Due to 'Pressable', the width of the children must be explicitly specified as a number. , null, children))))); }; const isHideGesture = (distanceY, velocityY) => { return distanceY > 125 || distanceY > 0 && velocityY > 0.1; }; const useModalPanResponder = (type, translateY, show, hide) => { if (type === 'fade' || type === 'slide-no-gesture') return { panHandlers: {} }; return React.useRef(PanResponder.create({ onMoveShouldSetPanResponderCapture: (_, { dy }) => dy > 8, // @ts-ignore onPanResponderGrant: () => translateY.setOffset(translateY.__getValue()), onPanResponderMove: (_, { dy }) => dy >= 0 && translateY.setValue(dy), // Animated.event([null, { dy: translateY }], { useNativeDriver: false }), onPanResponderRelease: (_, { dy, vy }) => { if (isHideGesture(dy, vy)) hide();else show(); } })).current; }; const useModalAnimation = type => { const initialY = type === 'fade' ? 0 : Dimensions.get('window').height; const baseAnimBackground = useRef(new Animated.Value(0)).current; const baseAnimContent = useRef(new Animated.Value(initialY)).current; const content = { opacity: baseAnimBackground.interpolate({ inputRange: [0, 1], outputRange: [type === 'fade' ? 0 : 1, 1] }), translateY: baseAnimContent }; const backdrop = { opacity: baseAnimBackground.interpolate({ inputRange: [0, 1], outputRange: [0, 1] }) }; const createTransition = toValue => { const config = { duration: 250, useNativeDriver: false }; return Animated.parallel([Animated.timing(baseAnimBackground, { toValue, ...config }), Animated.timing(baseAnimContent, { toValue: toValue === 0 ? initialY : 0, ...config })]).start; }; return { content, backdrop, showTransition: createTransition(1), hideTransition: createTransition(0) }; }; // NOTE: onDismiss is supports iOS only const useOnDismiss = (visible, onDismiss) => { const prevVisible = usePrevProp(visible); useEffect(() => { if (Platform.OS === 'ios') return; if (prevVisible && !visible) onDismiss === null || onDismiss === void 0 || onDismiss(); }, [prevVisible, visible]); }; const usePrevProp = prop => { const prev = useRef(prop); const curr = useRef(prop); useEffect(() => { prev.current = curr.current; curr.current = prop; }); return prev.current; }; const styles = createStyleSheet({ background: { flex: 1 } }); export default Modal; //# sourceMappingURL=index.js.map