This commit is contained in:
oneao committed 2026-02-03 22:19:45 +08:00
1 parent 7a9a22e02e
commit ba9ce9e81b
8 files changed
+377 -248

No files matched your search

@@ -1,6 +1,15 @@
import React, { useMemo, useRef, useState } from 'react'
import { Pressable, ScrollView, Text, View } from 'react-native'
import PagerView from 'react-native-pager-view'
import type {
LayoutChangeEvent,
} from 'react-native'
import React, { useEffect, useRef, useState } from 'react'
import {
NativeScrollEvent,
NativeSyntheticEvent,
ScrollView,
Text,
TouchableWithoutFeedback,
View,
} from 'react-native'
interface TabItem {
label: string
@@ -22,93 +31,93 @@ export default function Tab({
if (!data || data.length === 0)
return null
// 默认选中 key(没传就用第一个)
/* ================= state ================= */
const initialKey = defaultKey ?? data[0].key
const [selectedKey, setSelectedKey] = useState(initialKey)
const pagerRef = useRef<PagerView>(null)
/* ================= refs ================= */
const scrollRef = useRef<ScrollView>(null)
// 是否有 content
const hasContent = useMemo(
() => data.some(item => item.content),
[data],
)
// 每个 tab 的 layout 信息
const itemLayouts = useRef<
Record<
TabItem['key'],
{ x: number, width: number }
>
>({})
// key -> index
const getIndexByKey = (key: TabItem['key']) =>
data.findIndex(item => item.key === key)
// index -> key
const getKeyByIndex = (index: number) =>
data[index]?.key
const containerWidth = useRef(0)
/* ================= handlers ================= */
const handlePress = (key: TabItem['key']) => {
setSelectedKey(key)
onChange?.(key)
if (hasContent) {
const index = getIndexByKey(key)
if (index !== -1)
pagerRef.current?.setPage(index)
}
scrollToCenter(key)
}
const onPageSelected = (e: any) => {
const index = e.nativeEvent.position
const key = getKeyByIndex(index)
const scrollToCenter = (key: TabItem['key']) => {
const layout = itemLayouts.current[key]
if (!layout || !scrollRef.current)
return
if (key !== undefined) {
setSelectedKey(key)
onChange?.(key)
}
const offset
= layout.x + layout.width / 2 - containerWidth.current / 2
scrollRef.current.scrollTo({
x: Math.max(0, offset),
animated: true,
})
}
/* ================= effects ================= */
useEffect(() => {
// 首次挂载后,让默认选中项居中
requestAnimationFrame(() => {
scrollToCenter(selectedKey)
})
}, [])
/* ================= render ================= */
return (
<View className="flex-1">
{/* tab bar */}
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
flexDirection: 'row',
gap: 8,
}}
>
{data.map((item) => {
const isActive = item.key === selectedKey
return (
<Pressable
key={item.key}
onPress={() => handlePress(item.key)}
<ScrollView
ref={scrollRef}
horizontal
showsHorizontalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
onLayout={(e: LayoutChangeEvent) => {
containerWidth.current = e.nativeEvent.layout.width
}}
contentContainerStyle={{
flexDirection: 'row',
gap: 8,
}}
>
{data.map((item) => {
const isActive = item.key === selectedKey
return (
<TouchableWithoutFeedback
key={item.key}
onPress={() => handlePress(item.key)}
>
<View
onLayout={(e) => {
const { x, width } = e.nativeEvent.layout
itemLayouts.current[item.key] = { x, width }
}}
className={
isActive
? 'items-center justify-center rounded-md px-4 py-2 bg-gray-800'
: 'items-center justify-center rounded-md px-4 py-2 bg-gray-200'
}
>
<Text className={isActive ? 'text-white' : 'text-gray-700'}>
{item.label}
</Text>
</Pressable>
)
})}
</ScrollView>
{hasContent && (
<PagerView
ref={pagerRef}
style={{ flex: 1, marginTop: 16 }}
initialPage={getIndexByKey(initialKey)}
onPageSelected={onPageSelected}
>
{data.map(item => (
<View key={item.key} style={{ flex: 1 }}>
{item.content}
</View>
))}
</PagerView>
)}
</View>
</TouchableWithoutFeedback>
)
})}
</ScrollView>
)
}