added swipe gestures
This commit is contained in:
@@ -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
83
src/hooks/useSwipeNav.ts
Normal 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])
|
||||
}
|
||||
Reference in New Issue
Block a user