This commit is contained in:
oneao committed 2025-11-19 17:30:44 +08:00
1 parent 7419021927
commit 49285f1df2
16 files changed
+1184 -283

No files matched your search

@@ -0,0 +1,130 @@
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
initialIndex?: number
onChange?: (key: string) => void
indicatorWidth?: number
className?: string
style?: ViewStyle
}
export default function PagerTabs({
routes,
left,
right,
initialIndex = 0,
onChange,
indicatorWidth = 38,
className,
style,
}: PagerTabsProps) {
const pagerRef = React.useRef<PagerView>(null)
const [index, setIndex] = React.useState(initialIndex)
const [tabWidth, setTabWidth] = React.useState(0)
const scrollX = React.useRef(new Animated.Value(0)).current
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-1"
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={initialIndex}
onPageScroll={onPageScroll}
onPageSelected={e => onPageSelected(e.nativeEvent.position)}
>
{routes.map(r => (
<View key={r.key} className="flex-1">
{r.element ?? null}
</View>
))}
</PagerView>
</View>
)
}