{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "qr-code-download",
  "type": "registry:component",
  "title": "QR Code Download",
  "description": "A download menu that exports a QR code as PNG, SVG, JPG or PDF at the size you pick, or copies it as an image.",
  "author": "bucharitesh <https://qrdx.dev>",
  "dependencies": ["qrdx", "lucide-react"],
  "registryDependencies": ["button", "dropdown-menu"],
  "files": [
    {
      "path": "registry/qrdx/qr-code-download.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { Check, ChevronDown, Copy, Download, Loader2 } from \"lucide-react\";\nimport {\n  copyQRCodeToClipboard,\n  type DownloadFormat,\n  downloadQRCode,\n  getQRData,\n  type QRCode,\n} from \"qrdx\";\nimport { type ComponentProps, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\n/** qrdx style options: colors, gradients, patterns, logo, template. */\nexport type QrCodeStyle = Omit<ComponentProps<typeof QRCode>, \"value\" | \"size\">;\n\nconst FORMAT_HINTS: Record<string, string> = {\n  png: \"Image for screens and documents\",\n  svg: \"Vector, scales to any size\",\n  jpg: \"Image with a solid background\",\n  pdf: \"Single-page document\",\n  eps: \"Vector for print shops\",\n};\n\nconst VECTOR_FORMATS = new Set<string>([\"svg\", \"eps\"]);\n\nexport interface QrCodeDownloadProps\n  extends Omit<ComponentProps<\"div\">, \"children\" | \"onError\"> {\n  /** Text or URL to encode. */\n  value: string;\n  /** qrdx style options for the exported code. */\n  qr?: QrCodeStyle;\n  /**\n   * Formats in the menu. \"pdf\" needs the optional `jspdf` package.\n   * @default [\"png\", \"svg\", \"jpg\"]\n   */\n  formats?: DownloadFormat[];\n  /**\n   * Pixel sizes to choose from. Pass one size to hide the size picker.\n   * @default [512, 1024, 2048]\n   */\n  sizes?: number[];\n  /**\n   * Size selected when the menu first opens.\n   * @default 1024\n   */\n  defaultSize?: number;\n  /**\n   * Download file name without extension.\n   * @default \"qr-code\"\n   */\n  filename?: string;\n  /**\n   * Show a \"Copy image\" item that copies a PNG to the clipboard.\n   * @default true\n   */\n  copy?: boolean;\n  /**\n   * Trigger button label.\n   * @default \"Download\"\n   */\n  label?: string;\n  /**\n   * Trigger button variant.\n   * @default \"outline\"\n   */\n  variant?: ComponentProps<typeof Button>[\"variant\"];\n  /** Called after a file is saved or the image is copied. */\n  onComplete?: (action: DownloadFormat | \"copy\") => void;\n  /** Called when an export fails. */\n  onError?: (error: Error) => void;\n}\n\n/** getQRData lays a logo out for 1024px; scale it to the exported size. */\nfunction getFileProps(\n  value: string,\n  qr: QrCodeStyle | undefined,\n  size: number,\n) {\n  const data = getQRData({ margin: 2, ...qr, value });\n  const ratio = size / data.size;\n  return {\n    ...data,\n    size,\n    imageSettings: data.imageSettings && {\n      ...data.imageSettings,\n      width: data.imageSettings.width * ratio,\n      height: data.imageSettings.height * ratio,\n    },\n  };\n}\n\nexport function QrCodeDownload({\n  value,\n  qr,\n  formats = [\"png\", \"svg\", \"jpg\"],\n  sizes = [512, 1024, 2048],\n  defaultSize = 1024,\n  filename = \"qr-code\",\n  copy = true,\n  label = \"Download\",\n  variant = \"outline\",\n  onComplete,\n  onError,\n  className,\n  ...props\n}: QrCodeDownloadProps) {\n  const [size, setSize] = useState(\n    sizes.includes(defaultSize) ? defaultSize : (sizes[0] ?? 1024),\n  );\n  const [pending, setPending] = useState(false);\n  const [done, setDone] = useState(false);\n  const [status, setStatus] = useState(\"\");\n  const [error, setError] = useState(\"\");\n\n  async function run(action: DownloadFormat | \"copy\") {\n    setPending(true);\n    setDone(false);\n    setError(\"\");\n    try {\n      const file = getFileProps(value, qr, size);\n      if (action === \"copy\") {\n        await copyQRCodeToClipboard(file, { width: size, height: size });\n        setStatus(\"Copied the QR code image\");\n      } else {\n        await downloadQRCode(file, {\n          format: action,\n          size: { width: size, height: size },\n          filename: `${filename}.${action}`,\n        });\n        setStatus(`Downloaded ${filename}.${action}`);\n      }\n      setDone(true);\n      setTimeout(() => setDone(false), 1500);\n      onComplete?.(action);\n    } catch (cause) {\n      const failure =\n        cause instanceof Error\n          ? cause\n          : new Error(\"Could not export the code.\");\n      setError(failure.message);\n      onError?.(failure);\n    } finally {\n      setPending(false);\n    }\n  }\n\n  return (\n    <div\n      className={cn(\"inline-flex flex-col items-center gap-1.5\", className)}\n      data-slot=\"qr-code-download\"\n      {...props}\n    >\n      <DropdownMenu>\n        <DropdownMenuTrigger asChild>\n          <Button aria-busy={pending} disabled={pending} variant={variant}>\n            {pending ? (\n              <Loader2 aria-hidden=\"true\" className=\"animate-spin\" />\n            ) : done ? (\n              <Check aria-hidden=\"true\" />\n            ) : (\n              <Download aria-hidden=\"true\" />\n            )}\n            {label}\n            <ChevronDown aria-hidden=\"true\" className=\"opacity-60\" />\n          </Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent align=\"center\" className=\"w-64\">\n          <DropdownMenuLabel>Download as</DropdownMenuLabel>\n          {formats.map((format) => (\n            <DropdownMenuItem key={format} onSelect={() => run(format)}>\n              <span className=\"flex flex-col\">\n                <span className=\"font-medium\">{format.toUpperCase()}</span>\n                <span className=\"text-muted-foreground text-xs\">\n                  {FORMAT_HINTS[format]}\n                </span>\n              </span>\n              <DropdownMenuShortcut>\n                {VECTOR_FORMATS.has(format) ? \"vector\" : `${size}px`}\n              </DropdownMenuShortcut>\n            </DropdownMenuItem>\n          ))}\n          {sizes.length > 1 ? (\n            <>\n              <DropdownMenuSeparator />\n              <DropdownMenuLabel>Size</DropdownMenuLabel>\n              <DropdownMenuRadioGroup\n                onValueChange={(next) => setSize(Number(next))}\n                value={String(size)}\n              >\n                {sizes.map((option) => (\n                  <DropdownMenuRadioItem\n                    key={option}\n                    onSelect={(event) => event.preventDefault()}\n                    value={String(option)}\n                  >\n                    {option} × {option} px\n                  </DropdownMenuRadioItem>\n                ))}\n              </DropdownMenuRadioGroup>\n            </>\n          ) : null}\n          {copy ? (\n            <>\n              <DropdownMenuSeparator />\n              <DropdownMenuItem onSelect={() => run(\"copy\")}>\n                <Copy aria-hidden=\"true\" />\n                Copy image\n              </DropdownMenuItem>\n            </>\n          ) : null}\n        </DropdownMenuContent>\n      </DropdownMenu>\n      {error ? (\n        <p\n          className=\"max-w-xs text-center text-destructive text-xs\"\n          role=\"alert\"\n        >\n          {error}\n        </p>\n      ) : null}\n      <p aria-live=\"polite\" className=\"sr-only\">\n        {status}\n      </p>\n    </div>\n  );\n}\n"
    }
  ],
  "docs": "Import it with `import { QrCodeDownload } from \"@/components/qr-code-download\"`. Props and examples: https://qrdx.dev/docs/components/qr-code-download",
  "categories": ["qr-code"]
}
