{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "qrcode",
  "dependencies": [
    "qr-code-styling"
  ],
  "files": [
    {
      "path": "components/ui/qrcode.tsx",
      "content": "\"use client\"\n\nimport React, { useEffect, useRef } from \"react\"\nimport QRCodeStyling from \"qr-code-styling\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface QRCodeProps {\n  value: string\n  size?: number\n  bgColor?: string // Can be a CSS variable (e.g. --color-background) or a color value\n  fgColor?: string // Can be a CSS variable (e.g. --color-foreground) or a color value\n  level?: \"L\" | \"M\" | \"Q\" | \"H\"\n  marginSize?: number\n  className?: string\n  enableNavigation?: boolean // Enable click navigation to the QR code URL\n  dotStyle?:\n    | \"rounded\"\n    | \"dots\"\n    | \"classy\"\n    | \"classy-rounded\"\n    | \"square\"\n    | \"extra-rounded\"\n  cornerSquareStyle?:\n    | \"dot\"\n    | \"square\"\n    | \"extra-rounded\"\n    | \"rounded\"\n    | \"dots\"\n    | \"classy\"\n    | \"classy-rounded\"\n  cornerDotStyle?:\n    | \"dot\"\n    | \"square\"\n    | \"rounded\"\n    | \"dots\"\n    | \"classy\"\n    | \"classy-rounded\"\n    | \"extra-rounded\"\n  logoImage?: string // URL or data URI for the logo image\n  logoSize?: number // Size of the logo as a percentage of the QR code size (0-1)\n  logoMargin?: number // Margin around the logo in pixels\n}\n\n// Helper to resolve CSS variable or direct color\nfunction resolveColor(color?: string, fallback?: string) {\n  if (!color) return fallback\n  if (color.startsWith(\"--\")) {\n    if (typeof window !== \"undefined\") {\n      const value = getComputedStyle(document.documentElement).getPropertyValue(\n        color\n      )\n      if (value) return value.trim()\n    }\n    return fallback\n  }\n  return color\n}\n\nexport default function QRCode({\n  value,\n  size = 200,\n  // Use shadcn theme variables for best contrast by default\n  bgColor = \"--color-background\", // background\n  fgColor = \"--color-foreground\", // foreground (text/qr dots)\n  level = \"M\",\n  marginSize = 0,\n  className,\n  enableNavigation = false,\n  dotStyle = \"square\",\n  cornerSquareStyle = \"square\",\n  cornerDotStyle = \"square\",\n  logoImage,\n  logoSize = 0.2,\n  logoMargin = 0,\n}: QRCodeProps) {\n  const ref = useRef<HTMLDivElement>(null)\n  const qrCodeRef = useRef<QRCodeStyling | null>(null)\n\n  useEffect(() => {\n    if (!value || !ref.current) return\n\n    // Always resolve to actual color values\n    const resolvedBgColor = resolveColor(bgColor, \"hsl(0, 0%, 100%)\") // fallback: white\n    const resolvedFgColor = resolveColor(fgColor, \"hsl(222.2, 84%, 4.9%)\") // fallback: dark\n\n    const qrCode = new QRCodeStyling({\n      width: size,\n      height: size,\n      type: \"svg\",\n      data: value,\n      margin: marginSize,\n      image: logoImage,\n      qrOptions: {\n        typeNumber: 0,\n        mode: \"Byte\",\n        errorCorrectionLevel: level,\n      },\n      imageOptions: {\n        hideBackgroundDots: true,\n        imageSize: logoSize,\n        margin: logoMargin,\n        crossOrigin: \"anonymous\",\n      },\n      dotsOptions: {\n        color: resolvedFgColor,\n        type: dotStyle,\n      },\n      backgroundOptions: {\n        color: resolvedBgColor,\n      },\n      cornersSquareOptions: {\n        color: resolvedFgColor,\n        type: cornerSquareStyle,\n      },\n      cornersDotOptions: {\n        color: resolvedFgColor,\n        type: cornerDotStyle,\n      },\n    })\n\n    qrCodeRef.current = qrCode\n\n    // Clear previous content\n    ref.current.innerHTML = \"\"\n\n    // Append the QR code\n    qrCode.append(ref.current)\n  }, [\n    value,\n    size,\n    bgColor,\n    fgColor,\n    level,\n    marginSize,\n    dotStyle,\n    cornerSquareStyle,\n    cornerDotStyle,\n    logoImage,\n    logoSize,\n    logoMargin,\n  ])\n\n  // Re-render when theme changes by listening to color changes\n  useEffect(() => {\n    if (!qrCodeRef.current) return\n\n    const handleThemeChange = () => {\n      // Force re-render by updating the effect dependencies\n      const resolvedBgColor = resolveColor(bgColor, \"hsl(0, 0%, 100%)\")\n      const resolvedFgColor = resolveColor(fgColor, \"hsl(222.2, 84%, 4.9%)\")\n\n      qrCodeRef.current?.update({\n        dotsOptions: {\n          color: resolvedFgColor,\n          type: dotStyle,\n        },\n        backgroundOptions: {\n          color: resolvedBgColor,\n        },\n        cornersSquareOptions: {\n          color: resolvedFgColor,\n          type: cornerSquareStyle,\n        },\n        cornersDotOptions: {\n          color: resolvedFgColor,\n          type: cornerDotStyle,\n        },\n      })\n    }\n\n    // Listen for theme changes\n    const mediaQuery = window.matchMedia(\"(prefers-color-scheme: dark)\")\n    mediaQuery.addEventListener(\"change\", handleThemeChange)\n\n    // Also listen for class changes on document element (for manual theme switching)\n    const observer = new MutationObserver(handleThemeChange)\n    observer.observe(document.documentElement, {\n      attributes: true,\n      attributeFilter: [\"class\", \"data-theme\"],\n    })\n\n    return () => {\n      mediaQuery.removeEventListener(\"change\", handleThemeChange)\n      observer.disconnect()\n    }\n  }, [bgColor, fgColor, dotStyle, cornerSquareStyle, cornerDotStyle])\n\n  if (!value) {\n    return (\n      <div\n        className={cn(\n          \"flex items-center justify-center rounded-xl border-2 border-dashed border-gray-300 text-gray-500\",\n          className\n        )}\n        style={{ width: size, height: size }}\n      >\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          viewBox=\"0 0 368 368\"\n          className=\"animate-pulse rounded-lg opacity-30\"\n          style={{\n            width: `${size - marginSize * 8}px`,\n            height: `${size - marginSize * 8}px`,\n          }}\n        >\n          <path\n            d=\"m 16,16 0,16 0,16 0,16 0,16 0,16 0,16 0,16 16,0 16,0 16,0 16,0 16,0 16,0 16,0 0,-16 0,-16 0,-16 0,-16 0,-16 0,-16 0,-16 -16,0 -16,0 -16,0 -16,0 -16,0 -16,0 -16,0 z m 128,0 0,16 0,16 16,0 0,-16 16,0 0,-16 -16,0 -16,0 z m 32,16 0,16 16,0 0,-16 -16,0 z m 16,16 0,16 16,0 16,0 0,-16 0,-16 0,-16 -16,0 0,16 0,16 -16,0 z m 0,16 -16,0 -16,0 -16,0 0,16 16,0 16,0 0,16 -16,0 0,16 16,0 0,16 16,0 0,-16 16,0 0,16 -16,0 0,16 -16,0 0,16 16,0 16,0 0,16 16,0 0,-16 0,-16 0,-16 0,-16 0,-16 -16,0 0,-16 -16,0 0,-16 z m 16,112 -16,0 0,16 -16,0 0,16 0,16 16,0 0,16 0,16 -16,0 -16,0 0,-16 16,0 0,-16 -16,0 0,-16 0,-16 -16,0 0,-16 16,0 0,16 16,0 0,-16 0,-16 -16,0 -16,0 0,-16 -16,0 -16,0 -16,0 0,16 -16,0 0,16 -16,0 0,-16 16,0 0,-16 -16,0 -16,0 0,16 -16,0 0,16 0,16 0,16 16,0 0,-16 16,0 16,0 16,0 0,-16 16,0 0,-16 16,0 0,16 -16,0 0,16 16,0 0,16 -16,0 0,16 16,0 16,0 0,16 0,16 0,16 16,0 0,16 16,0 16,0 16,0 0,16 -16,0 -16,0 -16,0 0,-16 -16,0 0,16 0,16 16,0 0,16 -16,0 0,16 16,0 16,0 0,-16 16,0 0,16 16,0 16,0 16,0 16,0 0,-16 16,0 0,16 16,0 16,0 16,0 0,-16 -16,0 -16,0 0,-16 -16,0 0,-16 -16,0 0,16 -16,0 -16,0 0,16 -16,0 0,-16 16,0 0,-16 0,-16 0,-16 16,0 0,-16 -16,0 -16,0 0,-16 16,0 0,-16 0,-16 0,-16 -16,0 0,-16 z m 48,128 0,-16 -16,0 0,16 16,0 z m 32,16 16,0 16,0 0,-16 -16,0 -16,0 0,16 z m 32,-16 16,0 0,-16 0,-16 0,-16 0,-16 -16,0 -16,0 -16,0 0,-16 -16,0 0,16 -16,0 0,16 0,16 16,0 0,-16 16,0 0,16 0,16 16,0 16,0 0,16 z m -48,-80 0,-16 -16,0 -16,0 0,16 16,0 16,0 z m 16,0 16,0 0,-16 0,-16 0,-16 16,0 0,16 16,0 0,16 16,0 0,-16 0,-16 -16,0 0,-16 16,0 0,-16 -16,0 -16,0 0,16 -16,0 0,-16 -16,0 0,16 -16,0 0,16 16,0 0,16 0,16 0,16 z m -16,-48 -16,0 0,16 16,0 0,-16 z m 64,32 -16,0 0,16 16,0 0,-16 z m -224,0 0,-16 -16,0 0,16 16,0 z m -16,0 -16,0 -16,0 -16,0 0,16 16,0 16,0 16,0 0,-16 z m -64,0 -16,0 0,16 16,0 0,-16 z m 0,-48 0,-16 -16,0 0,16 16,0 z m 112,-16 16,0 0,-16 0,-16 -16,0 0,16 0,16 z m 96,-128 0,16 0,16 0,16 0,16 0,16 0,16 0,16 16,0 16,0 16,0 16,0 16,0 16,0 16,0 0,-16 0,-16 0,-16 0,-16 0,-16 0,-16 0,-16 -16,0 -16,0 -16,0 -16,0 -16,0 -16,0 -16,0 z m -208,16 16,0 16,0 16,0 16,0 16,0 0,16 0,16 0,16 0,16 0,16 -16,0 -16,0 -16,0 -16,0 -16,0 0,-16 0,-16 0,-16 0,-16 0,-16 z m 224,0 16,0 16,0 16,0 16,0 16,0 0,16 0,16 0,16 0,16 0,16 -16,0 -16,0 -16,0 -16,0 -16,0 0,-16 0,-16 0,-16 0,-16 0,-16 z m -208,16 0,16 0,16 0,16 16,0 16,0 16,0 0,-16 0,-16 0,-16 -16,0 -16,0 -16,0 z m 224,0 0,16 0,16 0,16 16,0 16,0 16,0 0,-16 0,-16 0,-16 -16,0 -16,0 -16,0 z m -32,96 0,16 16,0 0,-16 -16,0 z m -224,96 0,16 0,16 0,16 0,16 0,16 0,16 0,16 16,0 16,0 16,0 16,0 16,0 16,0 16,0 0,-16 0,-16 0,-16 0,-16 0,-16 0,-16 0,-16 -16,0 -16,0 -16,0 -16,0 -16,0 -16,0 -16,0 z m 16,16 16,0 16,0 16,0 16,0 16,0 0,16 0,16 0,16 0,16 0,16 -16,0 -16,0 -16,0 -16,0 -16,0 0,-16 0,-16 0,-16 0,-16 0,-16 z m 16,16 0,16 0,16 0,16 16,0 16,0 16,0 0,-16 0,-16 0,-16 -16,0 -16,0 -16,0 z m 288,48 0,16 16,0 0,-16 -16,0 z\"\n            fill=\"currentColor\"\n            className=\"text-gray-400 dark:text-gray-600\"\n          />\n        </svg>\n      </div>\n    )\n  }\n\n  const handleClick = () => {\n    if (enableNavigation && value) {\n      // Check if the value is a valid URL\n      try {\n        const url = new URL(value)\n        window.open(url.toString(), \"_blank\", \"noopener,noreferrer\")\n      } catch {\n        // If not a valid URL, try to open it anyway (might be a relative path)\n        window.open(value, \"_blank\", \"noopener,noreferrer\")\n      }\n    }\n  }\n\n  return (\n    <div\n      className={cn(\n        \"inline-block\",\n        enableNavigation &&\n          \"cursor-pointer transition-opacity hover:opacity-80\",\n        className\n      )}\n      onClick={handleClick}\n      role={enableNavigation ? \"button\" : undefined}\n      tabIndex={enableNavigation ? 0 : undefined}\n      onKeyDown={\n        enableNavigation\n          ? (e) => {\n              if (e.key === \"Enter\" || e.key === \" \") {\n                e.preventDefault()\n                handleClick()\n              }\n            }\n          : undefined\n      }\n    >\n      <div ref={ref} />\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}