Files
workspace/code/base-project/base-app/src/components/custom/PagerTabs.tsx
T
2025-11-19 22:55:09 +08:00

135 lines
3.4 KiB
TypeScript

import type { ViewStyle } from 'react-native'
import * as React from 'react'
import { Animated, Text, TouchableOpacity, View } from 'react-native'
import PagerView from 'react-native-pager-view'
export interface PagerRoute {
key: string
title: string
element?: React.ReactNode
}
interface PagerTabsProps {
routes: PagerRoute[]
left?: React.ReactNode
right?: React.ReactNode
selectedIndex?: number // 外部控制选中页
onChange?: (key: string) => void
indicatorWidth?: number
className?: string
style?: ViewStyle
}
export default function PagerTabs({
routes,
left,
right,
selectedIndex,
onChange,
indicatorWidth = 38,
className,
style,
}: PagerTabsProps) {
const pagerRef = React.useRef<PagerView>(null)
const [index, setIndex] = React.useState(selectedIndex ?? 0)
const [tabWidth, setTabWidth] = React.useState(0)
const scrollX = React.useRef(new Animated.Value(selectedIndex ?? 0)).current
// 外部 selectedIndex 更新时同步内部 index
React.useEffect(() => {
if (typeof selectedIndex === 'number' && selectedIndex !== index) {
setIndex(selectedIndex)
pagerRef.current?.setPage(selectedIndex)
}
}, [selectedIndex])
const onTabPress = (i: number) => {
pagerRef.current?.setPage(i)
}
const onPageScroll = (e: any) => {
const { position, offset } = e.nativeEvent
scrollX.setValue(position + offset)
}
const onPageSelected = (position: number) => {
setIndex(position)
if (onChange && routes[position]) {
onChange(routes[position].key)
}
}
const tabWidthPerTab = tabWidth / routes.length
const indicatorTranslateX = scrollX.interpolate({
inputRange: routes.map((_, i) => i),
outputRange: routes.map(
(_, i) => i * tabWidthPerTab + (tabWidthPerTab - indicatorWidth) / 2,
),
extrapolate: 'clamp',
})
return (
<View
className={`${className} flex-1`}
style={[style]}
>
{/* Header */}
<View className="flex flex-row items-center">
{left}
{/* Tabs */}
<View
className="flex-1 flex-row relative"
onLayout={(e) => {
const w = e.nativeEvent.layout.width
if (w !== tabWidth)
setTabWidth(w)
}}
>
{routes.map((r, i) => (
<TouchableOpacity
key={r.key}
className="flex-1 items-center justify-center py-2"
onPress={() => onTabPress(i)}
>
<Text
className={`text-lg ${index === i ? 'text-[#333] font-bold' : 'text-gray-500'}`}
>
{r.title}
</Text>
</TouchableOpacity>
))}
{tabWidth > 0 && (
<Animated.View
className="absolute bottom-0 h-0.5 bg-black"
style={{
width: indicatorWidth,
transform: [{ translateX: indicatorTranslateX }],
}}
/>
)}
</View>
{right}
</View>
{/* Pager */}
<PagerView
ref={pagerRef}
style={{ flex: 1 }}
initialPage={selectedIndex ?? 0}
onPageScroll={onPageScroll}
onPageSelected={e => onPageSelected(e.nativeEvent.position)}
>
{routes.map(r => (
<View key={r.key} className="flex-1">
{r.element ?? null}
</View>
))}
</PagerView>
</View>
)
}