u
This commit is contained in:
1 parent
f28107399e
commit
7823fe2768
13 files changed
+294
-28
No files matched your search
@@ -0,0 +1,127 @@
|
||||
import type { Href } from "expo-router";
|
||||
import { usePathname, useRouter } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withSequence,
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import * as icons from "lucide-react-native/icons";
|
||||
|
||||
export interface TabbarItem {
|
||||
name: string;
|
||||
href: Href;
|
||||
icon: keyof typeof icons; // 👈 传 icon 名字
|
||||
label: string; // 👈 label
|
||||
onPress?: () => void;
|
||||
}
|
||||
|
||||
interface AppTabbarProps {
|
||||
data: TabbarItem[];
|
||||
}
|
||||
|
||||
function TabButton({
|
||||
iconName,
|
||||
label,
|
||||
isFocused = false,
|
||||
onPress,
|
||||
}: {
|
||||
iconName: keyof typeof icons;
|
||||
label: string;
|
||||
isFocused?: boolean;
|
||||
onPress?: () => void;
|
||||
}) {
|
||||
const scale = useSharedValue(1);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ scale: scale.value }],
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
scale.value = withSequence(
|
||||
withTiming(0.9, { duration: 120 }),
|
||||
withTiming(1, { duration: 160 }),
|
||||
);
|
||||
}
|
||||
}, [isFocused]);
|
||||
|
||||
// 👇 在内部统一处理 icon
|
||||
const LucideIcon = icons[iconName];
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Animated.View style={animatedStyle}>
|
||||
<LucideIcon
|
||||
size={22}
|
||||
color={"#333"}
|
||||
fill={isFocused ? "#0284c7" : "#fff"} // 填充颜色
|
||||
strokeWidth={2}
|
||||
/>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 10,
|
||||
marginTop: 2,
|
||||
color: "#333",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CustomTabbar({ data }: AppTabbarProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const [activeTab, setActiveTab] = useState("");
|
||||
|
||||
// 路由同步
|
||||
useEffect(() => {
|
||||
const match = data.find((item) => item.href === pathname);
|
||||
if (match) {
|
||||
setActiveTab(match.name);
|
||||
}
|
||||
}, [pathname, data]);
|
||||
|
||||
const handlePressTab = (item: TabbarItem) => {
|
||||
setActiveTab(item.name);
|
||||
|
||||
router.push(item.href);
|
||||
|
||||
item.onPress?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
height: 48,
|
||||
alignItems: "center",
|
||||
backgroundColor: "#fff",
|
||||
}}
|
||||
>
|
||||
{data.map((item) => (
|
||||
<TabButton
|
||||
key={item.name}
|
||||
iconName={item.icon}
|
||||
label={item.label}
|
||||
isFocused={activeTab === item.name}
|
||||
onPress={() => handlePressTab(item)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user