getcert-backend / src /components /CodeHighlight.tsx
ggop213's picture
feat: normalize course titles into professional canonical titles and remove emojis across app
d08bef8
Raw History Blame Contribute Delete
4.35 kB
import React, { useEffect, useRef } from 'react';
import { Check, Copy } from 'lucide-react';
import { logger } from '../utils/secureLogger';
interface CodeHighlightProps {
children: string;
language?: string;
className?: string;
}
export function CodeHighlight({ children, language = 'text', className = '' }: CodeHighlightProps) {
const codeRef = useRef<HTMLElement>(null);
const [copied, setCopied] = React.useState(false);
useEffect(() => {
if (codeRef.current && typeof window !== 'undefined') {
// Simple syntax highlighting for common languages
const code = codeRef.current;
const text = code.textContent || '';
if (language === 'javascript' || language === 'js') {
// Basic JS highlighting - adapted for light theme text contrast
code.innerHTML = text
.replace(/\b(const|let|var|function|return|if|else|for|while|class|import|export|from|default)\b/g, '<span class="text-[#5E6AD2] font-semibold">$1</span>')
.replace(/\b(true|false|null|undefined)\b/g, '<span class="text-amber-600 font-semibold">$1</span>')
.replace(/\b(console|log|error|warn)\b/g, '<span class="text-blue-600 font-semibold">$1</span>')
.replace(/"([^"]*)"/g, '<span class="text-emerald-600">"$1"</span>')
.replace(/'([^']*)'/g, '<span class="text-emerald-600">\'$1\'</span>')
.replace(/\b(\d+)\b/g, '<span class="text-teal-600">$1</span>');
} else if (language === 'python' || language === 'py') {
// Basic Python highlighting - adapted for light theme text contrast
code.innerHTML = text
.replace(/\b(def|class|import|from|return|if|else|elif|for|while|try|except|finally|with|as|in|is|None|True|False)\b/g, '<span class="text-[#5E6AD2] font-semibold">$1</span>')
.replace(/\b(print|len|str|int|float|list|dict|set)\b/g, '<span class="text-blue-600 font-semibold">$1</span>')
.replace(/"([^"]*)"/g, '<span class="text-emerald-600">"$1"</span>')
.replace(/'([^']*)'/g, '<span class="text-emerald-600">\'$1\'</span>')
.replace(/\b(\d+)\b/g, '<span class="text-teal-600">$1</span>');
} else if (language === 'html') {
// Basic HTML highlighting
code.innerHTML = text
.replace(/(&lt;[^&]*&gt;)/g, '<span class="text-blue-600">$1</span>')
.replace(/(&lt;\/[^&]*&gt;)/g, '<span class="text-blue-600">$1</span>')
.replace(/(\w+)=/g, '<span class="text-[#5E6AD2]">$1</span>=');
} else if (language === 'css') {
// Basic CSS highlighting
code.innerHTML = text
.replace(/([^{}]+)\s*{/g, '<span class="text-blue-600">$1</span> {')
.replace(/(\w+):/g, '<span class="text-[#5E6AD2]">$1</span>:')
.replace(/(#[0-9a-fA-F]{3,6})/g, '<span class="text-emerald-600">$1</span>');
}
}
}, [children, language]);
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(children);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
logger.error('Failed to copy text: ', err);
}
};
return (
<div className={`relative bg-slate-50 border border-slate-200 rounded overflow-hidden font-mono text-[11px] ${className}`}>
{/* Header */}
<div className="flex items-center justify-between px-4 py-2 bg-slate-100 border-b border-slate-200">
<span className="text-[10px] text-slate-400 uppercase font-bold tracking-widest">{language}</span>
<button
onClick={copyToClipboard}
className="inline-flex items-center gap-1 px-2 py-0.5 text-[10px] font-bold text-slate-500 hover:text-slate-900 transition-colors uppercase tracking-widest cursor-pointer"
title="Copy code"
>
{copied ? (
<>
<Check className="w-3 h-3 text-emerald-600" />
<span>Copied</span>
</>
) : (
<>
<Copy className="w-3 h-3" />
<span>Copy</span>
</>
)}
</button>
</div>
{/* Code */}
<pre className="p-4 m-0 overflow-x-auto">
<code
ref={codeRef}
className="text-slate-800 font-mono leading-relaxed"
>
{children}
</code>
</pre>
</div>
);
}