import { useState, useCallback, useRef } from 'react' import { cn } from '@/lib/utils' import { RefreshCw } from 'lucide-react' interface PullToRefreshProps { onRefresh: () => Promise children: React.ReactNode className?: string } export function PullToRefresh({ onRefresh, children, className }: PullToRefreshProps) { const [pulling, setPulling] = useState(false) const [pullDistance, setPullDistance] = useState(0) const [refreshing, setRefreshing] = useState(false) const touchStartY = useRef(0) const containerRef = useRef(null) const maxPullDistance = 100 const refreshThreshold = 80 const onTouchStart = useCallback((e: React.TouchEvent) => { // Only allow pull-to-refresh when at top of scroll if (containerRef.current && containerRef.current.scrollTop === 0) { touchStartY.current = e.targetTouches[0].clientY setPulling(true) } }, []) const onTouchMove = useCallback((e: React.TouchEvent) => { if (!pulling) return const currentY = e.targetTouches[0].clientY const diff = currentY - touchStartY.current if (diff > 0) { // Resistance increases as user pulls further const resistance = 1 + (diff / maxPullDistance) * 0.5 const newDistance = Math.min(diff / resistance, maxPullDistance) setPullDistance(newDistance) } }, [pulling]) const onTouchEnd = useCallback(async () => { if (!pulling) return if (pullDistance >= refreshThreshold && !refreshing) { setRefreshing(true) try { await onRefresh() } finally { setRefreshing(false) } } setPulling(false) setPullDistance(0) }, [pulling, pullDistance, refreshing, onRefresh]) return (
{/* Pull indicator */}
= refreshThreshold && 'rotate-180' )} /> {refreshing ? 'Refreshing...' : pullDistance >= refreshThreshold ? 'Release to refresh' : 'Pull to refresh'}
{/* Content with offset when pulling */}
{children}
) }