Fixes to the tab bar

zio/stable
Paul Frazee 2023-05-17 21:54:40 -05:00
parent 6bf8e72157
commit 177df36330
3 changed files with 47 additions and 112 deletions

View File

@ -56,7 +56,6 @@ const FeedsTabBarDesktop = observer(
{...props} {...props}
key={items.join(',')} key={items.join(',')}
items={items} items={items}
indicatorPosition="bottom"
indicatorColor={pal.colors.link} indicatorColor={pal.colors.link}
/> />
</Animated.View> </Animated.View>

View File

@ -57,7 +57,6 @@ export const FeedsTabBar = observer(
key={items.join(',')} key={items.join(',')}
{...props} {...props}
items={items} items={items}
indicatorPosition="bottom"
indicatorColor={pal.colors.link} indicatorColor={pal.colors.link}
/> />
</Animated.View> </Animated.View>

View File

@ -1,22 +1,21 @@
import React, {createRef, useState, useMemo, useRef} from 'react' import React, {
import {Animated, StyleSheet, View, ScrollView} from 'react-native' useRef,
createRef,
useMemo,
useEffect,
useState,
useCallback,
} from 'react'
import {StyleSheet, View, ScrollView} from 'react-native'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {PressableWithHover} from '../util/PressableWithHover' import {PressableWithHover} from '../util/PressableWithHover'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection' import {isDesktopWeb} from 'platform/detection'
interface Layout {
x: number
width: number
}
export interface TabBarProps { export interface TabBarProps {
testID?: string testID?: string
selectedPage: number selectedPage: number
items: string[] items: string[]
position: Animated.Value
offset: Animated.Value
indicatorPosition?: 'top' | 'bottom'
indicatorColor?: string indicatorColor?: string
onSelect?: (index: number) => void onSelect?: (index: number) => void
onPressSelected?: () => void onPressSelected?: () => void
@ -26,81 +25,27 @@ export function TabBar({
testID, testID,
selectedPage, selectedPage,
items, items,
position,
offset,
indicatorPosition = 'bottom',
indicatorColor, indicatorColor,
onSelect, onSelect,
onPressSelected, onPressSelected,
}: TabBarProps) { }: TabBarProps) {
const pal = usePalette('default') const pal = usePalette('default')
const [itemLayouts, setItemLayouts] = useState<Layout[]>( const scrollElRef = useRef<ScrollView>(null)
items.map(() => ({x: 0, width: 0})), const [itemXs, setItemXs] = useState<number[]>([])
)
const itemRefs = useMemo( const itemRefs = useMemo(
() => Array.from({length: items.length}).map(() => createRef<View>()), () => Array.from({length: items.length}).map(() => createRef<View>()),
[items.length], [items.length],
) )
const panX = Animated.add(position, offset)
const containerRef = useRef<View>(null)
const [scrollX, setScrollX] = useState(0)
const indicatorStyle = useMemo( const indicatorStyle = useMemo(
() => ({ () => ({borderBottomColor: indicatorColor || pal.colors.link}),
backgroundColor: indicatorColor || pal.colors.link, [indicatorColor, pal],
bottom:
indicatorPosition === 'bottom' ? (isDesktopWeb ? 0 : -1) : undefined,
top: indicatorPosition === 'top' ? (isDesktopWeb ? 0 : -1) : undefined,
transform: [
{
translateX: panX.interpolate({
inputRange: items.map((_item, i) => i),
outputRange: itemLayouts.map(l => l.x + l.width / 2 - scrollX),
}),
},
{
scaleX: panX.interpolate({
inputRange: items.map((_item, i) => i),
outputRange: itemLayouts.map(l => l.width),
}),
},
],
}),
[
indicatorColor,
indicatorPosition,
itemLayouts,
items,
panX,
pal.colors.link,
scrollX,
],
) )
const onLayout = React.useCallback(() => { useEffect(() => {
const promises = [] scrollElRef.current?.scrollTo({x: itemXs[selectedPage] || 0})
for (let i = 0; i < items.length; i++) { }, [scrollElRef, itemXs, selectedPage])
promises.push(
new Promise<Layout>(resolve => {
if (!containerRef.current || !itemRefs[i].current) {
return resolve({x: 0, width: 0})
}
itemRefs[i].current?.measureLayout( const onPressItem = useCallback(
containerRef.current,
(x: number, _y: number, width: number) => {
resolve({x, width})
},
)
}),
)
}
Promise.all(promises).then((layouts: Layout[]) => {
setItemLayouts(layouts)
})
}, [containerRef, itemRefs, setItemLayouts, items.length])
const onPressItem = React.useCallback(
(index: number) => { (index: number) => {
onSelect?.(index) onSelect?.(index)
if (index === selectedPage) { if (index === selectedPage) {
@ -110,28 +55,38 @@ export function TabBar({
[onSelect, onPressSelected, selectedPage], [onSelect, onPressSelected, selectedPage],
) )
const onLayout = React.useCallback(() => {
const promises = []
for (let i = 0; i < items.length; i++) {
promises.push(
new Promise<number>(resolve => {
if (!itemRefs[i].current) {
return resolve(0)
}
itemRefs[i].current?.measure((x: number) => resolve(x))
}),
)
}
Promise.all(promises).then((Xs: number[]) => {
setItemXs(Xs)
})
}, [itemRefs, setItemXs, items.length])
return ( return (
<View <View testID={testID} style={[pal.view, styles.outer]}>
testID={testID}
style={[pal.view, styles.outer]}
onLayout={onLayout}
ref={containerRef}>
<Animated.View style={[styles.indicator, indicatorStyle]} />
<ScrollView <ScrollView
horizontal={true} horizontal={true}
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
onScroll={({nativeEvent}) => { ref={scrollElRef}
setScrollX(nativeEvent.contentOffset.x) onLayout={onLayout}>
}}>
{items.map((item, i) => { {items.map((item, i) => {
const selected = i === selectedPage const selected = i === selectedPage
return ( return (
<PressableWithHover <PressableWithHover
ref={itemRefs[i]} ref={itemRefs[i]}
key={item} key={item}
style={ style={[styles.item, selected && indicatorStyle]}
indicatorPosition === 'top' ? styles.itemTop : styles.itemBottom
}
hoverStyle={pal.viewLight} hoverStyle={pal.viewLight}
onPress={() => onPressItem(i)}> onPress={() => onPressItem(i)}>
<Text <Text
@ -154,43 +109,25 @@ const styles = isDesktopWeb
flexDirection: 'row', flexDirection: 'row',
paddingHorizontal: 18, paddingHorizontal: 18,
}, },
itemTop: { item: {
paddingTop: 16,
paddingBottom: 14,
paddingHorizontal: 12,
},
itemBottom: {
paddingTop: 14, paddingTop: 14,
paddingBottom: 16, paddingBottom: 16,
paddingHorizontal: 12, paddingHorizontal: 12,
}, borderBottomWidth: 3,
indicator: { borderBottomColor: 'transparent',
position: 'absolute',
left: 0,
width: 1,
height: 3,
zIndex: 1,
}, },
}) })
: StyleSheet.create({ : StyleSheet.create({
outer: { outer: {
flexDirection: 'row', flexDirection: 'row',
paddingHorizontal: 14, paddingLeft: 14,
paddingRight: 24,
}, },
itemTop: { item: {
paddingTop: 10,
paddingBottom: 10,
marginRight: 24,
},
itemBottom: {
paddingTop: 8, paddingTop: 8,
paddingBottom: 12, paddingBottom: 12,
marginRight: 24, marginRight: 24,
}, borderBottomWidth: 3,
indicator: { borderBottomColor: 'transparent',
position: 'absolute',
left: 0,
width: 1,
height: 3,
}, },
}) })