{
  "name": "qr-code",
  "type": "registry:ui",
  "dependencies": [
    "radix-ui",
    "qrcode"
  ],
  "devDependencies": [
    "@types/qrcode"
  ],
  "registryDependencies": [
    "@diceui/use-lazy-ref"
  ],
  "files": [
    {
      "path": "ui/qr-code.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { useComposedRefs } from \"@/lib/compose-refs\";\nimport { cn } from \"@/lib/utils\";\nimport { useLazyRef } from \"@/registry/bases/radix/hooks/use-lazy-ref\";\n\nconst ROOT_NAME = \"QRCode\";\nconst CANVAS_NAME = \"QRCodeCanvas\";\nconst SVG_NAME = \"QRCodeSvg\";\nconst IMAGE_NAME = \"QRCodeImage\";\nconst SKELETON_NAME = \"QRCodeSkeleton\";\n\ntype QRCodeLevel = \"L\" | \"M\" | \"Q\" | \"H\";\n\ninterface QRCodeCanvasOpts {\n  errorCorrectionLevel: QRCodeLevel;\n  type?: \"image/png\" | \"image/jpeg\" | \"image/webp\";\n  quality?: number;\n  margin?: number;\n  color?: {\n    dark: string;\n    light: string;\n  };\n  width?: number;\n  rendererOpts?: {\n    quality?: number;\n  };\n}\n\ninterface StoreState {\n  dataUrl: string | null;\n  svgString: string | null;\n  isGenerating: boolean;\n  error: Error | null;\n  generationKey: string;\n}\n\ninterface Store {\n  subscribe: (callback: () => void) => () => void;\n  getState: () => StoreState;\n  setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n  setStates: (updates: Partial<StoreState>) => void;\n  notify: () => void;\n}\n\ninterface QRCodeContextValue {\n  value: string;\n  size: number;\n  margin: number;\n  level: QRCodeLevel;\n  backgroundColor: string;\n  foregroundColor: string;\n  canvasRef: React.RefObject<HTMLCanvasElement | null>;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStore<T>(selector: (state: StoreState) => T): T {\n  const store = React.useContext(StoreContext);\n  if (!store) {\n    throw new Error(`\\`useQRCode\\` must be used within \\`${ROOT_NAME}\\``);\n  }\n\n  const getSnapshot = React.useCallback(\n    () => selector(store.getState()),\n    [store, selector],\n  );\n\n  return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\nconst QRCodeContext = React.createContext<QRCodeContextValue | null>(null);\n\nfunction useQRCodeContext(consumerName: string) {\n  const context = React.useContext(QRCodeContext);\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n  }\n  return context;\n}\n\ninterface QRCodeProps extends Omit<React.ComponentProps<\"div\">, \"onError\"> {\n  value: string;\n  size?: number;\n  level?: QRCodeLevel;\n  margin?: number;\n  quality?: number;\n  backgroundColor?: string;\n  foregroundColor?: string;\n  onError?: (error: Error) => void;\n  onGenerated?: () => void;\n  asChild?: boolean;\n}\n\nfunction QRCode(props: QRCodeProps) {\n  const {\n    value,\n    size = 200,\n    level = \"M\",\n    margin = 1,\n    quality = 0.92,\n    backgroundColor = \"#ffffff\",\n    foregroundColor = \"#000000\",\n    onError,\n    onGenerated,\n    className,\n    style,\n    asChild,\n    ...rootProps\n  } = props;\n\n  const canvasRef = React.useRef<HTMLCanvasElement>(null);\n\n  const listenersRef = useLazyRef(() => new Set<() => void>());\n  const stateRef = useLazyRef<StoreState>(() => ({\n    dataUrl: null,\n    svgString: null,\n    isGenerating: false,\n    error: null,\n    generationKey: \"\",\n  }));\n\n  const store = React.useMemo<Store>(() => {\n    return {\n      subscribe: (cb) => {\n        listenersRef.current.add(cb);\n        return () => listenersRef.current.delete(cb);\n      },\n      getState: () => stateRef.current,\n      setState: (key, value) => {\n        if (Object.is(stateRef.current[key], value)) return;\n        stateRef.current[key] = value;\n        store.notify();\n      },\n      setStates: (updates) => {\n        let hasChanged = false;\n\n        for (const key of Object.keys(updates) as Array<keyof StoreState>) {\n          const value = updates[key];\n          if (value !== undefined && !Object.is(stateRef.current[key], value)) {\n            Object.assign(stateRef.current, { [key]: value });\n            hasChanged = true;\n          }\n        }\n\n        if (hasChanged) {\n          store.notify();\n        }\n      },\n      notify: () => {\n        for (const cb of listenersRef.current) {\n          cb();\n        }\n      },\n    };\n  }, [listenersRef, stateRef]);\n\n  const canvasOpts = React.useMemo<QRCodeCanvasOpts>(\n    () => ({\n      errorCorrectionLevel: level,\n      type: \"image/png\",\n      quality,\n      margin,\n      color: {\n        dark: foregroundColor,\n        light: backgroundColor,\n      },\n      width: size,\n    }),\n    [level, margin, foregroundColor, backgroundColor, size, quality],\n  );\n\n  const generationKey = React.useMemo(() => {\n    if (!value) return \"\";\n\n    return JSON.stringify({\n      value,\n      size,\n      level,\n      margin,\n      quality,\n      foregroundColor,\n      backgroundColor,\n    });\n  }, [value, level, margin, foregroundColor, backgroundColor, size, quality]);\n\n  const onQRCodeGenerate = React.useCallback(\n    async (targetGenerationKey: string) => {\n      if (!value || !targetGenerationKey) return;\n\n      const currentState = store.getState();\n      if (\n        currentState.isGenerating ||\n        currentState.generationKey === targetGenerationKey\n      )\n        return;\n\n      store.setStates({\n        isGenerating: true,\n        error: null,\n      });\n\n      try {\n        const QRCode = (await import(\"qrcode\")).default;\n\n        let dataUrl: string | null = null;\n\n        try {\n          dataUrl = await QRCode.toDataURL(value, canvasOpts);\n        } catch {\n          dataUrl = null;\n        }\n\n        if (canvasRef.current) {\n          await QRCode.toCanvas(canvasRef.current, value, canvasOpts);\n        }\n\n        const svgString = await QRCode.toString(value, {\n          errorCorrectionLevel: canvasOpts.errorCorrectionLevel,\n          margin: canvasOpts.margin,\n          color: canvasOpts.color,\n          width: canvasOpts.width,\n          type: \"svg\",\n        });\n\n        store.setStates({\n          dataUrl,\n          svgString,\n          isGenerating: false,\n          generationKey: targetGenerationKey,\n        });\n\n        onGenerated?.();\n      } catch (error) {\n        const parsedError =\n          error instanceof Error\n            ? error\n            : new Error(\"Failed to generate QR code\");\n        store.setStates({\n          error: parsedError,\n          isGenerating: false,\n        });\n        onError?.(parsedError);\n      }\n    },\n    [value, canvasOpts, store, onError, onGenerated],\n  );\n\n  const contextValue = React.useMemo<QRCodeContextValue>(\n    () => ({\n      value,\n      size,\n      level,\n      margin,\n      backgroundColor,\n      foregroundColor,\n      canvasRef,\n    }),\n    [value, size, backgroundColor, foregroundColor, level, margin],\n  );\n\n  React.useLayoutEffect(() => {\n    if (generationKey) {\n      const rafId = requestAnimationFrame(() => {\n        onQRCodeGenerate(generationKey);\n      });\n\n      return () => cancelAnimationFrame(rafId);\n    }\n  }, [generationKey, onQRCodeGenerate]);\n\n  const RootPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <StoreContext.Provider value={store}>\n      <QRCodeContext.Provider value={contextValue}>\n        <RootPrimitive\n          data-slot=\"qr-code\"\n          {...rootProps}\n          className={cn(className, \"relative flex flex-col items-center gap-2\")}\n          style={\n            {\n              \"--qr-code-size\": `${size}px`,\n              ...style,\n            } as React.CSSProperties\n          }\n        />\n      </QRCodeContext.Provider>\n    </StoreContext.Provider>\n  );\n}\n\ninterface QRCodeCanvasProps extends React.ComponentProps<\"canvas\"> {\n  asChild?: boolean;\n}\n\nfunction QRCodeCanvas(props: QRCodeCanvasProps) {\n  const { asChild, className, ref, ...canvasProps } = props;\n\n  const context = useQRCodeContext(CANVAS_NAME);\n  const generationKey = useStore((state) => state.generationKey);\n\n  const composedRef = useComposedRefs(ref, context.canvasRef);\n\n  const CanvasPrimitive = asChild ? SlotPrimitive.Slot : \"canvas\";\n\n  return (\n    <CanvasPrimitive\n      data-slot=\"qr-code-canvas\"\n      {...canvasProps}\n      ref={composedRef}\n      width={context.size}\n      height={context.size}\n      className={cn(\n        \"relative max-h-(--qr-code-size) max-w-(--qr-code-size)\",\n        !generationKey && \"invisible\",\n        className,\n      )}\n    />\n  );\n}\n\ninterface QRCodeSvgProps extends React.ComponentProps<\"div\"> {\n  asChild?: boolean;\n}\n\nfunction QRCodeSvg(props: QRCodeSvgProps) {\n  const { asChild, className, style, ...svgProps } = props;\n\n  const context = useQRCodeContext(SVG_NAME);\n  const svgString = useStore((state) => state.svgString);\n\n  if (!svgString) return null;\n\n  const SvgPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <SvgPrimitive\n      data-slot=\"qr-code-svg\"\n      {...svgProps}\n      className={cn(\n        \"relative max-h-(--qr-code-size) max-w-(--qr-code-size)\",\n        className,\n      )}\n      style={{ width: context.size, height: context.size, ...style }}\n      dangerouslySetInnerHTML={{ __html: svgString }}\n    />\n  );\n}\n\ninterface QRCodeImageProps extends React.ComponentProps<\"img\"> {\n  asChild?: boolean;\n}\n\nfunction QRCodeImage(props: QRCodeImageProps) {\n  const { alt = \"QR Code\", asChild, className, ...imageProps } = props;\n\n  const context = useQRCodeContext(IMAGE_NAME);\n  const dataUrl = useStore((state) => state.dataUrl);\n\n  if (!dataUrl) return null;\n\n  const ImagePrimitive = asChild ? SlotPrimitive.Slot : \"img\";\n\n  return (\n    <ImagePrimitive\n      data-slot=\"qr-code-image\"\n      {...imageProps}\n      src={dataUrl}\n      alt={alt}\n      width={context.size}\n      height={context.size}\n      className={cn(\n        \"relative max-h-(--qr-code-size) max-w-(--qr-code-size)\",\n        className,\n      )}\n    />\n  );\n}\n\ninterface QRCodeDownloadProps extends React.ComponentProps<\"button\"> {\n  filename?: string;\n  format?: \"png\" | \"svg\";\n  asChild?: boolean;\n}\n\nfunction QRCodeDownload(props: QRCodeDownloadProps) {\n  const {\n    filename = \"qrcode\",\n    format = \"png\",\n    asChild,\n    className,\n    children,\n    ...buttonProps\n  } = props;\n\n  const dataUrl = useStore((state) => state.dataUrl);\n  const svgString = useStore((state) => state.svgString);\n\n  const onClick = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      buttonProps.onClick?.(event);\n      if (event.defaultPrevented) return;\n\n      const link = document.createElement(\"a\");\n\n      if (format === \"png\" && dataUrl) {\n        link.href = dataUrl;\n        link.download = `${filename}.png`;\n      } else if (format === \"svg\" && svgString) {\n        const blob = new Blob([svgString], { type: \"image/svg+xml\" });\n        link.href = URL.createObjectURL(blob);\n        link.download = `${filename}.svg`;\n      } else {\n        return;\n      }\n\n      document.body.appendChild(link);\n      link.click();\n      document.body.removeChild(link);\n\n      if (format === \"svg\" && svgString) {\n        URL.revokeObjectURL(link.href);\n      }\n    },\n    [dataUrl, svgString, filename, format, buttonProps.onClick],\n  );\n\n  const ButtonPrimitive = asChild ? SlotPrimitive.Slot : \"button\";\n\n  return (\n    <ButtonPrimitive\n      type=\"button\"\n      data-slot=\"qr-code-download\"\n      {...buttonProps}\n      className={cn(\"max-w-(--qr-code-size)\", className)}\n      onClick={onClick}\n    >\n      {children ?? `Download ${format.toUpperCase()}`}\n    </ButtonPrimitive>\n  );\n}\n\ninterface QRCodeOverlayProps extends React.ComponentProps<\"div\"> {\n  asChild?: boolean;\n}\n\nfunction QRCodeOverlay(props: QRCodeOverlayProps) {\n  const { asChild, className, ...overlayProps } = props;\n\n  const OverlayPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <OverlayPrimitive\n      data-slot=\"qr-code-overlay\"\n      {...overlayProps}\n      className={cn(\n        \"absolute top-1/2 left-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-sm bg-background\",\n        className,\n      )}\n    />\n  );\n}\n\ninterface QRCodeSkeletonProps extends React.ComponentProps<\"div\"> {\n  asChild?: boolean;\n}\n\nfunction QRCodeSkeleton(props: QRCodeSkeletonProps) {\n  const { asChild, className, style, ...skeletonProps } = props;\n\n  const context = useQRCodeContext(SKELETON_NAME);\n  const dataUrl = useStore((state) => state.dataUrl);\n  const svgString = useStore((state) => state.svgString);\n  const generationKey = useStore((state) => state.generationKey);\n\n  const isLoaded = dataUrl || svgString || generationKey;\n\n  if (isLoaded) return null;\n\n  const SkeletonPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <SkeletonPrimitive\n      data-slot=\"qr-code-skeleton\"\n      {...skeletonProps}\n      className={cn(\n        \"absolute max-h-(--qr-code-size) max-w-(--qr-code-size) animate-pulse bg-accent\",\n        className,\n      )}\n      style={{\n        width: context.size,\n        height: context.size,\n        ...style,\n      }}\n    />\n  );\n}\n\nexport {\n  QRCode,\n  QRCodeCanvas,\n  QRCodeDownload,\n  QRCodeImage,\n  QRCodeOverlay,\n  type QRCodeProps,\n  QRCodeSkeleton,\n  QRCodeSvg,\n  useStore as useQRCode,\n};\n",
      "target": ""
    }
  ]
}