This commit is contained in:
imeepos
2025-11-12 13:19:06 +08:00
parent a7d922b535
commit 3ebcc24e9b
14 changed files with 483 additions and 265 deletions

View File

@@ -1,5 +1,5 @@
import { memo } from 'react';
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { memo, useCallback, useEffect, useRef } from 'react';
import { Pressable, ScrollView, StyleSheet, Text, View, useWindowDimensions } from 'react-native';
type Category = {
id: string;
@@ -13,18 +13,77 @@ type CategoryTabsProps = {
};
export function CategoryTabs({ categories, activeId, onChange }: CategoryTabsProps) {
const scrollViewRef = useRef<ScrollView>(null);
const tabPositionsRef = useRef<Map<string, { x: number; width: number }>>(new Map());
const containerWidthRef = useRef<number>(0);
const { width: screenWidth } = useWindowDimensions();
const scrollToActiveTab = useCallback(() => {
if (!activeId) {
console.log('[CategoryTabs] No activeId');
return;
}
const position = tabPositionsRef.current.get(activeId);
if (!position || !scrollViewRef.current) {
console.log('[CategoryTabs] Missing position or ref:', { position, hasRef: !!scrollViewRef.current });
return;
}
const centerOffset = screenWidth / 2;
const tabCenter = position.x + position.width / 2;
const scrollX = tabCenter - centerOffset;
const maxScrollX = Math.max(0, containerWidthRef.current - screenWidth);
const targetX = Math.max(0, Math.min(scrollX, maxScrollX));
console.log('[CategoryTabs] Scrolling to:', {
activeId,
position,
screenWidth,
tabCenter,
targetX,
containerWidth: containerWidthRef.current,
});
scrollViewRef.current.scrollTo({
x: targetX,
animated: true,
});
}, [activeId, screenWidth]);
useEffect(() => {
const timer = setTimeout(scrollToActiveTab, 150);
return () => clearTimeout(timer);
}, [scrollToActiveTab]);
const handleContentSizeChange = useCallback((width: number) => {
containerWidthRef.current = width;
console.log('[CategoryTabs] Content size:', width);
}, []);
return (
<ScrollView
ref={scrollViewRef}
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.container}
style={styles.scrollView}
onContentSizeChange={handleContentSizeChange}
>
{categories.map(category => (
{categories.map((category, index) => (
<CategoryTabItem
key={category.id}
category={category}
isActive={category.id === activeId}
onPress={onChange}
onLayout={(layout) => {
tabPositionsRef.current.set(category.id, {
x: layout.x,
width: layout.width,
});
console.log(`[CategoryTabs] Tab layout [${category.label}]:`, layout);
}}
/>
))}
</ScrollView>
@@ -35,11 +94,19 @@ type CategoryTabItemProps = {
category: Category;
isActive: boolean;
onPress: (id: string) => void;
onLayout: (layout: { x: number; width: number }) => void;
};
const CategoryTabItem = memo(({ category, isActive, onPress }: CategoryTabItemProps) => {
const CategoryTabItem = memo(({ category, isActive, onPress, onLayout }: CategoryTabItemProps) => {
return (
<Pressable onPress={() => onPress(category.id)} style={styles.tabButton}>
<Pressable
onPress={() => onPress(category.id)}
style={styles.tabButton}
onLayout={(event) => {
const { x, width } = event.nativeEvent.layout;
onLayout({ x, width });
}}
>
<Text style={[styles.label, isActive && styles.labelActive]}>{category.label}</Text>
<View style={[styles.indicator, isActive && styles.indicatorActive]} />
</Pressable>
@@ -48,6 +115,10 @@ const CategoryTabItem = memo(({ category, isActive, onPress }: CategoryTabItemPr
CategoryTabItem.displayName = 'CategoryTabItem';
const styles = StyleSheet.create({
scrollView: {
flexGrow: 0,
flexShrink: 0,
},
container: {
paddingVertical: 10,
paddingHorizontal: 6,