{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "qr-code",
  "title": "QR Code",
  "description": "A crisp SVG QR code in dots, rounded or square modules. Renders on the server, and can draw itself in.",
  "dependencies": [
    "qrcode@^1.5.4"
  ],
  "devDependencies": [
    "@types/qrcode@^1.5.6"
  ],
  "registryDependencies": [
    "@corsair-ui/utils",
    "@corsair-ui/use-entrance"
  ],
  "files": [
    {
      "path": "registry/default/ui/qr-code.tsx",
      "content": "import { create } from \"qrcode\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\n// Encoding runs where the component renders; on the server that means no\n// encoder in the browser bundle.\n\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { QRCodeReveal } from \"@/registry/default/ui/qr-code-reveal\";\n\n/** The three 7×7 corner squares scanners lock on to. */\nfunction inFinder(row: number, column: number, size: number) {\n  return (\n    (row < 7 && column < 7) || (row < 7 && column >= size - 7) || (row >= size - 7 && column < 7)\n  );\n}\n\n/** A rounded rectangle as path data, so a ring can be cut out with even-odd filling. */\nfunction roundedRect(x: number, y: number, size: number, radius: number) {\n  const r = Math.min(radius, size / 2);\n  return `M${x + r} ${y}h${size - 2 * r}a${r} ${r} 0 0 1 ${r} ${r}v${size - 2 * r}a${r} ${r} 0 0 1 -${r} ${r}h-${size - 2 * r}a${r} ${r} 0 0 1 -${r} -${r}v-${size - 2 * r}a${r} ${r} 0 0 1 ${r} -${r}z`;\n}\n\ninterface QRCodeProps extends Omit<ComponentProps<\"svg\">, \"children\"> {\n  /** The text or URL to encode. */\n  value: string;\n  /** Width and height in px; the SVG scales cleanly beyond it. */\n  size?: number;\n  /** How the modules are drawn. Every style keeps the corner squares solid, so it scans. */\n  variant?: \"dots\" | \"rounded\" | \"squares\";\n  /**\n   * How much damage the code survives: L 7%, M 15%, Q 25%, H 30%. Higher\n   * levels make denser codes; use Q or H when something covers the middle.\n   */\n  errorCorrection?: \"L\" | \"M\" | \"Q\" | \"H\";\n  /** Quiet zone around the code, in modules. Scanners want at least 2; the standard asks for 4. */\n  margin?: number;\n  /** Colour of the modules. Keep it dark on a light background: many scanners fail on inverted codes. */\n  color?: string;\n  /** Colour behind the modules. */\n  background?: string;\n  /**\n   * Opt-in: the modules draw themselves from the centre outwards, on first\n   * paint (\"load\") or when the code scrolls into view (\"in-view\"). The\n   * background never animates, and with `prefers-reduced-motion` the code\n   * simply shows. Leave it unset for a plain server-only image.\n   */\n  reveal?: \"load\" | \"in-view\";\n  /** How long the reveal takes, in ms. */\n  revealDuration?: number;\n}\n\n/**\n * A QR code drawn as one crisp SVG, from the `qrcode` encoder. It has no\n * state and no effects, so it renders on the server with no JavaScript in\n * the page. It is an image named after what it encodes; pass `aria-label`\n * to describe it better (\"QR code to download the app\").\n *\n * @example\n * <QRCode value=\"https://github.com/KevinGirelli/corsair-ui\" size={180} />\n *\n * @example\n * <QRCode value=\"https://github.com/KevinGirelli/corsair-ui\" reveal=\"in-view\" />\n */\nfunction QRCode({\n  value,\n  size = 200,\n  variant = \"dots\",\n  errorCorrection = \"M\",\n  margin = 2,\n  color = \"#0b0b0c\",\n  background = \"#ffffff\",\n  reveal,\n  revealDuration = 800,\n  className,\n  \"aria-label\": label,\n  ...props\n}: QRCodeProps) {\n  let modules: ReturnType<typeof create>[\"modules\"];\n  try {\n    modules = create(value, { errorCorrectionLevel: errorCorrection }).modules;\n  } catch {\n    // Too much data for a QR code at this level.\n    return null;\n  }\n\n  const count = modules.size;\n  const quiet = Math.max(0, margin);\n  const total = count + quiet * 2;\n  const shapes: ReactNode[] = [];\n\n  for (let row = 0; row < count; row++) {\n    for (let column = 0; column < count; column++) {\n      if (!modules.get(row, column) || inFinder(row, column, count)) continue;\n      const x = column + quiet;\n      const y = row + quiet;\n      shapes.push(\n        variant === \"dots\" ? (\n          <circle key={`${row}-${column}`} cx={x + 0.5} cy={y + 0.5} r={0.4} />\n        ) : (\n          <rect\n            key={`${row}-${column}`}\n            x={x + (variant === \"rounded\" ? 0.05 : 0)}\n            y={y + (variant === \"rounded\" ? 0.05 : 0)}\n            width={variant === \"rounded\" ? 0.9 : 1}\n            height={variant === \"rounded\" ? 0.9 : 1}\n            rx={variant === \"rounded\" ? 0.3 : 0}\n          />\n        )\n      );\n    }\n  }\n\n  const corners = [\n    [quiet, quiet],\n    [quiet + count - 7, quiet],\n    [quiet, quiet + count - 7],\n  ] as const;\n  const square = variant === \"squares\";\n  const modulesGroup = (\n    <g fill={color}>\n      {corners.map(([x, y]) => (\n        <g key={`${x}-${y}`}>\n          {/* The ring: an outer square with the inner one cut out. */}\n          <path\n            fillRule=\"evenodd\"\n            d={`${roundedRect(x, y, 7, square ? 0 : 2.2)}${roundedRect(x + 1, y + 1, 5, square ? 0 : 1.4)}`}\n          />\n          <path d={roundedRect(x + 2, y + 2, 3, square ? 0 : 1)} />\n        </g>\n      ))}\n      {shapes}\n    </g>\n  );\n\n  return (\n    <svg\n      role=\"img\"\n      aria-label={label ?? `QR code: ${value}`}\n      data-slot=\"qr-code\"\n      width={size}\n      height={size}\n      viewBox={`0 0 ${total} ${total}`}\n      shapeRendering={square ? \"crispEdges\" : undefined}\n      className={cn(\"block h-auto max-w-full\", className)}\n      {...props}\n    >\n      <rect width={total} height={total} rx={square ? 0 : Math.min(quiet, 2)} fill={background} />\n      {reveal ? (\n        <QRCodeReveal total={total} trigger={reveal} duration={revealDuration}>\n          {modulesGroup}\n        </QRCodeReveal>\n      ) : (\n        modulesGroup\n      )}\n    </svg>\n  );\n}\n\nexport { QRCode, type QRCodeProps };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/default/ui/qr-code-reveal.tsx",
      "content": "\"use client\";\n\nimport { useId, type ReactNode } from \"react\";\n\nimport { useEntrance, type EntranceTrigger } from \"@/registry/default/hooks/use-entrance\";\nimport { cn } from \"@/registry/default/lib/utils\";\n\ninterface QRCodeRevealProps {\n  /** The dark modules to reveal. The background stays outside, so the quiet zone always shows. */\n  children: ReactNode;\n  /** Width and height of the code in viewBox units, quiet zone included. */\n  total: number;\n  /** \"load\" plays on first paint with CSS alone; \"in-view\" when the code scrolls into view. */\n  trigger: EntranceTrigger;\n  /** How long the reveal takes, in ms. */\n  duration: number;\n}\n\n/**\n * The client half of `QRCode reveal`: masks the modules with a circle that\n * grows from the centre, so the code seems to draw itself. At rest the circle\n * covers every corner, so server HTML, reduced motion and the end of the\n * animation all show the complete, scannable code. `QRCode` renders it for\n * you when `reveal` is set.\n *\n * @example\n * <QRCodeReveal total={29} trigger=\"in-view\" duration={800}>\n *   <g fill=\"currentColor\">…</g>\n * </QRCodeReveal>\n */\nfunction QRCodeReveal({ children, total, trigger, duration }: QRCodeRevealProps) {\n  const [observe, phase] = useEntrance<SVGGElement>({ trigger, once: true });\n  // useId can contain characters url(#…) does not accept.\n  const maskId = `qr-code-reveal-${useId().replace(/[^\\w-]/g, \"\")}`;\n  const centre = total / 2;\n\n  return (\n    <g ref={observe} data-slot=\"qr-code-reveal\" data-state={phase}>\n      <defs>\n        <mask id={maskId} maskUnits=\"userSpaceOnUse\" x={0} y={0} width={total} height={total}>\n          {/* Half the diagonal: the full circle reaches every corner. */}\n          <circle\n            data-slot=\"qr-code-reveal-shape\"\n            cx={centre}\n            cy={centre}\n            r={centre * Math.SQRT2}\n            fill=\"white\"\n            className={cn(\n              \"origin-center [transform-box:fill-box]\",\n              phase !== \"static\" && \"motion-safe:animate-qr-code-reveal\"\n            )}\n            style={\n              phase === \"static\"\n                ? undefined\n                : {\n                    animationDuration: `${duration}ms`,\n                    animationPlayState: phase === \"armed\" ? \"paused\" : undefined,\n                  }\n            }\n          />\n        </mask>\n      </defs>\n      <g mask={`url(#${maskId})`}>{children}</g>\n    </g>\n  );\n}\n\nexport { QRCodeReveal, type QRCodeRevealProps };\n",
      "type": "registry:ui"
    }
  ],
  "tailwind": {
    "config": {
      "theme": {
        "extend": {
          "keyframes": {
            "qr-code-reveal": {
              "from": {
                "transform": "scale(0)"
              },
              "to": {
                "transform": "scale(1)"
              }
            }
          },
          "animation": {
            "qr-code-reveal": "qr-code-reveal 800ms cubic-bezier(0.16, 1, 0.3, 1) both"
          }
        }
      }
    }
  },
  "cssVars": {
    "theme": {
      "animate-qr-code-reveal": "qr-code-reveal 800ms cubic-bezier(0.16, 1, 0.3, 1) both"
    }
  },
  "css": {
    "@keyframes qr-code-reveal": {
      "from": {
        "transform": "scale(0)"
      },
      "to": {
        "transform": "scale(1)"
      }
    }
  },
  "type": "registry:ui"
}