added swipe gestures

This commit is contained in:
2026-04-14 15:02:33 +05:30
parent 09464aaa96
commit 84019c3881
2 changed files with 90 additions and 0 deletions

View File

@@ -2,8 +2,14 @@ import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider } from './contexts/AuthContext'
import { ProtectedRoute } from './components/ProtectedRoute'
import { useSwipeNav } from './hooks/useSwipeNav'
import './App.css'
function SwipeNavHandler() {
useSwipeNav()
return null
}
const HomePage = lazy(() => import('./pages/HomePage'))
const HistoryPage = lazy(() => import('./pages/HistoryPage'))
const SettingsPage = lazy(() => import('./pages/SettingsPage'))
@@ -16,6 +22,7 @@ function App() {
return (
<AuthProvider>
<BrowserRouter>
<SwipeNavHandler />
<Suspense fallback={null}>
<Routes>
<Route path="/" element={<LoginPage />} />

83
src/hooks/useSwipeNav.ts Normal file
View File

@@ -0,0 +1,83 @@
import { useEffect } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
const PAGES = ['/write', '/history', '/settings']
const SWIPE_THRESHOLD = 55 // minimum horizontal px to count as a swipe
const DESKTOP_BREAKPOINT = 860
/** Walk up the DOM and return true if any ancestor is horizontally scrollable */
function isInHScrollable(el: Element | null): boolean {
while (el && el !== document.body) {
const style = window.getComputedStyle(el)
const ox = style.overflowX
if ((ox === 'scroll' || ox === 'auto') && el.scrollWidth > el.clientWidth) {
return true
}
el = el.parentElement
}
return false
}
/** Swipe left/right to navigate between the three main pages (mobile only) */
export function useSwipeNav() {
const navigate = useNavigate()
const location = useLocation()
useEffect(() => {
let startX = 0
let startY = 0
let startTarget: Element | null = null
let cancelled = false
const onTouchStart = (e: TouchEvent) => {
startX = e.touches[0].clientX
startY = e.touches[0].clientY
startTarget = e.target as Element
cancelled = false
}
const onTouchMove = (e: TouchEvent) => {
// If vertical movement dominates early, cancel the swipe so we never
// accidentally navigate while the user is scrolling.
const dx = Math.abs(e.touches[0].clientX - startX)
const dy = Math.abs(e.touches[0].clientY - startY)
if (!cancelled && dy > dx && dy > 10) cancelled = true
}
const onTouchEnd = (e: TouchEvent) => {
if (cancelled) return
if (window.innerWidth >= DESKTOP_BREAKPOINT) return
const dx = e.changedTouches[0].clientX - startX
const dy = e.changedTouches[0].clientY - startY
// Must be predominantly horizontal
if (Math.abs(dx) <= Math.abs(dy)) return
// Must clear the distance threshold
if (Math.abs(dx) < SWIPE_THRESHOLD) return
// Don't swipe-navigate when inside a horizontal scroll container
if (isInHScrollable(startTarget)) return
// Don't swipe-navigate when a modal/overlay is open
if (document.querySelector('.confirm-modal-overlay, .cropper-overlay, .reminder-modal-overlay')) return
const idx = PAGES.indexOf(location.pathname)
if (idx === -1) return
if (dx < 0 && idx < PAGES.length - 1) {
navigate(PAGES[idx + 1]) // swipe left → next page
} else if (dx > 0 && idx > 0) {
navigate(PAGES[idx - 1]) // swipe right → previous page
}
}
document.addEventListener('touchstart', onTouchStart, { passive: true })
document.addEventListener('touchmove', onTouchMove, { passive: true })
document.addEventListener('touchend', onTouchEnd, { passive: true })
return () => {
document.removeEventListener('touchstart', onTouchStart)
document.removeEventListener('touchmove', onTouchMove)
document.removeEventListener('touchend', onTouchEnd)
}
}, [navigate, location.pathname])
}