Files
casadoc-app/app/(tabs)/_layout.tsx
T
2026-08-26 23:01:07 +03:00

130 lines
4.0 KiB
TypeScript

import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons';
import { Tabs, useRouter } from 'expo-router';
import type { BottomTabBarProps } from '@react-navigation/bottom-tabs';
import { useT } from '@/i18n';
import { colors, fontSize, shadow, spacing } from '@/theme';
const FAB_SIZE = 58;
/** Width reserved for the centre action button, flanked by two tabs a side. */
const FAB_SLOT = 84;
const TAB_ICONS: Record<string, keyof typeof Ionicons.glyphMap> = {
index: 'home',
search: 'search',
expiring: 'time',
profile: 'person',
};
const TAB_LABEL_KEYS: Record<string, string> = {
index: 'tabs.home',
search: 'tabs.search',
expiring: 'tabs.expiring',
profile: 'tabs.profile',
};
function TabBar({ state, navigation }: BottomTabBarProps) {
const insets = useSafeAreaInsets();
const router = useRouter();
const t = useT();
const renderTab = (routeName: string) => {
const index = state.routes.findIndex((r) => r.name === routeName);
if (index < 0) return <View style={styles.tab} key={routeName} />;
const route = state.routes[index];
const focused = state.index === index;
return (
<Pressable
key={routeName}
style={styles.tab}
onPress={() => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!focused && !event.defaultPrevented) navigation.navigate(route.name);
}}
>
<Ionicons
name={TAB_ICONS[routeName]}
size={22}
color={focused ? colors.teal600 : colors.slate400}
/>
<Text style={[styles.tabLabel, focused && { color: colors.teal600 }]} numberOfLines={1}>
{t(TAB_LABEL_KEYS[routeName])}
</Text>
</Pressable>
);
};
// Two tabs, action, two tabs: symmetric, so the button sits on the midpoint.
return (
<View style={[styles.bar, { paddingBottom: insets.bottom || spacing[3] }]}>
<View style={styles.row}>
<View style={styles.group}>
{renderTab('index')}
{renderTab('search')}
</View>
<View style={styles.fabSlot}>
<Pressable
style={({ pressed }) => [styles.fab, pressed && { opacity: 0.9 }]}
onPress={() => router.push('/upload')}
accessibilityLabel={t('tabs.upload')}
>
<Ionicons name="cloud-upload-outline" size={26} color={colors.onDark} />
</Pressable>
<Text style={styles.fabLabel} numberOfLines={1}>
{t('tabs.upload')}
</Text>
</View>
<View style={styles.group}>
{renderTab('expiring')}
{renderTab('profile')}
</View>
</View>
</View>
);
}
export default function TabsLayout() {
return (
<Tabs screenOptions={{ headerShown: false }} tabBar={(props) => <TabBar {...props} />}>
<Tabs.Screen name="index" />
<Tabs.Screen name="search" />
<Tabs.Screen name="expiring" />
<Tabs.Screen name="profile" />
</Tabs>
);
}
const styles = StyleSheet.create({
bar: {
backgroundColor: colors.surface,
borderTopWidth: 1,
borderTopColor: colors.border,
paddingTop: spacing[2],
...shadow.sm,
},
row: { flexDirection: 'row', alignItems: 'flex-end', paddingHorizontal: spacing[1] },
group: { flex: 1, flexDirection: 'row' },
tab: { flex: 1, alignItems: 'center', gap: 3, paddingVertical: 4 },
tabLabel: { fontSize: fontSize['2xs'], fontWeight: '600', color: colors.slate400 },
fabSlot: { width: FAB_SLOT, alignItems: 'center' },
fab: {
width: FAB_SIZE,
height: FAB_SIZE,
borderRadius: FAB_SIZE / 2,
backgroundColor: colors.teal600,
alignItems: 'center',
justifyContent: 'center',
marginTop: -FAB_SIZE / 2,
...shadow.md,
},
fabLabel: { fontSize: fontSize['2xs'], fontWeight: '600', color: colors.teal700, marginTop: 3 },
});