Skip to content

Instantly share code, notes, and snippets.

@akimabs
Created August 10, 2021 04:14
Show Gist options
  • Save akimabs/150fce1df6636344aaa8cd9b82a12f94 to your computer and use it in GitHub Desktop.
Save akimabs/150fce1df6636344aaa8cd9b82a12f94 to your computer and use it in GitHub Desktop.
Modal PanResponder
import React, { FC, useCallback, useEffect, useRef } from 'react';
import { PanResponder, Animated, View, StyleSheet, TouchableOpacity } from 'react-native';
import { heightPercentageToDP, widthPercentageToDP } from 'react-native-responsive-screen';
type ModalProps = {
isVisible: boolean;
height: number;
onClose: () => void;
children: any;
};
const ModalSheet: FC<ModalProps> = ({ isVisible, height, onClose, children }) => {
height = heightPercentageToDP(height);
const cardStyle = useRef(new Animated.Value(0)).current;
const mainPosition: any = useRef(new Animated.ValueXY()).current;
const mainPanResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (e, gestureState: any) => {
animate(height + -mainPosition.y._value, 0);
Animated.event([null, { dy: mainPosition.y }], { useNativeDriver: false })(e, gestureState);
},
onPanResponderRelease: (e, gesture) => {
mainPosition.flattenOffset();
if (gesture.dy > 100) {
animate(0, 250);
setTimeout(() => {
onClose();
}, 250);
} else {
animate(height, 250);
}
mainPosition.setValue({ x: 0, y: 0 });
},
onMoveShouldSetPanResponder: (evt, gestureState) => {
const { dx, dy } = gestureState;
return dx > 2 || dx < -2 || dy > 2 || dy < -2;
},
});
const animate = useCallback(
(value, timing) => {
Animated.spring(cardStyle, {
toValue: value,
useNativeDriver: false,
friction: 8.5,
}).start();
},
[cardStyle],
);
useEffect(() => {
if (isVisible) {
animate(height, 250);
} else {
animate(0, 250);
}
}, [animate, height, isVisible]);
const panStyle = cardStyle.interpolate({
inputRange: [0, height],
outputRange: [0, height],
});
const panStyleOpacityBlur = cardStyle.interpolate({
inputRange: [0, height],
outputRange: [0, 0.5],
});
if (isVisible === false) {
return null;
}
return (
<React.Fragment>
<Animated.View style={[styles.blur, { opacity: panStyleOpacityBlur }]}>
<TouchableOpacity
style={{ flex: 1 }}
onPress={() => {
animate(0, 250);
setTimeout(() => {
onClose();
}, 250);
}}
/>
</Animated.View>
<Animated.View
{...mainPanResponder.panHandlers}
style={{
...styles.panelCard,
height: panStyle,
}}>
<View style={{ alignItems: 'center' }}>
<View style={styles.line_drag} />
<View style={[styles.line_drag, { marginTop: 5 }]} />
</View>
{children}
</Animated.View>
</React.Fragment>
);
};
export default ModalSheet;
const styles = StyleSheet.create({
blur: {
position: 'absolute',
top: 0,
width: widthPercentageToDP(100),
height: heightPercentageToDP(100),
backgroundColor: 'black',
},
line_drag: {
width: 25,
height: 2,
backgroundColor: '#E1E1E1',
borderRadius: 2,
marginTop: 10,
},
panelCard: {
backgroundColor: '#FFFFFF',
elevation: 2,
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
position: 'absolute',
bottom: 0,
width: widthPercentageToDP(100),
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment