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
JavaScript
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;