UNPKG

react-native-expo-bottomsheet

Version:

A lightWeight and customizeble bottom sheet components for React Native Expo support. Includes Smooth animation

135 lines (134 loc) 5.97 kB
import React, { useState, useRef, forwardRef, useImperativeHandle, useEffect, } from "react"; import { Animated as RNAnimated, PanResponder, TouchableOpacity, Modal, KeyboardAvoidingView, Platform, View, StyleSheet, BackHandler, } from "react-native"; import Animated from "react-native-reanimated"; const AnimatedKeyboardAvoidingView = Animated.createAnimatedComponent(KeyboardAvoidingView); export const BottomSheet = forwardRef((props, ref) => { const { height = 260, openDuration = 350, closeDuration = 300, closeOnPressMask = true, closeOnPressBack = false, draggable = false, dragOnContent = false, useNativeDriver = false, customStyles = {}, customModalProps = {}, customAvoidingViewProps = {}, onOpen = null, onClose = null, children = React.createElement(View, null), mainContainer = {}, wrapperColors = {}, } = props; const [modalVisible, setModalVisible] = useState(false); const animatedHeight = useRef(new RNAnimated.Value(0)).current; const pan = useRef(new RNAnimated.ValueXY()).current; useEffect(() => { const handleBackPress = () => { if (modalVisible) { handleSetVisible(false); return true; // Prevent default back action } return false; }; const backHandler = BackHandler.addEventListener("hardwareBackPress", handleBackPress); return () => backHandler.remove(); // Ensure removal on unmount }, [modalVisible]); useImperativeHandle(ref, () => ({ open: () => handleSetVisible(true), close: () => handleSetVisible(false), })); const createPanResponder = () => { return PanResponder.create({ onStartShouldSetPanResponder: () => draggable, onMoveShouldSetPanResponder: (e, gestureState) => draggable && dragOnContent && gestureState.dy > 0, onPanResponderMove: (e, gestureState) => { gestureState.dy > 0 && RNAnimated.event([null, { dy: pan.y }], { useNativeDriver })(e, gestureState); }, onPanResponderRelease: (e, gestureState) => { if (gestureState.dy > 150) { handleSetVisible(false); } else { RNAnimated.spring(pan, { toValue: { x: 0, y: 0 }, useNativeDriver, // damping: 0.7, }).start(); } }, }); }; const panResponder = useRef(createPanResponder()).current; const handleSetVisible = (visible) => { if (visible) { setModalVisible(visible); if (typeof onOpen === "function") onOpen(); RNAnimated.timing(animatedHeight, { useNativeDriver, toValue: height, duration: openDuration, // easing: Easing.bounce, }).start(); } else { RNAnimated.timing(animatedHeight, { useNativeDriver, toValue: 0, duration: closeDuration, }).start(() => { setModalVisible(visible); pan.setValue({ x: 0, y: 0 }); if (typeof onClose === "function") onClose(); }); } }; useEffect(() => { if (modalVisible) { RNAnimated.timing(animatedHeight, { useNativeDriver, toValue: height, duration: openDuration, }).start(); } }, [height, modalVisible]); return (React.createElement(Modal, Object.assign({ animationType: "fade", testID: "Modal", transparent: true, visible: modalVisible, // onRequestClose={ // closeOnPressBack ? () => handleSetVisible(false) : undefined // } onRequestClose: () => handleSetVisible(false) }, customModalProps), React.createElement(AnimatedKeyboardAvoidingView // entering={FadeInDown.springify().damping(8)} , Object.assign({ // entering={FadeInDown.springify().damping(8)} testID: "KeyboardAvoidingView", behavior: Platform.OS === "ios" ? "padding" : "height", style: [styles.wrapper, customStyles.wrapper, wrapperColors] }, customAvoidingViewProps), React.createElement(TouchableOpacity, { testID: "TouchableOpacity", style: styles.mask, activeOpacity: 1, onPress: closeOnPressMask ? () => handleSetVisible(false) : undefined }), React.createElement(RNAnimated.View, Object.assign({ testID: "AnimatedView" }, (dragOnContent && panResponder.panHandlers), { style: [ styles.container, { transform: pan.getTranslateTransform() }, { height: animatedHeight }, customStyles, mainContainer, ] }), draggable && (React.createElement(View, Object.assign({ testID: "DraggableView" }, (!dragOnContent && panResponder.panHandlers), { style: styles.draggableContainer }), React.createElement(View, { testID: "DraggableIcon", style: [styles.draggableIcon, customStyles.draggableIcon] }))), children)))); }); const styles = StyleSheet.create({ wrapper: { flex: 1, backgroundColor: "#00000077", }, mask: { flex: 1, backgroundColor: "transparent", }, container: { backgroundColor: "#fff", width: "100%", height: 0, overflow: "hidden", borderTopRightRadius: 30, borderTopLeftRadius: 30, }, draggableContainer: { width: "100%", alignItems: "center", backgroundColor: "transparent", }, draggableIcon: { width: 35, height: 5, borderRadius: 5, margin: 10, backgroundColor: "#ccc", }, }); export default styles;