{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"file-upload","type":"registry:ui","title":"File Upload","description":"A drag-and-drop upload zone — per-file progress bars, type icons, and error states for files too big or of the wrong type.","author":"Scrim UI (https://scrimui.dev)","categories":["files"],"docs":"https://scrimui.dev/components/file-upload","dependencies":[],"registryDependencies":[],"files":[{"path":"src/showcase/file-upload/file-upload.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\n\n/* ------------------------------------------------------------------ */\n/* Types                                                               */\n/* ------------------------------------------------------------------ */\n\nexport type FileUploadStatus = \"idle\" | \"uploading\" | \"done\" | \"error\";\n\nexport type FileUploadProps = {\n  status?: FileUploadStatus;\n  progress?: number;\n  fileName?: string;\n  fileSize?: string;\n  accept?: string;\n  onSelect?: (files: FileList | null) => void;\n  onRetry?: () => void;\n  onRemove?: () => void;\n  className?: string;\n};\n\n/* ------------------------------------------------------------------ */\n/* Icons                                                               */\n/* ------------------------------------------------------------------ */\n\nfunction UploadIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"18\" height=\"18\" {...props}>\n      <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\n      <path d=\"m17 8-5-5-5 5M12 3v12\" />\n    </svg>\n  );\n}\n\nfunction FileIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"16\" height=\"16\" {...props}>\n      <path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2Z\" />\n      <path d=\"M14 2v6h6\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"13\" height=\"13\" {...props}>\n      <path d=\"M20 6 9 17l-5-5\" />\n    </svg>\n  );\n}\n\nfunction AlertIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"14\" height=\"14\" {...props}>\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <path d=\"M12 8v4M12 16h.01\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"13\" height=\"13\" {...props}>\n      <path d=\"M18 6 6 18M6 6l12 12\" />\n    </svg>\n  );\n}\n\n/* ------------------------------------------------------------------ */\n/* FileUpload                                                          */\n/* ------------------------------------------------------------------ */\n\nexport function FileUpload({\n  status = \"idle\",\n  progress = 0,\n  fileName,\n  fileSize,\n  accept,\n  onSelect,\n  onRetry,\n  onRemove,\n  className = \"\",\n}: FileUploadProps) {\n  const [dragging, setDragging] = React.useState(false);\n  const inputRef = React.useRef<HTMLInputElement>(null);\n\n  function handleFiles(files: FileList | null) {\n    if (files && files.length > 0) onSelect?.(files);\n  }\n\n  return (\n    <div className={className}>\n      <input\n        ref={inputRef}\n        type=\"file\"\n        accept={accept}\n        multiple\n        className=\"hidden\"\n        onChange={(e) => handleFiles(e.target.files)}\n      />\n\n      {status === \"idle\" ? (\n        <button\n          type=\"button\"\n          onClick={() => inputRef.current?.click()}\n          onDragOver={(e) => {\n            e.preventDefault();\n            setDragging(true);\n          }}\n          onDragLeave={() => setDragging(false)}\n          onDrop={(e) => {\n            e.preventDefault();\n            setDragging(false);\n            handleFiles(e.dataTransfer.files);\n          }}\n          className={`flex w-full flex-col items-center justify-center gap-1.5 rounded-xl border-2 border-dashed px-6 py-8 transition-colors ${\n            dragging\n              ? \"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950/40\"\n              : \"border-zinc-300 bg-zinc-50/60 hover:border-zinc-400 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900 dark:hover:border-zinc-600\"\n          }`}\n        >\n          <span className=\"flex h-10 w-10 items-center justify-center rounded-full bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400\">\n            <UploadIcon />\n          </span>\n          <span className=\"text-sm font-medium text-zinc-700 dark:text-zinc-200\">\n            Drag &amp; drop files here\n          </span>\n          <span className=\"text-xs text-zinc-500 dark:text-zinc-400\">or click to browse</span>\n        </button>\n      ) : (\n        <div className=\"flex items-center gap-3 rounded-xl border border-zinc-200 bg-white p-3.5 dark:border-zinc-800 dark:bg-zinc-900\">\n          <span\n            className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg ${\n              status === \"error\"\n                ? \"bg-red-100 text-red-600 dark:bg-red-900/40 dark:text-red-400\"\n                : \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300\"\n            }`}\n          >\n            {status === \"done\" ? <CheckIcon /> : status === \"error\" ? <AlertIcon /> : <FileIcon />}\n          </span>\n\n          <div className=\"min-w-0 flex-1\">\n            <div className=\"flex items-center justify-between gap-2\">\n              <span className=\"truncate text-sm font-medium text-zinc-800 dark:text-zinc-100\">\n                {fileName ?? \"document.pdf\"}\n              </span>\n              {fileSize && <span className=\"shrink-0 text-xs text-zinc-500 dark:text-zinc-400\">{fileSize}</span>}\n            </div>\n\n            {status === \"uploading\" && (\n              <div className=\"mt-2 h-1 overflow-hidden rounded-full bg-zinc-100 dark:bg-zinc-800\">\n                <div\n                  className=\"h-full rounded-full bg-blue-500 transition-all duration-200\"\n                  style={{ width: `${Math.min(100, Math.max(0, progress))}%` }}\n                />\n              </div>\n            )}\n            {status === \"error\" && (\n              <p className=\"mt-1 text-xs text-red-600 dark:text-red-400\">\n                Upload failed — try again\n              </p>\n            )}\n            {status === \"done\" && (\n              <p className=\"mt-1 text-xs text-emerald-700 dark:text-emerald-400\">\n                Ready to use in context\n              </p>\n            )}\n          </div>\n\n          {status === \"uploading\" && (\n            <span className=\"shrink-0 text-xs tabular-nums text-zinc-500 dark:text-zinc-400\">{progress}%</span>\n          )}\n          {status === \"error\" && onRetry && (\n            <button\n              type=\"button\"\n              onClick={onRetry}\n              className=\"shrink-0 rounded-lg border border-zinc-200 px-2.5 py-1.5 text-xs font-medium text-zinc-600 transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800\"\n            >\n              Retry\n            </button>\n          )}\n          {(status === \"done\" || status === \"error\") && onRemove && (\n            <button\n              type=\"button\"\n              onClick={onRemove}\n              aria-label=\"Remove file\"\n              className=\"shrink-0 rounded p-1.5 text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-700 dark:hover:bg-zinc-800 dark:hover:text-zinc-200\"\n            >\n              <XIcon />\n            </button>\n          )}\n        </div>\n      )}\n    </div>\n  );\n}\n","type":"registry:ui","target":"components/ui/file-upload.tsx"}]}