{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "qr-code",
  "title": "Qr Code",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/qr-code/qr-code.component.ts",
      "content": "import {\n  Component,\n  ElementRef,\n  EventEmitter,\n  Input,\n  type OnChanges,\n  type OnDestroy,\n  Output,\n  TemplateRef,\n  ViewChild,\n  booleanAttribute,\n  effect,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore -- qrcode ships no types unless @types/qrcode (a devDependency) is installed; @ts-ignore stays valid either way\nimport QRCodeLib from 'qrcode'\nimport { cn } from '@/lib/utils'\nimport { UiRenderTemplateDirective } from '@/ui/popper/popper'\n\nexport type QRCodeType = 'canvas' | 'svg'\nexport type QRCodeStatus = 'active' | 'expired' | 'loading' | 'scanned'\nexport type QRCodeErrorLevel = 'L' | 'M' | 'Q' | 'H'\n\n/**\n * Angular port of UIPKGE QrCode (React `QrCode`). Generates the code with the `qrcode`\n * package (PNG data URL for `type=\"canvas\"`, inline SVG for `type=\"svg\"`) whenever the\n * value / size / colors / level / margin / status change, overlays an optional centre\n * icon, shows expired / scanned / loading overlays (expired offers Refresh when\n * `(refresh)` is bound, like React's `onRefresh`), and renders a Download link for active\n * canvas codes. `extra` (a TemplateRef, React's `extra` node) replaces the Download link.\n * Colors are hex because `qrcode` cannot resolve CSS variables.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-qr-code, [ui-qr-code]',\n  standalone: true,\n  imports: [UiRenderTemplateDirective],\n  host: {\n    '[attr.data-slot]': '\"qr-code\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.aria-busy]': 'status === \"loading\" ? true : null',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <div\n      class=\"relative inline-flex items-center justify-center overflow-hidden\"\n      [style.width.px]=\"size\"\n      [style.height.px]=\"size\"\n    >\n      @if (type === 'svg' && qrSvg()) {\n        <div #svgHost class=\"size-full\"></div>\n      } @else if (qrDataUrl()) {\n        <img [src]=\"qrDataUrl()\" [alt]=\"'QR Code for ' + value\" class=\"size-full\" />\n      }\n\n      @if (icon && status === 'active') {\n        <div class=\"absolute inset-0 flex items-center justify-center\">\n          <div\n            class=\"bg-background overflow-hidden rounded-md shadow-sm\"\n            [style.width.px]=\"iconDimensions.width\"\n            [style.height.px]=\"iconDimensions.height\"\n          >\n            <img [src]=\"icon\" alt=\"\" class=\"size-full object-cover\" />\n          </div>\n        </div>\n      }\n\n      @if (status !== 'active') {\n        <div class=\"bg-background/90 absolute inset-0 flex flex-col items-center justify-center gap-2 backdrop-blur-sm\">\n          @switch (status) {\n            @case ('expired') {\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"24\"\n                height=\"24\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                stroke-width=\"2\"\n                stroke-linecap=\"round\"\n                stroke-linejoin=\"round\"\n                class=\"lucide lucide-rotate-ccw size-8\"\n                aria-hidden=\"true\"\n              >\n                <path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\" />\n                <path d=\"M3 3v5h5\" />\n              </svg>\n            }\n            @case ('scanned') {\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"24\"\n                height=\"24\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                stroke-width=\"2\"\n                stroke-linecap=\"round\"\n                stroke-linejoin=\"round\"\n                class=\"lucide lucide-check size-8\"\n                aria-hidden=\"true\"\n              >\n                <path d=\"M20 6 9 17l-5-5\" />\n              </svg>\n            }\n            @case ('loading') {\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"24\"\n                height=\"24\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                stroke-width=\"2\"\n                stroke-linecap=\"round\"\n                stroke-linejoin=\"round\"\n                class=\"lucide lucide-loader-circle size-8 animate-spin\"\n                aria-hidden=\"true\"\n              >\n                <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" />\n              </svg>\n            }\n          }\n          <span class=\"text-foreground text-sm font-medium\">{{ statusText }}</span>\n          @if (status === 'expired' && refresh.observed) {\n            <button\n              type=\"button\"\n              class=\"bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-ring inline-flex items-center gap-1 rounded-md px-3 py-1 text-xs font-medium focus-visible:ring-2 focus-visible:outline-none\"\n              (click)=\"refresh.emit()\"\n            >\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"24\"\n                height=\"24\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                stroke-width=\"2\"\n                stroke-linecap=\"round\"\n                stroke-linejoin=\"round\"\n                class=\"lucide lucide-scan-line size-3\"\n                aria-hidden=\"true\"\n              >\n                <path d=\"M3 7V5a2 2 0 0 1 2-2h2\" />\n                <path d=\"M17 3h2a2 2 0 0 1 2 2v2\" />\n                <path d=\"M21 17v2a2 2 0 0 1-2 2h-2\" />\n                <path d=\"M7 21H5a2 2 0 0 1-2-2v-2\" />\n                <path d=\"M7 12h10\" />\n              </svg>\n              Refresh\n            </button>\n          }\n        </div>\n      }\n    </div>\n\n    @if (extra) {\n      <ng-container [uiRenderTemplate]=\"extra\" />\n    } @else if (type === 'canvas' && status === 'active' && qrDataUrl()) {\n      <button\n        type=\"button\"\n        class=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring text-xs underline-offset-2 hover:underline focus-visible:ring-2 focus-visible:outline-none\"\n        (click)=\"download()\"\n      >\n        Download\n      </button>\n    }\n  `,\n})\nexport class UiQRCodeComponent implements OnChanges, OnDestroy {\n  @Input({ required: true }) value = ''\n  @Input() type: QRCodeType = 'canvas'\n  @Input() size = 160\n  @Input() color = '#000000'\n  @Input() bgColor = '#ffffff'\n  @Input() icon?: string\n  @Input() iconSize?: number | { width: number; height: number }\n  @Input() errorLevel: QRCodeErrorLevel = 'M'\n  @Input({ transform: booleanAttribute }) bordered = true\n  @Input() status: QRCodeStatus = 'active'\n  @Input() marginSize = 0\n  @Input('class') className?: string\n  /** Replaces the default Download link (React `extra`). */\n  @Input() extra?: TemplateRef<unknown>\n  /** Fired by the expired overlay's Refresh button; the button only shows when this is bound. */\n  @Output() refresh = new EventEmitter<void>()\n\n  @ViewChild('svgHost') set svgHost(ref: ElementRef<HTMLElement> | undefined) {\n    this._svgHost.set(ref?.nativeElement)\n  }\n\n  /** Signals: generation resolves asynchronously, so zoneless apps need them to repaint. */\n  readonly qrDataUrl = signal('')\n  readonly qrSvg = signal('')\n  private readonly _svgHost = signal<HTMLElement | undefined>(undefined)\n  private run = 0\n\n  constructor() {\n    // Write the generated SVG markup directly (bypasses Angular's HTML sanitizer, which strips SVG).\n    effect(() => {\n      const host = this._svgHost()\n      if (host) host.innerHTML = this.qrSvg()\n    })\n  }\n\n  get iconDimensions(): { width: number; height: number } {\n    if (typeof this.iconSize === 'number') return { width: this.iconSize, height: this.iconSize }\n    return this.iconSize ?? { width: 40, height: 40 }\n  }\n\n  get statusText(): string {\n    return this.status === 'expired' ? 'Expired' : this.status === 'scanned' ? 'Scanned' : 'Loading...'\n  }\n\n  get hostClass(): string {\n    return cn(\n      'inline-flex flex-col items-center gap-2',\n      this.bordered && 'bg-background rounded-lg border p-4',\n      this.className,\n    )\n  }\n\n  ngOnChanges(): void {\n    void this.generate()\n  }\n\n  ngOnDestroy(): void {\n    this.run++\n  }\n\n  private async generate(): Promise<void> {\n    const run = ++this.run\n    if (!this.value || this.status === 'loading') return\n    try {\n      const options = {\n        width: this.size,\n        margin: this.marginSize,\n        color: { dark: this.color, light: this.bgColor },\n        errorCorrectionLevel: this.errorLevel,\n      }\n      if (this.type === 'svg') {\n        const svg: string = await QRCodeLib.toString(this.value, { type: 'svg', ...options })\n        if (run === this.run) this.qrSvg.set(svg)\n      } else {\n        const url: string = await QRCodeLib.toDataURL(this.value, options)\n        if (run === this.run) this.qrDataUrl.set(url)\n      }\n    } catch (e) {\n      console.error('QR Code generation failed:', e)\n    }\n  }\n\n  download(): void {\n    const link = document.createElement('a')\n    link.download = `qrcode-${this.value.slice(0, 20)}.png`\n    link.href = this.qrDataUrl()\n    link.click()\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/qr-code/qr-code.component.ts"
    },
    {
      "path": "packages/registry-angular/components/qr-code/index.ts",
      "content": "export { UiQRCodeComponent, type QRCodeType, type QRCodeStatus, type QRCodeErrorLevel } from './qr-code.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/qr-code/index.ts"
    }
  ],
  "dependencies": [
    "qrcode"
  ],
  "devDependencies": [
    "@types/qrcode"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/popper.json"
  ],
  "description": "QR code renderer — pass `value` and a size, get a canvas PNG or SVG. Useful for sign-in links, share URLs, and Wi-Fi credentials. Depends on the `qrcode` package.",
  "categories": [
    "data-display"
  ]
}