import { useEffect } from "react" import type { DrawerDirection } from "./drawer.type" interface UseSwipeToCloseProps { isOpen: boolean direction: DrawerDirection onClose: () => void threshold?: number } export function useSwipeToClose({ isOpen, direction, onClose, threshold = 50, }: UseSwipeToCloseProps) { useEffect(() => { if (!isOpen) return let startX = 0 let startY = 0 let isTracking = false let wheelAccumulator = { x: 0, y: 0 } let wheelTimeout: NodeJS.Timeout | null = null // Reset wheel accumulator after a timeout const resetWheelAccumulator = () => { wheelAccumulator = { x: 0, y: 0 } } // Handle trackpad swipe via wheel events const handleWheel = (e: WheelEvent) => { // Skip if ctrl key is pressed (likely pinch-to-zoom) if (e.ctrlKey) return // Accumulate wheel delta for better trackpad detection wheelAccumulator.x += e.deltaX wheelAccumulator.y += e.deltaY // Clear previous timeout and set new one if (wheelTimeout) clearTimeout(wheelTimeout) wheelTimeout = setTimeout(resetWheelAccumulator, 100) // Check if accumulated delta meets threshold for swipe direction const shouldClose = (() => { switch (direction) { case "bottom": return wheelAccumulator.y < -threshold // Swipe up to close bottom drawer case "top": return wheelAccumulator.y > threshold // Swipe down to close top drawer case "left": return wheelAccumulator.x > threshold // Swipe right to close left drawer case "right": return wheelAccumulator.x < -threshold // Swipe left to close right drawer default: return false } })() if (shouldClose) { e.preventDefault() onClose() resetWheelAccumulator() } } // Handle touch swipe gestures const handleTouchStart = (e: TouchEvent) => { if (e.touches.length !== 1) return const touch = e.touches[0] startX = touch.clientX startY = touch.clientY isTracking = true } const handleTouchMove = (e: TouchEvent) => { if (!isTracking || e.touches.length !== 1) return const touch = e.touches[0] const deltaX = touch.clientX - startX const deltaY = touch.clientY - startY // Determine if swipe direction matches drawer direction const shouldClose = (() => { switch (direction) { case "bottom": return deltaY < -threshold && Math.abs(deltaY) > Math.abs(deltaX) // Swipe up to close bottom drawer case "top": return deltaY > threshold && Math.abs(deltaY) > Math.abs(deltaX) // Swipe down to close top drawer case "left": return deltaX > threshold && Math.abs(deltaX) > Math.abs(deltaY) // Swipe right to close left drawer case "right": return deltaX < -threshold && Math.abs(deltaX) > Math.abs(deltaY) // Swipe left to close right drawer default: return false } })() if (shouldClose) { e.preventDefault() onClose() isTracking = false } } const handleTouchEnd = () => { isTracking = false } // Handle keyboard escape const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { onClose() } } // Add event listeners to document for global swipe detection document.addEventListener("wheel", handleWheel, { passive: false }) document.addEventListener("touchstart", handleTouchStart, { passive: false }) document.addEventListener("touchmove", handleTouchMove, { passive: false }) document.addEventListener("touchend", handleTouchEnd) document.addEventListener("keydown", handleKeyDown) return () => { document.removeEventListener("wheel", handleWheel) document.removeEventListener("touchstart", handleTouchStart) document.removeEventListener("touchmove", handleTouchMove) document.removeEventListener("touchend", handleTouchEnd) document.removeEventListener("keydown", handleKeyDown) if (wheelTimeout) clearTimeout(wheelTimeout) } }, [isOpen, direction, onClose, threshold]) }