import { Ionicons } from '@expo/vector-icons'; import { Tabs } from 'expo-router/js-tabs'; import type { BottomTabBarProps } from 'expo-router/js-tabs'; import { useEffect, useRef } from 'react'; import { Animated, Easing, Pressable, StyleSheet, View } from 'react-native'; import { colors } from '../../src/theme'; /** 无 index 路由,默认进入「岗位」Tab 作为落地页 */ export const unstable_settings = { initialRouteName: 'jobs', }; /** 未选中用 -outline 线性,选中用填充实心 */ const ICON_MAP = { jobs: { outline: 'briefcase-outline', filled: 'briefcase' }, chat: { outline: 'chatbubble-outline', filled: 'chatbubble' }, growth: { outline: 'trending-up-outline', filled: 'trending-up' }, profile: { outline: 'person-outline', filled: 'person' }, } as const; type TabRoute = keyof typeof ICON_MAP; /** * 单个 Tab 项:极简图标 + 文字。 * 点击选中时: * - 图标/文字从「灰色描边」平滑渐变到「主色填充」 * (灰层 + 主色层叠放,opacity 交叉淡入,200ms,纯原生驱动) * - 图标上浮 2px(150ms) * - 按压反馈:轻微缩放 0.92 */ function TabItem({ focused, route, label, onPress, onLongPress, }: { focused: boolean; route: TabRoute; label: string; onPress: () => void; onLongPress: () => void; }) { const { outline, filled } = ICON_MAP[route]; // 图标纵向位移:选中上浮 2px,未选中复位 const translateY = useRef(new Animated.Value(focused ? -2 : 0)).current; // 选中进度:0 = 未选中,1 = 选中 const progress = useRef(new Animated.Value(focused ? 1 : 0)).current; useEffect(() => { const translateAnim = Animated.timing(translateY, { toValue: focused ? -2 : 0, duration: 150, easing: Easing.out(Easing.ease), useNativeDriver: true, }); const progressAnim = Animated.timing(progress, { toValue: focused ? 1 : 0, duration: 200, easing: Easing.inOut(Easing.ease), useNativeDriver: true, }); translateAnim.start(); progressAnim.start(); // 清理:组件卸载或 focused 变化前停止动画,避免更新已卸载的节点 return () => { translateAnim.stop(); progressAnim.stop(); }; }, [focused, translateY, progress]); // 未选中层(灰 + 描边)透明度:1 → 0;选中层(主色 + 填充)透明度:0 → 1 const inactiveOpacity = progress.interpolate({ inputRange: [0, 1], outputRange: [1, 0], }); return ( [styles.tabItem, pressed && styles.tabItemPressed]} onPress={onPress} onLongPress={onLongPress} accessibilityRole="button" accessibilityState={{ selected: focused }} > {/* 未选中层:灰色描边图标 */} {/* 选中层:主色填充图标 */} {/* 未选中文字:灰色常规字重 */} {label} {/* 选中文字:主色加粗 */} {label} ); } /** * 底部 Tab 栏:占据底部布局空间(非悬浮),纯白底、无分割线, * 内容只在 tab 栏上方滚动,不会从图标后面穿出。 */ function TabBar({ state, descriptors, navigation, insets }: BottomTabBarProps) { return ( {state.routes.map((route, index) => { const { options } = descriptors[route.key]; const focused = state.index === index; const label = typeof options.tabBarLabel === 'string' ? options.tabBarLabel : options.title ?? ''; const onPress = () => { const event = navigation.emit({ type: 'tabPress', target: route.key, canPreventDefault: true, }); if (!focused && !event.defaultPrevented) { navigation.navigate(route.name); } }; const onLongPress = () => navigation.emit({ type: 'tabLongPress', target: route.key }); return ( ); })} ); } /** * 底部 Tab 导航:4 个 Tab(岗位/沟通/成长/我的),高级感极简风格 */ export default function TabLayout() { return ( } > ); } const styles = StyleSheet.create({ // 纯白底、无分割线、无阴影,占底部布局空间 tabBar: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.white, paddingTop: 4, }, tabItem: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingVertical: 2, }, tabItemPressed: { transform: [{ scale: 0.92 }], }, iconBox: { width: 22, height: 22, }, center: { alignItems: 'center', justifyContent: 'center', }, labelBox: { height: 14, marginTop: 1, alignSelf: 'stretch', }, labelLayer: { position: 'absolute', top: 0, left: 0, right: 0, }, tabLabel: { fontSize: 11, lineHeight: 14, fontWeight: '400', textAlign: 'center', color: colors.textHint, }, tabLabelActive: { fontWeight: '600', color: colors.primary, }, });