Skip to content

Instantly share code, notes, and snippets.

@donatoaguirre24
Created June 21, 2023 13:02
Show Gist options
  • Save donatoaguirre24/db554f19bf753c64cd580ddec3f942c9 to your computer and use it in GitHub Desktop.
Save donatoaguirre24/db554f19bf753c64cd580ddec3f942c9 to your computer and use it in GitHub Desktop.
import * as React from "react";
import { Modal, Pressable, StyleProp, StyleSheet, View, ViewStyle } from "react-native";
import { PanGestureHandler } from "react-native-gesture-handler";
import Animated, {
Easing,
interpolate,
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { useSafeAreaFrame, useSafeAreaInsets } from "react-native-safe-area-context";
import { Colors, Spacing } from "../../ui";
export interface CustomBottomSheetRef {
open(): void;
close(): void;
}
export interface CustomBottomSheetProps {
children: React.ReactNode;
hideHandle?: boolean;
style?: StyleProp<ViewStyle>;
onClose?(): void;
onOpen?(): void;
}
export const CustomBottomSheetComponent = React.forwardRef<
CustomBottomSheetRef,
CustomBottomSheetProps
>(({ children, hideHandle, style, onClose, onOpen }, ref) => {
const [isVisible, setIsVisible] = React.useState(false);
const insets = useSafeAreaInsets();
const dimensions = useSafeAreaFrame();
const translateY = useSharedValue(dimensions.height);
const overlayStyle = useAnimatedStyle(() => ({
opacity: interpolate(translateY.value, [dimensions.height, 0], [0, 1]),
}));
const sheetStyle = useAnimatedStyle(() => ({
transform: [{ translateY: translateY.value }],
}));
const handleAnimationFinished = React.useCallback(() => {
setIsVisible(false);
if (typeof onClose === "function") onClose();
}, [onClose]);
const showModal = React.useCallback(() => {
setIsVisible(true);
if (typeof onOpen === "function") onOpen();
translateY.value = withTiming(0, OPEN_CONFIG);
}, [onOpen, translateY]);
const hideModal = React.useCallback(() => {
translateY.value = withTiming(dimensions.height, CLOSE_CONFIG, (finished) => {
if (finished) runOnJS(handleAnimationFinished)();
});
}, [handleAnimationFinished, dimensions.height, translateY]);
const gestureHandler = useAnimatedGestureHandler({
onStart(event, ctx: { startY: number }) {
ctx.startY = event.absoluteY;
},
onActive(event) {
translateY.value = Math.max(0, event.translationY);
},
onEnd(event, ctx) {
if (event.translationY < ctx.startY / 2) {
translateY.value = withTiming(0, OPEN_CONFIG);
} else {
translateY.value = withTiming(dimensions.height, CLOSE_CONFIG, (finished) => {
if (finished) runOnJS(handleAnimationFinished)();
});
}
},
});
React.useImperativeHandle(ref, () => ({
open: showModal,
close: hideModal,
}));
return (
<Modal transparent animationType="none" visible={isVisible} onRequestClose={hideModal}>
<View style={styles.container}>
<Animated.View style={[styles.overlay, overlayStyle]}>
<Pressable style={StyleSheet.absoluteFill} onPress={hideModal} />
</Animated.View>
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View
style={[styles.sheet, sheetStyle, { paddingBottom: insets.bottom }, style]}
>
{!hideHandle && <View style={styles.handle} />}
{children}
</Animated.View>
</PanGestureHandler>
</View>
</Modal>
);
});
export const CustomBottomSheet = React.memo(CustomBottomSheetComponent);
CustomBottomSheet.displayName = "CustomBottomSheet";
const OPEN_CONFIG = {
duration: 300,
easing: Easing.inOut(Easing.ease),
};
const CLOSE_CONFIG = {
duration: 250,
easing: Easing.inOut(Easing.ease),
};
const MAX_TOP_MARGIN = 64;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "flex-end",
},
sheet: {
flexShrink: 1,
marginTop: MAX_TOP_MARGIN,
backgroundColor: Colors.Background.DarkSubdued,
borderTopLeftRadius: Spacing.triple,
borderTopRightRadius: Spacing.triple,
width: "100%",
overflow: "hidden",
},
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: Colors.Background.DarkOverlay,
},
handle: {
alignSelf: "center",
width: 55,
height: 5,
borderRadius: 10,
backgroundColor: Colors.Background.White,
opacity: 0.2,
marginVertical: Spacing.single,
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment