Files
time_to_leave/apps/mobile/src/navigation/AppNavigator.tsx
T
2026-05-18 17:01:27 +02:00

149 lines
5.1 KiB
TypeScript

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
import { faBars } from '@fortawesome/free-solid-svg-icons/faBars';
import { Image, Modal, Pressable, StyleSheet, Text, View } from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { StatusBar } from 'expo-status-bar';
import { useState } from 'react';
import { EventListScreen } from '../screens/EventListScreen';
import { EventDetailScreen } from '../screens/EventDetailScreen';
import { AddEventScreen } from '../screens/AddEventScreen';
import { SettingsScreen } from '../screens/SettingsScreen';
import { CalendarImportScreen } from '../screens/CalendarImportScreen';
import type { RootStack } from '../types/navigation';
import navLogo from '../../assets/nav-logo.png';
// ── Root Stack ──
const Root = createNativeStackNavigator<RootStack>();
const byPrefixAndName = { fas: { bars: faBars } };
type HeaderMenuProps = {
navigation: {
navigate: (screen: 'CalendarImport' | 'Settings') => void;
};
};
function HeaderMenu({ navigation }: HeaderMenuProps) {
const [isOpen, setIsOpen] = useState(false);
const navigateTo = (screen: 'CalendarImport' | 'Settings') => {
setIsOpen(false);
navigation.navigate(screen);
};
return (
<>
<Pressable
accessibilityLabel="Open menu"
accessibilityRole="button"
hitSlop={12}
onPress={() => setIsOpen(true)}
style={({ pressed }) => [styles.menuButton, pressed && styles.menuButtonPressed]}
>
<FontAwesomeIcon icon={byPrefixAndName.fas['bars']} color="#F4F1EA" size={22} />
</Pressable>
<Modal animationType="fade" transparent visible={isOpen} onRequestClose={() => setIsOpen(false)}>
<Pressable style={styles.menuOverlay} onPress={() => setIsOpen(false)}>
<View style={styles.menuPanel}>
<Pressable
accessibilityRole="menuitem"
onPress={() => navigateTo('CalendarImport')}
style={({ pressed }) => [styles.menuItem, pressed && styles.menuItemPressed]}
>
<Text style={styles.menuItemText}>Calendar</Text>
</Pressable>
<Pressable
accessibilityRole="menuitem"
onPress={() => navigateTo('Settings')}
style={({ pressed }) => [styles.menuItem, pressed && styles.menuItemPressed]}
>
<Text style={styles.menuItemText}>Settings</Text>
</Pressable>
</View>
</Pressable>
</Modal>
</>
);
}
/**
* Root native stack navigator for the app.
* Wraps all screens in SafeAreaProvider/SafeAreaView with a dark header bar.
*/
export default function AppNavigator() {
return (
<SafeAreaProvider>
<SafeAreaView style={{ flex: 1, backgroundColor: '#090816' }}>
<StatusBar style="light" />
<NavigationContainer>
<Root.Navigator
initialRouteName="EventList"
screenOptions={({ navigation }) => ({
headerStyle: { backgroundColor: '#17112A' },
headerTintColor: '#F4F1EA',
headerRight: () => <HeaderMenu navigation={navigation} />,
headerTitle: () => (
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
<Image source={navLogo} style={{ width: 28, height: 28, borderRadius: 6 }} resizeMode="contain" />
<Text style={{ color: '#F4F1EA', fontSize: 18, fontWeight: '600' }}>Time</Text>
<Text style={{ color: '#ea1579', fontSize: 18, fontWeight: '600' }}>To </Text>
<Text style={{ color: '#F4F1EA', fontSize: 18, fontWeight: '600' }}>Leave</Text>
</View>
),
})}
>
<Root.Screen name="EventList" component={EventListScreen} />
<Root.Screen name="AddEvent" component={AddEventScreen} />
<Root.Screen name="EventDetail" component={EventDetailScreen} />
<Root.Screen name="Settings" component={SettingsScreen} />
<Root.Screen name="CalendarImport" component={CalendarImportScreen} />
</Root.Navigator>
</NavigationContainer>
</SafeAreaView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
menuButton: {
alignItems: 'center',
height: 40,
justifyContent: 'center',
width: 40,
},
menuButtonPressed: {
opacity: 0.65,
},
menuOverlay: {
alignItems: 'flex-end',
backgroundColor: 'rgba(9, 8, 22, 0.45)',
flex: 1,
paddingRight: 12,
paddingTop: 72,
},
menuPanel: {
backgroundColor: '#17112A',
borderColor: '#3B3157',
borderRadius: 8,
borderWidth: 1,
minWidth: 168,
overflow: 'hidden',
},
menuItem: {
paddingHorizontal: 18,
paddingVertical: 14,
},
menuItemPressed: {
backgroundColor: '#2A2140',
},
menuItemText: {
color: '#F4F1EA',
fontSize: 16,
fontWeight: '600',
},
});