mirror of
https://github.com/AndersPier/v0-v0app.git
synced 2025-10-27 18:16:53 +00:00
130 lines
5.7 KiB
TypeScript
130 lines
5.7 KiB
TypeScript
|
|
"use client"
|
||
|
|
|
||
|
|
import ReactMarkdown from "react-markdown"
|
||
|
|
import remarkGfm from "remark-gfm"
|
||
|
|
|
||
|
|
interface MarkdownPreviewProps {
|
||
|
|
content: string
|
||
|
|
}
|
||
|
|
|
||
|
|
export function MarkdownPreview({ content }: MarkdownPreviewProps) {
|
||
|
|
return (
|
||
|
|
<div className="flex-1 overflow-auto bg-gradient-to-br from-white to-blue-50/30">
|
||
|
|
<div className="p-6 prose prose-slate dark:prose-invert max-w-none">
|
||
|
|
<ReactMarkdown
|
||
|
|
remarkPlugins={[remarkGfm]}
|
||
|
|
components={{
|
||
|
|
h1: ({ children }) => (
|
||
|
|
<h1 className="text-3xl font-bold bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent mb-4">
|
||
|
|
{children}
|
||
|
|
</h1>
|
||
|
|
),
|
||
|
|
h2: ({ children }) => (
|
||
|
|
<h2 className="text-2xl font-semibold text-gray-800 mb-3 pb-2 border-b-2 border-gradient-to-r from-blue-200 to-purple-200">
|
||
|
|
{children}
|
||
|
|
</h2>
|
||
|
|
),
|
||
|
|
h3: ({ children }) => <h3 className="text-xl font-semibold text-gray-700 mb-2">{children}</h3>,
|
||
|
|
p: ({ children }) => <p className="text-gray-700 leading-relaxed mb-4">{children}</p>,
|
||
|
|
strong: ({ children }) => <strong className="font-bold text-blue-700">{children}</strong>,
|
||
|
|
em: ({ children }) => <em className="italic text-purple-600">{children}</em>,
|
||
|
|
code({ inline, className, children, ...props }) {
|
||
|
|
if (inline) {
|
||
|
|
return (
|
||
|
|
<code
|
||
|
|
className="bg-gradient-to-r from-pink-100 to-purple-100 text-purple-800 px-2 py-1 rounded-md text-sm font-mono border border-purple-200"
|
||
|
|
{...props}
|
||
|
|
>
|
||
|
|
{children}
|
||
|
|
</code>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
// block code
|
||
|
|
return (
|
||
|
|
<div className="relative my-6 rounded-xl overflow-hidden shadow-lg">
|
||
|
|
<div className="bg-gradient-to-r from-gray-800 to-gray-900 px-4 py-2 flex items-center gap-2">
|
||
|
|
<div className="flex gap-1">
|
||
|
|
<div className="w-3 h-3 rounded-full bg-red-400"></div>
|
||
|
|
<div className="w-3 h-3 rounded-full bg-yellow-400"></div>
|
||
|
|
<div className="w-3 h-3 rounded-full bg-green-400"></div>
|
||
|
|
</div>
|
||
|
|
<span className="text-gray-300 text-xs font-mono ml-2">code</span>
|
||
|
|
</div>
|
||
|
|
<pre className="bg-gradient-to-br from-gray-900 to-black p-4 overflow-x-auto text-sm text-gray-100 font-mono">
|
||
|
|
<code {...props}>{children}</code>
|
||
|
|
</pre>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
},
|
||
|
|
blockquote: ({ children }) => (
|
||
|
|
<blockquote className="border-l-4 border-gradient-to-b from-blue-400 to-purple-400 bg-gradient-to-r from-blue-50 to-purple-50 pl-4 py-2 my-4 rounded-r-lg">
|
||
|
|
{children}
|
||
|
|
</blockquote>
|
||
|
|
),
|
||
|
|
ul: ({ children }) => <ul className="list-none space-y-2 my-4">{children}</ul>,
|
||
|
|
ol: ({ children }) => <ol className="list-none space-y-2 my-4 counter-reset-list">{children}</ol>,
|
||
|
|
li: ({ children, ...props }) => {
|
||
|
|
const isOrdered = props.node?.parent?.tagName === "ol"
|
||
|
|
return (
|
||
|
|
<li className={`flex items-start gap-3 ${isOrdered ? "counter-increment-list" : ""}`}>
|
||
|
|
{isOrdered ? (
|
||
|
|
<span className="flex-shrink-0 w-6 h-6 bg-gradient-to-r from-blue-500 to-purple-500 text-white text-xs font-bold rounded-full flex items-center justify-center counter-content">
|
||
|
|
{/* Counter will be handled by CSS */}
|
||
|
|
</span>
|
||
|
|
) : (
|
||
|
|
<span className="flex-shrink-0 w-2 h-2 bg-gradient-to-r from-pink-400 to-purple-400 rounded-full mt-2"></span>
|
||
|
|
)}
|
||
|
|
<div className="flex-1">{children}</div>
|
||
|
|
</li>
|
||
|
|
)
|
||
|
|
},
|
||
|
|
a: ({ children, href }) => (
|
||
|
|
<a
|
||
|
|
href={href}
|
||
|
|
className="text-blue-600 hover:text-purple-600 underline decoration-2 underline-offset-2 hover:decoration-purple-400 transition-colors duration-200"
|
||
|
|
target="_blank"
|
||
|
|
rel="noopener noreferrer"
|
||
|
|
>
|
||
|
|
{children}
|
||
|
|
</a>
|
||
|
|
),
|
||
|
|
table({ children }) {
|
||
|
|
return (
|
||
|
|
<div className="overflow-x-auto my-6 rounded-lg shadow-lg">
|
||
|
|
<table className="min-w-full border-collapse bg-white">{children}</table>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
},
|
||
|
|
th({ children }) {
|
||
|
|
return (
|
||
|
|
<th className="border border-gray-200 px-4 py-3 bg-gradient-to-r from-blue-50 to-purple-50 font-semibold text-left text-gray-800">
|
||
|
|
{children}
|
||
|
|
</th>
|
||
|
|
)
|
||
|
|
},
|
||
|
|
td({ children }) {
|
||
|
|
return (
|
||
|
|
<td className="border border-gray-200 px-4 py-3 text-gray-700 hover:bg-gray-50 transition-colors duration-150">
|
||
|
|
{children}
|
||
|
|
</td>
|
||
|
|
)
|
||
|
|
},
|
||
|
|
hr: () => (
|
||
|
|
<hr className="my-8 border-0 h-1 bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 rounded-full" />
|
||
|
|
),
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{content || (
|
||
|
|
<div className="text-center py-12 text-gray-400">
|
||
|
|
<div className="text-4xl mb-4">✨</div>
|
||
|
|
<p className="text-lg">Start typing to see the magic happen...</p>
|
||
|
|
<p className="text-sm mt-2">Your markdown will appear here in real-time!</p>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</ReactMarkdown>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|