import { PlatformPressable } from '@react-navigation/elements' import React, { useEffect, useRef, useState } from 'react' import { Animated, Easing, StyleSheet, View, ViewStyle } from 'react-native' import { useTheme } from '../../hooks' // ================================= // ====== Component ================ // ================================= interface TabsProps { data: ArrayLike | null | undefined activeTab?: ItemT keyExtractor?: (_item: ItemT, _index?: number) => string onTabPress?: (_item: ItemT) => void renderItem: (_: { item: ItemT; index: number }) => React.ReactNode style?: ViewStyle contentContainerStyle?: ViewStyle } const defaultKeyExtractor = (item: any, index?: number) => { if (typeof item === 'string') return item return item.id || index?.toString() || item.toString() } export function Tabs({ activeTab = { id: '' } as ItemT, contentContainerStyle, data, keyExtractor = defaultKeyExtractor, onTabPress, renderItem, style, }: TabsProps) { const [tabDimSet, setTabDimensions] = useState>(new Set()) const tabDimensions = Array.from(tabDimSet) const [tabHeight, setTabHeight] = useState(0) const styles = useStyles() const opacity = useRef(new Animated.Value(0)).current const tabCursorPosition = useRef(new Animated.Value(0)).current const dataArray = Array.from(data || []) const activeTabIndex = dataArray.findIndex( (item, index) => keyExtractor(item, index) === keyExtractor(activeTab) ) const gap = 8 const tabCursorSpacing = tabDimensions .slice(0, activeTabIndex) .reduce((acc, { width }) => acc + width + gap, 0) Animated.timing(tabCursorPosition, { toValue: tabCursorSpacing, easing: Easing.inOut(Easing.ease), duration: 100, useNativeDriver: true, }).start() useEffect(() => { if (activeTabIndex === -1) return Animated.timing(opacity, { toValue: 1, easing: Easing.inOut(Easing.ease), duration: 500, useNativeDriver: true, }).start() }, [opacity, activeTabIndex]) return ( { const { height } = event.nativeEvent.layout setTabHeight(height) }} > {dataArray.map((item, index) => { return ( { onTabPress?.(item) }} > { const { width } = event.nativeEvent.layout setTabDimensions(dimensions => dimensions.add({ index, width })) }} > {renderItem({ item, index })} ) })} ) } // ================================= // ====== Styles =================== // ================================= const useStyles = () => { const theme = useTheme() return StyleSheet.create({ container: { flexDirection: 'row', justifyContent: 'center', }, contentContainer: {}, cursor: { borderBottomWidth: 3, borderBottomColor: theme.colors.interaction, height: 5, flex: 1, position: 'absolute', zIndex: 5, }, tab: {}, tabsContainer: { flex: 1, flexDirection: 'row', gap: 8, }, }) }