{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "file-uploader",
  "type": "registry:block",
  "title": "File Uploader",
  "description": "A component for uploading multiple files with preview and progress indicators.",
  "dependencies": [
    "react-dropzone",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://edgestore.dev/r/dropzone.json",
    "https://edgestore.dev/r/progress-bar.json",
    "https://edgestore.dev/r/uploader-provider.json"
  ],
  "files": [
    {
      "path": "examples/components/src/components/upload/multi-file.tsx",
      "content": "'use client';\n\nimport { cn } from '@/lib/utils';\nimport {\n  AlertCircleIcon,\n  CheckCircleIcon,\n  FileIcon,\n  Trash2Icon,\n  XIcon,\n} from 'lucide-react';\nimport * as React from 'react';\nimport { type DropzoneOptions } from 'react-dropzone';\nimport { Dropzone } from './dropzone';\nimport { ProgressBar } from './progress-bar';\nimport { formatFileSize, useUploader } from './uploader-provider';\n\n/**\n * Displays a list of files with their upload status, progress, and controls.\n *\n * @component\n * @example\n * ```tsx\n * <FileList className=\"my-4\" />\n * ```\n */\nconst FileList = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n  const { fileStates, removeFile, cancelUpload } = useUploader();\n\n  if (!fileStates.length) return null;\n\n  return (\n    <div\n      ref={ref}\n      className={cn('mt-3 flex w-full flex-col gap-2', className)}\n      {...props}\n    >\n      {fileStates.map(({ file, abortController, progress, status, key }) => {\n        return (\n          <div\n            key={key}\n            className=\"shadow-xs flex flex-col justify-center rounded border border-border px-4 py-3\"\n          >\n            <div className=\"flex items-center gap-3 text-foreground\">\n              <FileIcon className=\"h-8 w-8 shrink-0 text-muted-foreground\" />\n              <div className=\"min-w-0 flex-1\">\n                <div className=\"flex items-center justify-between text-xs\">\n                  <div className=\"truncate text-sm\">\n                    <div className=\"overflow-hidden text-ellipsis whitespace-nowrap font-medium\">\n                      {file.name}\n                    </div>\n                    <div className=\"text-xs text-muted-foreground\">\n                      {formatFileSize(file.size)}\n                    </div>\n                  </div>\n\n                  <div className=\"ml-2 flex items-center gap-2\">\n                    {status === 'ERROR' && (\n                      <div className=\"flex items-center text-xs text-destructive\">\n                        <AlertCircleIcon className=\"mr-1 h-4 w-4\" />\n                      </div>\n                    )}\n\n                    {status === 'UPLOADING' && (\n                      <div className=\"flex flex-col items-end\">\n                        {abortController && (\n                          <button\n                            type=\"button\"\n                            className=\"rounded-md p-0.5 transition-colors duration-200 hover:bg-secondary\"\n                            disabled={progress === 100}\n                            onClick={() => {\n                              cancelUpload(key);\n                            }}\n                          >\n                            <XIcon className=\"block h-4 w-4 shrink-0 text-muted-foreground\" />\n                          </button>\n                        )}\n                        <div>{Math.round(progress)}%</div>\n                      </div>\n                    )}\n\n                    {status !== 'UPLOADING' && status !== 'COMPLETE' && (\n                      <button\n                        type=\"button\"\n                        className=\"rounded-md p-1 text-muted-foreground transition-colors duration-200 hover:bg-secondary hover:text-destructive\"\n                        onClick={() => {\n                          removeFile(key);\n                        }}\n                        title=\"Remove\"\n                      >\n                        <Trash2Icon className=\"block h-4 w-4 shrink-0\" />\n                      </button>\n                    )}\n\n                    {status === 'COMPLETE' && (\n                      <CheckCircleIcon className=\"h-5 w-5 shrink-0 text-primary\" />\n                    )}\n                  </div>\n                </div>\n              </div>\n            </div>\n\n            {/* Progress Bar */}\n            {status === 'UPLOADING' && <ProgressBar progress={progress} />}\n          </div>\n        );\n      })}\n    </div>\n  );\n});\nFileList.displayName = 'FileList';\n\n/**\n * Props for the FileUploader component.\n *\n * @interface FileUploaderProps\n * @extends {React.HTMLAttributes<HTMLDivElement>}\n */\nexport interface FileUploaderProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  /**\n   * Maximum number of files allowed.\n   */\n  maxFiles?: number;\n\n  /**\n   * Maximum file size in bytes.\n   */\n  maxSize?: number;\n\n  /**\n   * Accepted file types.\n   *\n   * @example\n   * ```tsx\n   * accept={{ 'image/*': ['.png', '.jpg', '.jpeg'] }}\n   * ```\n   */\n  accept?: DropzoneOptions['accept'];\n\n  /**\n   * Whether the uploader is disabled.\n   */\n  disabled?: boolean;\n\n  /**\n   * Additional className for the dropzone component.\n   */\n  dropzoneClassName?: string;\n\n  /**\n   * Additional className for the file list component.\n   */\n  fileListClassName?: string;\n\n  /**\n   * Ref for the input element inside the Dropzone.\n   */\n  inputRef?: React.Ref<HTMLInputElement>;\n}\n\n/**\n * A complete file uploader component with dropzone and file list.\n *\n * @component\n * @example\n * ```tsx\n * <FileUploader\n *   maxFiles={5}\n *   maxSize={1024 * 1024 * 10} // 10MB\n *   accept={{ 'application/pdf': [] }}\n * />\n * ```\n */\nconst FileUploader = React.forwardRef<HTMLDivElement, FileUploaderProps>(\n  (\n    {\n      maxFiles,\n      maxSize,\n      accept,\n      disabled,\n      className,\n      dropzoneClassName,\n      fileListClassName,\n      inputRef,\n      ...props\n    },\n    ref,\n  ) => {\n    return (\n      <div ref={ref} className={cn('w-full space-y-4', className)} {...props}>\n        <Dropzone\n          ref={inputRef}\n          dropzoneOptions={{\n            maxFiles,\n            maxSize,\n            accept,\n          }}\n          disabled={disabled}\n          className={dropzoneClassName}\n        />\n\n        <FileList className={fileListClassName} />\n      </div>\n    );\n  },\n);\nFileUploader.displayName = 'FileUploader';\n\nexport { FileList, FileUploader };\n",
      "type": "registry:component",
      "target": "components/upload/multi-file.tsx"
    }
  ]
}