/ components / BreadcrumbNavigation.tsx
BreadcrumbNavigation.tsx
1 import React from 'react'; 2 import { 3 View, 4 Text, 5 TouchableOpacity, 6 StyleSheet, 7 ScrollView, 8 } from 'react-native'; 9 import { Ionicons } from '@expo/vector-icons'; 10 import { useTheme } from '@/constants/ThemeContext'; 11 import { Colors } from '@/constants/Colors'; 12 import { useRouter, usePathname } from 'expo-router'; 13 import { useHapticFeedback } from '@/utils/haptics'; 14 15 interface BreadcrumbItem { 16 path: string; 17 title: string; 18 icon?: keyof typeof Ionicons.glyphMap; 19 isClickable?: boolean; 20 } 21 22 interface BreadcrumbNavigationProps { 23 customBreadcrumbs?: BreadcrumbItem[]; 24 showIcons?: boolean; 25 maxItems?: number; 26 style?: any; 27 } 28 29 const BreadcrumbNavigation: React.FC<BreadcrumbNavigationProps> = ({ 30 customBreadcrumbs, 31 showIcons = true, 32 maxItems = 4, 33 style, 34 }) => { 35 const { actualTheme } = useTheme(); 36 const colors = Colors[actualTheme]; 37 const router = useRouter(); 38 const pathname = usePathname(); 39 const haptics = useHapticFeedback(); 40 41 const generateBreadcrumbs = (): BreadcrumbItem[] => { 42 if (customBreadcrumbs) return customBreadcrumbs; 43 44 const breadcrumbs: BreadcrumbItem[] = []; 45 46 // Add home breadcrumb 47 breadcrumbs.push({ 48 path: '/', 49 title: 'Home', 50 icon: 'home', 51 isClickable: true, 52 }); 53 54 // Add context-specific breadcrumbs based on current path 55 if (pathname.includes('/manga/')) { 56 // Add search/discovery breadcrumb 57 breadcrumbs.push({ 58 path: '/mangasearch', 59 title: 'Search', 60 icon: 'search', 61 isClickable: true, 62 }); 63 64 if (pathname.includes('/chapter/')) { 65 // Extract manga ID for manga detail link 66 const mangaMatch = pathname.match(/\/manga\/([^\/]+)/); 67 if (mangaMatch) { 68 breadcrumbs.push({ 69 path: `/manga/${mangaMatch[1]}`, 70 title: 'Manga', 71 icon: 'book', 72 isClickable: true, 73 }); 74 } 75 76 // Add current chapter (not clickable) 77 const chapterMatch = pathname.match(/\/chapter\/([^\/]+)/); 78 if (chapterMatch) { 79 breadcrumbs.push({ 80 path: pathname, 81 title: `Chapter ${chapterMatch[1]}`, 82 icon: 'bookmark', 83 isClickable: false, 84 }); 85 } 86 } else { 87 // On manga detail page (not clickable) 88 breadcrumbs.push({ 89 path: pathname, 90 title: 'Manga Details', 91 icon: 'book', 92 isClickable: false, 93 }); 94 } 95 } else if (pathname === '/bookmarks') { 96 breadcrumbs.push({ 97 path: pathname, 98 title: 'Library', 99 icon: 'library', 100 isClickable: false, 101 }); 102 } else if (pathname === '/mangasearch') { 103 breadcrumbs.push({ 104 path: pathname, 105 title: 'Search', 106 icon: 'search', 107 isClickable: false, 108 }); 109 } else if (pathname === '/settings') { 110 breadcrumbs.push({ 111 path: pathname, 112 title: 'Settings', 113 icon: 'settings', 114 isClickable: false, 115 }); 116 } 117 118 return breadcrumbs; 119 }; 120 121 const breadcrumbs = generateBreadcrumbs(); 122 const displayBreadcrumbs = breadcrumbs.slice(-maxItems); 123 124 const handleBreadcrumbPress = (item: BreadcrumbItem) => { 125 if (!item.isClickable) return; 126 127 haptics.onSelection(); 128 router.navigate(item.path as any); 129 }; 130 131 if (breadcrumbs.length <= 1) return null; 132 133 return ( 134 <View style={[styles.container, style]}> 135 <ScrollView 136 horizontal 137 showsHorizontalScrollIndicator={false} 138 contentContainerStyle={styles.scrollContent} 139 > 140 {displayBreadcrumbs.map((item, index) => ( 141 <React.Fragment key={item.path}> 142 {index > 0 && ( 143 <Ionicons 144 name="chevron-forward" 145 size={16} 146 color={colors.tabIconDefault} 147 style={styles.separator} 148 /> 149 )} 150 151 <TouchableOpacity 152 onPress={() => handleBreadcrumbPress(item)} 153 disabled={!item.isClickable} 154 style={[ 155 styles.breadcrumbItem, 156 !item.isClickable && styles.disabledItem, 157 ]} 158 accessibilityRole="button" 159 accessibilityLabel={`Navigate to ${item.title}`} 160 accessibilityHint={ 161 item.isClickable ? `Go to ${item.title} page` : 'Current page' 162 } 163 > 164 <View style={styles.breadcrumbContent}> 165 {showIcons && item.icon && ( 166 <Ionicons 167 name={item.icon} 168 size={14} 169 color={ 170 item.isClickable ? colors.primary : colors.tabIconDefault 171 } 172 style={styles.icon} 173 /> 174 )} 175 176 <Text 177 style={[ 178 styles.breadcrumbText, 179 { 180 color: item.isClickable ? colors.primary : colors.text, 181 }, 182 !item.isClickable && styles.currentText, 183 ]} 184 numberOfLines={1} 185 > 186 {item.title} 187 </Text> 188 </View> 189 </TouchableOpacity> 190 </React.Fragment> 191 ))} 192 </ScrollView> 193 </View> 194 ); 195 }; 196 197 const styles = StyleSheet.create({ 198 container: { 199 paddingHorizontal: 16, 200 paddingVertical: 8, 201 }, 202 scrollContent: { 203 alignItems: 'center', 204 paddingRight: 16, 205 }, 206 breadcrumbItem: { 207 paddingHorizontal: 8, 208 paddingVertical: 4, 209 borderRadius: 6, 210 maxWidth: 120, 211 }, 212 disabledItem: { 213 opacity: 1, 214 }, 215 breadcrumbContent: { 216 flexDirection: 'row', 217 alignItems: 'center', 218 }, 219 icon: { 220 marginRight: 4, 221 }, 222 breadcrumbText: { 223 fontSize: 14, 224 fontWeight: '500', 225 }, 226 currentText: { 227 fontWeight: '600', 228 }, 229 separator: { 230 marginHorizontal: 4, 231 }, 232 }); 233 234 export default BreadcrumbNavigation;