This commit is contained in:
oneao committed 2025-11-20 22:32:10 +08:00
1 parent 069b4506e4
commit bb780a42ab
10 files changed
+389 -246

No files matched your search

@@ -7,6 +7,7 @@ import FormItem from './FormItem'
import Input from './Input'
import LucideIcon from './LucideIcon'
import Modal from './Modal'
import SegmentedTabs from './SegmentedTabs'
export {
Button as AButton,
@@ -19,5 +20,6 @@ export {
FormRef as AFormRef,
Input as AInput,
Modal as AModal,
SegmentedTabs as ASegmentedTabs,
LucideIcon,
}
@@ -70,14 +70,17 @@ export default function CustomHeader(props: CustomHeaderProps) {
style,
]}
>
{
left || (
<View className="w-10 h-full justify-center">
<TouchableOpacity onPress={() => router.back()}>
<LucideIcon icon="ArrowLeft" size={24} clickable={false} />
</TouchableOpacity>
</View>
)
}
{/* 左侧 */}
<View className="w-10 h-full justify-center">
{left || (
<TouchableOpacity onPress={() => router.back()}>
<LucideIcon icon="ArrowLeft" size={24} clickable={false} />
</TouchableOpacity>
)}
</View>
{/* 标题 */}
<View className="flex-1 items-center justify-center">
@@ -86,15 +89,15 @@ export default function CustomHeader(props: CustomHeaderProps) {
: title}
</View>
{/* 右侧 */}
<View className="w-10 h-full items-center justify-center">
{right || (
{right || (
<View className="w-10 h-full items-center justify-center">
<Image
source={require('@/static/images/logo.png')}
style={{ width: 22, height: 22 }}
/>
)}
</View>
</View>
)}
</View>
),
}}
@@ -1,168 +0,0 @@
import type { ReactNode } from 'react'
import type { LayoutChangeEvent } from 'react-native'
import React, { createContext, useContext, useEffect, useRef, useState } from 'react'
import {
Animated,
LayoutChangeEvent as RNLayoutChangeEvent,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native'
import PagerView from 'react-native-pager-view'
interface TabsContextType {
pagerRef: React.RefObject<PagerView>
scrollX: Animated.Value
index: number
setIndex: (i: number) => void
}
const TabsContext = createContext<TabsContextType | null>(null)
interface TabsProviderProps {
children: ReactNode
}
export function TabsProvider({ children }: TabsProviderProps) {
const pagerRef = useRef<PagerView>(null!)
const scrollX = useRef(new Animated.Value(0)).current
const [index, setIndex] = useState(0)
return (
<TabsContext.Provider value={{ pagerRef, scrollX, index, setIndex }}>
{children}
</TabsContext.Provider>
)
}
// ---------------- Tabs (Header) ----------------
interface TabsProps {
tabs: string[]
leftContent?: ReactNode
rightContent?: ReactNode
}
function useSafeTabsContext(): TabsContextType {
const ctx = useContext(TabsContext)
if (!ctx) {
const pagerRef = useRef<PagerView>(null!)
const scrollX = useRef(new Animated.Value(0)).current
const [index, setIndex] = useState(0)
return { pagerRef, scrollX, index, setIndex }
}
return ctx
}
TabsProvider.Tabs = function Tabs({ tabs, leftContent, rightContent }: TabsProps) {
const ctx = useSafeTabsContext()
const tabLayouts = useRef<{ x: number, width: number }[]>([])
const scrollRef = useRef<ScrollView>(null)
const onTabLayout = (e: LayoutChangeEvent, i: number) => {
const { x, width } = e.nativeEvent.layout
tabLayouts.current[i] = { x, width }
}
const measured = tabLayouts.current.length === tabs.length
const underlineX = measured
? ctx.scrollX.interpolate({
inputRange: tabs.map((_, i) => i),
outputRange: tabLayouts.current.map(t => t.x),
})
: 0
const underlineWidth = measured
? ctx.scrollX.interpolate({
inputRange: tabs.map((_, i) => i),
outputRange: tabLayouts.current.map(t => t.width),
})
: 0
// 自动滚动 ScrollView 保持选中 Tab 可见
useEffect(() => {
if (!measured || !scrollRef.current)
return
const layout = tabLayouts.current[ctx.index]
if (!layout)
return
scrollRef.current.scrollTo({ x: Math.max(layout.x - 50, 0), animated: true })
}, [ctx.index, measured])
return (
<View style={{ flexDirection: 'row', alignItems: 'center', borderBottomWidth: 1, borderColor: '#eee' }}>
{leftContent && <View>{leftContent}</View>}
<ScrollView
ref={scrollRef}
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ flexDirection: 'row', position: 'relative' }}
style={{ flex: 1 }}
>
{tabs.map((tab, i) => (
<TouchableOpacity
key={i}
onPress={() => {
ctx.pagerRef.current?.setPage(i)
ctx.setIndex(i)
}}
onLayout={e => onTabLayout(e, i)}
style={{ paddingHorizontal: 20, paddingVertical: 12 }}
>
<Text style={{ color: ctx.index === i ? '#000' : '#999' }}>{tab}</Text>
</TouchableOpacity>
))}
{measured && (
<Animated.View
style={[
styles.underline,
{ width: underlineWidth, transform: [{ translateX: underlineX }] },
]}
/>
)}
</ScrollView>
{rightContent && <View>{rightContent}</View>}
</View>
)
}
// ---------------- Content ----------------
TabsProvider.Content = function Content({ tabs }: { tabs: string[] }) {
const ctx = useSafeTabsContext()
return (
<PagerView
ref={ctx.pagerRef}
style={{ flex: 1 }}
initialPage={0}
onPageScroll={e =>
ctx.scrollX.setValue(e.nativeEvent.position + e.nativeEvent.offset)}
onPageSelected={e => ctx.setIndex(e.nativeEvent.position)}
>
{tabs.map((tab, i) => (
<View key={i} style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 20 }}>
{tab}
{' '}
页面内容
</Text>
</View>
))}
</PagerView>
)
}
const styles = StyleSheet.create({
underline: {
position: 'absolute',
bottom: 0,
height: 2,
backgroundColor: '#007AFF',
},
})