{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ui-codeblocks",
  "title": "UI Codeblocks",
  "author": "AlgorizeTech",
  "description": "A compact code surface with syntax colouring and a copy button, used inside the docs site. Install it for a lightweight code display without tabs.",
  "dependencies": [
    "lucide-react",
    "shiki"
  ],
  "registryDependencies": [
    "https://avaraui.com/r/ui-code-renderer.json"
  ],
  "files": [
    {
      "path": "src/components/custom/ui/codeblocks.tsx",
      "content": "\"use client\";\n\nimport { CodeRenderer } from \"./code-renderer\";\nimport type { BuiltinLanguage } from \"shiki\";\nimport { useState, useMemo, useRef } from \"react\";\nimport { Check, Copy, ChevronDown, ChevronUp, Loader2 } from \"lucide-react\";\n/** Initial lines shown before reader asks for more. */\nconst INITIAL_LINES = 30;\n/** Additional lines added on each \"Show More\" click. */\nconst STEP_LINES = 30;\n\nexport interface CodeBlocksProps {\n  /** The source to render. */\n  code: string;\n  /** Shiki language id; defaults to tsx. */\n  language?: BuiltinLanguage;\n}\n\nexport const CodeBlocks = ({ code, language = \"tsx\" }: CodeBlocksProps) => {\n  const [copied, setCopied] = useState(false);\n  const [visibleLinesCount, setVisibleLinesCount] = useState(INITIAL_LINES);\n  const [isLoadingMore, setIsLoadingMore] = useState(false);\n  const [isLoadingLess, setIsLoadingLess] = useState(false);\n  const blockRef = useRef<HTMLDivElement>(null);\n\n  const lines = useMemo(() => (code ? code.split(\"\\n\") : []), [code]);\n  const totalLines = lines.length;\n\n  const displayedCode = useMemo(() => {\n    if (totalLines <= INITIAL_LINES) return code;\n    return lines.slice(0, visibleLinesCount).join(\"\\n\");\n  }, [code, lines, totalLines, visibleLinesCount]);\n\n  const handleCopy = () => {\n    navigator.clipboard.writeText(code);\n    setCopied(true);\n    setTimeout(() => setCopied(false), 2000);\n  };\n\n  const handleShowMore = () => {\n    if (isLoadingMore) return;\n    setIsLoadingMore(true);\n    setTimeout(() => {\n      setVisibleLinesCount((prev) => Math.min(prev + STEP_LINES, totalLines));\n      setIsLoadingMore(false);\n    }, 250);\n  };\n\n  const handleShowLess = () => {\n    if (isLoadingLess) return;\n    setIsLoadingLess(true);\n    setTimeout(() => {\n      setVisibleLinesCount(INITIAL_LINES);\n      setIsLoadingLess(false);\n      if (blockRef.current) {\n        blockRef.current.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\n      }\n    }, 250);\n  };\n\n  return (\n    <div ref={blockRef} className=\"relative group w-full bg-muted/70 dark:bg-background border-t border-border transition-all duration-300 ease-in-out\">\n      <button\n        onClick={handleCopy}\n        className=\"absolute right-4 top-4 z-10 text-xs bg-card/80 hover:bg-muted text-foreground transition-all p-2.5 rounded-lg cursor-pointer opacity-0 group-hover:opacity-100 focus:opacity-100 border border-border backdrop-blur-xs shadow-none\"\n        aria-label=\"Copy code\"\n      >\n        {copied ? (\n          <Check className=\"w-4 h-4 text-emerald-600 dark:text-emerald-400\" />\n        ) : (\n          <Copy className=\"w-4 h-4\" />\n        )}\n      </button>\n\n      <div className=\"w-full overflow-hidden transition-all duration-300 ease-in-out\">\n        <div className=\"py-2 px-4 sm:px-6 text-sm md:text-[14px] leading-relaxed\">\n          <CodeRenderer code={displayedCode} lang={language} />\n        </div>\n      </div>\n\n      {totalLines > INITIAL_LINES && (\n        <div className=\"relative w-full flex items-center justify-center py-4 bg-muted dark:bg-background border-t border-border/50\">\n          {visibleLinesCount < totalLines ? (\n            <button\n              onClick={handleShowMore}\n              disabled={isLoadingMore}\n              className=\"flex items-center justify-center min-w-[160px] h-9 gap-1.5 px-4 text-xs font-semibold text-foreground bg-card hover:bg-muted border border-border rounded-full transition-all cursor-pointer hover:scale-105 active:scale-95 shadow-none disabled:opacity-80 disabled:cursor-not-allowed\"\n            >\n              {isLoadingMore ? (\n                <Loader2 className=\"w-4 h-4 animate-spin text-muted-foreground\" />\n              ) : (\n                <>\n                  <span>Show More ({totalLines - visibleLinesCount} lines left)</span>\n                  <ChevronDown className=\"w-3.5 h-3.5\" />\n                </>\n              )}\n            </button>\n          ) : (\n            <button\n              onClick={handleShowLess}\n              disabled={isLoadingLess}\n              className=\"flex items-center justify-center min-w-[120px] h-9 gap-1.5 px-4 text-xs font-semibold text-foreground bg-card hover:bg-muted border border-border rounded-full transition-all cursor-pointer hover:scale-105 active:scale-95 shadow-none disabled:opacity-80 disabled:cursor-not-allowed\"\n            >\n              {isLoadingLess ? (\n                <Loader2 className=\"w-4 h-4 animate-spin text-muted-foreground\" />\n              ) : (\n                <>\n                  <span>Show Less</span>\n                  <ChevronUp className=\"w-3.5 h-3.5\" />\n                </>\n              )}\n            </button>\n          )}\n        </div>\n      )}\n    </div>\n  );\n};",
      "type": "registry:ui",
      "target": "components/custom/ui/codeblocks.tsx"
    }
  ],
  "docs": "Named export: CodeBlocks. The minimal internal code viewer — for multi-file tabs and filename headers, use `custom-code-block`; for CLI commands, use `terminal`. Installs `ui-code-renderer` alongside it, which brings in Shiki.",
  "categories": [
    "code"
  ],
  "type": "registry:ui"
}
