A React Native app for the ultimate thinking partner.
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;