129 lines
4.6 KiB
TypeScript
129 lines
4.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { CodeSnippet } from '@/lib/data';
|
|
import { Check, Copy, Download, FileCode, Terminal } from 'lucide-react';
|
|
|
|
interface CodeBlockProps {
|
|
snippets: CodeSnippet[];
|
|
}
|
|
|
|
export function CodeBlock({ snippets }: CodeBlockProps) {
|
|
const [activeTab, setActiveTab] = useState(0);
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
if (!snippets || snippets.length === 0) return null;
|
|
|
|
const currentSnippet = snippets[activeTab] || snippets[0];
|
|
|
|
const handleCopy = async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(currentSnippet.code);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
} catch (err) {
|
|
console.error('Copy error:', err);
|
|
}
|
|
};
|
|
|
|
const handleDownloadFile = () => {
|
|
const element = document.createElement('a');
|
|
const file = new Blob([currentSnippet.code], { type: 'text/plain;charset=utf-8' });
|
|
element.href = URL.createObjectURL(file);
|
|
element.download = currentSnippet.fileName.split('/').pop() || 'code.txt';
|
|
document.body.appendChild(element);
|
|
element.click();
|
|
document.body.removeChild(element);
|
|
};
|
|
|
|
const lines = currentSnippet.code.split('\n');
|
|
|
|
return (
|
|
<div className="w-full rounded-2xl bg-slate-950 border border-slate-800 shadow-2xl overflow-hidden my-6">
|
|
|
|
{/* Tab Navigation Header */}
|
|
<div className="flex flex-wrap items-center justify-between bg-slate-900/90 px-4 py-2.5 border-b border-slate-800 gap-3">
|
|
|
|
{/* File Tabs */}
|
|
<div className="flex items-center gap-1.5 overflow-x-auto py-1 scrollbar-none">
|
|
{snippets.map((snip, index) => (
|
|
<button
|
|
key={snip.fileName}
|
|
onClick={() => setActiveTab(index)}
|
|
className={`flex items-center gap-2 px-3.5 py-1.5 rounded-lg text-xs font-mono font-medium transition-all ${
|
|
activeTab === index
|
|
? 'bg-slate-800 text-white shadow-sm border border-slate-700/80'
|
|
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/40'
|
|
}`}
|
|
>
|
|
<FileCode className={`w-3.5 h-3.5 ${activeTab === index ? 'text-red-400' : 'text-slate-500'}`} />
|
|
<span>{snip.fileName}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={handleDownloadFile}
|
|
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-300 text-xs font-semibold transition-colors border border-slate-700/60"
|
|
title="Download File"
|
|
>
|
|
<Download className="w-3.5 h-3.5 text-slate-400" />
|
|
<span className="hidden sm:inline">Download</span>
|
|
</button>
|
|
|
|
<button
|
|
onClick={handleCopy}
|
|
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all border ${
|
|
copied
|
|
? 'bg-emerald-950/80 text-emerald-300 border-emerald-800/80'
|
|
: 'bg-red-600 hover:bg-red-500 text-white border-red-500 shadow-sm shadow-red-600/20'
|
|
}`}
|
|
>
|
|
{copied ? (
|
|
<>
|
|
<Check className="w-3.5 h-3.5 text-emerald-400" />
|
|
<span>Copied!</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Copy className="w-3.5 h-3.5" />
|
|
<span>Copy Code</span>
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{/* Snippet Description bar */}
|
|
{currentSnippet.description && (
|
|
<div className="px-4 py-2 bg-slate-900/50 border-b border-slate-800/60 text-xs text-slate-400 font-sans flex items-center gap-2">
|
|
<Terminal className="w-3.5 h-3.5 text-red-400 flex-shrink-0" />
|
|
<span>{currentSnippet.description}</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Code Display Area with Line Numbers */}
|
|
<div className="p-4 overflow-x-auto font-mono text-xs leading-relaxed max-h-[480px] bg-slate-950">
|
|
<table className="w-full border-collapse">
|
|
<tbody>
|
|
{lines.map((line, idx) => (
|
|
<tr key={idx} className="hover:bg-slate-900/40 transition-colors group">
|
|
<td className="w-10 select-none text-right pr-4 text-slate-600 group-hover:text-slate-500 font-mono text-[11px]">
|
|
{idx + 1}
|
|
</td>
|
|
<td className="whitespace-pre text-slate-200 pl-2">
|
|
{line}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
</div>
|
|
);
|
|
}
|