/** * @file use-swipe-navigation.ts * @description Hook for handling swipe gestures to navigate between tabs * * This hook provides gesture handlers for PanGestureHandler that detect * horizontal swipes and trigger navigation callbacks. */ import { useCallback, useRef } from 'react' import { Dimensions } from 'react-native' import type { PanGestureHandlerGestureEvent, PanGestureHandlerStateChangeEvent } from 'react-native-gesture-handler' const SCREEN_WIDTH = Dimensions.get('window').width const DEFAULT_THRESHOLD = SCREEN_WIDTH * 0.2 // 20% of screen width const DEFAULT_VELOCITY_THRESHOLD = 300 // pixels per second // Gesture handler state constants (to avoid import issues in tests) const GESTURE_STATE_END = 5 export interface UseSwipeNavigationOptions { /** Callback when user swipes left (to go to next tab) */ onSwipeLeft: () => void /** Callback when user swipes right (to go to previous tab) */ onSwipeRight: () => void /** Minimum distance to trigger swipe (default: 20% of screen width) */ threshold?: number /** Minimum velocity to trigger swipe regardless of distance (default: 300) */ velocityThreshold?: number /** Whether swipe navigation is enabled (default: true) */ enabled?: boolean /** Whether user can swipe left (default: true) */ canSwipeLeft?: boolean /** Whether user can swipe right (default: true) */ canSwipeRight?: boolean } export interface UseSwipeNavigationReturn { /** Handler for gesture events (for tracking) */ handleGestureEvent: (event: PanGestureHandlerGestureEvent) => void /** Handler for gesture state changes (for triggering navigation) */ handleGestureStateChange: (event: PanGestureHandlerStateChangeEvent) => void } /** * Hook for handling swipe gestures to navigate between tabs * * @example * ```tsx * const { handleGestureEvent, handleGestureStateChange } = useSwipeNavigation({ * onSwipeLeft: () => setActiveTab(prev => prev + 1), * onSwipeRight: () => setActiveTab(prev => prev - 1), * canSwipeLeft: activeTab < tabs.length - 1, * canSwipeRight: activeTab > 0, * }) * * return ( * * {children} * * ) * ``` */ export function useSwipeNavigation({ onSwipeLeft, onSwipeRight, threshold = DEFAULT_THRESHOLD, velocityThreshold = DEFAULT_VELOCITY_THRESHOLD, enabled = true, canSwipeLeft = true, canSwipeRight = true, }: UseSwipeNavigationOptions): UseSwipeNavigationReturn { // Track translation for potential animation use const translationX = useRef(0) const handleGestureEvent = useCallback( (event: PanGestureHandlerGestureEvent) => { if (!enabled) return translationX.current = event.nativeEvent.translationX }, [enabled] ) const handleGestureStateChange = useCallback( (event: PanGestureHandlerStateChangeEvent) => { if (!enabled) return const { state, translationX: tx, velocityX } = event.nativeEvent // Only process when gesture ends if (state !== GESTURE_STATE_END) return const isSwipeLeft = tx < 0 const isSwipeRight = tx > 0 const absTranslation = Math.abs(tx) const absVelocity = Math.abs(velocityX) // Check if swipe meets threshold (distance OR velocity) const meetsThreshold = absTranslation >= threshold || absVelocity >= velocityThreshold if (!meetsThreshold) return if (isSwipeLeft && canSwipeLeft) { onSwipeLeft() } else if (isSwipeRight && canSwipeRight) { onSwipeRight() } }, [enabled, threshold, velocityThreshold, canSwipeLeft, canSwipeRight, onSwipeLeft, onSwipeRight] ) return { handleGestureEvent, handleGestureStateChange, } }