{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "file-upload",
  "title": "File Upload",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/file-upload/file-upload.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { FileIcon, X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\n\nexport interface FileUploadProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'value' | 'defaultValue' | 'content'> {\n  accept?: string\n  multiple?: boolean\n  disabled?: boolean\n  /** Controlled file list. */\n  value?: File[]\n  /** Uncontrolled initial file list. */\n  defaultValue?: File[]\n  onValueChange?: (files: File[]) => void\n  /** Custom dropzone icon. */\n  icon?: React.ReactNode\n  /** Custom dropzone label content (replaces the default click/drag copy). */\n  children?: React.ReactNode\n  /** Rendered below the dropzone (e.g. the file list). */\n  content?: React.ReactNode\n  className?: string\n}\n\nconst FileUpload = React.forwardRef<HTMLInputElement, FileUploadProps>(\n  (\n    { className, accept, multiple, disabled, value, defaultValue, onValueChange, icon, children, content, ...props },\n    ref,\n  ) => {\n    const innerRef = React.useRef<HTMLInputElement | null>(null)\n    const setRefs = React.useCallback(\n      (node: HTMLInputElement | null) => {\n        innerRef.current = node\n        if (typeof ref === 'function') ref(node)\n        else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node\n      },\n      [ref],\n    )\n\n    const isControlled = value !== undefined\n    const [internal, setInternal] = React.useState<File[]>(defaultValue ?? [])\n\n    const [isDragging, setIsDragging] = React.useState(false)\n\n    function emit(files: File[]) {\n      if (!isControlled) setInternal(files)\n      onValueChange?.(files)\n    }\n\n    function handleFiles(files: FileList | null) {\n      if (!files) return\n      const fileArray = Array.from(files)\n      const first = fileArray[0]\n      emit(multiple ? fileArray : first ? [first] : [])\n    }\n\n    function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {\n      handleFiles(e.target.files)\n    }\n\n    function handleDrop(e: React.DragEvent) {\n      e.preventDefault()\n      setIsDragging(false)\n      handleFiles(e.dataTransfer?.files ?? null)\n    }\n\n    function handleDragOver(e: React.DragEvent) {\n      e.preventDefault()\n      setIsDragging(true)\n    }\n\n    function handleDragLeave() {\n      setIsDragging(false)\n    }\n\n    function openFilePicker() {\n      innerRef.current?.click()\n    }\n\n    return (\n      <div className={cn('space-y-3', className)} {...props}>\n        <input\n          ref={setRefs}\n          type=\"file\"\n          accept={accept}\n          multiple={multiple}\n          disabled={disabled}\n          className=\"sr-only\"\n          onChange={handleInputChange}\n        />\n\n        <div\n          className={cn(\n            'border-muted-foreground/25 hover:border-muted-foreground/50 bg-muted/50 flex flex-col items-center justify-center rounded-lg border-2 border-dashed p-8 transition-colors duration-200',\n            isDragging && 'border-primary bg-primary/5',\n            disabled && 'pointer-events-none opacity-50',\n          )}\n          onClick={openFilePicker}\n          onDrop={handleDrop}\n          onDragOver={handleDragOver}\n          onDragLeave={handleDragLeave}\n        >\n          {icon ?? (\n            <svg className=\"text-muted-foreground mb-2 size-10\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" viewBox=\"0 0 24 24\">\n              <path\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5\"\n              />\n            </svg>\n          )}\n          {children ?? (\n            <>\n              <p className=\"text-muted-foreground text-sm\">\n                <span className=\"text-foreground font-semibold\">Click to upload</span> or drag and drop\n              </p>\n              {accept && <p className=\"text-muted-foreground/70 mt-1 text-xs\">{accept}</p>}\n            </>\n          )}\n        </div>\n\n        {content}\n      </div>\n    )\n  },\n)\nFileUpload.displayName = 'FileUpload'\n\nexport interface FileUploadTriggerProps extends React.HTMLAttributes<HTMLDivElement> {\n  className?: string\n}\n\nconst FileUploadTrigger = React.forwardRef<HTMLDivElement, FileUploadTriggerProps>(\n  ({ className, children, ...props }, ref) => (\n    <div ref={ref} className={cn('cursor-pointer', className)} {...props}>\n      {children}\n    </div>\n  ),\n)\nFileUploadTrigger.displayName = 'FileUploadTrigger'\n\nexport interface FileUploadContentProps extends React.HTMLAttributes<HTMLDivElement> {\n  className?: string\n}\n\nconst FileUploadContent = React.forwardRef<HTMLDivElement, FileUploadContentProps>(\n  ({ className, children, ...props }, ref) => (\n    <div ref={ref} className={cn('space-y-2', className)} {...props}>\n      {children}\n    </div>\n  ),\n)\nFileUploadContent.displayName = 'FileUploadContent'\n\nexport interface FileUploadItemProps extends React.HTMLAttributes<HTMLDivElement> {\n  file: File\n  onRemove?: () => void\n  className?: string\n}\n\nconst FileUploadItem = React.forwardRef<HTMLDivElement, FileUploadItemProps>(\n  ({ className, file, onRemove, children, ...props }, ref) => (\n    <div ref={ref} className={cn('bg-muted/50 flex items-center gap-3 rounded-md border p-3', className)} {...props}>\n      {children ?? (\n        <>\n          <FileIcon className=\"text-muted-foreground size-8 shrink-0\" />\n          <div className=\"min-w-0 flex-1\">\n            <p className=\"truncate text-sm font-medium\">{file.name}</p>\n            <p className=\"text-muted-foreground text-xs\">{(file.size / 1024).toFixed(1)} KB</p>\n          </div>\n          <button\n            type=\"button\"\n            className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring ml-auto rounded-sm transition-colors duration-200 focus-visible:ring-1 focus-visible:outline-none\"\n            onClick={onRemove}\n          >\n            <X className=\"size-4\" aria-hidden=\"true\" />\n            <span className=\"sr-only\">Remove file</span>\n          </button>\n        </>\n      )}\n    </div>\n  ),\n)\nFileUploadItem.displayName = 'FileUploadItem'\n\nexport interface FileUploadItemNameProps extends React.HTMLAttributes<HTMLParagraphElement> {\n  className?: string\n}\n\nconst FileUploadItemName = React.forwardRef<HTMLParagraphElement, FileUploadItemNameProps>(\n  ({ className, children, ...props }, ref) => (\n    <p ref={ref} className={cn('truncate text-sm font-medium', className)} {...props}>\n      {children}\n    </p>\n  ),\n)\nFileUploadItemName.displayName = 'FileUploadItemName'\n\nexport interface FileUploadItemSizeProps extends React.HTMLAttributes<HTMLSpanElement> {\n  className?: string\n}\n\nconst FileUploadItemSize = React.forwardRef<HTMLSpanElement, FileUploadItemSizeProps>(\n  ({ className, children, ...props }, ref) => (\n    <span ref={ref} className={cn('text-muted-foreground text-xs', className)} {...props}>\n      {children}\n    </span>\n  ),\n)\nFileUploadItemSize.displayName = 'FileUploadItemSize'\n\nexport {\n  FileUpload,\n  FileUploadTrigger,\n  FileUploadContent,\n  FileUploadItem,\n  FileUploadItemName,\n  FileUploadItemSize,\n}\n",
      "type": "registry:ui",
      "target": "~/components/ui/file-upload/file-upload.tsx"
    },
    {
      "path": "packages/registry-react/components/file-upload/index.ts",
      "content": "export {\n  FileUpload,\n  FileUploadTrigger,\n  FileUploadContent,\n  FileUploadItem,\n  FileUploadItemName,\n  FileUploadItemSize,\n  type FileUploadProps,\n  type FileUploadTriggerProps,\n  type FileUploadContentProps,\n  type FileUploadItemProps,\n  type FileUploadItemNameProps,\n  type FileUploadItemSizeProps,\n} from './file-upload'\n",
      "type": "registry:ui",
      "target": "~/components/ui/file-upload/index.ts"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Drag-and-drop file dropzone with click-to-browse fallback, file-type filtering, multi-file support, and per-file progress + remove controls. Wraps native `<input type=\"file\">` with proper a11y.",
  "categories": [
    "form"
  ]
}