{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block",
  "title": "Code Block",
  "description": "Code panel with tabs, line numbers, highlighted lines and a copy button.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/velora/code-block.tsx",
      "content": "\"use client\";\n\nimport { useId, useMemo, useState } from \"react\";\nimport { Check, Copy } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface CodeFile {\n  name: string;\n  code: string;\n  language?: string;\n  highlightLines?: number[];\n}\n\ninterface CodeBlockProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Source to show (ignored when `tabs` is set) */\n  code?: string;\n  /** Language for highlighting: tsx, ts, jsx, js, css, bash, json */\n  language?: string;\n  /** File name shown in the header and used as the code region's label */\n  filename?: string;\n  /** Several files, shown as tabs */\n  tabs?: CodeFile[];\n  /** 1-based line numbers to highlight */\n  highlightLines?: number[];\n  /** Show the line-number gutter */\n  lineNumbers?: boolean;\n}\n\nconst ring = \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none\";\n\n// Token colours, readable on bg-card in both themes\nconst C: Record<string, string> = {\n  c: \"text-muted-foreground italic\",\n  s: \"text-emerald-700 dark:text-emerald-400\",\n  t: \"text-sky-700 dark:text-sky-400\",\n  a: \"text-violet-700 dark:text-violet-300\",\n  k: \"text-rose-700 dark:text-rose-400\",\n  n: \"text-amber-700 dark:text-amber-300\",\n  f: \"text-blue-700 dark:text-blue-400\",\n  p: \"text-muted-foreground\",\n};\n\nconst KW =\n  \"as|async|await|break|case|catch|class|const|default|do|else|export|extends|false|for|from|function|if|import|in|interface|let|new|null|of|return|switch|this|throw|true|try|type|typeof|undefined|var|while|then|fi|done|esac\";\n\n// [class, pattern]: the first alternative that matches wins\nfunction rules(lang: string): [string, string][] {\n  const sh = /sh/.test(lang);\n  const css = /css$/.test(lang);\n  const r: [string, string | false][] = [\n    [\"c\", sh ? \"#.*\" : \"//.*|/\\\\*[\\\\s\\\\S]*?\\\\*/\"],\n    [\"s\", \"\\\"(?:\\\\\\\\.|[^\\\"\\\\\\\\\\\\n])*\\\"|'(?:\\\\\\\\.|[^'\\\\\\\\\\\\n])*'|`(?:\\\\\\\\.|[^`\\\\\\\\])*`\"],\n    [\"t\", /sx|html/.test(lang) && \"</?[A-Za-z][\\\\w.:-]*\"],\n    [\"a\", sh ? \"(?<=\\\\s)--?[\\\\w-]+\" : css ? \"^\\\\s*[\\\\w-]+(?=\\\\s*:[^{\\\\n]*$)\" : \"[\\\\w-]+(?==[\\\"'{])\"],\n    [\"k\", css ? \"@[\\\\w-]+|!important\" : `\\\\b(?:${KW})\\\\b`],\n    [\"n\", \"\\\\b\\\\d+(?:\\\\.\\\\d+)?[a-z%]*\"],\n    [\"f\", sh ? \"(?<=^[$\\\\s]*|[|&;]\\\\s*)[\\\\w./@-]+\" : \"[A-Za-z_$][\\\\w$-]*(?=\\\\()\"],\n    [\"t\", !sh && !css && \"\\\\b[A-Z][\\\\w$]*\"],\n    [\"p\", !sh && \"[{}()[\\\\];,.<>/=+*!&|?:-]+\"],\n  ];\n  return r.filter((x): x is [string, string] => !!x[1]);\n}\n\nfunction highlight(code: string, lang: string) {\n  const list = rules(lang);\n  const re = new RegExp(list.map(([, s]) => `(${s})`).join(\"|\"), \"gm\");\n  const lines: React.ReactNode[][] = [[]];\n  let key = 0;\n  const push = (text: string, cls?: string) =>\n    text.split(\"\\n\").forEach((part, i) => {\n      if (i) lines.push([]);\n      if (part) lines.at(-1)!.push(cls ? <span key={key++} className={C[cls]}>{part}</span> : part);\n    });\n  let last = 0;\n  for (const m of code.matchAll(re)) {\n    push(code.slice(last, m.index));\n    push(m[0], list[m.findIndex((g, i) => i && g !== undefined) - 1][0]);\n    last = m.index + m[0].length;\n  }\n  push(code.slice(last));\n  return lines;\n}\n\n/**\n * Code panel with a file name or tabs, line numbers, highlighted lines and a\n * copy button. A tiny built-in tokenizer colours the code — no dependency.\n */\nexport function CodeBlock({\n  code = \"\",\n  language = \"tsx\",\n  filename,\n  tabs,\n  highlightLines = [],\n  lineNumbers = true,\n  className,\n  ...props\n}: CodeBlockProps) {\n  const id = useId();\n  const [active, setActive] = useState(0);\n  const [copied, setCopied] = useState(false);\n  const file: CodeFile = tabs?.[active] ?? { name: filename ?? \"\", code, language, highlightLines };\n  const lang = file.language ?? language;\n  const source = file.code.replace(/\\n$/, \"\");\n  const lines = useMemo(() => highlight(source, lang), [source, lang]);\n  const marked = new Set(file.highlightLines ?? highlightLines);\n\n  const copy = () =>\n    navigator.clipboard.writeText(source).then(() => {\n      setCopied(true);\n      setTimeout(() => setCopied(false), 2000);\n    });\n\n  const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    const n = tabs!.length;\n    const i = { ArrowRight: active + 1, ArrowLeft: active + n - 1, Home: 0, End: n - 1 }[e.key];\n    if (i == null) return;\n    e.preventDefault();\n    setActive(i % n);\n    (e.currentTarget.children[i % n] as HTMLElement).focus();\n  };\n\n  return (\n    <div\n      {...props}\n      data-slot=\"code-block\"\n      className={cn(\"overflow-hidden rounded-xl border bg-card text-card-foreground\", className)}\n    >\n      <div className=\"flex min-h-11 items-center gap-2 border-b bg-muted/50 pr-2 pl-4\">\n        {tabs ? (\n          <div role=\"tablist\" aria-label=\"Files\" onKeyDown={onKeyDown} className=\"-ml-2 flex min-w-0 gap-1 self-stretch overflow-x-auto\">\n            {tabs.map((t, i) => (\n              <button\n                key={i}\n                type=\"button\"\n                role=\"tab\"\n                id={`${id}t${i}`}\n                aria-selected={i == active}\n                aria-controls={`${id}p`}\n                tabIndex={i == active ? 0 : -1}\n                onClick={() => setActive(i)}\n                className={cn(\"shrink-0 border-b-2 border-transparent px-2 font-mono text-xs text-muted-foreground hover:text-foreground focus-visible:ring-inset aria-selected:border-brand aria-selected:text-foreground\", ring)}\n              >\n                {t.name}\n              </button>\n            ))}\n          </div>\n        ) : (\n          <span className=\"truncate font-mono text-xs text-muted-foreground\">{filename}</span>\n        )}\n        <span className=\"ml-auto rounded-md border bg-background px-1.5 py-0.5 font-mono text-[11px] text-muted-foreground uppercase\">\n          {lang}\n        </span>\n        <button\n          type=\"button\"\n          onClick={copy}\n          aria-label=\"Copy code\"\n          className={cn(\"grid size-8 place-items-center rounded-md text-muted-foreground hover:bg-background hover:text-foreground\", ring)}\n        >\n          {copied ? <Check className=\"size-4\" /> : <Copy className=\"size-4\" />}\n        </button>\n        <span role=\"status\" className=\"sr-only\">\n          {copied && \"Copied to clipboard\"}\n        </span>\n      </div>\n      <div\n        role={tabs && \"tabpanel\"}\n        id={`${id}p`}\n        aria-labelledby={tabs && `${id}t${active}`}\n      >\n        <pre\n          tabIndex={0}\n          aria-label={file.name || `${lang} code`}\n          className={cn(\"overflow-x-auto py-4 font-mono text-[13px] leading-6 focus-visible:ring-inset\", ring)}\n        >\n          <code className=\"grid w-max min-w-full\">\n            {lines.map((tokens, i) => (\n              <span\n                key={i}\n                className={cn(\n                  \"px-4\",\n                  marked.has(i + 1) && \"bg-brand/10 shadow-[inset_2px_0_0_var(--brand)]\"\n                )}\n              >\n                {lineNumbers && (\n                  <span\n                    aria-hidden\n                    style={{ width: `${String(lines.length).length}ch` }}\n                    className=\"mr-5 inline-block text-right text-muted-foreground/60 select-none\"\n                  >\n                    {i + 1}\n                  </span>\n                )}\n                {tokens.length ? tokens : \" \"}\n              </span>\n            ))}\n          </code>\n        </pre>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/velora/code-block.tsx"
    }
  ],
  "type": "registry:ui"
}