{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "codeblock",
  "title": "Code block",
  "description": "CodeBlock and CodeBlockTabs for Fumadocs MDX's Shiki output: title, icon, copy button, code tabs, and the styles for dual themes, highlights, diffs, focus and line numbers.",
  "dependencies": [
    "cn",
    "@fuma-translate/react@^1.0.2",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "tabs",
    "@docscn/docs-utils"
  ],
  "files": [
    {
      "path": "registry/base/docs/components/codeblock.tsx",
      "content": "// Adapted from Fumadocs UI (https://github.com/fuma-nama/fumadocs)\n// Copyright (c) 2023 Fuma, MIT License\n\"use client\";\n\nimport { useTranslations } from \"@fuma-translate/react\";\nimport { cn } from \"cn\";\nimport { Check, Clipboard } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createContext,\n  type CSSProperties,\n  type HTMLAttributes,\n  type ReactNode,\n  type RefObject,\n  use,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { mergeRefs } from \"../utils/merge-refs\";\nimport { useTabsGroup } from \"../utils/tabs-group\";\nimport { useCopyButton } from \"../utils/use-copy-button\";\n\nexport interface CodeBlockProps extends Omit<\n  ComponentProps<\"figure\">,\n  \"title\"\n> {\n  title?: ReactNode;\n\n  /**\n   * Icon of code block\n   *\n   * When passed as a string, it assumes the value is the HTML of icon\n   */\n  icon?: ReactNode;\n\n  /**\n   * Allow to copy code with copy button\n   *\n   * @defaultValue true\n   */\n  allowCopy?: boolean | \"true\" | \"false\";\n\n  /**\n   * Keep original background color generated by Shiki or Rehype Code\n   *\n   * @defaultValue false\n   */\n  keepBackground?: boolean;\n\n  viewportProps?: HTMLAttributes<HTMLElement>;\n\n  /**\n   * show line numbers\n   */\n  \"data-line-numbers\"?: boolean;\n\n  /**\n   * @defaultValue 1\n   */\n  \"data-line-numbers-start\"?: number;\n\n  Actions?: (props: { className?: string; children?: ReactNode }) => ReactNode;\n}\n\nconst TabsContext = createContext<{\n  containerRef: RefObject<HTMLDivElement | null>;\n  nested: boolean;\n} | null>(null);\n\nexport function Pre(props: ComponentProps<\"pre\">) {\n  return (\n    <pre\n      {...props}\n      className={cn(\"w-max min-w-full *:flex *:flex-col\", props.className)}\n    >\n      {props.children}\n    </pre>\n  );\n}\n\nexport function CodeBlock({\n  ref,\n  title,\n  allowCopy = true,\n  keepBackground = false,\n  icon,\n  viewportProps = {},\n  children,\n  Actions = (props) => (\n    <div {...props} className={cn(\"empty:hidden\", props.className)} />\n  ),\n  ...props\n}: CodeBlockProps) {\n  const inTab = use(TabsContext) !== null;\n  const areaRef = useRef<HTMLDivElement>(null);\n  if (allowCopy === \"true\") allowCopy = true;\n  else if (allowCopy === \"false\") allowCopy = false;\n\n  return (\n    <figure\n      ref={ref}\n      dir=\"ltr\"\n      {...props}\n      tabIndex={-1}\n      className={cn(\n        inTab\n          ? \"-mx-px -mb-px bg-muted/50 last:rounded-b-xl\"\n          : \"my-4 rounded-xl bg-card\",\n        keepBackground && \"bg-(--shiki-light-bg) dark:bg-(--shiki-dark-bg)\",\n        \"shiki not-docs-typeset relative overflow-hidden border text-sm\",\n        props.className,\n      )}\n    >\n      {title ? (\n        <div className=\"flex h-9.5 items-center gap-2 border-b px-4 text-muted-foreground\">\n          {typeof icon === \"string\" ? (\n            <div\n              className=\"[&_svg]:size-3.5\"\n              dangerouslySetInnerHTML={{\n                __html: icon,\n              }}\n            />\n          ) : (\n            icon\n          )}\n          <figcaption className=\"flex-1 truncate\">{title}</figcaption>\n          {Actions({\n            className: \"-me-2\",\n            children: allowCopy && <CopyButton containerRef={areaRef} />,\n          })}\n        </div>\n      ) : (\n        Actions({\n          className:\n            \"absolute top-2 right-2 z-2 rounded-lg text-muted-foreground backdrop-blur-lg\",\n          children: allowCopy && <CopyButton containerRef={areaRef} />,\n        })\n      )}\n      <div\n        ref={areaRef}\n        {...viewportProps}\n        role=\"region\"\n        tabIndex={0}\n        className={cn(\n          \"max-h-[600px] overflow-auto py-3.5 text-[0.8125rem] focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-inset\",\n          viewportProps.className,\n        )}\n        style={\n          {\n            // space for toolbar\n            \"--padding-right\": !title ? \"calc(var(--spacing) * 8)\" : undefined,\n            counterSet: props[\"data-line-numbers\"]\n              ? `line ${Number(props[\"data-line-numbers-start\"] ?? 1) - 1}`\n              : undefined,\n            ...viewportProps.style,\n          } as CSSProperties\n        }\n      >\n        {children}\n      </div>\n    </figure>\n  );\n}\n\nfunction CopyButton({\n  className,\n  containerRef,\n  ...props\n}: ComponentProps<typeof Button> & {\n  containerRef: RefObject<HTMLElement | null>;\n}) {\n  const t = useTranslations({ note: \"code block\" });\n  const [checked, onClick] = useCopyButton(() => {\n    const pre = containerRef.current?.getElementsByTagName(\"pre\").item(0);\n    if (!pre) return;\n\n    const clone = pre.cloneNode(true) as HTMLElement;\n    clone.querySelectorAll(\".nd-copy-ignore\").forEach((node) => {\n      node.replaceWith(\"\\n\");\n    });\n\n    return navigator.clipboard.writeText(clone.textContent ?? \"\");\n  });\n\n  return (\n    <Button\n      type=\"button\"\n      variant=\"ghost\"\n      size=\"icon-xs\"\n      data-checked={checked || undefined}\n      className={cn(\"data-checked:text-foreground\", className)}\n      aria-live=\"polite\"\n      onClick={onClick}\n      {...props}\n    >\n      {checked ? <Check /> : <Clipboard />}\n      <span className=\"sr-only\">\n        {checked\n          ? t(\"Copied Text\", { note: \"aria-label\" })\n          : t(\"Copy Text\", { note: \"aria-label\" })}\n      </span>\n    </Button>\n  );\n}\n\ntype WithStringClassName<T> = Omit<T, \"className\"> & { className?: string };\n\nexport interface CodeBlockTabsProps extends WithStringClassName<\n  ComponentProps<typeof Tabs>\n> {\n  /**\n   * Identifier for sharing the selected tab between tab groups\n   */\n  groupId?: string;\n\n  /**\n   * Persist the selected tab in `localStorage` (requires `groupId`)\n   */\n  persist?: boolean;\n}\n\nexport function CodeBlockTabs({\n  ref,\n  className,\n  groupId,\n  persist = false,\n  defaultValue,\n  value: _value,\n  onValueChange: _onValueChange,\n  ...props\n}: CodeBlockTabsProps) {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const nested = use(TabsContext) !== null;\n  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n  // the value selected in another tab group with the same `groupId`\n  const [groupValue, setGroupValue] = useTabsGroup(groupId, persist);\n  const value = _value ?? groupValue ?? uncontrolledValue;\n\n  return (\n    <Tabs\n      ref={mergeRefs(containerRef, ref)}\n      value={value}\n      onValueChange={(v, details) => {\n        if (typeof v === \"string\") setGroupValue(v);\n        setUncontrolledValue(v);\n        _onValueChange?.(v, details);\n      }}\n      {...props}\n      className={cn(\n        \"not-docs-typeset gap-0 rounded-xl border bg-card\",\n        !nested && \"my-4\",\n        className,\n      )}\n    >\n      <TabsContext\n        value={useMemo(\n          () => ({\n            containerRef,\n            nested,\n          }),\n          [nested],\n        )}\n      >\n        {props.children}\n      </TabsContext>\n    </Tabs>\n  );\n}\n\nexport function CodeBlockTabsList({\n  className,\n  ...props\n}: WithStringClassName<ComponentProps<typeof TabsList>>) {\n  return (\n    <TabsList\n      variant=\"line\"\n      {...props}\n      className={cn(\n        \"no-scrollbar w-full justify-start gap-0 overflow-x-auto px-2 group-data-horizontal/tabs:h-auto\",\n        className,\n      )}\n    >\n      {props.children}\n    </TabsList>\n  );\n}\n\nexport function CodeBlockTabsTrigger({\n  className,\n  ...props\n}: WithStringClassName<ComponentProps<typeof TabsTrigger>>) {\n  return (\n    <TabsTrigger\n      {...props}\n      className={cn(\n        \"flex-none gap-2 px-2 py-1.5 text-sm text-muted-foreground group-data-horizontal/tabs:after:inset-x-2 group-data-horizontal/tabs:after:bottom-0 group-data-horizontal/tabs:after:h-px hover:text-foreground data-active:text-foreground [&_svg]:size-3.5\",\n        className,\n      )}\n    />\n  );\n}\n\nexport function CodeBlockTab(props: ComponentProps<typeof TabsContent>) {\n  return <TabsContent {...props} />;\n}\n",
      "type": "registry:component",
      "target": "@components/docs/components/codeblock.tsx"
    }
  ],
  "css": {
    "@layer components": {
      ".shiki:not(.not-docs-codeblock *)": {
        "--padding-left": "1rem",
        "--padding-right": "1rem"
      },
      ".shiki:not(.not-docs-codeblock *) code span": {
        "color": "var(--shiki-light)",
        "font-style": "var(--shiki-light-font-style)"
      },
      ".dark .shiki:not(.not-docs-codeblock *) code span": {
        "color": "var(--shiki-dark)",
        "font-style": "var(--shiki-dark-font-style)"
      },
      ".shiki:not(.not-docs-codeblock *) .line": {
        "position": "relative",
        "padding-left": "var(--padding-left)",
        "padding-right": "var(--padding-right)"
      },
      ".shiki:not(.not-docs-codeblock *) .line:empty": {
        "height": "1lh"
      },
      ".shiki.has-focused:not(.not-docs-codeblock *) .line:not(.focused)": {
        "filter": "blur(2px)",
        "transition": "filter 200ms"
      },
      ".shiki.has-focused:not(.not-docs-codeblock *):hover .line:not(.focused)": {
        "filter": "blur(0)"
      },
      ".shiki:not(.not-docs-codeblock *) .diff::before": {
        "position": "absolute",
        "left": "0.375rem"
      },
      ".shiki:not(.not-docs-codeblock *) .diff.remove": {
        "--docs-counter-color": "rgb(230, 10, 100)",
        "background-color": "rgba(200, 10, 100, 0.12)"
      },
      ".shiki:not(.not-docs-codeblock *) .diff.remove::before": {
        "content": "\"-\"",
        "color": "rgb(230, 10, 100)"
      },
      ".shiki:not(.not-docs-codeblock *) .diff.add": {
        "--docs-counter-color": "rgb(10, 200, 100)",
        "background-color": "rgba(14, 180, 100, 0.1)"
      },
      ".shiki:not(.not-docs-codeblock *) .diff.add::before": {
        "content": "\"+\"",
        "color": "rgb(10, 200, 100)"
      },
      ".shiki:not(.not-docs-codeblock *) .highlighted": {
        "--docs-counter-color": "var(--primary)",
        "background-color": "color-mix(in oklab, var(--primary) 10%, transparent)"
      },
      ".shiki:not(.not-docs-codeblock *) .highlighted::after": {
        "position": "absolute",
        "inset-block": "0",
        "left": "0",
        "border-left": "2px solid color-mix(in oklab, var(--primary) 50%, transparent)",
        "content": "\"\""
      },
      ".shiki:not(.not-docs-codeblock *) .highlighted-word": {
        "margin-block": "-1px",
        "padding": "1px",
        "border": "1px solid color-mix(in oklab, var(--primary) 30%, transparent)",
        "border-radius": "calc(var(--radius) * 0.8)",
        "background-color": "color-mix(in oklab, var(--primary) 10%, transparent)",
        "font-weight": "500"
      },
      ".shiki[data-line-numbers]:not(.not-docs-codeblock *) .line": {
        "counter-increment": "line",
        "padding-left": "calc(var(--padding-left) + 1.75rem)"
      },
      ".shiki[data-line-numbers]:not(.not-docs-codeblock *) .line::after": {
        "position": "absolute",
        "inset-block": "0",
        "left": "0",
        "width": "1.75rem",
        "color": "color-mix(in oklab, var(--docs-counter-color, var(--muted-foreground)) 60%, transparent)",
        "text-align": "right",
        "content": "counter(line)"
      },
      "code.shiki:not(.not-docs-codeblock *) span": {
        "color": "var(--shiki-light)",
        "font-style": "var(--shiki-light-font-style)"
      },
      ".dark code.shiki:not(.not-docs-codeblock *) span": {
        "color": "var(--shiki-dark)",
        "font-style": "var(--shiki-dark-font-style)"
      }
    }
  },
  "type": "registry:component"
}