A React Native app for the ultimate thinking partner.
at bf2a37bf805fa5ccc8b5badeeb098f842186e615 142 lines 3.7 kB view raw
1/** 2 * AppHeader Component 3 * 4 * MIGRATION STATUS: ✅ EXTRACTED - Ready for use 5 * 6 * REPLACES: App.tsx.monolithic lines 2083-2124 7 * - Header bar with menu button 8 * - App title ("co") with developer mode activation (7 clicks) 9 * - Conditional rendering based on message count 10 * 11 * FEATURES: 12 * - Menu button to toggle sidebar 13 * - Title with hidden developer mode toggle (tap 7 times in 2 seconds) 14 * - Responsive to safe area insets 15 * - Theme-aware styling 16 * - Hides when no messages (empty state) 17 * 18 * DEPENDENCIES: 19 * - Ionicons 20 * - react-native-safe-area-context 21 * - Theme system 22 * 23 * USED BY: (not yet integrated) 24 * - [ ] App.new.tsx (planned) 25 * 26 * RELATED COMPONENTS: 27 * - AppSidebar.tsx (triggered by menu button) 28 * - BottomNavigation.tsx (view switcher below this) 29 */ 30 31import React, { useRef, useState } from 'react'; 32import { View, Text, TouchableOpacity, StyleSheet, Platform, Alert } from 'react-native'; 33import { Ionicons } from '@expo/vector-icons'; 34import { useSafeAreaInsets } from 'react-native-safe-area-context'; 35import type { Theme } from '../theme'; 36 37interface AppHeaderProps { 38 theme: Theme; 39 colorScheme: 'light' | 'dark'; 40 hasMessages: boolean; 41 onMenuPress: () => void; 42 developerMode: boolean; 43 onDeveloperModeToggle: () => void; 44} 45 46export function AppHeader({ 47 theme, 48 colorScheme, 49 hasMessages, 50 onMenuPress, 51 developerMode, 52 onDeveloperModeToggle, 53}: AppHeaderProps) { 54 const insets = useSafeAreaInsets(); 55 const [headerClickCount, setHeaderClickCount] = useState(0); 56 const headerClickTimeoutRef = useRef<NodeJS.Timeout | null>(null); 57 58 const handleTitlePress = () => { 59 setHeaderClickCount(prev => prev + 1); 60 61 if (headerClickTimeoutRef.current) { 62 clearTimeout(headerClickTimeoutRef.current); 63 } 64 65 headerClickTimeoutRef.current = setTimeout(() => { 66 if (headerClickCount >= 6) { 67 onDeveloperModeToggle(); 68 if (Platform.OS === 'web') { 69 window.alert(developerMode ? 'Developer mode disabled' : 'Developer mode enabled'); 70 } else { 71 Alert.alert('Developer Mode', developerMode ? 'Disabled' : 'Enabled'); 72 } 73 } 74 setHeaderClickCount(0); 75 }, 2000); 76 }; 77 78 return ( 79 <View 80 style={[ 81 styles.header, 82 { 83 paddingTop: insets.top + 12, 84 backgroundColor: theme.colors.background.secondary, 85 borderBottomColor: theme.colors.border.primary, 86 }, 87 !hasMessages && { 88 backgroundColor: 'transparent', 89 borderBottomWidth: 0, 90 }, 91 ]} 92 > 93 <TouchableOpacity onPress={onMenuPress} style={styles.menuButton}> 94 <Ionicons 95 name="menu" 96 size={24} 97 color={colorScheme === 'dark' ? '#FFFFFF' : theme.colors.text.primary} 98 /> 99 </TouchableOpacity> 100 101 {hasMessages && ( 102 <> 103 <View style={styles.headerCenter}> 104 <TouchableOpacity onPress={handleTitlePress}> 105 <Text style={[styles.headerTitle, { color: theme.colors.text.primary }]}> 106 co 107 </Text> 108 </TouchableOpacity> 109 </View> 110 111 <View style={styles.headerSpacer} /> 112 </> 113 )} 114 </View> 115 ); 116} 117 118const styles = StyleSheet.create({ 119 header: { 120 flexDirection: 'row', 121 alignItems: 'center', 122 paddingHorizontal: 16, 123 paddingBottom: 12, 124 borderBottomWidth: 1, 125 }, 126 menuButton: { 127 padding: 8, 128 }, 129 headerCenter: { 130 flex: 1, 131 alignItems: 'center', 132 }, 133 headerTitle: { 134 fontSize: 36, 135 fontFamily: 'Lexend_700Bold', 136 }, 137 headerSpacer: { 138 width: 40, // Balance the menu button width 139 }, 140}); 141 142export default AppHeader;