Spaces:
Running
Running
"use client"; | |
import React from "react"; | |
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; | |
import { atomDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; | |
import { IconCheck, IconCopy } from "@tabler/icons-react"; | |
type CodeBlockProps = { | |
language: string; | |
filename: string; | |
highlightLines?: number[]; | |
} & ( | |
| { | |
code: string; | |
tabs?: never; | |
} | |
| { | |
code?: never; | |
tabs: Array<{ | |
name: string; | |
code: string; | |
language?: string; | |
highlightLines?: number[]; | |
}>; | |
} | |
); | |
export const CodeBlock = ({ | |
language, | |
filename, | |
code, | |
highlightLines = [], | |
tabs = [], | |
}: CodeBlockProps) => { | |
const [copied, setCopied] = React.useState(false); | |
const [activeTab, setActiveTab] = React.useState(0); | |
const tabsExist = tabs.length > 0; | |
const copyToClipboard = async () => { | |
const textToCopy = tabsExist ? tabs[activeTab].code : code; | |
if (textToCopy) { | |
await navigator.clipboard.writeText(textToCopy); | |
setCopied(true); | |
setTimeout(() => setCopied(false), 2000); | |
} | |
}; | |
const activeCode = tabsExist ? tabs[activeTab].code : code; | |
const activeLanguage = tabsExist | |
? tabs[activeTab].language || language | |
: language; | |
const activeHighlightLines = tabsExist | |
? tabs[activeTab].highlightLines || [] | |
: highlightLines; | |
return ( | |
<div className="relative w-full rounded-lg bg-slate-900 p-4 font-mono text-sm"> | |
<div className="flex flex-col gap-2"> | |
{tabsExist && ( | |
<div className="flex overflow-x-auto"> | |
{tabs.map((tab, index) => ( | |
<button | |
key={index} | |
onClick={() => setActiveTab(index)} | |
className={`px-3 !py-2 text-xs transition-colors font-sans ${ | |
activeTab === index | |
? "text-white" | |
: "text-zinc-400 hover:text-zinc-200" | |
}`} | |
> | |
{tab.name} | |
</button> | |
))} | |
</div> | |
)} | |
{!tabsExist && filename && ( | |
<div className="flex justify-between items-center py-2"> | |
<div className="text-xs text-zinc-400">{filename}</div> | |
<button | |
onClick={copyToClipboard} | |
className="flex items-center gap-1 text-xs text-zinc-400 hover:text-zinc-200 transition-colors font-sans" | |
> | |
{copied ? <IconCheck size={14} /> : <IconCopy size={14} />} | |
</button> | |
</div> | |
)} | |
</div> | |
<SyntaxHighlighter | |
language={activeLanguage} | |
style={atomDark} | |
customStyle={{ | |
margin: 0, | |
padding: 0, | |
background: "transparent", | |
fontSize: "0.875rem", // text-sm equivalent | |
}} | |
wrapLines={true} | |
showLineNumbers={true} | |
lineProps={(lineNumber) => ({ | |
style: { | |
backgroundColor: activeHighlightLines.includes(lineNumber) | |
? "rgba(255,255,255,0.1)" | |
: "transparent", | |
display: "block", | |
width: "100%", | |
}, | |
})} | |
PreTag="div" | |
> | |
{String(activeCode)} | |
</SyntaxHighlighter> | |
</div> | |
); | |
}; | |