{"name":"upload-progress","title":"Upload Progress","type":"registry:ui","dependencies":["@better-upload/client","lucide-react"],"registryDependencies":["progress"],"files":[{"path":"ui/upload-progress.tsx","type":"registry:ui","content":"import { Progress } from '@/components/ui/progress';\nimport { cn } from '@/lib/utils';\nimport type { UploadHookControl } from '@better-upload/client';\nimport { formatBytes } from '@better-upload/client/helpers';\nimport { Dot, File } from 'lucide-react';\n\ntype UploadProgressProps = {\n  control: UploadHookControl<true>;\n\n  // Add any additional props you need.\n};\n\nexport function UploadProgress({\n  control: { progresses },\n}: UploadProgressProps) {\n  return (\n    <div className=\"grid gap-2\">\n      {progresses.map((progress) => (\n        <div\n          key={progress.objectInfo.key}\n          className={cn(\n            'dark:bg-input/10 flex items-center gap-2 rounded-lg border bg-transparent p-3',\n            {\n              'bg-red-500/[0.04]! border-red-500/60':\n                progress.status === 'failed',\n            }\n          )}\n        >\n          <FileIcon type={progress.type} />\n\n          <div className=\"grid grow gap-1\">\n            <div className=\"flex items-center gap-0.5\">\n              <p className=\"max-w-40 truncate text-sm font-medium\">\n                {progress.name}\n              </p>\n              <Dot className=\"text-muted-foreground size-4\" />\n              <p className=\"text-muted-foreground text-xs\">\n                {formatBytes(progress.size)}\n              </p>\n            </div>\n\n            <div className=\"flex h-4 items-center\">\n              {progress.progress < 1 && progress.status !== 'failed' ? (\n                <Progress className=\"h-1.5\" value={progress.progress * 100} />\n              ) : progress.status === 'failed' ? (\n                <p className=\"text-xs text-red-500\">Failed</p>\n              ) : (\n                <p className=\"text-muted-foreground text-xs\">Completed</p>\n              )}\n            </div>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nconst iconCaptions = {\n  'image/': 'IMG',\n  'video/': 'VID',\n  'audio/': 'AUD',\n  'application/pdf': 'PDF',\n  'application/zip': 'ZIP',\n  'application/x-rar-compressed': 'RAR',\n  'application/x-7z-compressed': '7Z',\n  'application/x-tar': 'TAR',\n  'application/json': 'JSON',\n  'application/javascript': 'JS',\n  'text/plain': 'TXT',\n  'text/csv': 'CSV',\n  'text/html': 'HTML',\n  'text/css': 'CSS',\n  'application/xml': 'XML',\n  'application/x-sh': 'SH',\n  'application/x-python-code': 'PY',\n  'application/x-executable': 'EXE',\n  'application/x-disk-image': 'ISO',\n};\n\nfunction FileIcon({ type }: { type: string }) {\n  const caption = Object.entries(iconCaptions).find(([key]) =>\n    type.startsWith(key)\n  )?.[1];\n\n  return (\n    <div className=\"relative shrink-0\">\n      <File className=\"text-muted-foreground size-12\" strokeWidth={1} />\n\n      {caption && (\n        <span className=\"bg-primary text-primary-foreground absolute bottom-2.5 left-0.5 select-none rounded px-1 py-px text-xs font-semibold\">\n          {caption}\n        </span>\n      )}\n    </div>\n  );\n}\n"}]}