jefflix-website/components/music/swipeable-row.tsx

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>
)
}