{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "code-block",
  "title": "Code Block",
  "type": "registry:ui",
  "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"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/components/code-block/code-block.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport { Check, ChevronDown, ChevronUp, Copy } from 'lucide'\nimport { cn } from '../../lib/utils'\nimport { icon } from '../../lib/icon'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\n\nlet idCounter = 0\n\n/**\n * <uip-code-block> — Source code display with syntax highlighting / preformatted code,\n * line numbers, copy button, and collapse toggle.\n */\nexport class UipCodeBlock extends LitElement {\n  static styles = [\n    tailwind,\n    css`\n      :host {\n        display: block;\n        width: 100%;\n      }\n    `,\n  ]\n\n  static properties = {\n    code: { type: String },\n    language: { type: String },\n    showLineNumbers: {\n      type: Boolean,\n      attribute: 'show-line-numbers',\n      converter: { fromAttribute: (v: string | null) => v !== null && v !== 'false' },\n    },\n    maxHeight: { type: String, attribute: 'max-height' },\n    defaultExpanded: {\n      type: Boolean,\n      attribute: 'default-expanded',\n      converter: { fromAttribute: (v: string | null) => v !== null && v !== 'false' },\n    },\n    showHeader: {\n      type: Boolean,\n      attribute: 'show-header',\n      converter: { fromAttribute: (v: string | null) => v !== null && v !== 'false' },\n    },\n    isExpanded: { state: true },\n    copyStatus: { state: true },\n  }\n\n  code = ''\n  language = 'tsx'\n  showLineNumbers = true\n  maxHeight = '400px'\n  defaultExpanded = true\n  showHeader = true\n\n  isExpanded = true\n  copyStatus: 'idle' | 'copied' | 'error' = 'idle'\n\n  private bodyId = `uip-code-block-${++idCounter}`\n  private copyResetTimer: ReturnType<typeof setTimeout> | null = null\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'code-block')\n    this.isExpanded = this.defaultExpanded\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback()\n    if (this.copyResetTimer) clearTimeout(this.copyResetTimer)\n  }\n\n  private async copyToClipboard() {\n    try {\n      await navigator.clipboard.writeText(this.code)\n      this.copyStatus = 'copied'\n      this.dispatchEvent(new CustomEvent('copy', { detail: { code: this.code }, bubbles: true, composed: true }))\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  render() {\n    const lines = this.code ? this.code.split('\\n') : []\n    const bodyVisible = !this.showHeader || this.isExpanded\n    const copyLabel = this.copyStatus === 'copied' ? 'Copied' : this.copyStatus === 'error' ? 'Copy failed' : 'Copy'\n\n    return html`\n      <div\n        part=\"base\"\n        class=\"group border-border bg-muted/20 relative overflow-hidden rounded-lg border font-mono text-sm\"\n      >\n        ${this.showHeader\n          ? html`\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\">${this.language}</span>\n                <div class=\"flex items-center gap-1 font-sans\">\n                  <button\n                    type=\"button\"\n                    class=\"hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring inline-flex h-7 items-center gap-1.5 rounded-md px-2 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                    @click=${this.copyToClipboard}\n                  >\n                    ${this.copyStatus === 'copied'\n                      ? icon(Check, 'check', 'text-success size-3')\n                      : icon(Copy, 'copy', 'size-3')}\n                    <span\n                      class=${cn('text-xs', this.copyStatus === 'error' && 'text-destructive')}\n                      aria-live=\"polite\"\n                      aria-atomic=\"true\"\n                    >\n                      ${copyLabel}\n                    </span>\n                  </button>\n                  <button\n                    type=\"button\"\n                    class=\"hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring inline-flex h-7 items-center gap-1.5 rounded-md px-2 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                    aria-expanded=${this.isExpanded ? 'true' : 'false'}\n                    aria-controls=${this.bodyId}\n                    @click=${() => (this.isExpanded = !this.isExpanded)}\n                  >\n                    ${this.isExpanded\n                      ? icon(ChevronUp, 'chevron-up', 'size-3')\n                      : icon(ChevronDown, 'chevron-down', 'size-3')}\n                    <span class=\"text-xs\">${this.isExpanded ? 'Hide' : 'Show'} code</span>\n                  </button>\n                </div>\n              </div>\n            `\n          : nothing}\n\n        <div\n          id=${this.bodyId}\n          role=\"region\"\n          aria-label=\"${this.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: ${this.maxHeight};\"\n        >\n          <div class=\"flex w-max min-w-full\">\n            ${this.showLineNumbers\n              ? html`\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                    ${lines.map((_, i) => html`<span class=\"block\">${i + 1}</span>`)}\n                  </div>\n                `\n              : nothing}\n            <pre class=\"m-0 min-w-max flex-1\"><code class=\"block cursor-text px-4 py-3 whitespace-pre\">${this.code}</code></pre>\n          </div>\n        </div>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-code-block') || customElements.define('uip-code-block', UipCodeBlock)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-code-block': UipCodeBlock\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/components/code-block/code-block.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
