{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"dropzone","type":"registry:ui","title":"dropzone","description":"Allows users to drag-and-drop files into a container to upload or process them.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["lucide-react","react-dropzone"],"devDependencies":[],"registryDependencies":["button"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { UploadIcon } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\nimport { createContext, useContext } from \"react\";\nimport type { DropEvent, DropzoneOptions, FileRejection } from \"react-dropzone\";\nimport { useDropzone } from \"react-dropzone\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype DropzoneContextType = {\n  src?: File[];\n  accept?: DropzoneOptions[\"accept\"];\n  maxSize?: DropzoneOptions[\"maxSize\"];\n  minSize?: DropzoneOptions[\"minSize\"];\n  maxFiles?: DropzoneOptions[\"maxFiles\"];\n};\n\nconst renderBytes = (bytes: number) => {\n  const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"];\n  let size = bytes;\n  let unitIndex = 0;\n\n  while (size >= 1024 && unitIndex < units.length - 1) {\n    size /= 1024;\n    unitIndex++;\n  }\n\n  return `${size.toFixed(2)}${units[unitIndex]}`;\n};\n\nconst DropzoneContext = createContext<DropzoneContextType | undefined>(\n  undefined\n);\n\nexport type DropzoneProps = Omit<DropzoneOptions, \"onDrop\"> & {\n  src?: File[];\n  className?: string;\n  onDrop?: (\n    acceptedFiles: File[],\n    fileRejections: FileRejection[],\n    event: DropEvent\n  ) => void;\n  children?: ReactNode;\n};\n\nexport const Dropzone = ({\n  accept,\n  maxFiles = 1,\n  maxSize,\n  minSize,\n  onDrop,\n  onError,\n  disabled,\n  src,\n  className,\n  children,\n  ...props\n}: DropzoneProps) => {\n  const { getRootProps, getInputProps, isDragActive } = useDropzone({\n    accept,\n    maxFiles,\n    maxSize,\n    minSize,\n    onError,\n    disabled,\n    onDrop: (acceptedFiles, fileRejections, event) => {\n      if (fileRejections.length > 0) {\n        const message = fileRejections.at(0)?.errors.at(0)?.message;\n        onError?.(new Error(message));\n        return;\n      }\n\n      onDrop?.(acceptedFiles, fileRejections, event);\n    },\n    ...props,\n  });\n\n  return (\n    <DropzoneContext.Provider\n      key={JSON.stringify(src)}\n      value={{ src, accept, maxSize, minSize, maxFiles }}\n    >\n      <Button\n        className={cn(\n          \"relative h-auto w-full flex-col overflow-hidden p-8\",\n          isDragActive && \"outline-none ring-1 ring-ring\",\n          className\n        )}\n        disabled={disabled}\n        type=\"button\"\n        variant=\"outline\"\n        {...getRootProps()}\n      >\n        <input {...getInputProps()} disabled={disabled} />\n        {children}\n      </Button>\n    </DropzoneContext.Provider>\n  );\n};\n\nconst useDropzoneContext = () => {\n  const context = useContext(DropzoneContext);\n\n  if (!context) {\n    throw new Error(\"useDropzoneContext must be used within a Dropzone\");\n  }\n\n  return context;\n};\n\nexport type DropzoneContentProps = {\n  children?: ReactNode;\n  className?: string;\n};\n\nconst maxLabelItems = 3;\n\nexport const DropzoneContent = ({\n  children,\n  className,\n}: DropzoneContentProps) => {\n  const { src } = useDropzoneContext();\n\n  if (!src) {\n    return null;\n  }\n\n  if (children) {\n    return children;\n  }\n\n  return (\n    <div className={cn(\"flex flex-col items-center justify-center\", className)}>\n      <div className=\"flex size-8 items-center justify-center rounded-md bg-muted text-muted-foreground\">\n        <UploadIcon size={16} />\n      </div>\n      <p className=\"my-2 w-full truncate font-medium text-sm\">\n        {src.length > maxLabelItems\n          ? `${new Intl.ListFormat(\"en\").format(\n              src.slice(0, maxLabelItems).map((file) => file.name)\n            )} and ${src.length - maxLabelItems} more`\n          : new Intl.ListFormat(\"en\").format(src.map((file) => file.name))}\n      </p>\n      <p className=\"w-full text-wrap text-muted-foreground text-xs\">\n        Drag and drop or click to replace\n      </p>\n    </div>\n  );\n};\n\nexport type DropzoneEmptyStateProps = {\n  children?: ReactNode;\n  className?: string;\n};\n\nexport const DropzoneEmptyState = ({\n  children,\n  className,\n}: DropzoneEmptyStateProps) => {\n  const { src, accept, maxSize, minSize, maxFiles } = useDropzoneContext();\n\n  if (src) {\n    return null;\n  }\n\n  if (children) {\n    return children;\n  }\n\n  let caption = \"\";\n\n  if (accept) {\n    caption += \"Accepts \";\n    caption += new Intl.ListFormat(\"en\").format(Object.keys(accept));\n  }\n\n  if (minSize && maxSize) {\n    caption += ` between ${renderBytes(minSize)} and ${renderBytes(maxSize)}`;\n  } else if (minSize) {\n    caption += ` at least ${renderBytes(minSize)}`;\n  } else if (maxSize) {\n    caption += ` less than ${renderBytes(maxSize)}`;\n  }\n\n  return (\n    <div className={cn(\"flex flex-col items-center justify-center\", className)}>\n      <div className=\"flex size-8 items-center justify-center rounded-md bg-muted text-muted-foreground\">\n        <UploadIcon size={16} />\n      </div>\n      <p className=\"my-2 w-full truncate text-wrap font-medium text-sm\">\n        Upload {maxFiles === 1 ? \"a file\" : \"files\"}\n      </p>\n      <p className=\"w-full truncate text-wrap text-muted-foreground text-xs\">\n        Drag and drop or click to upload\n      </p>\n      {caption && (\n        <p className=\"text-wrap text-muted-foreground text-xs\">{caption}.</p>\n      )}\n    </div>\n  );\n};\n","target":"components/kibo-ui/dropzone/index.tsx"}],"css":{}}