Skip to content

Instantly share code, notes, and snippets.

@farynaio
Created March 25, 2025 14:35
Show Gist options
  • Save farynaio/e2f9e6ef5cc8522261d38d68c7c9dc38 to your computer and use it in GitHub Desktop.
Save farynaio/e2f9e6ef5cc8522261d38d68c7c9dc38 to your computer and use it in GitHub Desktop.
RN BottomSheet component
import { Dimensions, StyleSheet, View } from 'react-native'
import React, {
PropsWithChildren,
useCallback,
useImperativeHandle,
} from 'react'
import {
Gesture,
GestureDetector,
GestureHandlerRootView,
} from 'react-native-gesture-handler'
import Animated, {
Extrapolation,
interpolate,
useAnimatedStyle,
useSharedValue,
withSpring,
} from 'react-native-reanimated'
const { height: SCREEN_HEIGHT } = Dimensions.get('window')
const MAX_TRANSLATE_Y = -SCREEN_HEIGHT + 50
interface BottomSheetProps extends PropsWithChildren {}
export type BottomSheetRefProps = {
scrollTo: (destination: number) => void
isActive: () => boolean
}
const BottomSheet = React.forwardRef<BottomSheetRefProps, BottomSheetProps>(
({ children }, ref) => {
const translateY = useSharedValue(0)
const active = useSharedValue(false)
const scrollTo = useCallback(
(destination: number) => {
'worklet'
active.value = destination !== 0
translateY.value = withSpring(destination, { damping: 50 })
},
[active, translateY]
)
const isActive = useCallback(() => {
return active.value
}, [active.value])
useImperativeHandle(ref, () => ({ scrollTo, isActive }), [
scrollTo,
isActive,
])
const context = useSharedValue({ y: 0 })
const gesture = Gesture.Pan()
.onStart(() => {
context.value = { y: translateY.value }
})
.onUpdate((event) => {
translateY.value = event.translationY + context.value.y
translateY.value = Math.max(translateY.value, MAX_TRANSLATE_Y)
})
.onEnd(() => {
if (translateY.value > -SCREEN_HEIGHT / 3) {
scrollTo(0)
} else if (translateY.value < -SCREEN_HEIGHT / 1.5) {
scrollTo(MAX_TRANSLATE_Y)
}
})
const rBottomSheetStyle = useAnimatedStyle(() => {
const borderRadius = interpolate(
translateY.value,
[MAX_TRANSLATE_Y + 50, MAX_TRANSLATE_Y],
[25, 5],
Extrapolation.CLAMP
)
return {
borderRadius,
transform: [{ translateY: translateY.value }],
}
})
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<GestureDetector gesture={gesture}>
<Animated.View
style={[styles.bottomSheetContainer, rBottomSheetStyle]}
>
<View style={styles.line} />
{children}
</Animated.View>
</GestureDetector>
</GestureHandlerRootView>
)
}
)
const styles = StyleSheet.create({
bottomSheetContainer: {
position: 'absolute',
height: SCREEN_HEIGHT,
width: '100%',
backgroundColor: 'rgb(1,1,1)',
top: SCREEN_HEIGHT,
borderRadius: 25,
},
line: {
width: 75,
height: 4,
backgroundColor: 'red',
alignSelf: 'center',
marginVertical: 15,
borderRadius: 2,
},
})
export default BottomSheet
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment