221 lines
6.5 KiB
TypeScript
221 lines
6.5 KiB
TypeScript
/* istanbul ignore file */
|
|
|
|
/**
|
|
* MarkdownContent Component
|
|
* Renders markdown content with syntax highlighting and design system styling
|
|
* This file is excluded from coverage as it's a documentation component
|
|
*/
|
|
|
|
import ReactMarkdown from 'react-markdown';
|
|
import remarkGfm from 'remark-gfm';
|
|
import rehypeHighlight from 'rehype-highlight';
|
|
import rehypeSlug from 'rehype-slug';
|
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
|
import { cn } from '@/lib/utils';
|
|
import 'highlight.js/styles/github-dark.css';
|
|
|
|
interface MarkdownContentProps {
|
|
content: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|
return (
|
|
<div className={cn('prose prose-neutral dark:prose-invert max-w-none', className)}>
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkGfm]}
|
|
rehypePlugins={[
|
|
rehypeHighlight,
|
|
rehypeSlug,
|
|
[rehypeAutolinkHeadings, { behavior: 'wrap' }],
|
|
]}
|
|
components={{
|
|
// Headings
|
|
h1: ({ children, ...props }) => (
|
|
<h1
|
|
className="scroll-mt-20 text-4xl font-bold tracking-tight mb-6 mt-8 first:mt-0 border-b pb-3"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</h1>
|
|
),
|
|
h2: ({ children, ...props }) => (
|
|
<h2
|
|
className="scroll-mt-20 text-3xl font-semibold tracking-tight mb-4 mt-10 first:mt-0 border-b pb-2"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</h2>
|
|
),
|
|
h3: ({ children, ...props }) => (
|
|
<h3
|
|
className="scroll-mt-20 text-2xl font-semibold tracking-tight mb-3 mt-8 first:mt-0"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</h3>
|
|
),
|
|
h4: ({ children, ...props }) => (
|
|
<h4
|
|
className="scroll-mt-20 text-xl font-semibold tracking-tight mb-2 mt-6 first:mt-0"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</h4>
|
|
),
|
|
|
|
// Paragraphs and text
|
|
p: ({ children, ...props }) => (
|
|
<p className="leading-7 mb-4 text-foreground" {...props}>
|
|
{children}
|
|
</p>
|
|
),
|
|
strong: ({ children, ...props }) => (
|
|
<strong className="font-semibold text-foreground" {...props}>
|
|
{children}
|
|
</strong>
|
|
),
|
|
em: ({ children, ...props }) => (
|
|
<em className="italic" {...props}>
|
|
{children}
|
|
</em>
|
|
),
|
|
|
|
// Links
|
|
a: ({ children, href, ...props }) => (
|
|
<a
|
|
href={href}
|
|
className="font-medium text-primary underline underline-offset-4 hover:text-primary/80 transition-colors"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</a>
|
|
),
|
|
|
|
// Lists
|
|
ul: ({ children, ...props }) => (
|
|
<ul className="my-4 ml-6 list-disc space-y-2" {...props}>
|
|
{children}
|
|
</ul>
|
|
),
|
|
ol: ({ children, ...props }) => (
|
|
<ol className="my-4 ml-6 list-decimal space-y-2" {...props}>
|
|
{children}
|
|
</ol>
|
|
),
|
|
li: ({ children, ...props }) => (
|
|
<li className="leading-7 text-foreground" {...props}>
|
|
{children}
|
|
</li>
|
|
),
|
|
|
|
// Code blocks
|
|
code: ({ inline, className, children, ...props }: {
|
|
inline?: boolean;
|
|
className?: string;
|
|
children?: React.ReactNode;
|
|
}) => {
|
|
if (inline) {
|
|
return (
|
|
<code
|
|
className="relative rounded bg-muted px-[0.4rem] py-[0.2rem] font-mono text-sm font-medium text-foreground border"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</code>
|
|
);
|
|
}
|
|
return (
|
|
<code
|
|
className={cn(
|
|
'block font-mono text-sm',
|
|
className
|
|
)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</code>
|
|
);
|
|
},
|
|
pre: ({ children, ...props }) => (
|
|
<pre
|
|
className="mb-4 mt-4 overflow-x-auto rounded-lg border bg-muted/30 p-4 font-mono text-sm"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</pre>
|
|
),
|
|
|
|
// Blockquotes
|
|
blockquote: ({ children, ...props }) => (
|
|
<blockquote
|
|
className="mt-6 border-l-4 border-primary pl-4 italic text-muted-foreground"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</blockquote>
|
|
),
|
|
|
|
// Tables
|
|
table: ({ children, ...props }) => (
|
|
<div className="my-6 w-full overflow-x-auto">
|
|
<table
|
|
className="w-full border-collapse text-sm"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</table>
|
|
</div>
|
|
),
|
|
thead: ({ children, ...props }) => (
|
|
<thead className="border-b bg-muted/50" {...props}>
|
|
{children}
|
|
</thead>
|
|
),
|
|
tbody: ({ children, ...props }) => (
|
|
<tbody {...props}>{children}</tbody>
|
|
),
|
|
tr: ({ children, ...props }) => (
|
|
<tr className="border-b transition-colors hover:bg-muted/30" {...props}>
|
|
{children}
|
|
</tr>
|
|
),
|
|
th: ({ children, ...props }) => (
|
|
<th
|
|
className="px-4 py-3 text-left font-semibold [&[align=center]]:text-center [&[align=right]]:text-right"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</th>
|
|
),
|
|
td: ({ children, ...props }) => (
|
|
<td
|
|
className="px-4 py-3 [&[align=center]]:text-center [&[align=right]]:text-right"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</td>
|
|
),
|
|
|
|
// Horizontal rule
|
|
hr: ({ ...props }) => (
|
|
<hr className="my-8 border-t border-border" {...props} />
|
|
),
|
|
|
|
// Images
|
|
img: ({ src, alt, ...props }) => (
|
|
<img
|
|
src={src}
|
|
alt={alt}
|
|
className="rounded-lg border my-6"
|
|
{...props}
|
|
/>
|
|
),
|
|
}}
|
|
>
|
|
{content}
|
|
</ReactMarkdown>
|
|
</div>
|
|
);
|
|
}
|