{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "code-block",
  "title": "Code Block",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/code-block/code-block.component.ts",
      "content": "import {\n  Component,\n  Input,\n  OnInit,\n  OnChanges,\n  OnDestroy,\n  SimpleChanges,\n  booleanAttribute,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { codeToTokens, type ThemedToken, type BundledLanguage } from 'shiki/bundle/web'\n\nexport type CodeBlockCopyState = 'idle' | 'copied' | 'error'\n\ninterface HighlightSegment {\n  content: string\n  style?: ThemedToken['htmlStyle']\n}\n\nfunction toHighlightSegments(tokens: ThemedToken[][], source: string): HighlightSegment[] {\n  const segments: HighlightSegment[] = []\n  let cursor = 0\n\n  for (const token of tokens.flat()) {\n    if (token.offset > cursor) segments.push({ content: source.slice(cursor, token.offset) })\n    segments.push({ content: token.content, style: token.htmlStyle })\n    cursor = token.offset + token.content.length\n  }\n\n  if (cursor < source.length) segments.push({ content: source.slice(cursor) })\n  return segments\n}\n\nlet nextId = 0\n\n/**\n * Angular port of UIPKGE CodeBlock. Syntax-highlighted code preview with a\n * language header, copy button, optional line numbers, and collapsible `<pre>`\n * content. 1:1 React parity.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-code-block, [ui-code-block]',\n  standalone: true,\n  imports: [UiButtonComponent],\n  host: {\n    '[attr.data-slot]': '\"code-block\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[class]': 'hostClass',\n  },\n  template: `\n    @if (showHeader) {\n      <div class=\"border-border bg-muted/40 flex items-center justify-between gap-2 border-b px-3 py-2\">\n        <span class=\"text-muted-foreground text-xs font-medium tracking-wide uppercase\">{{ language }}</span>\n        <div class=\"flex items-center gap-1\">\n          <button\n            ui-button\n            variant=\"ghost\"\n            size=\"xs\"\n            class=\"h-7 gap-1.5 px-2\"\n            data-slot=\"code-block-copy\"\n            (click)=\"copyToClipboard()\"\n          >\n            @if (copyStatus === 'copied') {\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 text-success size-3\"\n                aria-hidden=\"true\"\n              >\n                <path d=\"M20 6 9 17l-5-5\" />\n              </svg>\n            } @else {\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-copy size-3\"\n                aria-hidden=\"true\"\n              >\n                <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n                <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n              </svg>\n            }\n            <span\n              class=\"text-xs\"\n              [class.text-destructive]=\"copyStatus === 'error'\"\n              aria-live=\"polite\"\n              aria-atomic=\"true\"\n            >\n              {{ copyLabel }}\n            </span>\n          </button>\n          <button\n            ui-button\n            variant=\"ghost\"\n            size=\"xs\"\n            class=\"h-7 gap-1.5 px-2\"\n            data-slot=\"code-block-toggle\"\n            [attr.aria-expanded]=\"isExpanded\"\n            [attr.aria-controls]=\"bodyId\"\n            (click)=\"isExpanded = !isExpanded\"\n          >\n            @if (isExpanded) {\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-chevron-up size-3\"\n                aria-hidden=\"true\"\n              >\n                <path d=\"m18 15-6-6-6 6\" />\n              </svg>\n            } @else {\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-chevron-down size-3\"\n                aria-hidden=\"true\"\n              >\n                <path d=\"m6 9 6 6 6-6\" />\n              </svg>\n            }\n            <span class=\"text-xs\">{{ isExpanded ? 'Hide' : 'Show' }} code</span>\n          </button>\n        </div>\n      </div>\n    }\n\n    <div\n      [id]=\"bodyId\"\n      data-slot=\"code-block-body\"\n      role=\"region\"\n      [attr.aria-label]=\"language + ' code sample'\"\n      tabindex=\"0\"\n      [hidden]=\"!bodyVisible\"\n      class=\"bg-background/40 focus-visible:ring-ring overflow-auto font-mono text-sm leading-relaxed focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset\"\n      [style.max-height]=\"maxHeight\"\n    >\n      <div class=\"flex w-max min-w-full\">\n        @if (showLineNumbers) {\n          <div\n            aria-hidden=\"true\"\n            class=\"text-muted-foreground/60 border-border/60 bg-muted/30 sticky left-0 border-r px-3 py-3 text-right tabular-nums select-none\"\n          >\n            @for (line of lines; track $index) {\n              <span class=\"block\">{{ $index + 1 }}</span>\n            }\n          </div>\n        }\n        <pre\n          class=\"m-0 min-w-max flex-1\"\n        ><code class=\"block cursor-text whitespace-pre px-4 py-3\">@if (highlightedSegments) {@for (segment of highlightedSegments; track $index) {<span data-syntax-token=\"\" class=\"text-[var(--shiki-light)] dark:text-[var(--shiki-dark)]\" [style]=\"segment.style\">{{ segment.content }}</span>}} @else {{{ code }}}</code></pre>\n      </div>\n    </div>\n  `,\n})\nexport class UiCodeBlockComponent implements OnInit, OnChanges, OnDestroy {\n  @Input() code = ''\n  @Input() language = 'tsx'\n  @Input({ transform: booleanAttribute }) showLineNumbers = true\n  @Input() maxHeight = '400px'\n  @Input({ transform: booleanAttribute }) defaultExpanded = true\n  @Input({ transform: booleanAttribute }) showHeader = true\n  @Input('class') className?: string\n\n  readonly bodyId = `code-block-${++nextId}`\n  isExpanded = true\n  copyStatus: CodeBlockCopyState = 'idle'\n  highlightedSegments: HighlightSegment[] | null = null\n\n  private copyResetTimer?: ReturnType<typeof setTimeout>\n  private highlightRequest = 0\n\n  ngOnInit(): void {\n    this.isExpanded = this.defaultExpanded\n    this.highlightCode()\n  }\n\n  ngOnChanges(changes: SimpleChanges): void {\n    if (changes['code'] || changes['language']) {\n      this.highlightCode()\n    }\n  }\n\n  ngOnDestroy(): void {\n    if (this.copyResetTimer) clearTimeout(this.copyResetTimer)\n  }\n\n  get hostClass(): string {\n    return cn('block group border-border bg-muted/20 relative overflow-hidden rounded-lg border', this.className)\n  }\n\n  get lines(): string[] {\n    return this.code.split('\\n')\n  }\n\n  get bodyVisible(): boolean {\n    return !this.showHeader || this.isExpanded\n  }\n\n  get copyLabel(): string {\n    if (this.copyStatus === 'copied') return 'Copied'\n    if (this.copyStatus === 'error') return 'Copy failed'\n    return 'Copy'\n  }\n\n  get copyLabelClass(): string {\n    return cn('text-xs', this.copyStatus === 'error' && 'text-destructive')\n  }\n\n  async copyToClipboard(): Promise<void> {\n    try {\n      await navigator.clipboard.writeText(this.code)\n      this.copyStatus = 'copied'\n    } catch {\n      this.copyStatus = 'error'\n    } finally {\n      if (this.copyResetTimer) clearTimeout(this.copyResetTimer)\n      this.copyResetTimer = setTimeout(() => {\n        this.copyStatus = 'idle'\n      }, 1600)\n    }\n  }\n\n  private async highlightCode(): Promise<void> {\n    const req = ++this.highlightRequest\n    this.highlightedSegments = null\n\n    try {\n      const { tokens } = await codeToTokens(this.code, {\n        lang: this.language.toLowerCase() as BundledLanguage,\n        themes: { light: 'github-light', dark: 'github-dark' },\n        defaultColor: false,\n      })\n      if (req === this.highlightRequest) {\n        this.highlightedSegments = toHighlightSegments(tokens, this.code)\n      }\n    } catch {\n      if (req === this.highlightRequest) {\n        this.highlightedSegments = null\n      }\n    }\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/code-block/code-block.component.ts"
    },
    {
      "path": "packages/registry-angular/components/code-block/index.ts",
      "content": "export { UiCodeBlockComponent } from './code-block.component'\nexport type { CodeBlockCopyState } from './code-block.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/code-block/index.ts"
    }
  ],
  "dependencies": [
    "shiki"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/button.json"
  ],
  "description": "Syntax-highlighted code preview with a language header, copy button, optional line numbers, and collapsible `<pre>` content. Use for installation snippets, API examples, and source you want users to copy verbatim.",
  "categories": [
    "data-display"
  ]
}