{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block",
  "title": "Code Block",
  "description": "Code with syntax colors in SF Mono, a title, line numbers and a copy button.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react",
    "shiki"
  ],
  "registryDependencies": [
    "@cupertinoui/macos-theme",
    "@cupertinoui/button"
  ],
  "files": [
    {
      "path": "registry/macos/ui/code-block.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { CheckIcon, CopyIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"@/registry/macos/ui/button\"\n\n// Code with syntax colors, like a snippet in Xcode or Terminal: the\n// content background with a hairline edge, in SF Mono.\nconst codeBlockVariants = cva(\n  \"group/code-block relative flex w-full min-w-0 flex-col overflow-hidden rounded-[10px] text-mac-body has-[>[data-slot=code-block-content]:focus-visible]:mac-focus-ring data-[size=sm]:text-mac-subheadline\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-mac-content text-mac-label shadow-[0_0_0_1px_var(--mac-grid)]\",\n        destructive: \"bg-mac-red/5 text-mac-red shadow-[0_0_0_1px_color-mix(in_srgb,var(--mac-red)_30%,transparent)]\",\n      },\n    },\n    defaultVariants: { variant: \"default\" },\n  }\n)\n\nfunction CodeBlock({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> &\n  VariantProps<typeof codeBlockVariants> & { size?: \"default\" | \"sm\" }) {\n  return (\n    <div\n      data-slot=\"code-block\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(codeBlockVariants({ variant }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction CodeBlockHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"code-block-header\"\n      className={cn(\n        \"flex min-w-0 shrink-0 items-center gap-2 px-3 pt-3 text-mac-label-secondary group-data-[size=sm]/code-block:px-2.5 group-data-[size=sm]/code-block:pt-2.5 group-data-[variant=destructive]/code-block:text-mac-red *:has-[>[data-slot=code-block-title]]:min-w-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CodeBlockTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"code-block-title\"\n      className={cn(\n        \"min-w-0 truncate font-sans text-mac-body font-medium text-mac-label group-data-[size=sm]/code-block:text-mac-subheadline group-data-[variant=destructive]/code-block:text-mac-red [&_svg]:pointer-events-none [&_svg]:inline-block [&_svg]:align-middle [&_svg:not([class*='size-'])]:size-3.5 *:[svg]:me-1.5\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CodeBlockActions({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"code-block-actions\"\n      className={cn(\n        \"-my-1 ms-auto -me-1.5 flex shrink-0 items-center gap-1 [[data-slot=code-block]>&]:absolute [[data-slot=code-block]>&]:end-2 [[data-slot=code-block]>&]:top-2 [[data-slot=code-block]>&]:z-10 [[data-slot=code-block]>&]:m-0 [[data-slot=code-block]>&]:opacity-0 [[data-slot=code-block]>&]:transition-opacity [[data-slot=code-block]>&]:group-focus-within/code-block:opacity-100 [[data-slot=code-block]>&]:group-hover/code-block:opacity-100 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:end-1.5 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:top-1.5 [[data-slot=code-block]>&]:pointer-coarse:opacity-100 [[data-slot=code-block]>&]:*:[button]:bg-mac-window [[data-slot=code-block]>&]:*:[button]:shadow-[0_0_0_1px_var(--mac-grid)]\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CodeBlockCopyButton({\n  value,\n  variant = \"ghost\",\n  size = \"icon-xs\",\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button> & { value: string }) {\n  const [copied, setCopied] = React.useState(false)\n\n  React.useEffect(() => {\n    if (!copied) {\n      return\n    }\n\n    const timeout = setTimeout(() => setCopied(false), 2000)\n    return () => clearTimeout(timeout)\n  }, [copied])\n\n  return (\n    <>\n      <Button\n        data-slot=\"code-block-copy-button\"\n        data-copied={copied || undefined}\n        variant={variant}\n        size={size}\n        aria-label={copied ? \"Copied\" : \"Copy\"}\n        className={cn(className)}\n        onClick={(event) => {\n          onClick?.(event)\n          navigator.clipboard\n            .writeText(value)\n            .then(() => setCopied(true))\n            .catch(() => {})\n        }}\n        {...props}\n      >\n        {copied ? (\n          <CheckIcon />\n        ) : (\n          <CopyIcon />\n        )}\n      </Button>\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {copied ? \"Copied\" : \"\"}\n      </span>\n    </>\n  )\n}\n\nfunction CodeBlockContent({\n  className,\n  children,\n  style,\n  wrap = false,\n  lineNumbers = false,\n  startLine = 1,\n  ...props\n}: React.ComponentProps<\"pre\"> & {\n  wrap?: boolean\n  lineNumbers?: boolean\n  startLine?: number\n}) {\n  return (\n    <pre\n      data-slot=\"code-block-content\"\n      data-wrap={wrap || undefined}\n      data-numbered={lineNumbers || undefined}\n      dir=\"ltr\"\n      tabIndex={0}\n      style={{ counterReset: `line ${startLine - 1}`, ...style }}\n      className={cn(\n        \"min-h-0 min-w-0 overflow-auto px-3 py-3 text-start font-mono text-[12px] leading-[18px] outline-none group-has-data-[slot=code-block-footer]/code-block:pb-2 group-has-data-[slot=code-block-header]/code-block:pt-2 group-data-[size=sm]/code-block:px-2.5 group-data-[size=sm]/code-block:py-2.5 group-data-[size=sm]/code-block:group-has-data-[slot=code-block-header]/code-block:pt-1.5 data-wrap:wrap-break-word data-wrap:whitespace-pre-wrap\",\n        className\n      )}\n      {...props}\n    >\n      <code\n        data-slot=\"code-block-code\"\n        className=\"grid min-h-lh min-w-max content-start in-data-numbered:grid-cols-[auto_1fr] in-data-wrap:min-w-0\"\n      >\n        {lineNumbers && typeof children === \"string\"\n          ? children\n              .replace(/\\n$/, \"\")\n              .split(\"\\n\")\n              .map((line, index) => (\n                <CodeBlockLine key={index}>{line}</CodeBlockLine>\n              ))\n          : children}\n      </code>\n    </pre>\n  )\n}\n\nconst codeBlockLineVariants = cva(\n  \"relative -mx-3 block min-h-lh px-3 group-data-[size=sm]/code-block:-mx-2.5 group-data-[size=sm]/code-block:px-2.5 before:min-w-6 before:text-end before:text-mac-label-secondary after:absolute after:start-1 after:top-0 after:text-mac-label-secondary after:select-none in-data-numbered:col-span-2 in-data-numbered:grid in-data-numbered:grid-cols-subgrid in-data-numbered:gap-x-3 in-data-numbered:[counter-increment:line] in-data-numbered:before:content-[counter(line)] in-data-numbered:before:select-none in-data-numbered:supports-[content:counter(line)_/_'']:before:content-[counter(line)_/_'']\",\n  {\n    variants: {\n      variant: {\n        default: \"\",\n        added: \"bg-mac-green/12 after:text-mac-green after:content-['+']\",\n        removed: \"bg-mac-red/10 after:text-mac-red after:content-['−']\",\n        highlighted: \"bg-mac-fill-secondary\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction CodeBlockLine({\n  className,\n  variant = \"default\",\n  children,\n  ...props\n}: React.ComponentProps<\"span\"> & VariantProps<typeof codeBlockLineVariants>) {\n  // An empty line holds a newline so copying a selection keeps blank lines.\n  const empty =\n    children === undefined ||\n    children === null ||\n    children === \"\" ||\n    (Array.isArray(children) && children.length === 0)\n\n  return (\n    <span\n      data-slot=\"code-block-line\"\n      data-variant={variant}\n      className={cn(codeBlockLineVariants({ variant }), className)}\n      {...props}\n    >\n      <span data-slot=\"code-block-line-content\" className=\"min-w-0\">\n        {empty ? \"\\n\" : children}\n      </span>\n    </span>\n  )\n}\n\nfunction CodeBlockFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"code-block-footer\"\n      className={cn(\n        \"flex min-w-0 shrink-0 items-center gap-2 px-3 pb-3 text-mac-label-secondary group-data-[size=sm]/code-block:px-2.5 group-data-[size=sm]/code-block:pb-2.5\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  CodeBlock,\n  CodeBlockHeader,\n  CodeBlockTitle,\n  CodeBlockActions,\n  CodeBlockCopyButton,\n  CodeBlockContent,\n  CodeBlockLine,\n  CodeBlockFooter,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/macos/hooks/use-highlighted-code.ts",
      "content": "import * as React from \"react\"\n\nimport { loadHighlighter } from \"@/registry/macos/lib/highlighter\"\n\nexport function useHighlightedCode(value: string, language: string) {\n  // A trailing newline would render as an extra empty line.\n  const code = value.endsWith(\"\\n\") ? value.slice(0, -1) : value\n  const [loaded, setLoaded] = React.useState<{\n    language: string\n    highlighter: Awaited<ReturnType<typeof loadHighlighter>>\n  } | null>(null)\n  const highlighter = loaded?.language === language ? loaded.highlighter : null\n\n  React.useEffect(() => {\n    let cancelled = false\n\n    loadHighlighter(language)\n      .then((next) => {\n        if (!cancelled) {\n          setLoaded({ language, highlighter: next })\n        }\n      })\n      .catch(() => {})\n\n    return () => {\n      cancelled = true\n    }\n  }, [language])\n\n  // The complete lines, highlighted. While the code streams, only the new\n  // lines are tokenized, starting from the grammar state of the last one.\n  const [done, setDone] = React.useState<{\n    highlighter: typeof highlighter\n    text: string\n    lines: React.ReactNode[]\n    state?: Parameters<NonNullable<typeof highlighter>[\"highlight\"]>[1]\n  }>({ highlighter: null, text: \"\", lines: [] })\n\n  let next = done\n\n  if (next.highlighter !== highlighter || !code.startsWith(next.text)) {\n    next = { highlighter, text: \"\", lines: [] }\n  }\n\n  const end = code.lastIndexOf(\"\\n\") + 1\n\n  if (highlighter && end > next.text.length) {\n    const result = highlighter.highlight(\n      code.slice(next.text.length, end - 1),\n      next.state\n    )\n\n    next = {\n      highlighter,\n      text: code.slice(0, end),\n      lines: [...next.lines, ...result.lines],\n      state: result.state,\n    }\n  }\n\n  if (next !== done) {\n    setDone(next)\n  }\n\n  // The last line is still being written, so it is tokenized on every render.\n  const tail = code.slice(end)\n\n  return React.useMemo(() => {\n    if (!highlighter) {\n      return code.split(\"\\n\")\n    }\n\n    return [...next.lines, highlighter.highlight(tail, next.state).lines[0]]\n  }, [code, highlighter, next, tail])\n}\n",
      "type": "registry:hook"
    },
    {
      "path": "registry/macos/lib/highlighter.tsx",
      "content": "import * as React from \"react\"\nimport {\n  createHighlighterCore,\n  type GrammarState,\n  type HighlighterCore,\n} from \"shiki/core\"\nimport { createJavaScriptRegexEngine } from \"shiki/engine/javascript\"\n\n// Add the languages you need. Each one loads the first time it is used.\nconst languages = {\n  bash: () => import(\"shiki/langs/shellscript.mjs\"),\n  css: () => import(\"shiki/langs/css.mjs\"),\n  diff: () => import(\"shiki/langs/diff.mjs\"),\n  html: () => import(\"shiki/langs/html.mjs\"),\n  javascript: () => import(\"shiki/langs/javascript.mjs\"),\n  json: () => import(\"shiki/langs/json.mjs\"),\n  jsx: () => import(\"shiki/langs/jsx.mjs\"),\n  markdown: () => import(\"shiki/langs/markdown.mjs\"),\n  python: () => import(\"shiki/langs/python.mjs\"),\n  sql: () => import(\"shiki/langs/sql.mjs\"),\n  tsx: () => import(\"shiki/langs/tsx.mjs\"),\n  typescript: () => import(\"shiki/langs/typescript.mjs\"),\n  yaml: () => import(\"shiki/langs/yaml.mjs\"),\n}\n\nconst aliases: Record<string, keyof typeof languages> = {\n  js: \"javascript\",\n  md: \"markdown\",\n  py: \"python\",\n  sh: \"bash\",\n  shell: \"bash\",\n  shellscript: \"bash\",\n  ts: \"typescript\",\n  yml: \"yaml\",\n  zsh: \"bash\",\n}\n\nlet highlighter: Promise<HighlighterCore> | undefined\n\nfunction getHighlighter() {\n  highlighter ??= createHighlighterCore({\n    themes: [\n      import(\"shiki/themes/github-light.mjs\"),\n      import(\"shiki/themes/github-dark.mjs\"),\n    ],\n    langs: [],\n    engine: createJavaScriptRegexEngine({ forgiving: true }),\n  })\n\n  return highlighter\n}\n\nfunction getLanguage(language: string) {\n  const name = language.toLowerCase()\n\n  if (name === \"ansi\") {\n    return name\n  }\n\n  if (name in languages) {\n    return name as keyof typeof languages\n  }\n\n  return aliases[name] ?? null\n}\n\n// Loads a language and returns a highlighter for it, or null for a language\n// that is not listed.\nexport async function loadHighlighter(language: string) {\n  const lang = getLanguage(language)\n\n  if (!lang) {\n    return null\n  }\n\n  const shiki = await getHighlighter()\n\n  if (lang !== \"ansi\" && !shiki.getLoadedLanguages().includes(lang)) {\n    await shiki.loadLanguage(languages[lang])\n  }\n\n  return {\n    // Returns one React node per line, and the grammar state after the last\n    // line. Pass that state back to continue where the code left off.\n    highlight(code: string, state?: GrammarState) {\n      const tokenize = (text: string) =>\n        shiki.codeToTokens(text, {\n          lang,\n          themes: { light: \"github-light\", dark: \"github-dark\" },\n          defaultColor: false,\n          grammarState: state,\n        }).tokens\n\n      // ANSI has no grammar state, so each line is tokenized on its own and\n      // gets the same colors whether it arrives in one piece or streams.\n      const tokens =\n        lang === \"ansi\"\n          ? code.split(\"\\n\").flatMap((line) => tokenize(line))\n          : tokenize(code)\n\n      return {\n        lines: tokens.map((line) =>\n          line.map((token, index) => (\n            <span\n              key={index}\n              style={token.htmlStyle as React.CSSProperties}\n              className=\"text-(--shiki-light) dark:text-(--shiki-dark)\"\n            >\n              {token.content}\n            </span>\n          ))\n        ),\n        state: shiki.getLastGrammarState(tokens),\n      }\n    },\n  }\n}\n\n// Returns one React node per line, or null for a language that is not listed.\nexport async function highlightCode(code: string, language: string) {\n  const highlighter = await loadHighlighter(language)\n\n  return highlighter ? highlighter.highlight(code).lines : null\n}\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:ui"
}