import { useEffect, useRef, useState } from "react"; import "../../styles/components/ConfirmSlideModal.css"; const HANDLE_SIZE = 40; export default function ConfirmSlideModal({ isOpen, title, description, confirmLabel = "Confirm", onClose, onConfirm }) { const trackRef = useRef(null); const endFlashTimeoutRef = useRef(null); const reachedEndRef = useRef(false); const [dragX, setDragX] = useState(0); const [dragging, setDragging] = useState(false); const [isAtEnd, setIsAtEnd] = useState(false); const [endFlash, setEndFlash] = useState(false); const getDragPositionFromClientX = (clientX) => { const track = trackRef.current; if (!track) return 0; const rect = track.getBoundingClientRect(); return Math.min( Math.max(0, clientX - rect.left - HANDLE_SIZE / 2), rect.width - HANDLE_SIZE ); }; const isEndPosition = (position) => { const track = trackRef.current; if (!track) return false; const maxDrag = track.clientWidth - HANDLE_SIZE; const endTolerancePx = 1; return position >= maxDrag - endTolerancePx; }; const triggerEndFeedback = () => { setEndFlash(true); if (endFlashTimeoutRef.current) { clearTimeout(endFlashTimeoutRef.current); } endFlashTimeoutRef.current = setTimeout(() => setEndFlash(false), 140); if (typeof navigator !== "undefined" && typeof navigator.vibrate === "function") { navigator.vibrate(16); } }; const handlePointerDown = (event) => { event.preventDefault(); setDragging(true); reachedEndRef.current = false; setIsAtEnd(false); event.currentTarget.setPointerCapture(event.pointerId); }; const handlePointerMove = (event) => { if (!dragging) return; const next = getDragPositionFromClientX(event.clientX); const nextAtEnd = isEndPosition(next); setDragX(next); setIsAtEnd((prev) => (prev === nextAtEnd ? prev : nextAtEnd)); if (nextAtEnd && !reachedEndRef.current) { reachedEndRef.current = true; triggerEndFeedback(); } if (!nextAtEnd) { reachedEndRef.current = false; } }; const handlePointerUp = (event) => { if (!dragging) return; setDragging(false); event.currentTarget.releasePointerCapture(event.pointerId); const releaseX = getDragPositionFromClientX(event.clientX); const releaseAtEnd = isEndPosition(releaseX); setIsAtEnd((prev) => (prev ? false : prev)); if (releaseAtEnd && !reachedEndRef.current) { triggerEndFeedback(); } setDragX(0); if (releaseAtEnd) { onConfirm(); } }; const handlePointerCancel = (event) => { if (!dragging) return; setDragging(false); event.currentTarget.releasePointerCapture(event.pointerId); setIsAtEnd((prev) => (prev ? false : prev)); setDragX(0); }; useEffect(() => { return () => { if (endFlashTimeoutRef.current) { clearTimeout(endFlashTimeoutRef.current); } }; }, []); useEffect(() => { if (!isOpen) return undefined; const handleKeyDown = (event) => { if (event.key === "Escape") { onClose(); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [isOpen, onClose]); useEffect(() => { if (!isOpen) { setDragX(0); setDragging(false); setIsAtEnd(false); setEndFlash(false); reachedEndRef.current = false; } }, [isOpen]); if (!isOpen) return null; const isActive = isAtEnd || endFlash; return (
event.stopPropagation()}>

{title}

{description ?

{description}

: null}
Slide to confirm
release
{confirmLabel}
); }