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)
}, },
}, },
+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 {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,32 +81,46 @@ export function TabBar({
horizontal={true} horizontal={true}
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
ref={scrollElRef} ref={scrollElRef}
contentContainerStyle={styles.contentContainer}> contentContainerStyle={styles.contentContainer}
{items.map((item, i) => { onLayout={e => {
const selected = i === selectedPage containerSize.set(e.nativeEvent.layout.width)
return ( }}>
<PressableWithHover <Animated.View
testID={`${testID}-selector-${i}`} onLayout={e => {
key={`${item}-${i}`} contentSize.set(e.nativeEvent.layout.width)
style={styles.item} }}
hoverStyle={pal.viewLight} style={[
onPress={() => onPressItem(i)} contentStyle,
accessibilityRole="tab"> {
<View style={[styles.itemInner, selected && indicatorStyle]}> flexDirection: 'row',
<Text },
emoji ]}>
type="lg-bold" {items.map((item, i) => {
testID={testID ? `${testID}-${item}` : undefined} const selected = i === selectedPage
style={[ return (
selected ? pal.text : pal.textLight, <PressableWithHover
{lineHeight: 20}, testID={`${testID}-selector-${i}`}
]}> key={`${item}-${i}`}
{item} style={styles.item}
</Text> hoverStyle={pal.viewLight}
</View> onPress={() => onPressItem(i)}
</PressableWithHover> 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> </ScrollView>
<View style={[pal.border, styles.outerBottomBorder]} /> <View style={[pal.border, styles.outerBottomBorder]} />
</View> </View>