import * as React from 'react'; import classNames from 'classnames'; interface SwipeableCardProps { children: React.ReactNode; onSwipeLeft?: () => void; onSwipeRight?: () => void; leftAction?: React.ReactNode; rightAction?: React.ReactNode; className?: string; } export function SwipeableCard({ children, onSwipeLeft, onSwipeRight, leftAction, rightAction, className, }: SwipeableCardProps) { const cardRef = React.useRef(null); const [startX, setStartX] = React.useState(0); const [currentX, setCurrentX] = React.useState(0); const [isSwiping, setIsSwiping] = React.useState(false); const threshold = 100; const maxSwipe = 150; const handleTouchStart = (e: React.TouchEvent) => { setStartX(e.touches[0].clientX); setIsSwiping(true); }; const handleTouchMove = (e: React.TouchEvent) => { if (!isSwiping) return; const diff = e.touches[0].clientX - startX; const clampedDiff = Math.max(-maxSwipe, Math.min(maxSwipe, diff)); // Only allow swiping in directions that have actions if (diff > 0 && !onSwipeRight) return; if (diff < 0 && !onSwipeLeft) return; setCurrentX(clampedDiff); }; const handleTouchEnd = () => { setIsSwiping(false); if (currentX > threshold && onSwipeRight) { onSwipeRight(); } else if (currentX < -threshold && onSwipeLeft) { onSwipeLeft(); } setCurrentX(0); }; const swipeProgress = Math.abs(currentX) / threshold; const direction = currentX > 0 ? 'right' : 'left'; return (
{/* Left action background */} {rightAction && (
0, 'opacity-0': currentX <= 0, } )} style={{ width: Math.abs(currentX) }} > {rightAction}
)} {/* Right action background */} {leftAction && (
= 0, } )} style={{ width: Math.abs(currentX) }} > {leftAction}
)} {/* Main card content */}
{children}
{/* Swipe indicator */} {isSwiping && Math.abs(currentX) > 20 && (
{direction === 'right' && onSwipeRight && 'Release to confirm'} {direction === 'left' && onSwipeLeft && 'Release to delete'}
)}
); } export default SwipeableCard;