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}
items={items}
indicatorColor={pal.colors.link}
dragGesture={props.dragGesture}
/>
</HomeHeaderLayout>
)
+1
View File
@@ -99,6 +99,7 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
onPageSelected(e: PagerViewOnPageSelectedEventData) {
'worklet'
dragPage.set(e.position)
dragProgress.set(0)
runOnJS(onPageSelectedJSThread)(e.position)
},
},
+74 -28
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 Animated, {
scrollTo,
useAnimatedReaction,
useAnimatedRef,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated'
import {usePalette} from '#/lib/hooks/usePalette'
import {PressableWithHover} from '../util/PressableWithHover'
@@ -21,9 +28,10 @@ export function TabBar({
indicatorColor,
onSelect,
onPressSelected,
dragGesture,
}: TabBarProps) {
const pal = usePalette('default')
const scrollElRef = useRef<ScrollView>(null)
const scrollElRef = useAnimatedRef()
const indicatorStyle = useMemo(
() => ({borderBottomColor: indicatorColor || pal.colors.link}),
[indicatorColor, pal],
@@ -39,6 +47,30 @@ export function TabBar({
[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 (
<View
testID={testID}
@@ -49,32 +81,46 @@ export function TabBar({
horizontal={true}
showsHorizontalScrollIndicator={false}
ref={scrollElRef}
contentContainerStyle={styles.contentContainer}>
{items.map((item, i) => {
const selected = i === selectedPage
return (
<PressableWithHover
testID={`${testID}-selector-${i}`}
key={`${item}-${i}`}
style={styles.item}
hoverStyle={pal.viewLight}
onPress={() => onPressItem(i)}
accessibilityRole="tab">
<View style={[styles.itemInner, selected && indicatorStyle]}>
<Text
emoji
type="lg-bold"
testID={testID ? `${testID}-${item}` : undefined}
style={[
selected ? pal.text : pal.textLight,
{lineHeight: 20},
]}>
{item}
</Text>
</View>
</PressableWithHover>
)
})}
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) => {
const selected = i === selectedPage
return (
<PressableWithHover
testID={`${testID}-selector-${i}`}
key={`${item}-${i}`}
style={styles.item}
hoverStyle={pal.viewLight}
onPress={() => onPressItem(i)}
accessibilityRole="tab">
<View style={[styles.itemInner, selected && indicatorStyle]}>
<Text
emoji
type="lg-bold"
testID={testID ? `${testID}-${item}` : undefined}
style={[
selected ? pal.text : pal.textLight,
{lineHeight: 20},
]}>
{item}
</Text>
</View>
</PressableWithHover>
)
})}
</Animated.View>
</ScrollView>
<View style={[pal.border, styles.outerBottomBorder]} />
</View>