{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "custom-code-block",
  "title": "Custom Code Block",
  "author": "AlgorizeTech",
  "description": "A tabbed code viewer with filenames, language badges and per-tab copy. Use it in documentation to show the same example across several files.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/CustomCodeBlock.tsx",
      "content": "\"use client\";\n\nimport React, { useState, useMemo } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Atom, Braces, Check, CodeXml, Copy, FileCode, Palette, Terminal } from \"lucide-react\";\n\nexport interface CodeTab {\n  code: string;\n  language: string;\n  fileName: string;\n}\n\nexport interface CustomCodeBlockProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"code\"> {\n  code?: string;\n  language?: string;\n  fileName?: string;\n  tabs?: CodeTab[];\n}\n\nconst LanguageIcon = ({ language }: { language: string }) => {\n  const lang = language.toLowerCase();\n  const iconClass = \"w-4 h-4\";\n  // lucide has no language logos; the brand colour on a generic glyph does the\n  // identifying instead.\n  if (lang === \"javascript\" || lang === \"js\" || lang === \"jsx\") return <Braces className={cn(iconClass, \"text-yellow-400\")} />;\n  if (lang === \"typescript\" || lang === \"ts\" || lang === \"tsx\") return <FileCode className={cn(iconClass, \"text-blue-500\")} />;\n  if (lang === \"react\") return <Atom className={cn(iconClass, \"text-sky-400\")} />;\n  if (lang === \"html\" || lang === \"xml\") return <CodeXml className={cn(iconClass, \"text-orange-500\")} />;\n  if (lang === \"css\") return <Palette className={cn(iconClass, \"text-blue-400\")} />;\n  if (lang === \"bash\" || lang === \"sh\") return <Terminal className={cn(iconClass, \"text-muted-foreground\")} />;\n  return <FileCode className={cn(iconClass, \"text-muted-foreground\")} />;\n};\n\nconst formatLanguageName = (language: string) => {\n  const lang = language.toLowerCase();\n  if (lang === \"javascript\" || lang === \"js\" || lang === \"jsx\") return \"JavaScript\";\n  if (lang === \"typescript\" || lang === \"ts\" || lang === \"tsx\") return \"TypeScript\";\n  if (lang === \"react\") return \"React\";\n  if (lang === \"html\" || lang === \"xml\") return \"HTML\";\n  if (lang === \"css\") return \"CSS\";\n  if (lang === \"bash\" || lang === \"sh\") return \"Terminal\";\n  return language;\n};\n\nconst highlightCode = (code: string, language: string) => {\n  let html = code\n    .replace(/&/g, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\");\n\n  // Atom One Light for Light Mode, One Dark Pro for Dark Mode\n  const colors = {\n    keyword: \"text-[#A626A4] dark:text-[#C678DD] font-medium\",\n    string: \"text-[#50A14F] dark:text-[#98C379]\",\n    function: \"text-[#4078F2] dark:text-[#61AFEF]\",\n    number: \"text-[#986801] dark:text-[#D19A66]\",\n    property: \"text-[#E45649] dark:text-[#E5C07B]\",\n    tag: \"text-[#E45649] dark:text-[#E06C75]\", \n    component: \"text-[#C18401] dark:text-[#E5C07B] font-semibold\",\n    operator: \"text-[#383A42] dark:text-[#ABB2BF]\",\n    comment: \"text-[#A0A1A7] dark:text-[#5C6370]\",\n    punctuation: \"text-[#383A42] dark:text-[#ABB2BF]\",\n    stringContent: \"text-[#50A14F] dark:text-[#98C379]\",\n  };\n\n  // Each branch below is a SINGLE pass over the escaped source. Chaining\n  // separate .replace() calls does not work here: a later rule matches the\n  // markup an earlier rule injected -- the attribute rule, for one, matched\n  // the class=\"...\" of an already-emitted <span> and nested another <span>\n  // inside it, which the browser then rendered as visible text.\n\n  if (language === \"html\" || language === \"xml\") {\n    return html.replace(\n      /(&lt;!--[\\s\\S]*?--&gt;)|(&lt;\\/?)([a-zA-Z0-9-]+)|([a-zA-Z0-9-]+)(=)([\"'])(.*?)\\6|(\\/&gt;|&gt;)/g,\n      (match, comment, brackets, tagName, attr, eq, quote, content, closer) => {\n        if (comment) return `<span class=\"${colors.comment}\">${comment}</span>`;\n        if (brackets !== undefined)\n          return `<span class=\"${colors.punctuation}\">${brackets}</span><span class=\"${colors.tag}\">${tagName}</span>`;\n        if (attr !== undefined)\n          return `<span class=\"${colors.property}\">${attr}</span><span class=\"${colors.operator}\">${eq}</span><span class=\"${colors.property}\">${quote}</span><span class=\"${colors.stringContent}\">${content}</span><span class=\"${colors.property}\">${quote}</span>`;\n        if (closer) return `<span class=\"${colors.punctuation}\">${closer}</span>`;\n        return match;\n      }\n    );\n  }\n\n  if (language === \"css\") {\n    return html.replace(\n      /(\\/\\*[\\s\\S]*?\\*\\/)|(@[a-zA-Z-]+)|([^\\s{}/;][^{}/;]*?)(?=\\s*\\{)|([a-zA-Z-]+)(\\s*:)([^;{}]+)(;?)|([{}])/g,\n      (match, comment, atRule, selector, prop, colon, val, semi, brace) => {\n        if (comment) return `<span class=\"${colors.comment}\">${comment}</span>`;\n        if (atRule) return `<span class=\"${colors.keyword}\">${atRule}</span>`;\n        if (selector !== undefined)\n          return `<span class=\"${colors.tag}\">${selector}</span>`;\n        if (prop !== undefined)\n          return `<span class=\"${colors.property}\">${prop}</span><span class=\"${colors.punctuation}\">${colon}</span><span class=\"${colors.stringContent}\">${val}</span><span class=\"${colors.punctuation}\">${semi}</span>`;\n        if (brace) return `<span class=\"${colors.punctuation}\">${brace}</span>`;\n        return match;\n      }\n    );\n  }\n\n  if (language === \"bash\" || language === \"sh\") {\n    return html.replace(\n      /(#[^\\n]*)|(\"[^\"\\n]*\"|'[^'\\n]*')|(\\b(?:npm|yarn|pnpm|npx|node|git|cd|ls|rm|mkdir|echo|cat|sudo|apt|brew)\\b)|(\\s--?[a-zA-Z0-9][a-zA-Z0-9-]*)/g,\n      (match, comment, str, command, flag) => {\n        if (comment) return `<span class=\"${colors.comment}\">${comment}</span>`;\n        if (str) return `<span class=\"${colors.stringContent}\">${str}</span>`;\n        if (command) return `<span class=\"${colors.function}\">${command}</span>`;\n        if (flag) return `<span class=\"${colors.property}\">${flag}</span>`;\n        return match;\n      }\n    );\n  }\n\n  // JS/TS/JSX\n  html = html.replace(\n    /([\"'](?:\\\\.|[^\\\\])*?[\"']|`(?:\\\\`|[^`])*`)|(\\/\\/.*|\\/\\*[\\s\\S]*?\\*\\/)|(&lt;\\/?[a-zA-Z0-9_-]+)|(\\/&gt;|&gt;)|([a-zA-Z0-9_-]+)(?=\\s*[:=])|(\\b\\d+\\.?\\d*\\b)|(\\b(?:const|let|var|function|return|if|else|for|while|import|export|from|default|class|extends|implements|new|this|typeof|instanceof|switch|case|break|continue|try|catch|finally|async|await|interface|type|public|private|protected|readonly)\\b)|([a-zA-Z_$][a-zA-Z0-9_$]*)(?=\\s*\\()|(\\b[A-Z][a-zA-Z0-9_$]*\\b)|([=+\\-*/!&|{}()[\\]]+)/g,\n    (match, str, comment, jsxTag, jsxBracket, attrKey, num, keyword, func, comp, operator) => {\n      if (str) {\n        const quote = str[0];\n        if ([\"'\", '\"', \"`\"].includes(quote)) {\n          const inside = str.slice(1, -1);\n          const endQuote = str[str.length - 1];\n          return `<span class=\"${colors.property}\">${quote}</span><span class=\"${colors.stringContent}\">${inside}</span><span class=\"${colors.property}\">${endQuote}</span>`;\n        }\n        return `<span class=\"${colors.string}\">${str}</span>`;\n      }\n      if (comment) return `<span class=\"${colors.comment}\">${comment}</span>`;\n      if (jsxTag) {\n        const isClosing = jsxTag.includes(\"/\");\n        const brackets = isClosing ? \"&lt;/\" : \"&lt;\";\n        const tagName = jsxTag.replace(brackets, \"\");\n        const isComponent = /^[A-Z]/.test(tagName);\n        const tagColor = isComponent ? colors.component : colors.tag;\n        return `<span class=\"${colors.punctuation}\">${brackets}</span><span class=\"${tagColor}\">${tagName}</span>`;\n      }\n      if (jsxBracket) return `<span class=\"${colors.punctuation}\">${jsxBracket}</span>`;\n      if (attrKey) return `<span class=\"${colors.property}\">${attrKey}</span>`;\n      if (num) return `<span class=\"${colors.number}\">${num}</span>`;\n      if (keyword) return `<span class=\"${colors.keyword}\">${keyword}</span>`;\n      if (func) return `<span class=\"${colors.function}\">${func}</span>`;\n      if (comp) return `<span class=\"${colors.component}\">${comp}</span>`;\n      if (operator) return `<span class=\"${colors.operator}\">${operator}</span>`;\n      return match;\n    }\n  );\n\n  html = html.replace(\n    /\\b(true|false|null|undefined)\\b/g,\n    `<span class=\"${colors.number}\">$1</span>`\n  );\n\n  return html;\n};\n\nexport function CustomCodeBlock({\n  code,\n  language = \"typescript\",\n  fileName,\n  tabs,\n  className,\n  ...props\n}: CustomCodeBlockProps) {\n  const [isCopied, setIsCopied] = useState(false);\n  const [activeTabIndex, setActiveTabIndex] = useState(0);\n\n  const activeCode = tabs && tabs.length > 0 ? tabs[activeTabIndex].code : code || \"\";\n  const activeLanguage = tabs && tabs.length > 0 ? tabs[activeTabIndex].language : language;\n  const activeFileName = tabs && tabs.length > 0 ? tabs[activeTabIndex].fileName : fileName;\n\n  // Pure derivation of the props: computing it in an effect rendered the raw\n  // code once, then re-rendered with the highlighted copy.\n  const highlightedCode = useMemo(\n    () => highlightCode(activeCode, activeLanguage),\n    [activeCode, activeLanguage],\n  );\n\n  const copyToClipboard = async () => {\n    try {\n      await navigator.clipboard.writeText(activeCode);\n      setIsCopied(true);\n      setTimeout(() => setIsCopied(false), 2000);\n    } catch (err) {\n      console.error(\"Failed to copy code: \", err);\n    }\n  };\n\n  const lines = (highlightedCode || activeCode).trim().split(\"\\n\");\n\n  return (\n    <div className={cn(\"w-full flex flex-col gap-3 font-sans\", className)} {...props}>\n      {/* Separate Tabs Above */}\n      {tabs && tabs.length > 0 && (\n        <div className=\"flex items-center gap-1 bg-muted/60 p-1 rounded-lg w-max border border-border/80\">\n          {tabs.map((tab, idx) => (\n            <button\n              key={idx}\n              onClick={() => setActiveTabIndex(idx)}\n              className={cn(\n                \"cursor-pointer flex items-center gap-2 px-3 py-1.5 text-xs font-semibold rounded-md transition-all\",\n                activeTabIndex === idx\n                  ? \"bg-card text-foreground shadow-xs\"\n                  : \"text-muted-foreground hover:text-foreground\"\n              )}\n            >\n              {tab.fileName || formatLanguageName(tab.language)}\n            </button>\n          ))}\n        </div>\n      )}\n\n      {/* Main Code Block */}\n      <div className=\"w-full rounded-xl overflow-hidden border border-border bg-card\">\n        {/* Header */}\n        <div className=\"flex items-center justify-between px-4 py-2.5 bg-muted/80 border-b border-border\">\n          <div className=\"flex items-center gap-4 min-w-0\">\n            {/* Mac Dots */}\n            <div className=\"flex items-center gap-1.5 shrink-0\">\n              <div className=\"w-3 h-3 rounded-full bg-rose-500\" />\n              <div className=\"w-3 h-3 rounded-full bg-amber-500\" />\n              <div className=\"w-3 h-3 rounded-full bg-emerald-500\" />\n            </div>\n            \n            {/* File Name / Language Badge */}\n            <div className=\"flex items-center gap-2 px-3 py-1.5 bg-muted/60 border border-border/60 rounded-md shrink-0 shadow-none max-w-full overflow-hidden\">\n              <LanguageIcon language={activeLanguage} />\n              <span className=\"text-xs font-medium text-foreground truncate\">\n                {activeFileName || formatLanguageName(activeLanguage)}\n              </span>\n            </div>\n          </div>\n\n          {/* Copy Button */}\n          <button\n            onClick={copyToClipboard}\n            className=\"cursor-pointer flex items-center justify-center shrink-0 w-8 h-8 rounded-md text-muted-foreground hover:text-foreground bg-muted/60 hover:bg-muted/60 border border-border/60 transition-colors ml-2 shadow-none\"\n          >\n            {isCopied ? <Check className=\"w-4 h-4 text-emerald-500\" /> : <Copy className=\"w-4 h-4\" />}\n          </button>\n        </div>\n\n        {/* Code View */}\n        <div className=\"relative overflow-x-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar]:h-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-foreground/10 hover:[&::-webkit-scrollbar-thumb]:bg-foreground/20 [&::-webkit-scrollbar-thumb]:rounded-full shadow-none\">\n          <pre className=\"p-4 text-[13.5px] leading-relaxed font-mono whitespace-pre min-w-fit pb-6\">\n            <code className=\"block text-foreground\">\n              {lines.map((line, i) => (\n                <div key={i} className=\"flex min-h-[1.5rem]\">\n                  <span className=\"shrink-0 w-10 text-right pr-4 mr-4 select-none text-muted-foreground/60 border-r border-border/60\">\n                    {i + 1}\n                  </span>\n                  <span dangerouslySetInnerHTML={{ __html: line || \" \" }} />\n                </div>\n              ))}\n            </code>\n          </pre>\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/custom/blocks/CustomCodeBlock.tsx"
    }
  ],
  "docs": "Named exports: CustomCodeBlock, CodeTab. `tabs` take { code, language, fileName }; a single tab renders without the tab strip. Uses `lucide-react` for file-type icons. Highlighting is a built-in regex tokenizer, not Shiki or Prism — good for snippets, not a full editor.",
  "categories": [
    "code"
  ],
  "type": "registry:component"
}
