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 }