mirror of
https://github.com/hanzoai/shadcn.git
synced 2026-11-03 03:18:52 +00:00
Major fixes: - Fixed wagmi/Web3 SSR errors by lazy-loading config in Web3Provider - Added SidebarProvider to sidebar-demo components - Added 'use client' to 14 demo files with hooks/state/event handlers - Temporarily disabled force-static on blocks page Build progress: 0 → 258/1035 pages (25%) before hitting /blocks error Remaining issue: - /blocks page has function-passing error during static generation Package versions confirmed: - React 19.2.0 ✅ - Next.js 16.0.0 ✅ - Tailwind CSS 4.1.14 ✅
15 lines
8.7 KiB
JSON
15 lines
8.7 KiB
JSON
{
|
|
"name": "code-block",
|
|
"description": "Syntax highlighted code block with copy functionality",
|
|
"dependencies": [
|
|
"shiki",
|
|
"react-copy-to-clipboard"
|
|
],
|
|
"files": [
|
|
{
|
|
"name": "code-block.tsx",
|
|
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Check, Copy, FileText } from \"lucide-react\"\nimport { codeToHtml } from \"shiki\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Badge } from \"@/registry/default/ui/badge\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Skeleton } from \"@/registry/default/ui/skeleton\"\n\nconst codeBlockVariants = cva(\n \"relative overflow-hidden rounded-lg border bg-muted/50\",\n {\n variants: {\n theme: {\n dark: \"bg-slate-950 border-slate-800\",\n light: \"bg-slate-50 border-slate-200\",\n github: \"bg-white border-slate-200\",\n \"github-dark\": \"bg-slate-900 border-slate-700\",\n \"vs-dark\": \"bg-slate-950 border-slate-800\",\n monokai: \"bg-slate-900 border-slate-700\",\n dracula: \"bg-slate-900 border-purple-800/30\",\n nord: \"bg-slate-800 border-slate-600\",\n },\n size: {\n sm: \"text-xs\",\n default: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n theme: \"dark\",\n size: \"default\",\n },\n }\n)\n\nexport interface CodeBlockProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof codeBlockVariants> {\n code: string\n language?: string\n filename?: string\n showLineNumbers?: boolean\n highlightLines?: number[]\n showCopyButton?: boolean\n maxHeight?: string\n diff?: {\n added?: number[]\n removed?: number[]\n }\n}\n\ninterface CodeLineProps {\n line: string\n lineNumber: number\n showLineNumbers: boolean\n isHighlighted?: boolean\n isDiffAdded?: boolean\n isDiffRemoved?: boolean\n}\n\nconst CodeLine = React.memo(\n ({\n line,\n lineNumber,\n showLineNumbers,\n isHighlighted = false,\n isDiffAdded = false,\n isDiffRemoved = false,\n }: CodeLineProps) => {\n return (\n <div\n className={cn(\n \"group relative flex min-h-[1.5rem] items-center px-4 py-0.5\",\n isHighlighted && \"bg-blue-500/10 border-l-2 border-l-blue-500\",\n isDiffAdded && \"bg-green-500/10 border-l-2 border-l-green-500\",\n isDiffRemoved && \"bg-red-500/10 border-l-2 border-l-red-500\"\n )}\n >\n {showLineNumbers && (\n <span className=\"mr-4 inline-block w-8 select-none text-right text-xs text-muted-foreground\">\n {lineNumber}\n </span>\n )}\n {(isDiffAdded || isDiffRemoved) && (\n <span className=\"mr-2 select-none text-xs\">\n {isDiffAdded ? \"+\" : isDiffRemoved ? \"-\" : \" \"}\n </span>\n )}\n <div\n className=\"flex-1 overflow-x-auto\"\n dangerouslySetInnerHTML={{ __html: line }}\n />\n </div>\n )\n }\n)\n\nCodeLine.displayName = \"CodeLine\"\n\nconst CodeBlock = React.forwardRef<HTMLDivElement, CodeBlockProps>(\n (\n {\n className,\n theme,\n size,\n code,\n language = \"text\",\n filename,\n showLineNumbers = true,\n highlightLines = [],\n showCopyButton = true,\n maxHeight = \"400px\",\n diff,\n ...props\n },\n ref\n ) => {\n const [highlightedCode, setHighlightedCode] = React.useState<string>(\"\")\n const [copied, setCopied] = React.useState(false)\n const [isLoading, setIsLoading] = React.useState(true)\n\n const themeMap = {\n dark: \"github-dark\",\n light: \"github-light\",\n github: \"github-light\",\n \"github-dark\": \"github-dark\",\n \"vs-dark\": \"dark-plus\",\n monokai: \"monokai\",\n dracula: \"dracula\",\n nord: \"nord\",\n }\n\n React.useEffect(() => {\n const highlightCode = async () => {\n setIsLoading(true)\n try {\n const highlighted = await codeToHtml(code, {\n lang: language,\n theme: themeMap[theme || \"dark\"],\n transformers: [\n {\n code(node) {\n // Remove the outer pre and code tags since we'll handle structure ourselves\n const codeElement = node.children.find(\n (child) =>\n child.type === \"element\" && child.tagName === \"code\"\n )\n if (\n codeElement &&\n \"children\" in codeElement &&\n codeElement.children\n ) {\n node.children = codeElement.children\n }\n },\n },\n ],\n })\n\n // Extract just the inner HTML content\n const tempDiv = document.createElement(\"div\")\n tempDiv.innerHTML = highlighted\n const codeElement = tempDiv.querySelector(\"code\")\n setHighlightedCode(codeElement?.innerHTML || highlighted)\n } catch (error) {\n console.error(\"Failed to highlight code:\", error)\n setHighlightedCode(code)\n } finally {\n setIsLoading(false)\n }\n }\n\n highlightCode()\n }, [code, language, theme])\n\n const copyToClipboard = React.useCallback(async () => {\n try {\n await navigator.clipboard.writeText(code)\n setCopied(true)\n setTimeout(() => setCopied(false), 2000)\n } catch (error) {\n console.error(\"Failed to copy code:\", error)\n }\n }, [code])\n\n const lines = highlightedCode.split(\"\\n\")\n\n return (\n <div\n ref={ref}\n data-testid=\"code-block\"\n className={cn(codeBlockVariants({ theme, size, className }))}\n {...props}\n >\n {/* Header */}\n {(filename || language || showCopyButton) && (\n <div className=\"flex items-center justify-between border-b px-4 py-2\">\n <div className=\"flex items-center gap-2\">\n {filename && (\n <div className=\"flex items-center gap-1.5\">\n <FileText className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <span className=\"text-sm font-medium\">{filename}</span>\n </div>\n )}\n {language && (\n <Badge variant=\"secondary\" className=\"text-xs\">\n {language}\n </Badge>\n )}\n </div>\n {showCopyButton && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={copyToClipboard}\n className=\"h-8 w-8 p-0\"\n >\n {copied ? (\n <Check className=\"h-3.5 w-3.5 text-green-500\" />\n ) : (\n <Copy className=\"h-3.5 w-3.5\" />\n )}\n <span className=\"sr-only\">\n {copied ? \"Copied\" : \"Copy code\"}\n </span>\n </Button>\n )}\n </div>\n )}\n\n {/* Code Content */}\n <div className=\"overflow-auto\" style={{ maxHeight }}>\n {isLoading ? (\n <div className=\"p-4 space-y-2\">\n {Array.from({ length: 10 }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-4\">\n {showLineNumbers && (\n <Skeleton data-testid=\"skeleton\" className=\"h-4 w-8\" />\n )}\n <Skeleton data-testid=\"skeleton\" className=\"h-4 flex-1\" />\n </div>\n ))}\n </div>\n ) : (\n <div className=\"font-mono\">\n {lines.map((line, index) => {\n const lineNumber = index + 1\n const isHighlighted = highlightLines.includes(lineNumber)\n const isDiffAdded = diff?.added?.includes(lineNumber)\n const isDiffRemoved = diff?.removed?.includes(lineNumber)\n\n return (\n <CodeLine\n key={index}\n line={line}\n lineNumber={lineNumber}\n showLineNumbers={showLineNumbers}\n isHighlighted={isHighlighted}\n isDiffAdded={isDiffAdded}\n isDiffRemoved={isDiffRemoved}\n />\n )\n })}\n </div>\n )}\n </div>\n </div>\n )\n }\n)\n\nCodeBlock.displayName = \"CodeBlock\"\n\nexport { CodeBlock, codeBlockVariants }\n"
|
|
}
|
|
],
|
|
"type": "components:code"
|
|
} |