import React, {useMemo, useCallback, useState} from 'react' import Animated, {useAnimatedReaction, useAnimatedRef, useAnimatedStyle, useDerivedValue, useSharedValue, measure, interpolate, interpolateColor, scrollTo, withSpring} from 'react-native-reanimated' import {Dimensions, StyleSheet, View, ScrollView} from 'react-native' import {PressableWithHover} from '../util/PressableWithHover' import {usePalette} from 'lib/hooks/usePalette' import {useTheme} from 'lib/ThemeContext' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {isWeb} from 'platform/detection' import {DraggableScrollView} from './DraggableScrollView' export interface TabBarProps { testID?: string selectedPage: number items: string[] indicatorColor?: string onSelect?: (index: number) => void onPressSelected?: () => void } const SCREEN = Dimensions.get('screen') export function TabBar({ testID, dragProgress, selectedPage, items, indicatorColor, onSelect, onPressSelected, }: TabBarProps) { const pal = usePalette('default') const contentSize = useSharedValue(0) const scrollElRef = useAnimatedRef(null) const {isDesktop, isTablet} = useWebMediaQueries() const [layouts, setLayouts] = useState([]) const approxIndex = useDerivedValue(() => dragProgress.value * (items.length - 1)) const indicatorStyle = useAnimatedStyle(() => { if (layouts.length < items.length - 1 || layouts.some(l => l === undefined)) { return {} } return { width: interpolate(approxIndex.value, layouts.map((l, i) => i), layouts.map(l => l.width)), left: interpolate(approxIndex.value, layouts.map((l, i) => i), layouts.map(l => l.x)), } }) const onPressItem = useCallback( (index: number) => { onSelect?.(index) if (index === selectedPage) { onPressSelected?.() } }, [onSelect, selectedPage, onPressSelected], ) useAnimatedReaction(() => { return dragProgress.value * (contentSize.value - SCREEN.width) }, (nextX, prevX) => { if (prevX !== nextX) { scrollTo(scrollElRef, nextX, 0, false); } }, [dragProgress, contentSize]) 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] } })) } const styles = isDesktop || isTablet ? desktopStyles : mobileStyles return ( { contentSize.value = e }}> {items.map((item, i) => { return ( onItemLayout(e, i)} style={[styles.item]} hoverStyle={pal.viewLight} onPress={() => onPressItem(i)}> {item} ) })} ) } export function MaybeHighlightedText({ approxIndex, index, style, type, ...rest }) { const pal = usePalette('default') const theme = useTheme() const typography = theme.typography[type] const animatedStyle = useAnimatedStyle(() => ({ color: interpolateColor(Math.min(Math.abs(approxIndex.value - index), 1), [0, 1], [pal.text.color, pal.textLight.color]) })) return ( ) } const desktopStyles = StyleSheet.create({ outer: { flexDirection: 'row', width: 598, }, contentContainer: { columnGap: 8, marginLeft: 14, paddingRight: 14, backgroundColor: 'transparent', }, item: { paddingTop: 14, paddingBottom: 12, paddingHorizontal: 10, borderBottomWidth: 3, borderBottomColor: 'transparent', justifyContent: 'center', }, }) const mobileStyles = StyleSheet.create({ outer: { flex: 1, flexDirection: 'row', backgroundColor: 'transparent', maxWidth: '100%', }, contentContainer: { columnGap: isWeb ? 0 : 20, marginLeft: isWeb ? 0 : 18, paddingRight: isWeb ? 0 : 36, backgroundColor: 'transparent', }, item: { paddingTop: 10, paddingBottom: 10, paddingHorizontal: isWeb ? 8 : 0, borderBottomWidth: 3, borderBottomColor: 'transparent', justifyContent: 'center', }, })