/ src / hooks / use-swipe.ts
use-swipe.ts
 1  import { useRef, useCallback } from 'react'
 2  
 3  interface UseSwipeOptions {
 4    onSwipe: (direction: 'left' | 'right') => void
 5    /** Only trigger right-swipe from this many pixels from the left edge */
 6    edgeWidth?: number
 7    /** Minimum horizontal distance to count as a swipe */
 8    threshold?: number
 9    /** Whether left-swipe is currently allowed (e.g. sidebar is open) */
10    leftSwipeEnabled?: boolean
11  }
12  
13  export function useSwipe({
14    onSwipe,
15    edgeWidth = 40,
16    threshold = 50,
17    leftSwipeEnabled = false,
18  }: UseSwipeOptions) {
19    const startX = useRef(0)
20    const startY = useRef(0)
21    const isEdge = useRef(false)
22  
23    const onTouchStart = useCallback((e: React.TouchEvent) => {
24      const touch = e.touches[0]
25      startX.current = touch.clientX
26      startY.current = touch.clientY
27      isEdge.current = touch.clientX <= edgeWidth
28    }, [edgeWidth])
29  
30    // No-op — we only evaluate on touchend, but callers may wire this for consistency
31    const onTouchMove = useCallback(() => {}, [])
32  
33    const onTouchEnd = useCallback((e: React.TouchEvent) => {
34      const touch = e.changedTouches[0]
35      const dx = touch.clientX - startX.current
36      const dy = touch.clientY - startY.current
37      // Ignore if vertical movement dominates
38      if (Math.abs(dy) > Math.abs(dx)) return
39      if (Math.abs(dx) < threshold) return
40  
41      if (dx > 0 && isEdge.current) {
42        onSwipe('right')
43      } else if (dx < 0 && leftSwipeEnabled) {
44        onSwipe('left')
45      }
46    }, [threshold, leftSwipeEnabled, onSwipe])
47  
48    return { onTouchStart, onTouchMove, onTouchEnd }
49  }