import * as React from 'react'; import classNames from 'classnames'; interface PullToRefreshProps { onRefresh: () => Promise; children: React.ReactNode; className?: string; } export function PullToRefresh({ onRefresh, children, className }: PullToRefreshProps) { const containerRef = React.useRef(null); const [startY, setStartY] = React.useState(0); const [pullDistance, setPullDistance] = React.useState(0); const [isRefreshing, setIsRefreshing] = React.useState(false); const [isPulling, setIsPulling] = React.useState(false); const threshold = 80; const maxPull = 120; const resistance = 2.5; const handleTouchStart = (e: React.TouchEvent) => { // Only start if scrolled to top if (containerRef.current && containerRef.current.scrollTop === 0) { setStartY(e.touches[0].clientY); setIsPulling(true); } }; const handleTouchMove = (e: React.TouchEvent) => { if (!isPulling || isRefreshing) return; const currentY = e.touches[0].clientY; const diff = (currentY - startY) / resistance; if (diff > 0) { const distance = Math.min(maxPull, diff); setPullDistance(distance); // Prevent default scroll when pulling if (containerRef.current && containerRef.current.scrollTop === 0) { e.preventDefault(); } } }; const handleTouchEnd = async () => { if (!isPulling || isRefreshing) return; setIsPulling(false); if (pullDistance >= threshold) { setIsRefreshing(true); setPullDistance(60); // Keep indicator visible during refresh try { await onRefresh(); } finally { setIsRefreshing(false); setPullDistance(0); } } else { setPullDistance(0); } }; const progress = Math.min(1, pullDistance / threshold); const rotation = pullDistance * 3; return (
{/* Pull indicator */}
10 ? 1 : 0, }} >
{/* Pull text */} {pullDistance > 10 && !isRefreshing && (
{pullDistance >= threshold ? 'Release to refresh' : 'Pull to refresh'}
)} {/* Content */}
{children}
); } export default PullToRefresh;