{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "code-block",
  "title": "Code Block",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/code-block/CodeBlock.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface CodeBlockProps extends HTMLAttributes<HTMLDivElement> {\n    /** Source code to display. */\n    code: string\n    /** Language label shown in the header. */\n    language?: string\n    /** Render line numbers in the gutter. */\n    showLineNumbers?: boolean\n    /** Maximum height of the code body before scrolling kicks in. CSS length (e.g. '400px'). */\n    maxHeight?: string\n    /** Render expanded on first paint. Default true. */\n    defaultExpanded?: boolean\n    /** Show the language label / copy / collapse header. */\n    showHeader?: boolean\n  }\n\n  export interface HighlightSegment {\n    content: string\n    style?: string\n  }\n</script>\n\n<script lang=\"ts\">\n  import { onDestroy } from 'svelte'\n  import { Check, ChevronDown, ChevronUp, Copy } from '@lucide/svelte'\n  import { codeToTokens, type BundledLanguage, type ThemedToken } from 'shiki/bundle/web'\n  import { cn } from '$lib/utils'\n  import { Button } from '$lib/components/ui/button'\n\n  let {\n    class: className,\n    code,\n    language = 'vue',\n    showLineNumbers = true,\n    maxHeight = '400px',\n    defaultExpanded = true,\n    showHeader = true,\n    ...restProps\n  }: CodeBlockProps = $props()\n\n  // Intentional one-shot seed: the header toggle owns expansion after mount.\n  // svelte-ignore state_referenced_locally\n  let isExpanded = $state(defaultExpanded)\n  let copyStatus = $state<'idle' | 'copied' | 'error'>('idle')\n  let highlightedSegments: HighlightSegment[] | null = $state(null)\n  let copyResetTimer: ReturnType<typeof setTimeout> | undefined\n  let highlightRequest = 0\n\n  const bodyId = `code-block-${Math.random().toString(36).slice(2, 9)}`\n  const lines = $derived(code.split('\\n'))\n  const bodyVisible = $derived(!showHeader || isExpanded)\n  const copyLabel = $derived(copyStatus === 'copied' ? 'Copied' : copyStatus === 'error' ? 'Copy failed' : 'Copy')\n\n  function serializeStyle(style: ThemedToken['htmlStyle']): string | undefined {\n    if (!style) return undefined\n    if (typeof style === 'string') return style\n    return Object.entries(style)\n      .map(([key, value]) => `${key}:${value}`)\n      .join(';')\n  }\n\n  function 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: serializeStyle(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\n  $effect(() => {\n    const source = code\n    const lang = language\n    const request = ++highlightRequest\n    highlightedSegments = null\n\n    // Fire-and-forget: the request counter guards against out-of-order\n    // completion when code/language change in quick succession.\n    codeToTokens(source, {\n      lang: lang.toLowerCase() as BundledLanguage,\n      themes: { light: 'github-light', dark: 'github-dark' },\n      defaultColor: false,\n    }).then(\n      ({ tokens }) => {\n        if (request === highlightRequest) highlightedSegments = toHighlightSegments(tokens, source)\n      },\n      () => {\n        if (request === highlightRequest) highlightedSegments = null\n      },\n    )\n  })\n\n  function scheduleCopyStatusReset() {\n    clearTimeout(copyResetTimer)\n    copyResetTimer = setTimeout(() => (copyStatus = 'idle'), 1600)\n  }\n\n  async function copyToClipboard() {\n    try {\n      await navigator.clipboard.writeText(code)\n      copyStatus = 'copied'\n    } catch (e) {\n      copyStatus = 'error'\n      console.warn('Clipboard write failed', e)\n    } finally {\n      scheduleCopyStatusReset()\n    }\n  }\n\n  onDestroy(() => {\n    highlightRequest++\n    clearTimeout(copyResetTimer)\n  })\n</script>\n\n<div\n  data-uipkge\n  data-slot=\"code-block\"\n  class={cn('group border-border bg-muted/20 relative overflow-hidden rounded-lg border', className)}\n  {...restProps}\n>\n  <!-- Header -->\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\">\n        {language}\n      </span>\n      <div class=\"flex items-center gap-1\">\n        <Button variant=\"ghost\" size=\"xs\" class=\"h-7 gap-1.5 px-2\" onclick={copyToClipboard}>\n          {#if copyStatus === 'copied'}\n            <Check class=\"text-success size-3\" aria-hidden=\"true\" />\n          {:else}\n            <Copy class=\"size-3\" aria-hidden=\"true\" />\n          {/if}\n          <span class={cn('text-xs', copyStatus === 'error' && 'text-destructive')} aria-live=\"polite\" aria-atomic=\"true\">\n            {copyLabel}\n          </span>\n        </Button>\n        <Button\n          variant=\"ghost\"\n          size=\"xs\"\n          class=\"h-7 gap-1.5 px-2\"\n          aria-expanded={isExpanded}\n          aria-controls={bodyId}\n          onclick={() => (isExpanded = !isExpanded)}\n        >\n          {#if isExpanded}\n            <ChevronUp class=\"size-3\" aria-hidden=\"true\" />\n          {:else}\n            <ChevronDown class=\"size-3\" aria-hidden=\"true\" />\n          {/if}\n          <span class=\"text-xs\">{isExpanded ? 'Hide' : 'Show'} code</span>\n        </Button>\n      </div>\n    </div>\n  {/if}\n\n  <!-- Code body. Shiki token offsets let the highlighted spans preserve the\n       original source exactly, including whitespace between tokens. -->\n  <!-- Focusable scroll region with an accessible name, mirroring the Vue twin. -->\n  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->\n  <div\n    id={bodyId}\n    role=\"region\"\n    aria-label={`${language} code sample`}\n    tabindex=\"0\"\n    class={cn(\n      '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      !bodyVisible && 'hidden',\n    )}\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          {#each lines as _, i (i)}\n            <span class=\"block\">{i + 1}</span>\n          {/each}\n        </div>\n      {/if}\n      <pre class=\"m-0 min-w-max flex-1\"><code class=\"block cursor-text whitespace-pre px-4 py-3\">{#if highlightedSegments}{#each highlightedSegments as segment, i (i)}<span\n              data-syntax-token\n              class=\"text-[var(--shiki-light)] dark:text-[var(--shiki-dark)]\"\n              style={segment.style}\n            >{segment.content}</span>{/each}{:else}{code}{/if}</code></pre>\n    </div>\n  </div>\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/code-block/CodeBlock.svelte"
    },
    {
      "path": "packages/registry-svelte/components/code-block/index.ts",
      "content": "export { default as CodeBlock, type CodeBlockProps, type HighlightSegment } from './CodeBlock.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/code-block/index.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte",
    "shiki"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/svelte/button.json"
  ],
  "description": "Syntax-highlighted code preview with a language header, copy button, optional line numbers, and collapsible `<pre>` content. Shiki `codeToTokens` highlighting with github-light/github-dark dual themes and plain-text fallback. Use for installation snippets, API examples, and source you want users to copy verbatim.",
  "categories": [
    "data-display"
  ]
}