Download apps/web/src/components/shared/FileDropZone.tsx from hunian003/message: direct link, hf CLI and curl.
- Browser
- Download file 3.45 kB
-
https://huggingface.co/spaces/hunian003/message/resolve/main/apps/web/src/components/shared/FileDropZone.tsx
- Command line
-
hf download hf://spaces/hunian003/message/apps/web/src/components/shared/FileDropZone.tsx
-
curl -L -o FileDropZone.tsx https://huggingface.co/spaces/hunian003/message/resolve/main/apps/web/src/components/shared/FileDropZone.tsx
3.45 kB
| import { useState, useRef, useCallback } from 'react'; | |
| import { Upload } from 'lucide-react'; | |
| import { cn } from '@/lib/utils'; | |
| interface FileDropZoneProps { | |
| accept?: string; | |
| multiple?: boolean; | |
| onFilesSelected: (files: File[]) => void; | |
| description?: string; | |
| disabled?: boolean; | |
| className?: string; | |
| } | |
| export function FileDropZone({ | |
| accept, | |
| multiple = false, | |
| onFilesSelected, | |
| description, | |
| disabled = false, | |
| className, | |
| }: FileDropZoneProps) { | |
| const [isDragging, setIsDragging] = useState(false); | |
| const fileInputRef = useRef<HTMLInputElement>(null); | |
| const handleDragOver = useCallback((e: React.DragEvent) => { | |
| e.preventDefault(); | |
| if (!disabled) { | |
| setIsDragging(true); | |
| } | |
| }, [disabled]); | |
| const handleDragLeave = useCallback((e: React.DragEvent) => { | |
| e.preventDefault(); | |
| setIsDragging(false); | |
| }, []); | |
| const handleDrop = useCallback( | |
| (e: React.DragEvent) => { | |
| e.preventDefault(); | |
| setIsDragging(false); | |
| if (disabled) return; | |
| const files = Array.from(e.dataTransfer.files); | |
| if (files.length > 0) { | |
| if (multiple) { | |
| onFilesSelected(files); | |
| } else { | |
| onFilesSelected([files[0]]); | |
| } | |
| } | |
| }, | |
| [disabled, multiple, onFilesSelected] | |
| ); | |
| const handleClick = useCallback(() => { | |
| if (!disabled) { | |
| fileInputRef.current?.click(); | |
| } | |
| }, [disabled]); | |
| const handleFileInputChange = useCallback( | |
| (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const files = Array.from(e.target.files || []); | |
| if (files.length > 0) { | |
| onFilesSelected(files); | |
| } | |
| // Reset input value to allow selecting same file again | |
| if (fileInputRef.current) { | |
| fileInputRef.current.value = ''; | |
| } | |
| }, | |
| [onFilesSelected] | |
| ); | |
| return ( | |
| <div | |
| className={cn( | |
| 'relative border-2 border-dashed rounded-xl p-8 text-center cursor-pointer transition-all duration-200', | |
| 'bg-gradient-to-b from-teal-50/30 to-cyan-50/20', | |
| isDragging && 'border-teal-400 bg-teal-50/50 scale-[1.02]', | |
| !isDragging && 'border-teal-200/50 hover:border-teal-300/70 hover:bg-teal-50/40', | |
| disabled && 'opacity-50 cursor-not-allowed', | |
| className | |
| )} | |
| onDragOver={handleDragOver} | |
| onDragLeave={handleDragLeave} | |
| onDrop={handleDrop} | |
| onClick={handleClick} | |
| > | |
| {/* 图标容器 */} | |
| <div | |
| className={cn( | |
| 'mx-auto mb-4 h-12 w-12 rounded-xl flex items-center justify-center', | |
| 'bg-gradient-to-br from-teal-500 to-cyan-600', | |
| 'shadow-lg shadow-teal-500/20', | |
| 'transition-transform duration-200', | |
| isDragging && 'scale-110' | |
| )} | |
| > | |
| <Upload className="h-6 w-6 text-white" /> | |
| </div> | |
| {/* 主文字 */} | |
| <p className={cn( | |
| 'text-sm font-medium mb-1 transition-colors', | |
| isDragging ? 'text-teal-700' : 'text-gray-700' | |
| )}> | |
| {isDragging ? '松开以上传文件' : '点击或拖放文件到此处'} | |
| </p> | |
| {/* 副文字 */} | |
| {description && ( | |
| <p className="text-xs text-teal-600/60">{description}</p> | |
| )} | |
| {/* 隐藏的文件输入 */} | |
| <input | |
| ref={fileInputRef} | |
| type="file" | |
| accept={accept} | |
| multiple={multiple} | |
| onChange={handleFileInputChange} | |
| className="hidden" | |
| disabled={disabled} | |
| /> | |
| </div> | |
| ); | |
| } |