| import React, { useState, useMemo } from "react"; |
| import { Check, Copy } from "lucide-react"; |
| import Prism from "prismjs"; |
|
|
| interface CodeBlockProps { |
| language: string; |
| value: string; |
| } |
|
|
| export const CodeBlock: React.FC<CodeBlockProps> = ({ language, value }) => { |
| const [copied, setCopied] = useState(false); |
|
|
| const handleCopy = async () => { |
| try { |
| await navigator.clipboard.writeText(value); |
| setCopied(true); |
| setTimeout(() => setCopied(false), 2000); |
| } catch (err) { |
| console.error("Failed to copy code block content: ", err); |
| } |
| }; |
|
|
| const highlightedHtml = useMemo(() => { |
| const lang = (language || "markup").toLowerCase(); |
| const grammar = Prism.languages[lang] || Prism.languages.markup; |
| try { |
| return Prism.highlight(value, grammar, lang); |
| } catch (e) { |
| return value; |
| } |
| }, [language, value]); |
|
|
| return ( |
| <div className="my-4 rounded-xl border border-slate-800 bg-[#09090B] overflow-hidden shadow-md font-sans"> |
| {/* Header bar */} |
| <div className="flex items-center justify-between px-4 py-1.5 bg-black/35 border-b border-white/[0.03] select-none"> |
| <span className="text-[10px] font-mono text-slate-400 uppercase tracking-wider font-bold"> |
| {language || "code"} |
| </span> |
| <button |
| type="button" |
| onClick={handleCopy} |
| className="text-[9px] text-slate-400 hover:text-white flex items-center gap-1 transition px-2 py-0.5 rounded hover:bg-slate-800 cursor-pointer font-bold font-mono" |
| > |
| {copied ? ( |
| <> |
| <Check className="h-3 w-3 text-emerald-450" /> |
| <span>COPIED</span> |
| </> |
| ) : ( |
| <> |
| <Copy className="h-3 w-3 text-blue-500" /> |
| <span>COPY CODE</span> |
| </> |
| )} |
| </button> |
| </div> |
| {} |
| <pre className="p-4 overflow-x-auto text-xs m-0 font-mono text-left scrollbar-thin"> |
| <code |
| className={`language-${language}`} |
| dangerouslySetInnerHTML={{ __html: highlightedHtml }} |
| /> |
| </pre> |
| </div> |
| ); |
| }; |
|
|