u
This commit is contained in:
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',
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user