Download vetcopilot-app/src/components/PullToRefresh.tsx from WWsCa/VetNet-LCPS: direct link, hf CLI and curl.
- Browser
- Download file 3.51 kB
-
https://huggingface.co/WWsCa/VetNet-LCPS/resolve/main/vetcopilot-app/src/components/PullToRefresh.tsx
- Command line
-
hf download hf://WWsCa/VetNet-LCPS/vetcopilot-app/src/components/PullToRefresh.tsx
-
curl -L -o PullToRefresh.tsx https://huggingface.co/WWsCa/VetNet-LCPS/resolve/main/vetcopilot-app/src/components/PullToRefresh.tsx
3.51 kB
| import React, { useState, useRef, useCallback, useEffect } from 'react' | |
| import { useTranslation } from '../i18n' | |
| interface PullToRefreshProps { | |
| onRefresh: () => Promise<void> | |
| children: React.ReactNode | |
| className?: string | |
| } | |
| export default function PullToRefresh({ onRefresh, children, className = '' }: PullToRefreshProps) { | |
| const { t } = useTranslation() | |
| const [refreshing, setRefreshing] = useState(false) | |
| const [pullDistance, setPullDistance] = useState(0) | |
| const containerRef = useRef<HTMLDivElement>(null) | |
| const startY = useRef(0) | |
| const pulling = useRef(false) | |
| const refreshThreshold = 60 | |
| const handleTouchStart = useCallback((e: React.TouchEvent) => { | |
| if (containerRef.current && containerRef.current.scrollTop <= 0) { | |
| startY.current = e.touches[0].clientY | |
| pulling.current = true | |
| } | |
| }, []) | |
| const handleTouchMove = useCallback((e: React.TouchEvent) => { | |
| if (!pulling.current || refreshing) return | |
| const delta = e.touches[0].clientY - startY.current | |
| if (delta > 0) { | |
| setPullDistance(Math.min(delta * 0.4, 100)) | |
| } | |
| }, [refreshing]) | |
| const handleTouchEnd = useCallback(async () => { | |
| if (!pulling.current) return | |
| pulling.current = false | |
| if (pullDistance >= refreshThreshold && !refreshing) { | |
| setRefreshing(true) | |
| try { | |
| await onRefresh() | |
| } finally { | |
| setRefreshing(false) | |
| setPullDistance(0) | |
| } | |
| } else { | |
| setPullDistance(0) | |
| } | |
| }, [pullDistance, refreshing, onRefresh]) | |
| useEffect(() => { | |
| if (!refreshing && pullDistance === 0) return | |
| if (!refreshing && pullDistance < refreshThreshold) { | |
| const timer = setTimeout(() => setPullDistance(0), 300) | |
| return () => clearTimeout(timer) | |
| } | |
| }, [refreshing, pullDistance]) | |
| return ( | |
| <div | |
| ref={containerRef} | |
| className={`relative overflow-y-auto ${className}`} | |
| onTouchStart={handleTouchStart} | |
| onTouchMove={handleTouchMove} | |
| onTouchEnd={handleTouchEnd} | |
| > | |
| <div | |
| className="flex items-center justify-center overflow-hidden transition-all duration-300" | |
| style={{ height: pullDistance }} | |
| > | |
| <div className={`flex items-center gap-2 text-xs text-gray-400 transition-all ${refreshing ? 'opacity-100' : ''}`}> | |
| {refreshing ? ( | |
| <> | |
| <svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"> | |
| <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" /> | |
| <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" /> | |
| </svg> | |
| {t('components.pullToRefresh.refreshing')} | |
| </> | |
| ) : pullDistance >= refreshThreshold ? ( | |
| <> | |
| <svg className="w-4 h-4 rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> | |
| <path strokeLinecap="round" strokeLinejoin="round" d="M19 14l-7 7m0 0l-7-7m7 7V3" /> | |
| </svg> | |
| {t('components.pullToRefresh.release')} | |
| </> | |
| ) : ( | |
| <> | |
| <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> | |
| <path strokeLinecap="round" strokeLinejoin="round" d="M19 14l-7 7m0 0l-7-7m7 7V3" /> | |
| </svg> | |
| {t('components.pullToRefresh.pullDown')} | |
| </> | |
| )} | |
| </div> | |
| </div> | |
| {children} | |
| </div> | |
| ) | |
| } | |