'use client' import ReactMarkdown from 'react-markdown' import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism' import remarkGfm from 'remark-gfm' import React, { useState } from 'react' import { Copy, Check } from 'lucide-react' import { cn } from '../../utils/cn' /** * Themed markdown renderer for blog/article content. * * Uses shared design tokens (`text-foreground`, `border-border`, `bg-muted`, * `text-primary`, …) so it adopts each app's theme automatically. Decoupled from * `next-themes`: pass `theme="dark"` to switch the code-block syntax style (apps * using next-themes can forward `useTheme().resolvedTheme`). */ export interface MarkdownRendererProps { content: string /** Syntax-highlighting style for fenced code blocks. Default `"light"`. */ theme?: 'light' | 'dark' className?: string } function slugify(text: string): string { return text .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/(^-|-$)/g, '') } export function MarkdownRenderer({ content, theme = 'light', className }: MarkdownRendererProps) { const [copiedCode, setCopiedCode] = useState(null) const copyToClipboard = async (code: string) => { await navigator.clipboard.writeText(code) setCopiedCode(code) setTimeout(() => setCopiedCode(null), 2000) } return (
. h1: ({ children, ...props }) => (

{children}

), h2: ({ children, ...props }) => (

{children}

), h3: ({ children, ...props }) => (

{children}

), h4: ({ children, ...props }) => (

{children}

), a: ({ children, ...props }) => ( {children} ), code: (codeProps) => { const { className: codeClassName, children, ...props } = codeProps as { className?: string children?: React.ReactNode } const match = /language-(\w+)/.exec(codeClassName || '') const language = match ? match[1] : '' const codeString = String(children).replace(/\n$/, '') if (language) { const syntaxStyle = theme === 'dark' ? oneDark : oneLight return (
{language}
} language={language} PreTag="div" customStyle={{ margin: 0, padding: '1rem', background: 'transparent', fontSize: '0.875rem', lineHeight: '1.5' }} codeTagProps={{ style: { background: 'transparent', fontFamily: "ui-monospace, SFMono-Regular, 'SF Mono', Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace", }, }} > {codeString}
) } return ( {children} ) }, table: ({ children, ...props }) => (
{children}
), thead: ({ children, ...props }) => ( {children} ), th: ({ children, ...props }) => ( {children} ), td: ({ children, ...props }) => ( {children} ), tr: ({ children, ...props }) => ( {children} ), p: ({ children, ...props }) => (

{children}

), strong: ({ children, ...props }) => ( {children} ), em: ({ children, ...props }) => ( {children} ), ul: ({ children, ...props }) => (
    {children}
), ol: ({ children, ...props }) => (
    {children}
), li: ({ children, ...props }) => (
  • {children}
  • ), hr: ({ ...props }) =>
    , blockquote: ({ children, ...props }) => (
    {children}
    ), }} > {content}
    ) }