Instantly share code, notes, and snippets.
Last active
July 22, 2024 17:49
-
Star
(4)
4
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save alexandrius/f264251515f2a9b453fa9aeb253574b4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useMemo, useState } from 'react'; | |
import { type ViewProps, StyleSheet } from 'react-native'; | |
import { GestureDetector, Gesture } from 'react-native-gesture-handler'; | |
import Animated, { | |
interpolate, | |
useAnimatedStyle, | |
useSharedValue, | |
withTiming, | |
Easing, | |
type SharedValue, | |
Extrapolation, | |
runOnJS, | |
} from 'react-native-reanimated'; | |
import { getTopInset } from 'rn-iphone-helper'; | |
import { PageProps } from '../pages/PageProps'; | |
import { iOS } from '@/constants/bits'; | |
import { heights, widths } from '@/constants/dimens'; | |
import useBackButton from '@/hooks/useBackButton'; | |
const { screen: screenWidth } = widths; | |
type NavigatorProps = { | |
pages: React.FC<PageProps>[]; | |
dismissSheet: PageProps['dismissSheet']; | |
}; | |
type NavigatorPageProps = { | |
index: number; | |
children: ViewProps['children']; | |
animation: SharedValue<number>; | |
inputRange: number[]; | |
}; | |
const animationConfig = { | |
duration: 150, | |
easing: Easing.inOut(Easing.quad), | |
}; | |
const GESTURE_DETECTOR_TOP = 60; | |
const styles = StyleSheet.create({ | |
gestureDetector: { | |
position: 'absolute', | |
height: heights.screen - getTopInset() - GESTURE_DETECTOR_TOP, | |
top: GESTURE_DETECTOR_TOP, | |
width: 40, | |
}, | |
}); | |
const NavigatorPage = ({ index, children, animation, inputRange }: NavigatorPageProps) => { | |
const outputRange = useMemo(() => { | |
return inputRange.map((_, i) => { | |
if (i === index) return 0; | |
else if (i > index) return -screenWidth * 0.3; | |
return screenWidth; | |
}); | |
}, []); | |
const animatedStyle = useAnimatedStyle(() => { | |
return { | |
transform: [ | |
{ | |
translateX: interpolate(animation.value, inputRange, outputRange, Extrapolation.CLAMP), | |
}, | |
], | |
}; | |
}); | |
return ( | |
<Animated.View className='absolute w-full h-full bg-light dark:bg-dark' style={animatedStyle}> | |
{children} | |
</Animated.View> | |
); | |
}; | |
export default function Navigator({ pages, dismissSheet }: NavigatorProps) { | |
const animation = useSharedValue(0); | |
const [currentIndex, setCurrentIndex] = useState(0); | |
const setCurrentIndexUI = (index: number) => { | |
'worklet'; | |
runOnJS(setCurrentIndex)(index); | |
}; | |
const goBack = () => { | |
const nextIndex = currentIndex - 1; | |
animation.value = withTiming(nextIndex, animationConfig, () => { | |
setCurrentIndexUI(nextIndex); | |
}); | |
}; | |
const goNext = () => { | |
const nextIndex = currentIndex + 1; | |
animation.value = withTiming(nextIndex, animationConfig, () => { | |
setCurrentIndexUI(nextIndex); | |
}); | |
}; | |
useBackButton(() => { | |
goBack(); | |
}, animation.value > 0); | |
const inputRange = useMemo(() => { | |
return Array.from({ length: pages.length }, (_, index) => { | |
return index; | |
}); | |
}, []); | |
const panGesture = Gesture.Pan() | |
.enabled(currentIndex > 0) | |
.activeOffsetX(5) | |
.onUpdate(({ translationX }) => { | |
if (translationX > 0) { | |
animation.value = currentIndex - translationX / screenWidth; | |
} | |
}) | |
.onEnd(({ translationX, velocityX }) => { | |
if (translationX > screenWidth / 5 || velocityX > 500) { | |
runOnJS(goBack)(); | |
} else { | |
animation.value = withTiming(currentIndex, animationConfig); | |
} | |
}); | |
return ( | |
<> | |
{pages.map((Component, index) => { | |
return ( | |
<NavigatorPage key={index.toString()} {...{ index, animation, inputRange }}> | |
<Component dismissSheet={dismissSheet} nextPage={goNext} prevPage={goBack} /> | |
</NavigatorPage> | |
); | |
})} | |
{iOS && ( | |
<GestureDetector gesture={panGesture}> | |
<Animated.View style={styles.gestureDetector} /> | |
</GestureDetector> | |
)} | |
</> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment