{ "name": "file-upload", "type": "registry:ui", "registryDependencies": [], "dependencies": [], "devDependencies": [], "tailwind": {}, "cssVars": { "light": {}, "dark": {} }, "description": "A component for creating drag-and-drop file upload interfaces with support for single or multiple files, custom triggers, and visual feedback during file dragging operations.", "files": [ { "path": "file-upload.tsx", "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n Children,\n cloneElement,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\"\nimport { createPortal } from \"react-dom\"\n\ntype FileUploadContextValue = {\n isDragging: boolean\n inputRef: React.RefObject\n multiple?: boolean\n disabled?: boolean\n}\n\nconst FileUploadContext = createContext(null)\n\nexport type FileUploadProps = {\n onFilesAdded: (files: File[]) => void\n children: React.ReactNode\n multiple?: boolean\n accept?: string\n disabled?: boolean\n}\n\nfunction FileUpload({\n onFilesAdded,\n children,\n multiple = true,\n accept,\n disabled = false,\n}: FileUploadProps) {\n const inputRef = useRef(null)\n const [isDragging, setIsDragging] = useState(false)\n const dragCounter = useRef(0)\n\n const handleFiles = useCallback(\n (files: FileList) => {\n const newFiles = Array.from(files)\n if (multiple) {\n onFilesAdded(newFiles)\n } else {\n onFilesAdded(newFiles.slice(0, 1))\n }\n },\n [multiple, onFilesAdded]\n )\n\n useEffect(() => {\n const handleDrag = (e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n }\n\n const handleDragIn = (e: DragEvent) => {\n handleDrag(e)\n dragCounter.current++\n if (e.dataTransfer?.items.length) setIsDragging(true)\n }\n\n const handleDragOut = (e: DragEvent) => {\n handleDrag(e)\n dragCounter.current--\n if (dragCounter.current === 0) setIsDragging(false)\n }\n\n const handleDrop = (e: DragEvent) => {\n handleDrag(e)\n setIsDragging(false)\n dragCounter.current = 0\n if (e.dataTransfer?.files.length) {\n handleFiles(e.dataTransfer.files)\n }\n }\n\n window.addEventListener(\"dragenter\", handleDragIn)\n window.addEventListener(\"dragleave\", handleDragOut)\n window.addEventListener(\"dragover\", handleDrag)\n window.addEventListener(\"drop\", handleDrop)\n\n return () => {\n window.removeEventListener(\"dragenter\", handleDragIn)\n window.removeEventListener(\"dragleave\", handleDragOut)\n window.removeEventListener(\"dragover\", handleDrag)\n window.removeEventListener(\"drop\", handleDrop)\n }\n }, [handleFiles, onFilesAdded, multiple])\n\n const handleFileSelect = (e: React.ChangeEvent) => {\n if (e.target.files?.length) {\n handleFiles(e.target.files)\n e.target.value = \"\"\n }\n }\n\n return (\n \n \n {children}\n \n )\n}\n\nexport type FileUploadTriggerProps =\n React.ComponentPropsWithoutRef<\"button\"> & {\n asChild?: boolean\n }\n\nfunction FileUploadTrigger({\n asChild = false,\n className,\n children,\n ...props\n}: FileUploadTriggerProps) {\n const context = useContext(FileUploadContext)\n const handleClick = () => context?.inputRef.current?.click()\n\n if (asChild) {\n const child = Children.only(children) as React.ReactElement<\n React.HTMLAttributes\n >\n return cloneElement(child, {\n ...props,\n role: \"button\",\n className: cn(className, child.props.className),\n onClick: (e: React.MouseEvent) => {\n e.stopPropagation()\n handleClick()\n child.props.onClick?.(e as React.MouseEvent)\n },\n })\n }\n\n return (\n \n {children}\n \n )\n}\n\ntype FileUploadContentProps = React.HTMLAttributes\n\nfunction FileUploadContent({ className, ...props }: FileUploadContentProps) {\n const context = useContext(FileUploadContext)\n const [mounted, setMounted] = useState(false)\n\n useEffect(() => {\n setMounted(true)\n return () => setMounted(false)\n }, [])\n\n if (!context?.isDragging || !mounted || context?.disabled) {\n return null\n }\n\n const content = (\n \n )\n\n return createPortal(content, document.body)\n}\n\nexport { FileUpload, FileUploadTrigger, FileUploadContent }\n", "type": "registry:ui" } ] }