Files
workspace/code/allapp/allapp-rn/src/components/common/ao/Tab.tsx
T
2026-02-05 22:46:50 +08:00

122 lines
2.9 KiB
TypeScript

import type {
LayoutChangeEvent,
} from 'react-native'
import React, { useEffect, useRef, useState } from 'react'
import {
ScrollView,
Text,
TouchableWithoutFeedback,
View,
} from 'react-native'
interface TabItem {
label: string
key: string | number
content?: React.ReactNode
}
interface TabProps {
data?: TabItem[]
defaultKey?: TabItem['key']
onChange?: (key: TabItem['key']) => void
}
export default function Tab({
data,
defaultKey,
onChange,
}: TabProps) {
if (!data || data.length === 0)
return null
/* ================= state ================= */
const initialKey = defaultKey ?? data[0].key
const [selectedKey, setSelectedKey] = useState(initialKey)
/* ================= refs ================= */
const scrollRef = useRef<ScrollView>(null)
// 每个 tab 的 layout 信息
const itemLayouts = useRef<
Record<
TabItem['key'],
{ x: number, width: number }
>
>({})
const containerWidth = useRef(0)
/* ================= handlers ================= */
const handlePress = (key: TabItem['key']) => {
setSelectedKey(key)
onChange?.(key)
scrollToCenter(key)
}
const scrollToCenter = (key: TabItem['key']) => {
const layout = itemLayouts.current[key]
if (!layout || !scrollRef.current)
return
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 (
<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>
</View>
</TouchableWithoutFeedback>
)
})}
</ScrollView>
)
}