bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
18 lines • 9.29 kB
JSON
{
"name": "ui-components-codeblock",
"type": "registry:component",
"dependencies": [
"@radix-ui/react-icons",
"shiki"
],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/ui-components/CodeBlock.tsx",
"type": "registry:component",
"content": "import { CheckIcon, CopyIcon } from \"@radix-ui/react-icons\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { bundledLanguages, createHighlighter } from \"shiki\";\nimport { cn } from \"../../lib/utils.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Card, CardContent, CardHeader } from \"../ui/card.js\";\n\nexport interface CodeBlockProps {\n\t/**\n\t * The programming language for syntax highlighting\n\t */\n\tlanguage?: string;\n\n\t/**\n\t * The code content to display\n\t */\n\tcode: string;\n\n\t/**\n\t * Whether to show the copy button (default: true)\n\t */\n\tshowCopy?: boolean;\n\n\t/**\n\t * Optional title for the code block\n\t */\n\ttitle?: string;\n\n\t/**\n\t * Optional filename to display\n\t */\n\tfilename?: string;\n\n\t/**\n\t * Whether to show line numbers (default: false)\n\t */\n\tshowLineNumbers?: boolean;\n\n\t/**\n\t * Line numbers to highlight\n\t */\n\thighlightLines?: number[];\n\n\t/**\n\t * Whether to wrap in terminal window design (default: false)\n\t */\n\tterminal?: boolean;\n\n\t/**\n\t * Theme variant\n\t */\n\tvariant?: \"default\" | \"ghost\" | \"outline\";\n\n\t/**\n\t * Component size\n\t */\n\tsize?: \"sm\" | \"default\" | \"lg\";\n\n\t/**\n\t * Theme for syntax highlighting\n\t */\n\ttheme?: \"github-dark\" | \"github-light\" | \"nord\" | \"dracula\";\n}\n\nexport function CodeBlock({\n\tlanguage = \"text\",\n\tcode,\n\tshowCopy = true,\n\ttitle,\n\tfilename,\n\tshowLineNumbers = false,\n\thighlightLines = [],\n\tterminal = false,\n\tvariant = \"default\",\n\tsize = \"default\",\n\ttheme = \"github-dark\",\n}: CodeBlockProps) {\n\tconst [copied, setCopied] = useState(false);\n\tconst [html, setHtml] = useState<string>(\"\");\n\tconst codeRef = useRef<HTMLDivElement>(null);\n\n\t// Highlight code using Shiki\n\tuseEffect(() => {\n\t\tasync function highlightCode() {\n\t\t\ttry {\n\t\t\t\tconst highlighter = await createHighlighter({\n\t\t\t\t\tthemes: [theme],\n\t\t\t\t\tlangs: Object.keys(bundledLanguages),\n\t\t\t\t});\n\n\t\t\t\t// Build decorations for line highlighting\n\t\t\t\tconst decorations = highlightLines.map((lineNumber) => {\n\t\t\t\t\tconst lineIndex = lineNumber - 1;\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstart: { line: lineIndex, character: 0 },\n\t\t\t\t\t\tend: { line: lineIndex + 1, character: 0 }, // Start of next line\n\t\t\t\t\t\tproperties: { class: \"highlighted-line\" },\n\t\t\t\t\t\ttagName: \"span\" as const,\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\tconst highlighted = highlighter.codeToHtml(code || \"\", {\n\t\t\t\t\tlang: language,\n\t\t\t\t\ttheme,\n\t\t\t\t\tdecorations: decorations.length > 0 ? decorations : undefined,\n\t\t\t\t});\n\n\t\t\t\tsetHtml(highlighted);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"Failed to highlight code:\", error);\n\t\t\t\t// Fallback to plain text\n\t\t\t\tconst safeCode = code || \"\";\n\t\t\t\tsetHtml(\n\t\t\t\t\t`<pre><code>${safeCode.replace(/</g, \"<\").replace(/>/g, \">\")}</code></pre>`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\thighlightCode();\n\t}, [code, language, theme, highlightLines]);\n\n\tconst handleCopy = async () => {\n\t\ttry {\n\t\t\tawait navigator.clipboard.writeText(code || \"\");\n\t\t\tsetCopied(true);\n\t\t\tsetTimeout(() => setCopied(false), 2000);\n\t\t} catch (err) {\n\t\t\tconsole.error(\"Failed to copy code:\", err);\n\t\t}\n\t};\n\n\t// Size variants for padding\n\tconst sizeClasses = {\n\t\tsm: \"p-3\",\n\t\tdefault: \"p-4\",\n\t\tlg: \"p-6\",\n\t};\n\n\t// Custom styles for Shiki output\n\tconst shikiStyles = `\n .shiki {\n margin: 0;\n padding: 0.75rem;\n border-radius: ${terminal ? \"0\" : \"0.375rem\"};\n overflow-x: auto;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n font-size: 14px;\n line-height: 1.5;\n }\n \n .shiki code {\n display: block;\n width: fit-content;\n min-width: 100%;\n }\n \n .shiki .line {\n display: inline-block;\n width: 100%;\n }\n \n .shiki .line.highlighted {\n background-color: hsl(var(--muted) / 0.5);\n margin: 0 -0.75rem;\n padding: 0 0.75rem;\n }\n \n ${\n\t\t\tshowLineNumbers\n\t\t\t\t? `\n .shiki code {\n counter-reset: line;\n }\n \n .shiki .line::before {\n counter-increment: line;\n content: counter(line);\n display: inline-block;\n width: 3ch;\n margin-right: 1.5ch;\n text-align: right;\n color: hsl(var(--muted-foreground));\n user-select: none;\n }\n `\n\t\t\t\t: \"\"\n\t\t}\n \n /* Highlighted line styles */\n .highlighted-line {\n background-color: hsl(var(--accent));\n border-left: 2px solid hsl(var(--primary));\n margin: 0 -1rem;\n padding: 0 1rem;\n display: block;\n }\n `;\n\n\t// Terminal mode rendering\n\tif (terminal) {\n\t\treturn (\n\t\t\t<div className=\"relative rounded-lg overflow-hidden border bg-card\">\n\t\t\t\t{/* biome-ignore lint/security/noDangerouslySetInnerHtml: Required for Shiki syntax highlighting */}\n\t\t\t\t<style dangerouslySetInnerHTML={{ __html: shikiStyles }} />\n\n\t\t\t\t{/* Terminal Header */}\n\t\t\t\t<div className=\"flex items-center justify-between px-4 py-3 bg-muted border-b\">\n\t\t\t\t\t<div className=\"flex items-center gap-3\">\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<div className=\"w-3 h-3 rounded-full bg-destructive\" />\n\t\t\t\t\t\t\t<div className=\"w-3 h-3 rounded-full bg-secondary\" />\n\t\t\t\t\t\t\t<div className=\"w-3 h-3 rounded-full bg-primary\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{(filename || title) && (\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t<span className=\"text-sm text-foreground\">\n\t\t\t\t\t\t\t\t\t{filename || title}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{showCopy && (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\tonClick={handleCopy}\n\t\t\t\t\t\t\tclassName=\"h-7 px-2 opacity-70 hover:opacity-100 transition-opacity\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{copied ? (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<CheckIcon className=\"h-3.5 w-3.5 mr-1\" />\n\t\t\t\t\t\t\t\t\tCopied\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<CopyIcon className=\"h-3.5 w-3.5 mr-1\" />\n\t\t\t\t\t\t\t\t\tCopy\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{/* Terminal Code Content */}\n\t\t\t\t<div\n\t\t\t\t\tref={codeRef}\n\t\t\t\t\tclassName=\"p-4\"\n\t\t\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: Required for Shiki syntax highlighting\n\t\t\t\t\tdangerouslySetInnerHTML={{ __html: html }}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t);\n\t}\n\n\t// Regular mode rendering\n\tconst cardVariantClasses = {\n\t\tdefault: \"\",\n\t\tghost: \"border-0 shadow-none\",\n\t\toutline: \"shadow-none\",\n\t};\n\n\treturn (\n\t\t<Card className={cn(\"overflow-hidden\", cardVariantClasses[variant])}>\n\t\t\t{/* biome-ignore lint/security/noDangerouslySetInnerHtml: Required for Shiki syntax highlighting */}\n\t\t\t<style dangerouslySetInnerHTML={{ __html: shikiStyles }} />\n\n\t\t\t{/* Header with title/filename and copy button */}\n\t\t\t{(title || filename || showCopy) && (\n\t\t\t\t<CardHeader className={cn(\"pb-3\", sizeClasses[size])}>\n\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{title && <h3 className=\"text-base font-medium\">{title}</h3>}\n\t\t\t\t\t\t\t{filename && (\n\t\t\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">{filename}</p>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{showCopy && (\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\tonClick={handleCopy}\n\t\t\t\t\t\t\t\tclassName=\"h-8 px-2\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{copied ? (\n\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t<CheckIcon className=\"h-3.5 w-3.5 mr-1\" />\n\t\t\t\t\t\t\t\t\t\tCopied\n\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t<CopyIcon className=\"h-3.5 w-3.5 mr-1\" />\n\t\t\t\t\t\t\t\t\t\tCopy\n\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</CardHeader>\n\t\t\t)}\n\n\t\t\t<CardContent\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"p-0\",\n\t\t\t\t\t!title && !filename && !showCopy && sizeClasses[size],\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tref={codeRef}\n\t\t\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: Required for Shiki syntax highlighting\n\t\t\t\t\tdangerouslySetInnerHTML={{ __html: html }}\n\t\t\t\t/>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n\n// Re-export for convenience\nexport default CodeBlock;\n",
"target": "<%- config.aliases.components %>/codeblock.tsx"
}
]
}