{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "generate-badge",
  "title": "Generate Badge",
  "description": "Hook and utilities for generating badge images from SVG, with PNG/JPEG export support",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/badges/generate-badge/components/elements/generate-badge.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface GenerateBadgeOptions {\n  /** Scale factor for output image (default: 1) */\n  scale?: number;\n  /** Output format */\n  format?: \"png\" | \"jpeg\";\n  /** JPEG quality (0-1, default: 0.9) */\n  quality?: number;\n  /** Background color for JPEG (transparent areas) */\n  backgroundColor?: string;\n}\n\nexport interface UseGenerateBadgeReturn {\n  /** Ref to attach to the badge SVG element */\n  badgeRef: React.RefObject<SVGSVGElement | null>;\n  /** Generate and download the badge as an image */\n  generateAndDownload: (\n    filename?: string,\n    options?: GenerateBadgeOptions,\n  ) => Promise<void>;\n  /** Generate badge as a Blob */\n  generateBlob: (options?: GenerateBadgeOptions) => Promise<Blob | null>;\n  /** Generate badge as a data URL */\n  generateDataUrl: (options?: GenerateBadgeOptions) => Promise<string | null>;\n  /** Whether generation is in progress */\n  isGenerating: boolean;\n  /** Error if generation failed */\n  error: Error | null;\n}\n\n/**\n * Convert an image URL to a base64 data URL\n */\nasync function imageUrlToBase64(url: string): Promise<string> {\n  // Skip if already a data URL\n  if (url.startsWith(\"data:\")) {\n    return url;\n  }\n\n  try {\n    // Try fetching through a proxy or directly\n    const response = await fetch(url, {\n      mode: \"cors\",\n      credentials: \"omit\",\n    });\n\n    if (!response.ok) {\n      throw new Error(`Failed to fetch image: ${response.status}`);\n    }\n\n    const blob = await response.blob();\n    return new Promise((resolve, reject) => {\n      const reader = new FileReader();\n      reader.onloadend = () => resolve(reader.result as string);\n      reader.onerror = reject;\n      reader.readAsDataURL(blob);\n    });\n  } catch {\n    // If CORS fails, try loading via canvas\n    return new Promise((resolve, reject) => {\n      const img = new Image();\n      img.crossOrigin = \"anonymous\";\n\n      img.onload = () => {\n        const canvas = document.createElement(\"canvas\");\n        canvas.width = img.naturalWidth;\n        canvas.height = img.naturalHeight;\n        const ctx = canvas.getContext(\"2d\");\n        if (!ctx) {\n          reject(new Error(\"Could not get canvas context\"));\n          return;\n        }\n        ctx.drawImage(img, 0, 0);\n        try {\n          resolve(canvas.toDataURL(\"image/png\"));\n        } catch {\n          // If tainted canvas, return original URL\n          resolve(url);\n        }\n      };\n\n      img.onerror = () => {\n        // Return original URL if all else fails\n        resolve(url);\n      };\n\n      img.src = url;\n    });\n  }\n}\n\n/**\n * Embed all external images in SVG as base64 data URLs\n */\nasync function embedImagesInSvg(svg: SVGSVGElement): Promise<SVGSVGElement> {\n  const clonedSvg = svg.cloneNode(true) as SVGSVGElement;\n  const images = clonedSvg.querySelectorAll(\"image\");\n\n  const embedPromises = Array.from(images).map(async (img) => {\n    const href =\n      img.getAttribute(\"href\") ||\n      img.getAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\");\n    if (href && !href.startsWith(\"data:\")) {\n      try {\n        const base64 = await imageUrlToBase64(href);\n        img.setAttribute(\"href\", base64);\n        // Also set xlink:href for compatibility\n        img.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", base64);\n      } catch (err) {\n        console.warn(\"Failed to embed image:\", href, err);\n      }\n    }\n  });\n\n  await Promise.all(embedPromises);\n  return clonedSvg;\n}\n\n/**\n * Hook for generating badge images from SVG\n */\nexport function useGenerateBadge(): UseGenerateBadgeReturn {\n  const badgeRef = useRef<SVGSVGElement>(null);\n  const [isGenerating, setIsGenerating] = useState(false);\n  const [error, setError] = useState<Error | null>(null);\n\n  const generateBlob = useCallback(\n    async (options: GenerateBadgeOptions = {}): Promise<Blob | null> => {\n      const svg = badgeRef.current;\n      if (!svg) {\n        setError(new Error(\"Badge SVG ref not attached\"));\n        return null;\n      }\n\n      const {\n        scale = 1,\n        format = \"png\",\n        quality = 0.9,\n        backgroundColor,\n      } = options;\n\n      setIsGenerating(true);\n      setError(null);\n\n      try {\n        // Get SVG dimensions\n        const svgRect = svg.getBoundingClientRect();\n        const width = svg.viewBox.baseVal.width || svgRect.width;\n        const height = svg.viewBox.baseVal.height || svgRect.height;\n\n        // Clone SVG and embed all external images as base64\n        const clonedSvg = await embedImagesInSvg(svg);\n        clonedSvg.setAttribute(\"width\", String(width));\n        clonedSvg.setAttribute(\"height\", String(height));\n\n        // Serialize SVG\n        const serializer = new XMLSerializer();\n        const svgString = serializer.serializeToString(clonedSvg);\n        const svgBlob = new Blob([svgString], {\n          type: \"image/svg+xml;charset=utf-8\",\n        });\n        const svgUrl = URL.createObjectURL(svgBlob);\n\n        // Create canvas\n        const canvas = document.createElement(\"canvas\");\n        canvas.width = width * scale;\n        canvas.height = height * scale;\n        const ctx = canvas.getContext(\"2d\");\n\n        if (!ctx) {\n          throw new Error(\"Could not get canvas context\");\n        }\n\n        // Fill background if specified or format is JPEG\n        if (backgroundColor || format === \"jpeg\") {\n          ctx.fillStyle = backgroundColor || \"#FFFFFF\";\n          ctx.fillRect(0, 0, canvas.width, canvas.height);\n        }\n\n        // Scale context\n        ctx.scale(scale, scale);\n\n        // Load and draw SVG\n        const img = new Image();\n        img.crossOrigin = \"anonymous\";\n\n        const blob = await new Promise<Blob | null>((resolve, reject) => {\n          img.onload = () => {\n            ctx.drawImage(img, 0, 0);\n            URL.revokeObjectURL(svgUrl);\n\n            canvas.toBlob(\n              (blob) => {\n                resolve(blob);\n              },\n              format === \"jpeg\" ? \"image/jpeg\" : \"image/png\",\n              quality,\n            );\n          };\n\n          img.onerror = () => {\n            URL.revokeObjectURL(svgUrl);\n            reject(new Error(\"Failed to load SVG as image\"));\n          };\n\n          img.src = svgUrl;\n        });\n\n        return blob;\n      } catch (err) {\n        const error =\n          err instanceof Error ? err : new Error(\"Failed to generate badge\");\n        setError(error);\n        return null;\n      } finally {\n        setIsGenerating(false);\n      }\n    },\n    [],\n  );\n\n  const generateDataUrl = useCallback(\n    async (options: GenerateBadgeOptions = {}): Promise<string | null> => {\n      const blob = await generateBlob(options);\n      if (!blob) return null;\n\n      return new Promise((resolve) => {\n        const reader = new FileReader();\n        reader.onloadend = () => resolve(reader.result as string);\n        reader.onerror = () => resolve(null);\n        reader.readAsDataURL(blob);\n      });\n    },\n    [generateBlob],\n  );\n\n  const generateAndDownload = useCallback(\n    async (\n      filename: string = \"badge\",\n      options: GenerateBadgeOptions = {},\n    ): Promise<void> => {\n      const blob = await generateBlob(options);\n      if (!blob) return;\n\n      const { format = \"png\" } = options;\n      const extension = format === \"jpeg\" ? \"jpg\" : \"png\";\n      const finalFilename = filename.includes(\".\")\n        ? filename\n        : `${filename}.${extension}`;\n\n      // Create download link\n      const url = URL.createObjectURL(blob);\n      const link = document.createElement(\"a\");\n      link.href = url;\n      link.download = finalFilename;\n      link.style.display = \"none\";\n\n      document.body.appendChild(link);\n      link.click();\n\n      // Cleanup\n      setTimeout(() => {\n        document.body.removeChild(link);\n        URL.revokeObjectURL(url);\n      }, 100);\n    },\n    [generateBlob],\n  );\n\n  return {\n    badgeRef,\n    generateAndDownload,\n    generateBlob,\n    generateDataUrl,\n    isGenerating,\n    error,\n  };\n}\n\nexport interface GenerateBadgeButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  /** The generate badge hook return value */\n  badge: UseGenerateBadgeReturn;\n  /** Filename for download (without extension) */\n  filename?: string;\n  /** Generation options */\n  options?: GenerateBadgeOptions;\n  /** Content to show while generating */\n  loadingContent?: React.ReactNode;\n}\n\n/**\n * Button component for downloading badge\n */\nexport function GenerateBadgeButton({\n  badge,\n  filename = \"badge\",\n  options,\n  loadingContent = \"Generating...\",\n  children = \"Download Badge\",\n  className,\n  disabled,\n  ...props\n}: GenerateBadgeButtonProps) {\n  const handleClick = async () => {\n    await badge.generateAndDownload(filename, options);\n  };\n\n  return (\n    <button\n      data-slot=\"generate-badge-button\"\n      type=\"button\"\n      onClick={handleClick}\n      disabled={disabled || badge.isGenerating}\n      className={cn(\n        \"inline-flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium\",\n        \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        \"disabled:pointer-events-none disabled:opacity-50\",\n        className,\n      )}\n      {...props}\n    >\n      {badge.isGenerating ? loadingContent : children}\n    </button>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "Provides useGenerateBadge hook for converting SVG badges to downloadable PNG/JPEG images. Includes GenerateBadgeButton component for easy integration.",
  "categories": [
    "badges"
  ],
  "type": "registry:ui"
}