"use client"; /** * Code Snippet Field * * Displays code with Shiki syntax highlighting and copy functionality. * Supports light/dark themes automatically. */ import { Check, Copy } from "lucide-react"; import { useTheme } from "next-themes"; import { useCallback, useEffect, useState } from "react"; import { codeToHtml } from "shiki"; import { ANIMATION_CLASSES, getStaggerStyle, STAGGER_PRESETS, } from "../animations"; import { cx } from "../lib/utils"; import type { CodeSnippetFieldProps } from "../types/fields"; export function CodeSnippetField({ title, description, code, language = "typescript", showLineNumbers = false, showCopyButton = true, copyButtonText = "Copy", copiedText = "Copied!", animationIndex = 0, }: CodeSnippetFieldProps) { const { resolvedTheme } = useTheme(); const [highlightedCode, setHighlightedCode] = useState(""); const [copied, setCopied] = useState(false); const [mounted, setMounted] = useState(false); // Handle mounting to avoid hydration mismatch useEffect(() => { setMounted(true); }, []); // Highlight code when code, language, or theme changes useEffect(() => { if (!mounted) return; const highlight = async () => { try { const html = await codeToHtml(code, { lang: language, theme: resolvedTheme === "dark" ? "github-dark" : "github-light", }); setHighlightedCode(html); } catch { // Fallback for unsupported languages setHighlightedCode(`
${escapeHtml(code)}
`); } }; highlight(); }, [code, language, resolvedTheme, mounted]); const handleCopy = useCallback(async () => { try { await navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error("Failed to copy:", err); } }, [code]); return (
{title && (

{title}

)} {description && (

{description}

)}
{mounted && highlightedCode ? (
) : (
							{code}
						
)}
{showCopyButton && ( )}
); } // Helper to escape HTML for fallback function escapeHtml(text: string): string { return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }