{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dropzone-intake-router",
  "title": "Intake Router",
  "description": "One intake target routes selected files into document, image, and table lanes.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@retab/dropzone",
    "@retab/file-thumbnail",
    "@retab/utils",
    "@retab/file-size-format"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/blocks/dropzone-intake-router.tsx",
      "content": "\"use client\";\n\nimport { Upload, X } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { useDropzone, type DropzoneFileItem } from \"@/components/ui/dropzone\";\nimport { FileThumbnail } from \"@/components/ui/file-thumbnail\";\n\nimport {\n  RejectionRows,\n  type DropzoneExampleProps,\n} from \"./dropzone-example-shared\";\n\nexport function IntakeRouter({ className }: DropzoneExampleProps) {\n  const dropzone = useDropzone({\n    accept:\n      \".pdf,.doc,.docx,.csv,.xls,.xlsx,.png,.jpg,.jpeg,image/*,application/pdf,text/csv\",\n    maxFiles: 12,\n    multiple: true,\n  });\n  const groups = getRoutedFiles(dropzone.files);\n\n  return (\n    <section\n      {...dropzone.getRootProps({\n        className: cn(\n          \"rounded-lg border bg-background p-4 transition-colors\",\n          dropzone.isDragging && \"border-foreground/40 bg-accent/35\",\n          className,\n        ),\n      })}\n    >\n      <input {...dropzone.getInputProps({ className: \"hidden\" })} />\n      <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n        <div>\n          <div className=\"text-sm font-medium\">Intake router</div>\n          <div className=\"text-muted-foreground mt-1 text-xs\">\n            One target, derived lanes by file type.\n          </div>\n        </div>\n        <button\n          {...dropzone.getTriggerProps({\n            native: true,\n            className:\n              \"inline-flex h-8 cursor-pointer items-center gap-2 rounded-md border bg-background px-3 text-xs font-medium outline-none hover:bg-muted focus-visible:ring-[3px] focus-visible:ring-ring/24\",\n          })}\n        >\n          <Upload className=\"size-3.5\" aria-hidden />\n          Add batch\n        </button>\n      </div>\n      <div className=\"grid gap-3 md:grid-cols-3\">\n        <RoutedLane\n          label=\"Documents\"\n          files={groups.documents}\n          onRemove={dropzone.removeFile}\n        />\n        <RoutedLane\n          label=\"Images\"\n          files={groups.images}\n          onRemove={dropzone.removeFile}\n        />\n        <RoutedLane\n          label=\"Tables\"\n          files={groups.tables}\n          onRemove={dropzone.removeFile}\n        />\n      </div>\n      <RejectionRows rejections={dropzone.lastIntake.fileRejections} />\n    </section>\n  );\n}\n\nfunction getRoutedFiles(files: DropzoneFileItem[]) {\n  const groups = {\n    documents: [] as DropzoneFileItem[],\n    images: [] as DropzoneFileItem[],\n    tables: [] as DropzoneFileItem[],\n  };\n\n  for (const item of files) {\n    const fileName = item.file.name.toLowerCase();\n    const fileType = item.file.type;\n\n    if (\n      fileType.startsWith(\"image/\") ||\n      /\\.(png|jpe?g|gif|webp|heic)$/.test(fileName)\n    ) {\n      groups.images.push(item);\n    } else if (\n      fileType.includes(\"spreadsheet\") ||\n      fileType === \"text/csv\" ||\n      /\\.(csv|xls|xlsx)$/.test(fileName)\n    ) {\n      groups.tables.push(item);\n    } else {\n      groups.documents.push(item);\n    }\n  }\n\n  return groups;\n}\n\nfunction RoutedLane({\n  files,\n  label,\n  onRemove,\n}: {\n  files: DropzoneFileItem[];\n  label: string;\n  onRemove: (fileId: string) => void;\n}) {\n  return (\n    <div className=\"bg-muted/20 min-h-44 rounded-md border border-dashed p-2\">\n      <div className=\"mb-2 flex items-center justify-between text-xs\">\n        <span className=\"font-medium\">{label}</span>\n        <span className=\"text-muted-foreground\">{files.length}</span>\n      </div>\n      {files.length ? (\n        <div className=\"space-y-2\">\n          {files.slice(0, 3).map((item) => (\n            <div\n              key={item.id}\n              className=\"bg-background flex min-w-0 items-center gap-2 rounded-md border p-2\"\n            >\n              <FileThumbnail\n                file={item.file}\n                thumbnailShape=\"square\"\n                thumbnailSize=\"xs\"\n                className=\"shrink-0\"\n              />\n              <div className=\"min-w-0 flex-1 truncate text-xs font-medium\">\n                {item.file.name}\n              </div>\n              <button\n                type=\"button\"\n                aria-label={`Remove ${item.file.name}`}\n                className=\"text-muted-foreground hover:bg-muted hover:text-foreground grid size-6 shrink-0 place-items-center rounded-md\"\n                onClick={() => onRemove(item.id)}\n              >\n                <X className=\"size-3.5\" aria-hidden />\n              </button>\n            </div>\n          ))}\n          {files.length > 3 ? (\n            <div className=\"text-muted-foreground text-center text-xs\">\n              +{files.length - 3} more\n            </div>\n          ) : null}\n        </div>\n      ) : (\n        <div className=\"text-muted-foreground grid h-32 place-items-center text-center text-xs\">\n          No {label.toLowerCase()} yet.\n        </div>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/blocks/dropzone-intake-router.tsx"
    },
    {
      "path": "registry/new-york-v4/blocks/dropzone-example-shared.tsx",
      "content": "\"use client\";\n\nimport { FileText, X } from \"lucide-react\";\n\nimport type {\n  DropzoneFileItem,\n  DropzoneFileRejection,\n} from \"@/components/ui/dropzone\";\nimport { formatFileSize } from \"@/components/ui/file-size-format\";\n\nexport type DropzoneExampleProps = {\n  className?: string;\n};\n\nexport function InlineFileRows({\n  files,\n  onRemove,\n}: {\n  files: DropzoneFileItem[];\n  onRemove: (fileId: string) => void;\n}) {\n  if (files.length === 0) return null;\n\n  return (\n    <div className=\"mt-3 space-y-1\">\n      {files.map((item) => (\n        <div\n          key={item.id}\n          className=\"bg-background flex min-w-0 items-center gap-2 rounded-md border px-2 py-1.5 text-xs\"\n        >\n          <FileText className=\"text-muted-foreground size-3.5 shrink-0\" />\n          <div className=\"min-w-0 flex-1 truncate\">{item.file.name}</div>\n          <div className=\"text-muted-foreground shrink-0\">\n            {formatFileSize(item.file.size)}\n          </div>\n          <button\n            type=\"button\"\n            aria-label={`Remove ${item.file.name}`}\n            className=\"text-muted-foreground hover:bg-muted hover:text-foreground grid size-5 shrink-0 place-items-center rounded-[4px]\"\n            onClick={() => onRemove(item.id)}\n          >\n            <X className=\"size-3\" aria-hidden />\n          </button>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nexport function RejectionRows({\n  rejections,\n}: {\n  rejections: DropzoneFileRejection[];\n}) {\n  if (rejections.length === 0) return null;\n\n  return (\n    <div className=\"text-destructive mt-3 space-y-1 text-xs\">\n      {rejections.map((rejection) => (\n        <div key={`${rejection.file.name}-${rejection.reason}`}>\n          {rejection.file.name}: {getDropzoneRejectionMessage(rejection)}\n        </div>\n      ))}\n    </div>\n  );\n}\n\nfunction getDropzoneRejectionMessage(rejection: DropzoneFileRejection): string {\n  if (rejection.reason === \"file-invalid-type\") {\n    return \"This file type is not supported here.\";\n  }\n  if (rejection.reason === \"file-too-large\") {\n    return `File must be ${formatFileSize(rejection.maxSize)} or smaller.`;\n  }\n  if (rejection.reason === \"custom\") {\n    return rejection.code;\n  }\n  return rejection.maxFiles === 1\n    ? \"Only one file can be selected.\"\n    : `Only ${rejection.maxFiles} files can be selected.`;\n}\n",
      "type": "registry:component",
      "target": "@components/blocks/dropzone-example-shared.tsx"
    }
  ],
  "categories": [
    "dropzone"
  ],
  "type": "registry:block"
}