{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block",
  "title": "code-block",
  "description": "A block of code in the mono face on the ground colour, with a copy button (or, for a one-line command, the whole block as the copy control) and an optional title row. Long lines scroll sideways or wrap. Shiki output takes the colours of the site theme.",
  "dependencies": [
    "class-variance-authority@^0.7"
  ],
  "registryDependencies": [
    "@dwellir/dwellir-theme",
    "@dwellir/utils",
    "@dwellir/rune-icons"
  ],
  "files": [
    {
      "path": "components/ui/code-block.tsx",
      "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n  type ComponentProps,\n  type KeyboardEvent,\n  type ReactNode,\n  useEffect,\n  useRef,\n  useState,\n} from 'react';\nimport { RuneCheck, RuneCopy } from '@/components/brand/rune-icons';\nimport { cn } from '@/lib/utils';\n\n// Zero encodes a newline without repeating an escaped string in the document.\nexport type CodeToken = string | 0 | [style: number, children: string | CodeToken[]];\nexport type CompactCode = { classes: string[]; tokens: CodeToken[] };\n\nfunction renderTokens(tokens: CodeToken[], classes: string[]): ReactNode {\n  return tokens.map((token, index) => {\n    if (token === 0) return '\\n';\n    if (typeof token === 'string') return token;\n    return (\n      <span key={index} className={classes[token[0]]}>\n        {typeof token[1] === 'string' ? token[1] : renderTokens(token[1], classes)}\n      </span>\n    );\n  });\n}\n\n// Keep line height visible to cn, so a font size can replace it.\nconst preVariants = cva('ds-code-block-pre-variants leading-relaxed', {\n  variants: {\n    /* scroll: long lines scroll sideways (commands, configs). wrap: long lines wrap (one-line payloads). */\n    lines: { scroll: 'whitespace-pre', wrap: 'whitespace-pre-wrap wrap-anywhere' },\n    /* md: a snippet beside other content. lg: code in running text (a blog article); it stays readable on a phone. */\n    size: { md: 'text-xs sm:text-sm', lg: 'text-sm' },\n    /*\n     * plain: the code is text. shiki: the code is Shiki output with two themes\n     * (defaultColor: false); each token takes the colour of the site's theme.\n     */\n    tokens: {\n      plain: '',\n      shiki: 'ds-code-block-shiki',\n    },\n  },\n  defaultVariants: { lines: 'scroll', size: 'md', tokens: 'plain' },\n});\n\n/** Copies with the Clipboard API, or with a hidden field where that API is missing (plain HTTP). */\nasync function copyText(text: string): Promise<boolean> {\n  try {\n    await navigator.clipboard.writeText(text);\n    return true;\n  } catch {\n    const field = document.createElement('textarea');\n    field.value = text;\n    field.setAttribute('readonly', '');\n    field.className = 'fixed opacity-0';\n    document.body.appendChild(field);\n    field.select();\n    const copied = document.execCommand('copy');\n    field.remove();\n    return copied;\n  }\n}\n\ntype CodeBlockProps = Omit<ComponentProps<'figure'>, 'children'> &\n  VariantProps<typeof preVariants> & {\n    /** The text the copy button copies. Without it, the button copies the text the block shows. */\n    code?: string;\n    /** Shown in place of the plain code, such as the code with marks. The copy button still copies code. */\n    children?: ReactNode;\n    /** Highlighted token runs from the server, without a serialized React node per token. */\n    codeTokens?: CompactCode;\n    /** What the code is, for the copy button's label: \"Copy {label}\". */\n    label?: string;\n    /** Called after a successful copy, such as for analytics. */\n    onCopy?: () => void;\n    /**\n     * true: a copy button sits top right. false: no copy. block: for a one-line\n     * command; the whole block is the copy control, and a drag still selects text.\n     */\n    copy?: boolean | 'block';\n    /** A row over the code, such as its language or file name. The copy button sits in it. */\n    title?: ReactNode;\n  };\n\n/** The copied state, and the copy action. It resets after 1.5 s. */\nfunction useCopy(getText: () => string, onCopy?: () => void) {\n  const [copied, setCopied] = useState(false);\n  const timer = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n  useEffect(() => () => clearTimeout(timer.current), []);\n\n  const copy = async () => {\n    if (!(await copyText(getText()))) return;\n    setCopied(true);\n    onCopy?.();\n    clearTimeout(timer.current);\n    timer.current = setTimeout(() => setCopied(false), 1500);\n  };\n  return { copied, copy };\n}\n\nfunction CopyIndicator({ copied }: { copied: boolean }) {\n  return copied ? (\n    <RuneCheck aria-hidden=\"true\" className=\"size-4 text-accent-ink\" />\n  ) : (\n    <RuneCopy aria-hidden=\"true\" className=\"size-4\" />\n  );\n}\n\n/* The top-right copy button, or, for a block that copies as a whole, the icon that shows its state. */\nfunction CopyControl({\n  block,\n  copied,\n  label,\n  onCopy,\n}: {\n  block: boolean;\n  copied: boolean;\n  label: string;\n  onCopy: () => Promise<void>;\n}) {\n  if (block)\n    return (\n      <span\n        data-slot=\"code-block-copy\"\n        className=\"pointer-events-none absolute top-1/2 right-4 -translate-y-1/2 text-muted-foreground transition-colors group-hover/block:text-foreground\"\n      >\n        <CopyIndicator copied={copied} />\n      </span>\n    );\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"code-block-copy\"\n      onClick={onCopy}\n      aria-label={copied ? `Copied ${label}` : `Copy ${label}`}\n      className=\"ds-code-block-code-block-2\"\n    >\n      <CopyIndicator copied={copied} />\n    </button>\n  );\n}\n\n/* The pre as one button: a click, Enter or Space copies. A drag that selected text does not, so the text stays selectable. */\nfunction blockControl(copy: () => Promise<void>, name: string) {\n  return {\n    role: 'button',\n    'aria-label': name,\n    onClick: () => {\n      if (!window.getSelection()?.toString()) void copy();\n    },\n    onKeyDown: (event: KeyboardEvent<HTMLPreElement>) => {\n      if (event.key !== 'Enter' && event.key !== ' ') return;\n      event.preventDefault();\n      void copy();\n    },\n  };\n}\n\n/**\n * A block of code on the ground colour, in the mono face. It scrolls sideways\n * and takes keyboard focus to do so. The copy button sits top right; with\n * copy=\"block\" the whole block copies on a click, Enter or Space.\n */\nfunction CodeBlock({\n  className,\n  code,\n  label = 'code',\n  lines,\n  size,\n  tokens,\n  copy = true,\n  title,\n  onCopy,\n  children,\n  codeTokens,\n  ...props\n}: CodeBlockProps) {\n  const pre = useRef<HTMLPreElement>(null);\n  const { copied, copy: handleCopy } = useCopy(\n    () => code ?? pre.current?.textContent?.replace(/\\n[\\t ]*$/, '') ?? '',\n    onCopy,\n  );\n  const block = copy === 'block' && !title;\n  const blockProps = block\n    ? blockControl(handleCopy, `${copied ? 'Copied' : 'Copy'} ${label}${code ? `: ${code}` : ''}`)\n    : {};\n\n  return (\n    <figure\n      data-slot=\"code-block\"\n      className={cn(\n        'relative min-w-0 rounded-2xl border border-border bg-background',\n        block && 'group/block transition-colors duration-200 hover:bg-foreground-10',\n        className,\n      )}\n      {...props}\n    >\n      {title ? (\n        <figcaption data-slot=\"code-block-title\" className=\"ds-code-block-code-block\">\n          {title}\n        </figcaption>\n      ) : null}\n      <pre\n        ref={pre}\n        data-slot=\"code-block-pre\"\n        // biome-ignore lint/a11y/noNoninteractiveTabindex: a scrollable region must take keyboard focus.\n        tabIndex={0}\n        className={cn(\n          preVariants({ lines, size, tokens }),\n          copy && !title && 'pr-12',\n          block && 'cursor-pointer rounded-2xl',\n        )}\n        {...blockProps}\n      >\n        <code data-slot=\"code-block-code\">\n          {codeTokens ? renderTokens(codeTokens.tokens, codeTokens.classes) : (children ?? code)}\n        </code>\n      </pre>\n      {copy ? (\n        <CopyControl block={block} copied={copied} label={label} onCopy={handleCopy} />\n      ) : null}\n      <span data-slot=\"code-block-status\" role=\"status\" className=\"sr-only\">\n        {copied ? 'Copied to the clipboard' : ''}\n      </span>\n    </figure>\n  );\n}\n\nexport { CodeBlock, preVariants as codeBlockVariants };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}