useSwipeNavigation
ericts-ui/use-swipe-navigationFreeHook
A touch-first React hook for previous and next navigation with direction locking, velocity thresholds, gesture ownership, and reduced-motion feedback.
Install it
npx shadcn@latest add https://ui.ericts.com/r/use-swipe-navigation.jsonSource
1"use client";23import { useCallback, useEffect, useRef } from "react";45export type SwipeNavigationDirection = "previous" | "next";67type SwipeState = {8 touchIdentifier: number;9 startX: number;10 startY: number;11 currentX: number;12 currentY: number;13 startedAt: number;14 axis: "pending" | "horizontal" | "vertical";15 reduceMotion: boolean;16};1718export type UseSwipeNavigationOptions = {19 onPrevious: () => void;20 onNext: () => void;21 hasPrevious?: boolean;22 hasNext?: boolean;23 disabled?: boolean;24 ignoreOwnedGestures?: boolean;25 onIntentChange?: (direction: SwipeNavigationDirection | null) => void;26 distanceThreshold?: number;27 velocityThreshold?: number;28 directionLockThreshold?: number;29 feedbackDistance?: number;30};3132const DEFAULT_DISTANCE_THRESHOLD = 52;33const DEFAULT_VELOCITY_THRESHOLD = 0.35;34const DEFAULT_DIRECTION_LOCK_THRESHOLD = 8;35const DEFAULT_FEEDBACK_DISTANCE = 16;36const OWNED_GESTURE_SELECTOR = [37 "input",38 "textarea",39 "select",40 "[contenteditable]:not([contenteditable='false'])",41 "[role='slider']",42 "[draggable='true']",43 "video[controls]",44 "audio[controls]",45 "[data-swipe-navigation='ignore']",46].join(",");4748/**49 * Recognizes one-finger horizontal touch gestures without taking over vertical50 * scrolling. A non-passive touchmove listener lets nested horizontal controls51 * keep their own gestures while the surrounding surface remains swipeable.52 */53export function useSwipeNavigation<T extends HTMLElement>({54 onPrevious,55 onNext,56 hasPrevious = true,57 hasNext = true,58 disabled = false,59 ignoreOwnedGestures = false,60 onIntentChange,61 distanceThreshold = DEFAULT_DISTANCE_THRESHOLD,62 velocityThreshold = DEFAULT_VELOCITY_THRESHOLD,63 directionLockThreshold = DEFAULT_DIRECTION_LOCK_THRESHOLD,64 feedbackDistance = DEFAULT_FEEDBACK_DISTANCE,65}: UseSwipeNavigationOptions) {66 const elementRef = useRef<T>(null);67 const swipeStateRef = useRef<SwipeState | null>(null);68 const suppressClickRef = useRef(false);69 const resetTimeoutRef = useRef<number | null>(null);7071 const clearResetTimeout = useCallback(() => {72 if (resetTimeoutRef.current === null) return;7374 window.clearTimeout(resetTimeoutRef.current);75 resetTimeoutRef.current = null;76 }, []);7778 const clearFeedbackStyles = useCallback(() => {79 const element = elementRef.current;8081 if (!element) return;8283 element.style.removeProperty("transition");84 element.style.removeProperty("transform");85 element.style.removeProperty("will-change");86// … truncated
Files it writes
More from ericts-ui
All 43 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| useElementHeightuse-element-height | ericts-ui | Hooks | Free | no deps | |
| useElementSizeMapuse-element-size-map | ericts-ui | Hooks | Free | no deps | |
| useReducedMotionuse-reduced-motion | ericts-ui | Hooks | Free | no deps | |
| useScrollAnchoruse-scroll-anchor | ericts-ui | Hooks | Free | no deps | |
| useScrollProgressuse-scroll-progress | ericts-ui | Hooks | Free | no deps | |
| useSequencePlayeruse-sequence-player | ericts-ui | Hooks | Free | no deps |
