/ 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;