73 lines
2.1 KiB
TypeScript
73 lines
2.1 KiB
TypeScript
'use client'
|
|
|
|
import { useRef, useState, useCallback, type ReactNode } from 'react'
|
|
import { ListPlus } from 'lucide-react'
|
|
|
|
const THRESHOLD = 60
|
|
|
|
export function SwipeableRow({
|
|
onSwipeRight,
|
|
children,
|
|
}: {
|
|
onSwipeRight: () => void
|
|
children: ReactNode
|
|
}) {
|
|
const touchStartX = useRef(0)
|
|
const [offset, setOffset] = useState(0)
|
|
const [swiping, setSwiping] = useState(false)
|
|
const [confirmed, setConfirmed] = useState(false)
|
|
|
|
const handleTouchStart = useCallback((e: React.TouchEvent) => {
|
|
touchStartX.current = e.touches[0].clientX
|
|
setSwiping(true)
|
|
}, [])
|
|
|
|
const handleTouchMove = useCallback((e: React.TouchEvent) => {
|
|
if (!swiping) return
|
|
const delta = e.touches[0].clientX - touchStartX.current
|
|
// Only allow right swipe, cap at 120px
|
|
setOffset(Math.max(0, Math.min(delta, 120)))
|
|
}, [swiping])
|
|
|
|
const handleTouchEnd = useCallback(() => {
|
|
if (offset > THRESHOLD) {
|
|
onSwipeRight()
|
|
setConfirmed(true)
|
|
setTimeout(() => setConfirmed(false), 600)
|
|
}
|
|
setOffset(0)
|
|
setSwiping(false)
|
|
}, [offset, onSwipeRight])
|
|
|
|
return (
|
|
<div className="relative overflow-hidden touch-pan-y">
|
|
{/* Green reveal strip behind */}
|
|
<div
|
|
className={`absolute inset-y-0 left-0 flex items-center gap-2 pl-4 transition-colors ${
|
|
confirmed ? 'bg-green-500' : offset > THRESHOLD ? 'bg-green-600' : 'bg-green-600/80'
|
|
}`}
|
|
style={{ width: Math.max(offset, confirmed ? 200 : 0) }}
|
|
>
|
|
<ListPlus className="h-4 w-4 text-white flex-shrink-0" />
|
|
<span className="text-sm text-white font-medium whitespace-nowrap">
|
|
{confirmed ? 'Added!' : 'Add to Queue'}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Swipeable content */}
|
|
<div
|
|
onTouchStart={handleTouchStart}
|
|
onTouchMove={handleTouchMove}
|
|
onTouchEnd={handleTouchEnd}
|
|
className="relative bg-background"
|
|
style={{
|
|
transform: `translateX(${offset}px)`,
|
|
transition: swiping ? 'none' : 'transform 0.2s ease-out',
|
|
}}
|
|
>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|