import { useState, useRef, useCallback, ReactNode, TouchEvent } from 'react'; import { RefreshCw } from 'lucide-react'; interface PullToRefreshProps { onRefresh: () => Promise; children: ReactNode; disabled?: boolean; } export function PullToRefresh({ onRefresh, children, disabled = false }: PullToRefreshProps) { const [pulling, setPulling] = useState(false); const [refreshing, setRefreshing] = useState(false); const [pullDistance, setPullDistance] = useState(0); const startY = useRef(0); const containerRef = useRef(null); const THRESHOLD = 80; // pixels to pull before triggering refresh const MAX_PULL = 120; const handleTouchStart = useCallback((e: TouchEvent) => { if (disabled || refreshing) return; const scrollTop = containerRef.current?.scrollTop || 0; if (scrollTop > 0) return; // Only trigger at top startY.current = e.touches[0].clientY; setPulling(true); }, [disabled, refreshing]); const handleTouchMove = useCallback((e: TouchEvent) => { if (!pulling || refreshing) return; const currentY = e.touches[0].clientY; const diff = currentY - startY.current; if (diff > 0) { // Add resistance to pull const resistance = Math.min(diff * 0.5, MAX_PULL); setPullDistance(resistance); } }, [pulling, refreshing]); const handleTouchEnd = useCallback(async () => { if (!pulling) return; setPulling(false); if (pullDistance >= THRESHOLD && !refreshing) { setRefreshing(true); setPullDistance(THRESHOLD); try { await onRefresh(); } finally { setRefreshing(false); setPullDistance(0); } } else { setPullDistance(0); } }, [pulling, pullDistance, refreshing, onRefresh]); const progress = Math.min(pullDistance / THRESHOLD, 1); const rotation = progress * 180; return (
{/* Pull indicator */}
{/* Content with pull transform */}
{children}
); }