Sync scroll to swipe

This commit is contained in:
Dan Abramov
2024-11-29 21:17:47 +00:00
parent a26a84eb60
commit 020d71a741
3 changed files with 76 additions and 28 deletions
+1
View File
@@ -62,6 +62,7 @@ export function HomeHeader(
testID={props.testID} testID={props.testID}
items={items} items={items}
indicatorColor={pal.colors.link} indicatorColor={pal.colors.link}
dragGesture={props.dragGesture}
/> />
</HomeHeaderLayout> </HomeHeaderLayout>
) )
+1
View File
@@ -99,6 +99,7 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
onPageSelected(e: PagerViewOnPageSelectedEventData) { onPageSelected(e: PagerViewOnPageSelectedEventData) {
'worklet' 'worklet'
dragPage.set(e.position) dragPage.set(e.position)
dragProgress.set(0)
runOnJS(onPageSelectedJSThread)(e.position) runOnJS(onPageSelectedJSThread)(e.position)
}, },
}, },
+49 -3
View File
@@ -1,5 +1,12 @@
import {useCallback, useMemo, useRef} from 'react' import {useCallback, useMemo} from 'react'
import {ScrollView, StyleSheet, View} from 'react-native' import {ScrollView, StyleSheet, View} from 'react-native'
import Animated, {
scrollTo,
useAnimatedReaction,
useAnimatedRef,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {PressableWithHover} from '../util/PressableWithHover' import {PressableWithHover} from '../util/PressableWithHover'
@@ -21,9 +28,10 @@ export function TabBar({
indicatorColor, indicatorColor,
onSelect, onSelect,
onPressSelected, onPressSelected,
dragGesture,
}: TabBarProps) { }: TabBarProps) {
const pal = usePalette('default') const pal = usePalette('default')
const scrollElRef = useRef<ScrollView>(null) const scrollElRef = useAnimatedRef()
const indicatorStyle = useMemo( const indicatorStyle = useMemo(
() => ({borderBottomColor: indicatorColor || pal.colors.link}), () => ({borderBottomColor: indicatorColor || pal.colors.link}),
[indicatorColor, pal], [indicatorColor, pal],
@@ -39,6 +47,30 @@ export function TabBar({
[onSelect, selectedPage, onPressSelected], [onSelect, selectedPage, onPressSelected],
) )
const contentSize = useSharedValue(0)
const containerSize = useSharedValue(0)
const itemsLength = items.length
const {dragPage, dragProgress} = dragGesture
useAnimatedReaction(
() => dragPage.get(),
(page, prevPage) => {
if (page !== prevPage) {
const offsetPerPage = contentSize.get() - containerSize.get()
const offset = (dragPage.get() / (itemsLength - 1)) * offsetPerPage
scrollTo(scrollElRef, offset, 0, false)
}
},
)
const contentStyle = useAnimatedStyle(() => {
const offsetPerPage = contentSize.get() - containerSize.get()
const offset = (dragProgress.get() / (itemsLength - 1)) * offsetPerPage
return {
transform: [{translateX: -offset}],
}
})
return ( return (
<View <View
testID={testID} testID={testID}
@@ -49,7 +81,20 @@ export function TabBar({
horizontal={true} horizontal={true}
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
ref={scrollElRef} ref={scrollElRef}
contentContainerStyle={styles.contentContainer}> contentContainerStyle={styles.contentContainer}
onLayout={e => {
containerSize.set(e.nativeEvent.layout.width)
}}>
<Animated.View
onLayout={e => {
contentSize.set(e.nativeEvent.layout.width)
}}
style={[
contentStyle,
{
flexDirection: 'row',
},
]}>
{items.map((item, i) => { {items.map((item, i) => {
const selected = i === selectedPage const selected = i === selectedPage
return ( return (
@@ -75,6 +120,7 @@ export function TabBar({
</PressableWithHover> </PressableWithHover>
) )
})} })}
</Animated.View>
</ScrollView> </ScrollView>
<View style={[pal.border, styles.outerBottomBorder]} /> <View style={[pal.border, styles.outerBottomBorder]} />
</View> </View>