A React Native app for the ultimate thinking partner.
1/**
2 * AppHeader Component
3 *
4 * Application header with menu button and app title.
5 *
6 * Features:
7 * - Menu button triggers sidebar drawer
8 * - Hidden developer mode toggle (tap "co" title 7 times in 2 seconds)
9 * - Responsive to safe area insets
10 * - Conditionally hides when no messages present
11 */
12
13import React, { useRef, useState } from 'react';
14import { View, Text, TouchableOpacity, StyleSheet, Platform, Alert } from 'react-native';
15import { Ionicons } from '@expo/vector-icons';
16import { useSafeAreaInsets } from 'react-native-safe-area-context';
17import type { Theme } from '../theme';
18
19interface AppHeaderProps {
20 theme: Theme;
21 colorScheme: 'light' | 'dark';
22 hasMessages: boolean;
23 onMenuPress: () => void;
24 developerMode: boolean;
25 onDeveloperModeToggle: () => void;
26}
27
28export function AppHeader({
29 theme,
30 colorScheme,
31 hasMessages,
32 onMenuPress,
33 developerMode,
34 onDeveloperModeToggle,
35}: AppHeaderProps) {
36 const insets = useSafeAreaInsets();
37 const [headerClickCount, setHeaderClickCount] = useState(0);
38 const headerClickTimeoutRef = useRef<NodeJS.Timeout | null>(null);
39
40 const handleTitlePress = () => {
41 setHeaderClickCount(prev => prev + 1);
42
43 if (headerClickTimeoutRef.current) {
44 clearTimeout(headerClickTimeoutRef.current);
45 }
46
47 headerClickTimeoutRef.current = setTimeout(() => {
48 if (headerClickCount >= 6) {
49 onDeveloperModeToggle();
50 if (Platform.OS === 'web') {
51 window.alert(developerMode ? 'Developer mode disabled' : 'Developer mode enabled');
52 } else {
53 Alert.alert('Developer Mode', developerMode ? 'Disabled' : 'Enabled');
54 }
55 }
56 setHeaderClickCount(0);
57 }, 2000);
58 };
59
60 return (
61 <View
62 style={[
63 styles.header,
64 {
65 paddingTop: insets.top + 6,
66 backgroundColor: theme.colors.background.secondary,
67 borderBottomColor: theme.colors.border.primary,
68 },
69 !hasMessages && {
70 backgroundColor: 'transparent',
71 borderBottomWidth: 0,
72 },
73 ]}
74 >
75 <TouchableOpacity onPress={onMenuPress} style={styles.menuButton}>
76 <Ionicons
77 name="menu"
78 size={22}
79 color={colorScheme === 'dark' ? '#FFFFFF' : theme.colors.text.primary}
80 />
81 </TouchableOpacity>
82
83 {hasMessages && (
84 <>
85 <View style={styles.headerCenter}>
86 <TouchableOpacity onPress={handleTitlePress}>
87 <Text style={[styles.headerTitle, { color: theme.colors.text.primary }]}>
88 co
89 </Text>
90 </TouchableOpacity>
91 </View>
92
93 <View style={styles.headerSpacer} />
94 </>
95 )}
96 </View>
97 );
98}
99
100const styles = StyleSheet.create({
101 header: {
102 flexDirection: 'row',
103 alignItems: 'center',
104 paddingHorizontal: 16,
105 paddingBottom: 6,
106 borderBottomWidth: 1,
107 },
108 menuButton: {
109 padding: 6,
110 },
111 headerCenter: {
112 flex: 1,
113 alignItems: 'center',
114 },
115 headerTitle: {
116 fontSize: 36,
117 fontFamily: 'Lexend_700Bold',
118 },
119 headerSpacer: {
120 width: 34, // Balance the menu button width
121 },
122});
123
124export default AppHeader;