| 33 | } & React.HTMLProps<HTMLDivElement>; |
| 34 | |
| 35 | function CodeBlockCode({ |
| 36 | code, |
| 37 | language = "tsx", |
| 38 | theme = "github-light", |
| 39 | className, |
| 40 | ...props |
| 41 | }: CodeBlockCodeProps) { |
| 42 | const [highlightedHtml, setHighlightedHtml] = useState<string | null>(null); |
| 43 | |
| 44 | useEffect(() => { |
| 45 | async function highlight() { |
| 46 | if (!code) { |
| 47 | setHighlightedHtml("<pre><code></code></pre>"); |
| 48 | return; |
| 49 | } |
| 50 | |
| 51 | const html = await codeToHtml(code, { lang: language, theme }); |
| 52 | setHighlightedHtml(html); |
| 53 | } |
| 54 | highlight(); |
| 55 | }, [code, language, theme]); |
| 56 | |
| 57 | const classNames = cn( |
| 58 | "w-full overflow-x-auto text-[13px] [&>pre]:px-4 [&>pre]:py-4", |
| 59 | className, |
| 60 | ); |
| 61 | |
| 62 | // SSR fallback: render plain code if not hydrated yet |
| 63 | return highlightedHtml ? ( |
| 64 | <div |
| 65 | className={classNames} |
| 66 | dangerouslySetInnerHTML={{ __html: highlightedHtml }} |
| 67 | {...props} |
| 68 | /> |
| 69 | ) : ( |
| 70 | <div className={classNames} {...props}> |
| 71 | <pre> |
| 72 | <code>{code}</code> |
| 73 | </pre> |
| 74 | </div> |
| 75 | ); |
| 76 | } |
| 77 | |
| 78 | export type CodeBlockGroupProps = React.HTMLAttributes<HTMLDivElement>; |
| 79 | |