{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"code-block","type":"registry:ui","title":"code-block","description":"Provides syntax highlighting, line numbers, and copy to clipboard functionality for code blocks.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state","@shikijs/transformers","lucide-react","react-icons","shiki"],"devDependencies":[],"registryDependencies":["button","select"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport {\n  transformerNotationDiff,\n  transformerNotationErrorLevel,\n  transformerNotationFocus,\n  transformerNotationHighlight,\n  transformerNotationWordHighlight,\n} from \"@shikijs/transformers\";\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport type {\n  ComponentProps,\n  HTMLAttributes,\n  ReactElement,\n  ReactNode,\n} from \"react\";\nimport {\n  cloneElement,\n  createContext,\n  useContext,\n  useEffect,\n  useState,\n} from \"react\";\nimport type { IconType } from \"react-icons\";\nimport {\n  SiAstro,\n  SiBiome,\n  SiBower,\n  SiBun,\n  SiC,\n  SiCircleci,\n  SiCoffeescript,\n  SiCplusplus,\n  SiCss3,\n  SiCssmodules,\n  SiDart,\n  SiDocker,\n  SiDocusaurus,\n  SiDotenv,\n  SiEditorconfig,\n  SiEslint,\n  SiGatsby,\n  SiGitignoredotio,\n  SiGnubash,\n  SiGo,\n  SiGraphql,\n  SiGrunt,\n  SiGulp,\n  SiHandlebarsdotjs,\n  SiHtml5,\n  SiJavascript,\n  SiJest,\n  SiJson,\n  SiLess,\n  SiMarkdown,\n  SiMdx,\n  SiMintlify,\n  SiMocha,\n  SiMysql,\n  SiNextdotjs,\n  SiPerl,\n  SiPhp,\n  SiPostcss,\n  SiPrettier,\n  SiPrisma,\n  SiPug,\n  SiPython,\n  SiR,\n  SiReact,\n  SiReadme,\n  SiRedis,\n  SiRemix,\n  SiRive,\n  SiRollupdotjs,\n  SiRuby,\n  SiSanity,\n  SiSass,\n  SiScala,\n  SiSentry,\n  SiShadcnui,\n  SiStorybook,\n  SiStylelint,\n  SiSublimetext,\n  SiSvelte,\n  SiSvg,\n  SiSwift,\n  SiTailwindcss,\n  SiToml,\n  SiTypescript,\n  SiVercel,\n  SiVite,\n  SiVuedotjs,\n  SiWebassembly,\n} from \"react-icons/si\";\nimport {\n  type BundledLanguage,\n  type CodeOptionsMultipleThemes,\n  codeToHtml,\n} from \"shiki\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\n\nexport type { BundledLanguage } from \"shiki\";\n\nconst filenameIconMap = {\n  \".env\": SiDotenv,\n  \"*.astro\": SiAstro,\n  \"biome.json\": SiBiome,\n  \".bowerrc\": SiBower,\n  \"bun.lockb\": SiBun,\n  \"*.c\": SiC,\n  \"*.cpp\": SiCplusplus,\n  \".circleci/config.yml\": SiCircleci,\n  \"*.coffee\": SiCoffeescript,\n  \"*.module.css\": SiCssmodules,\n  \"*.css\": SiCss3,\n  \"*.dart\": SiDart,\n  Dockerfile: SiDocker,\n  \"docusaurus.config.js\": SiDocusaurus,\n  \".editorconfig\": SiEditorconfig,\n  \".eslintrc\": SiEslint,\n  \"eslint.config.*\": SiEslint,\n  \"gatsby-config.*\": SiGatsby,\n  \".gitignore\": SiGitignoredotio,\n  \"*.go\": SiGo,\n  \"*.graphql\": SiGraphql,\n  \"*.sh\": SiGnubash,\n  \"Gruntfile.*\": SiGrunt,\n  \"gulpfile.*\": SiGulp,\n  \"*.hbs\": SiHandlebarsdotjs,\n  \"*.html\": SiHtml5,\n  \"*.js\": SiJavascript,\n  \"*.json\": SiJson,\n  \"*.test.js\": SiJest,\n  \"*.less\": SiLess,\n  \"*.md\": SiMarkdown,\n  \"*.mdx\": SiMdx,\n  \"mintlify.json\": SiMintlify,\n  \"mocha.opts\": SiMocha,\n  \"*.mustache\": SiHandlebarsdotjs,\n  \"*.sql\": SiMysql,\n  \"next.config.*\": SiNextdotjs,\n  \"*.pl\": SiPerl,\n  \"*.php\": SiPhp,\n  \"postcss.config.*\": SiPostcss,\n  \"prettier.config.*\": SiPrettier,\n  \"*.prisma\": SiPrisma,\n  \"*.pug\": SiPug,\n  \"*.py\": SiPython,\n  \"*.r\": SiR,\n  \"*.rb\": SiRuby,\n  \"*.jsx\": SiReact,\n  \"*.tsx\": SiReact,\n  \"readme.md\": SiReadme,\n  \"*.rdb\": SiRedis,\n  \"remix.config.*\": SiRemix,\n  \"*.riv\": SiRive,\n  \"rollup.config.*\": SiRollupdotjs,\n  \"sanity.config.*\": SiSanity,\n  \"*.sass\": SiSass,\n  \"*.scss\": SiSass,\n  \"*.sc\": SiScala,\n  \"*.scala\": SiScala,\n  \"sentry.client.config.*\": SiSentry,\n  \"components.json\": SiShadcnui,\n  \"storybook.config.*\": SiStorybook,\n  \"stylelint.config.*\": SiStylelint,\n  \".sublime-settings\": SiSublimetext,\n  \"*.svelte\": SiSvelte,\n  \"*.svg\": SiSvg,\n  \"*.swift\": SiSwift,\n  \"tailwind.config.*\": SiTailwindcss,\n  \"*.toml\": SiToml,\n  \"*.ts\": SiTypescript,\n  \"vercel.json\": SiVercel,\n  \"vite.config.*\": SiVite,\n  \"*.vue\": SiVuedotjs,\n  \"*.wasm\": SiWebassembly,\n};\n\nconst lineNumberClassNames = cn(\n  \"[&_code]:[counter-reset:line]\",\n  \"[&_code]:[counter-increment:line_0]\",\n  \"[&_.line]:before:content-[counter(line)]\",\n  \"[&_.line]:before:inline-block\",\n  \"[&_.line]:before:[counter-increment:line]\",\n  \"[&_.line]:before:w-4\",\n  \"[&_.line]:before:mr-4\",\n  \"[&_.line]:before:text-[13px]\",\n  \"[&_.line]:before:text-right\",\n  \"[&_.line]:before:text-muted-foreground/50\",\n  \"[&_.line]:before:font-mono\",\n  \"[&_.line]:before:select-none\"\n);\n\nconst darkModeClassNames = cn(\n  \"dark:[&_.shiki]:!text-[var(--shiki-dark)]\",\n  // \"dark:[&_.shiki]:!bg-[var(--shiki-dark-bg)]\",\n  \"dark:[&_.shiki]:![font-style:var(--shiki-dark-font-style)]\",\n  \"dark:[&_.shiki]:![font-weight:var(--shiki-dark-font-weight)]\",\n  \"dark:[&_.shiki]:![text-decoration:var(--shiki-dark-text-decoration)]\",\n  \"dark:[&_.shiki_span]:!text-[var(--shiki-dark)]\",\n  \"dark:[&_.shiki_span]:![font-style:var(--shiki-dark-font-style)]\",\n  \"dark:[&_.shiki_span]:![font-weight:var(--shiki-dark-font-weight)]\",\n  \"dark:[&_.shiki_span]:![text-decoration:var(--shiki-dark-text-decoration)]\"\n);\n\nconst lineHighlightClassNames = cn(\n  \"[&_.line.highlighted]:bg-blue-50\",\n  \"[&_.line.highlighted]:after:bg-blue-500\",\n  \"[&_.line.highlighted]:after:absolute\",\n  \"[&_.line.highlighted]:after:left-0\",\n  \"[&_.line.highlighted]:after:top-0\",\n  \"[&_.line.highlighted]:after:bottom-0\",\n  \"[&_.line.highlighted]:after:w-0.5\",\n  \"dark:[&_.line.highlighted]:!bg-blue-500/10\"\n);\n\nconst lineDiffClassNames = cn(\n  \"[&_.line.diff]:after:absolute\",\n  \"[&_.line.diff]:after:left-0\",\n  \"[&_.line.diff]:after:top-0\",\n  \"[&_.line.diff]:after:bottom-0\",\n  \"[&_.line.diff]:after:w-0.5\",\n  \"[&_.line.diff.add]:bg-emerald-50\",\n  \"[&_.line.diff.add]:after:bg-emerald-500\",\n  \"[&_.line.diff.remove]:bg-rose-50\",\n  \"[&_.line.diff.remove]:after:bg-rose-500\",\n  \"dark:[&_.line.diff.add]:!bg-emerald-500/10\",\n  \"dark:[&_.line.diff.remove]:!bg-rose-500/10\"\n);\n\nconst lineFocusedClassNames = cn(\n  \"[&_code:has(.focused)_.line]:blur-[2px]\",\n  \"[&_code:has(.focused)_.line.focused]:blur-none\"\n);\n\nconst wordHighlightClassNames = cn(\n  \"[&_.highlighted-word]:bg-blue-50\",\n  \"dark:[&_.highlighted-word]:!bg-blue-500/10\"\n);\n\nconst codeBlockClassName = cn(\n  \"mt-0 bg-background text-sm\",\n  \"[&_pre]:py-4\",\n  // \"[&_.shiki]:!bg-[var(--shiki-bg)]\",\n  \"[&_.shiki]:!bg-transparent\",\n  \"[&_code]:w-full\",\n  \"[&_code]:grid\",\n  \"[&_code]:overflow-x-auto\",\n  \"[&_code]:bg-transparent\",\n  \"[&_.line]:px-4\",\n  \"[&_.line]:w-full\",\n  \"[&_.line]:relative\"\n);\n\nconst highlight = (\n  html: string,\n  language?: BundledLanguage,\n  themes?: CodeOptionsMultipleThemes[\"themes\"]\n) =>\n  codeToHtml(html, {\n    lang: language ?? \"typescript\",\n    themes: themes ?? {\n      light: \"github-light\",\n      dark: \"github-dark-default\",\n    },\n    transformers: [\n      transformerNotationDiff({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationHighlight({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationWordHighlight({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationFocus({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationErrorLevel({\n        matchAlgorithm: \"v3\",\n      }),\n    ],\n  });\n\ntype CodeBlockData = {\n  language: string;\n  filename: string;\n  code: string;\n};\n\ntype CodeBlockContextType = {\n  value: string | undefined;\n  onValueChange: ((value: string) => void) | undefined;\n  data: CodeBlockData[];\n};\n\nconst CodeBlockContext = createContext<CodeBlockContextType>({\n  value: undefined,\n  onValueChange: undefined,\n  data: [],\n});\n\nexport type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {\n  defaultValue?: string;\n  value?: string;\n  onValueChange?: (value: string) => void;\n  data: CodeBlockData[];\n};\n\nexport const CodeBlock = ({\n  value: controlledValue,\n  onValueChange: controlledOnValueChange,\n  defaultValue,\n  className,\n  data,\n  ...props\n}: CodeBlockProps) => {\n  const [value, onValueChange] = useControllableState({\n    defaultProp: defaultValue ?? \"\",\n    prop: controlledValue,\n    onChange: controlledOnValueChange,\n  });\n\n  return (\n    <CodeBlockContext.Provider value={{ value, onValueChange, data }}>\n      <div\n        className={cn(\"size-full overflow-hidden rounded-md border\", className)}\n        {...props}\n      />\n    </CodeBlockContext.Provider>\n  );\n};\n\nexport type CodeBlockHeaderProps = HTMLAttributes<HTMLDivElement>;\n\nexport const CodeBlockHeader = ({\n  className,\n  ...props\n}: CodeBlockHeaderProps) => (\n  <div\n    className={cn(\n      \"flex flex-row items-center border-b bg-secondary p-1\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type CodeBlockFilesProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  children: (item: CodeBlockData) => ReactNode;\n};\n\nexport const CodeBlockFiles = ({\n  className,\n  children,\n  ...props\n}: CodeBlockFilesProps) => {\n  const { data } = useContext(CodeBlockContext);\n\n  return (\n    <div\n      className={cn(\"flex grow flex-row items-center gap-2\", className)}\n      {...props}\n    >\n      {data.map(children)}\n    </div>\n  );\n};\n\nexport type CodeBlockFilenameProps = HTMLAttributes<HTMLDivElement> & {\n  icon?: IconType;\n  value?: string;\n};\n\nexport const CodeBlockFilename = ({\n  className,\n  icon,\n  value,\n  children,\n  ...props\n}: CodeBlockFilenameProps) => {\n  const { value: activeValue } = useContext(CodeBlockContext);\n  const defaultIcon = Object.entries(filenameIconMap).find(([pattern]) => {\n    const regex = new RegExp(\n      `^${pattern.replace(/\\\\/g, \"\\\\\\\\\").replace(/\\./g, \"\\\\.\").replace(/\\*/g, \".*\")}$`\n    );\n    return regex.test(children as string);\n  })?.[1];\n  const Icon = icon ?? defaultIcon;\n\n  if (value !== activeValue) {\n    return null;\n  }\n\n  return (\n    <div\n      className=\"flex items-center gap-2 bg-secondary px-4 py-1.5 text-muted-foreground text-xs\"\n      {...props}\n    >\n      {Icon && <Icon className=\"h-4 w-4 shrink-0\" />}\n      <span className=\"flex-1 truncate\">{children}</span>\n    </div>\n  );\n};\n\nexport type CodeBlockSelectProps = ComponentProps<typeof Select>;\n\nexport const CodeBlockSelect = (props: CodeBlockSelectProps) => {\n  const { value, onValueChange } = useContext(CodeBlockContext);\n\n  return <Select onValueChange={onValueChange} value={value} {...props} />;\n};\n\nexport type CodeBlockSelectTriggerProps = ComponentProps<typeof SelectTrigger>;\n\nexport const CodeBlockSelectTrigger = ({\n  className,\n  ...props\n}: CodeBlockSelectTriggerProps) => (\n  <SelectTrigger\n    className={cn(\n      \"w-fit border-none text-muted-foreground text-xs shadow-none\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type CodeBlockSelectValueProps = ComponentProps<typeof SelectValue>;\n\nexport const CodeBlockSelectValue = (props: CodeBlockSelectValueProps) => (\n  <SelectValue {...props} />\n);\n\nexport type CodeBlockSelectContentProps = Omit<\n  ComponentProps<typeof SelectContent>,\n  \"children\"\n> & {\n  children: (item: CodeBlockData) => ReactNode;\n};\n\nexport const CodeBlockSelectContent = ({\n  children,\n  ...props\n}: CodeBlockSelectContentProps) => {\n  const { data } = useContext(CodeBlockContext);\n\n  return <SelectContent {...props}>{data.map(children)}</SelectContent>;\n};\n\nexport type CodeBlockSelectItemProps = ComponentProps<typeof SelectItem>;\n\nexport const CodeBlockSelectItem = ({\n  className,\n  ...props\n}: CodeBlockSelectItemProps) => (\n  <SelectItem className={cn(\"text-sm\", className)} {...props} />\n);\n\nexport type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {\n  onCopy?: () => void;\n  onError?: (error: Error) => void;\n  timeout?: number;\n};\n\nexport const CodeBlockCopyButton = ({\n  asChild,\n  onCopy,\n  onError,\n  timeout = 2000,\n  children,\n  className,\n  ...props\n}: CodeBlockCopyButtonProps) => {\n  const [isCopied, setIsCopied] = useState(false);\n  const { data, value } = useContext(CodeBlockContext);\n  const code = data.find((item) => item.language === value)?.code;\n\n  const copyToClipboard = () => {\n    if (\n      typeof window === \"undefined\" ||\n      !navigator.clipboard.writeText ||\n      !code\n    ) {\n      return;\n    }\n\n    navigator.clipboard.writeText(code).then(() => {\n      setIsCopied(true);\n      onCopy?.();\n\n      setTimeout(() => setIsCopied(false), timeout);\n    }, onError);\n  };\n\n  if (asChild) {\n    return cloneElement(children as ReactElement, {\n      // @ts-expect-error - we know this is a button\n      onClick: copyToClipboard,\n    });\n  }\n\n  const Icon = isCopied ? CheckIcon : CopyIcon;\n\n  return (\n    <Button\n      className={cn(\"shrink-0\", className)}\n      onClick={copyToClipboard}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ?? <Icon className=\"text-muted-foreground\" size={14} />}\n    </Button>\n  );\n};\n\ntype CodeBlockFallbackProps = HTMLAttributes<HTMLDivElement>;\n\nconst CodeBlockFallback = ({ children, ...props }: CodeBlockFallbackProps) => (\n  <div {...props}>\n    <pre className=\"w-full\">\n      <code>\n        {children\n          ?.toString()\n          .split(\"\\n\")\n          .map((line, i) => (\n            <span className=\"line\" key={i}>\n              {line}\n            </span>\n          ))}\n      </code>\n    </pre>\n  </div>\n);\n\nexport type CodeBlockBodyProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  children: (item: CodeBlockData) => ReactNode;\n};\n\nexport const CodeBlockBody = ({ children, ...props }: CodeBlockBodyProps) => {\n  const { data } = useContext(CodeBlockContext);\n\n  return <div {...props}>{data.map(children)}</div>;\n};\n\nexport type CodeBlockItemProps = HTMLAttributes<HTMLDivElement> & {\n  value: string;\n  lineNumbers?: boolean;\n};\n\nexport const CodeBlockItem = ({\n  children,\n  lineNumbers = true,\n  className,\n  value,\n  ...props\n}: CodeBlockItemProps) => {\n  const { value: activeValue } = useContext(CodeBlockContext);\n\n  if (value !== activeValue) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        codeBlockClassName,\n        lineHighlightClassNames,\n        lineDiffClassNames,\n        lineFocusedClassNames,\n        wordHighlightClassNames,\n        darkModeClassNames,\n        lineNumbers && lineNumberClassNames,\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport type CodeBlockContentProps = HTMLAttributes<HTMLDivElement> & {\n  themes?: CodeOptionsMultipleThemes[\"themes\"];\n  language?: BundledLanguage;\n  syntaxHighlighting?: boolean;\n  children: string;\n};\n\nexport const CodeBlockContent = ({\n  children,\n  themes,\n  language,\n  syntaxHighlighting = true,\n  ...props\n}: CodeBlockContentProps) => {\n  const [html, setHtml] = useState<string | null>(null);\n\n  useEffect(() => {\n    if (!syntaxHighlighting) {\n      return;\n    }\n\n    highlight(children as string, language, themes)\n      .then(setHtml)\n      // biome-ignore lint/suspicious/noConsole: \"it's fine\"\n      .catch(console.error);\n  }, [children, themes, syntaxHighlighting, language]);\n\n  if (!(syntaxHighlighting && html)) {\n    return <CodeBlockFallback>{children}</CodeBlockFallback>;\n  }\n\n  return (\n    <div\n      // biome-ignore lint/security/noDangerouslySetInnerHtml: \"Kinda how Shiki works\"\n      dangerouslySetInnerHTML={{ __html: html }}\n      {...props}\n    />\n  );\n};\n","target":"components/kibo-ui/code-block/index.tsx"},{"type":"registry:ui","path":"server.tsx","content":"import {\n  transformerNotationDiff,\n  transformerNotationErrorLevel,\n  transformerNotationFocus,\n  transformerNotationHighlight,\n  transformerNotationWordHighlight,\n} from \"@shikijs/transformers\";\nimport type { HTMLAttributes } from \"react\";\nimport {\n  type BundledLanguage,\n  type CodeOptionsMultipleThemes,\n  codeToHtml,\n} from \"shiki\";\n\nexport type CodeBlockContentProps = HTMLAttributes<HTMLDivElement> & {\n  themes?: CodeOptionsMultipleThemes[\"themes\"];\n  language?: BundledLanguage;\n  children: string;\n  syntaxHighlighting?: boolean;\n};\n\nexport const CodeBlockContent = async ({\n  children,\n  themes,\n  language,\n  syntaxHighlighting = true,\n  ...props\n}: CodeBlockContentProps) => {\n  const html = syntaxHighlighting\n    ? await codeToHtml(children as string, {\n        lang: language ?? \"typescript\",\n        themes: themes ?? {\n          light: \"vitesse-light\",\n          dark: \"vitesse-dark\",\n        },\n        transformers: [\n          transformerNotationDiff({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationHighlight({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationWordHighlight({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationFocus({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationErrorLevel({\n            matchAlgorithm: \"v3\",\n          }),\n        ],\n      })\n    : children;\n\n  return (\n    <div\n      // biome-ignore lint/security/noDangerouslySetInnerHtml: \"Kinda how Shiki works\"\n      dangerouslySetInnerHTML={{ __html: html }}\n      {...props}\n    />\n  );\n};\n","target":"components/kibo-ui/code-block/server.tsx"}],"css":{}}