Interpolate the indicator

This commit is contained in:
Dan Abramov
2024-11-30 03:21:27 +00:00
parent 9557eee808
commit 24a24753bc
+68 -14
View File
@@ -1,10 +1,12 @@
import {useCallback, useMemo} from 'react' import {useCallback, useState} from 'react'
import {ScrollView, StyleSheet, View} from 'react-native' import {ScrollView, StyleSheet, View} from 'react-native'
import Animated, { import Animated, {
interpolate,
runOnUI, runOnUI,
scrollTo, scrollTo,
useAnimatedReaction, useAnimatedReaction,
useAnimatedRef, useAnimatedRef,
useAnimatedStyle,
useSharedValue, useSharedValue,
} from 'react-native-reanimated' } from 'react-native-reanimated'
@@ -113,6 +115,46 @@ export function TabBar({
[onSelect, selectedPage, onPressSelected, onPressUIThread], [onSelect, selectedPage, onPressSelected, onPressUIThread],
) )
const [layouts, setLayouts] = useState([])
const didLayout =
layouts.length === items.length && layouts.every(l => l !== undefined)
const indicatorStyle = useAnimatedStyle(() => {
if (!didLayout) {
return {}
}
return {
transform: [
{
translateX: interpolate(
dragProgress.get(),
layouts.map((l, i) => i),
layouts.map(l => l.x + l.width / 2 - contentSize.get() / 2),
),
},
{
scaleX: interpolate(
dragProgress.get(),
layouts.map((l, i) => i),
layouts.map(l => (l.width - 12) / contentSize.get()),
),
},
],
}
})
const onItemLayout = (e: LayoutChangeEvent, index: number) => {
const l = e.nativeEvent.layout
setLayouts(ls =>
items.map((item, i) => {
if (i === index) {
return l
} else {
return ls[i]
}
}),
)
}
return ( return (
<View <View
testID={testID} testID={testID}
@@ -142,16 +184,32 @@ export function TabBar({
style={{flexDirection: 'row'}}> style={{flexDirection: 'row'}}>
{items.map((item, i) => { {items.map((item, i) => {
return ( return (
<TabBarItem <View key={i} onLayout={e => onItemLayout(e, i)}>
key={i} <TabBarItem
index={i} index={i}
testID={testID} testID={testID}
selected={i === selectedPage} selected={i === selectedPage}
item={item} item={item}
onPressItem={onPressItem} onPressItem={onPressItem}
/> />
</View>
) )
})} })}
{didLayout && (
<Animated.View
style={[
indicatorStyle,
{
position: 'absolute',
left: 0,
bottom: 0,
right: 0,
borderBottomWidth: 3,
borderColor: pal.link.color,
},
]}
/>
)}
</Animated.View> </Animated.View>
</ScrollView> </ScrollView>
<View style={[pal.border, styles.outerBottomBorder]} /> <View style={[pal.border, styles.outerBottomBorder]} />
@@ -173,10 +231,6 @@ function TabBarItem({
onPressItem: (index: number) => void onPressItem: (index: number) => void
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const indicatorStyle = useMemo(
() => ({borderBottomColor: pal.colors.link}),
[pal],
)
return ( return (
<PressableWithHover <PressableWithHover
testID={`${testID}-selector-${index}`} testID={`${testID}-selector-${index}`}
@@ -184,7 +238,7 @@ function TabBarItem({
hoverStyle={pal.viewLight} hoverStyle={pal.viewLight}
onPress={() => onPressItem(index)} onPress={() => onPressItem(index)}
accessibilityRole="tab"> accessibilityRole="tab">
<View style={[styles.itemInner, selected && indicatorStyle]}> <View style={[styles.itemInner]}>
<Text <Text
emoji emoji
type="lg-bold" type="lg-bold"