import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react' import {LayoutChangeEvent, ScrollView, StyleSheet, View} from 'react-native' import {usePalette} from '#/lib/hooks/usePalette' import {PressableWithHover} from '../util/PressableWithHover' import {Text} from '../util/text/Text' export interface TabBarProps { testID?: string selectedPage: number items: string[] indicatorColor?: string onSelect?: (index: number) => void onPressSelected?: (index: number) => void } // How much of the previous/next item we're showing // to give the user a hint there's more to scroll. const OFFSCREEN_ITEM_WIDTH = 20 export function TabBar({ testID, selectedPage, items, indicatorColor, onSelect, onPressSelected, }: TabBarProps) { const pal = usePalette('default') const scrollElRef = useRef(null) const [itemXs, setItemXs] = useState([]) const indicatorStyle = useMemo( () => ({borderBottomColor: indicatorColor || pal.colors.link}), [indicatorColor, pal], ) useEffect(() => { // On native, the primary interaction is swiping. // We adjust the scroll little by little on every tab change. // Scroll into view but keep the end of the previous item visible. let x = itemXs[selectedPage] || 0 x = Math.max(0, x - OFFSCREEN_ITEM_WIDTH) scrollElRef.current?.scrollTo({x}) }, [scrollElRef, itemXs, selectedPage]) const onPressItem = useCallback( (index: number) => { onSelect?.(index) if (index === selectedPage) { onPressSelected?.(index) } }, [onSelect, selectedPage, onPressSelected], ) // calculates the x position of each item on mount and on layout change const onItemLayout = React.useCallback( (e: LayoutChangeEvent, index: number) => { const x = e.nativeEvent.layout.x setItemXs(prev => { const Xs = [...prev] Xs[index] = x return Xs }) }, [], ) return ( {items.map((item, i) => { const selected = i === selectedPage return ( onItemLayout(e, i)} style={styles.item} hoverStyle={pal.viewLight} onPress={() => onPressItem(i)} accessibilityRole="tab"> {item} ) })} ) } const styles = StyleSheet.create({ outer: { flexDirection: 'row', }, contentContainer: { backgroundColor: 'transparent', paddingHorizontal: 6, }, item: { paddingTop: 10, paddingHorizontal: 10, justifyContent: 'center', }, itemInner: { paddingBottom: 10, borderBottomWidth: 3, borderBottomColor: 'transparent', }, outerBottomBorder: { position: 'absolute', left: 0, right: 0, top: '100%', borderBottomWidth: StyleSheet.hairlineWidth, }, })