110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import ReactMarkdown from "react-markdown";
|
|
import rehypeRaw from "rehype-raw";
|
|
import remarkGfm from "remark-gfm";
|
|
|
|
interface MarkdownProps {
|
|
children: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function Markdown({ children, className = "" }: MarkdownProps) {
|
|
return (
|
|
<div className={`prose prose-sm dark:prose-invert max-w-none ${className}`}>
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkGfm]}
|
|
rehypePlugins={[rehypeRaw]}
|
|
components={{
|
|
// Style tables
|
|
table: ({ children }) => (
|
|
<div className="table-wrapper overflow-x-auto my-4">
|
|
<table className="min-w-full border-collapse text-sm">
|
|
{children}
|
|
</table>
|
|
</div>
|
|
),
|
|
thead: ({ children }) => (
|
|
<thead className="bg-[var(--secondary)]">{children}</thead>
|
|
),
|
|
th: ({ children }) => (
|
|
<th className="border border-[var(--border)] px-3 py-2 text-left font-semibold">
|
|
{children}
|
|
</th>
|
|
),
|
|
td: ({ children }) => (
|
|
<td className="border border-[var(--border)] px-3 py-2">
|
|
{children}
|
|
</td>
|
|
),
|
|
// Style inline code
|
|
code: ({ children, ...props }) => {
|
|
return (
|
|
<code
|
|
className="bg-[var(--secondary)] px-1.5 py-0.5 rounded text-sm font-mono"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</code>
|
|
);
|
|
},
|
|
// Style code blocks
|
|
pre: ({ children }) => {
|
|
return (
|
|
<pre className="bg-[var(--secondary)] p-4 rounded-lg overflow-x-auto text-sm">
|
|
{children}
|
|
</pre>
|
|
);
|
|
},
|
|
// Style unordered lists with spacing
|
|
ul: ({ children }) => {
|
|
return (
|
|
<ul className="list-disc list-inside space-y-1 mt-1 mb-1">
|
|
{children}
|
|
</ul>
|
|
);
|
|
},
|
|
// Style ordered lists with spacing
|
|
ol: ({ children }) => {
|
|
return (
|
|
<ol className="list-decimal list-inside space-y-1 mt-1 mb-1">
|
|
{children}
|
|
</ol>
|
|
);
|
|
},
|
|
// Style paragraphs - collapse spacer paragraphs
|
|
p: ({ children }) => {
|
|
// Check if this is just a spacer paragraph
|
|
const isNbsp =
|
|
children === "\u00A0" ||
|
|
(typeof children === "string" && children.trim() === "");
|
|
if (isNbsp) {
|
|
return <p className="mb-2" />;
|
|
}
|
|
return <p className="mb-3 last:mb-0">{children}</p>;
|
|
},
|
|
// Style bold text used as section headers
|
|
strong: ({ children }) => {
|
|
return <strong className="font-semibold">{children}</strong>;
|
|
},
|
|
// Style headings
|
|
h1: ({ children }) => (
|
|
<h1 className="text-xl font-bold mt-6 mb-3 first:mt-0">{children}</h1>
|
|
),
|
|
h2: ({ children }) => (
|
|
<h2 className="text-lg font-bold mt-5 mb-2 first:mt-0">{children}</h2>
|
|
),
|
|
h3: ({ children }) => (
|
|
<h3 className="text-base font-semibold mt-4 mb-2 first:mt-0">{children}</h3>
|
|
),
|
|
h4: ({ children }) => (
|
|
<h4 className="text-sm font-semibold mt-3 mb-1 first:mt-0">{children}</h4>
|
|
),
|
|
}}
|
|
>
|
|
{children}
|
|
</ReactMarkdown>
|
|
</div>
|
|
);
|
|
}
|