{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "file-upload",
  "title": "File Input",
  "description": "File upload component",
  "registryDependencies": [
    "https://formcn.dev/r/use-file-upload.json",
    "button"
  ],
  "files": [
    {
      "path": "src/components/form-fields/file-upload.tsx",
      "content": "'use client'\n\nimport { AlertCircleIcon, CloudUpload, File, XIcon } from 'lucide-react'\nimport { Button } from '@/components/ui/button'\nimport {\n\tformatBytes,\n\tuseFileUpload,\n} from '@/form-builder/hooks/use-file-upload'\n\nconst getFileIcon = (file: { file: File | { type: string; name: string } }) => {\n\tconst fileType = file.file.type\n\tif (fileType.startsWith('image/')) {\n\t\t// return preview\n\t\treturn (\n\t\t\t<div className=\"aspect-square size-10 overflow-hidden rounded-md\">\n\t\t\t\t<img\n\t\t\t\t\tsrc={URL.createObjectURL(file.file as Blob)}\n\t\t\t\t\tclassName=\"object-fill\"\n\t\t\t\t\talt=\"File preview\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t)\n\t}\n\treturn (\n\t\t<div className=\"aspect-square size-10 flex items-center justify-center overflow-hidden rounded-full\">\n\t\t\t<File className=\"size-5 opacity-60\" />\n\t\t</div>\n\t)\n}\n\nexport function FileUpload({\n\tmaxFiles,\n\tmaxSize,\n\tplaceholder,\n\t// description,\n\trequired,\n\tsetValue,\n\taccept,\n\tname,\n\tdisabled,\n}: {\n\tmaxFiles: number\n\tmaxSize: number\n\tplaceholder?: string\n\t// description?: string;\n\trequired?: boolean\n\tdisabled?: boolean\n\tsetValue: (\n\t\tname: string,\n\t\tvalue: any,\n\t\toptions?: {\n\t\t\tshouldValidate?: boolean\n\t\t\tshouldDirty?: boolean\n\t\t\tshouldTouch?: boolean\n\t\t},\n\t) => void\n\taccept?: string\n\tname: string\n}) {\n\tconst [\n\t\t{ files, isDragging, errors },\n\t\t{\n\t\t\thandleDragEnter,\n\t\t\thandleDragLeave,\n\t\t\thandleDragOver,\n\t\t\thandleDrop,\n\t\t\topenFileDialog,\n\t\t\tremoveFile,\n\t\t\tclearFiles,\n\t\t\tgetInputProps,\n\t\t},\n\t] = useFileUpload({\n\t\tmultiple: maxFiles > 1,\n\t\tmaxFiles,\n\t\tmaxSize,\n\t\taccept,\n\t\tonFilesChange: (files) => {\n\t\t\tsetValue(\n\t\t\t\tname,\n\t\t\t\tfiles.map((file) => file.file),\n\t\t\t\t{ shouldValidate: true, shouldDirty: true, shouldTouch: true },\n\t\t\t)\n\t\t},\n\t})\n\n\treturn (\n\t\t<div className=\"flex flex-col gap-2 pb-2\">\n\t\t\t{/* Drop area */}\n\t\t\t<div\n\t\t\t\trole=\"button\"\n\t\t\t\tonClick={openFileDialog}\n\t\t\t\tonDragEnter={handleDragEnter}\n\t\t\t\tonDragLeave={handleDragLeave}\n\t\t\t\tonDragOver={handleDragOver}\n\t\t\t\tonDrop={handleDrop}\n\t\t\t\tdata-dragging={isDragging || undefined}\n\t\t\t\tclassName=\"border-input hover:bg-accent/50 data-[dragging=true]:bg-accent/50 has-[input:focus]:border-ring has-[input:focus]:ring-ring/50 flex min-h-32 flex-col items-center justify-center rounded-md border border-dashed p-4 transition-colors has-disabled:pointer-events-none has-disabled:opacity-50 has-[input:focus]:ring-[3px] hover:cursor-pointer\"\n\t\t\t>\n\t\t\t\t{/* @ts-expect-error caused when upgrading to Nextjs 16 */}\n\t\t\t\t<input\n\t\t\t\t\t{...getInputProps()}\n\t\t\t\t\tclassName=\"sr-only\"\n\t\t\t\t\taria-label=\"Upload files\"\n\t\t\t\t\trequired={required}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t/>\n\n\t\t\t\t<div className=\"flex flex-col items-center justify-center text-center\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"bg-secondary mb-2 flex size-11 shrink-0 items-center justify-center rounded-full border\"\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<CloudUpload className=\"size-4 opacity-60\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t{/* <p className=\"mb-1.5 text-sm font-medium\">\n            Upload\n          </p> */}\n\t\t\t\t\t<p className=\"text-foreground font-medium text-sm mb-2\">\n\t\t\t\t\t\tDrag & drop or click to browse\n\t\t\t\t\t</p>\n\t\t\t\t\t<div className=\"text-muted-foreground/70 flex flex-wrap justify-center gap-1 text-xs\">\n\t\t\t\t\t\t{placeholder}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{errors.length > 0 && (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"text-destructive flex items-center gap-1 text-xs\"\n\t\t\t\t\trole=\"alert\"\n\t\t\t\t>\n\t\t\t\t\t<AlertCircleIcon className=\"size-3 shrink-0\" />\n\t\t\t\t\t<span>{errors[0]}</span>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* File list */}\n\t\t\t{files.length > 0 && (\n\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t{files.map((file) => (\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tkey={file.id}\n\t\t\t\t\t\t\tclassName=\"flex items-center justify-between gap-2 rounded-lg border p-2 pe-3\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-1.5 overflow-hidden\">\n\t\t\t\t\t\t\t\t{getFileIcon(file)}\n\t\t\t\t\t\t\t\t<div className=\"flex min-w-0 flex-col gap-0.5\">\n\t\t\t\t\t\t\t\t\t<p className=\"truncate text-[11px] font-medium max-w-[200px]\">\n\t\t\t\t\t\t\t\t\t\t{file.file.name}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-xs\">\n\t\t\t\t\t\t\t\t\t\t{formatBytes(file.file.size)}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground/80 hover:text-foreground -me-2 size-8 hover:bg-transparent\"\n\t\t\t\t\t\t\t\tonClick={() => removeFile(file.id)}\n\t\t\t\t\t\t\t\taria-label=\"Remove file\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<XIcon className=\"size-4\" aria-hidden=\"true\" />\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\n\t\t\t\t\t{/* Remove all files button */}\n\t\t\t\t\t{files.length > 1 && (\n\t\t\t\t\t\t<div className=\"flex justify-end\">\n\t\t\t\t\t\t\t<Button size=\"sm\" variant=\"outline\" onClick={clearFiles}>\n\t\t\t\t\t\t\t\tRemove all files\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t)\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}