84 lines
3.0 KiB
TypeScript
84 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import ReactMarkdown from "react-markdown";
|
|
import remarkGfm from "remark-gfm";
|
|
import type { Components } from "react-markdown";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogTitle,
|
|
DialogTrigger
|
|
} from "@/components/ui/dialog";
|
|
|
|
type ProjectIntroDialogProps = {
|
|
title: string;
|
|
intro?: string;
|
|
};
|
|
|
|
const markdownComponents: Components = {
|
|
a: ({ href, children }) => (
|
|
<a href={href} target="_blank" rel="noopener noreferrer">
|
|
{children}
|
|
</a>
|
|
),
|
|
img: ({ src, alt }) => (
|
|
<img
|
|
src={typeof src === "string" ? src : undefined}
|
|
alt={alt ?? ""}
|
|
className="my-3 h-auto w-full rounded-lg border border-border"
|
|
/>
|
|
),
|
|
table: ({ children }) => (
|
|
<div className="mb-3 overflow-x-auto">
|
|
<table className="w-full border-collapse text-xs">{children}</table>
|
|
</div>
|
|
),
|
|
th: ({ children }) => (
|
|
<th className="border border-border bg-muted/50 px-2 py-1.5 text-left font-medium text-foreground">
|
|
{children}
|
|
</th>
|
|
),
|
|
td: ({ children }) => (
|
|
<td className="border border-border px-2 py-1.5">{children}</td>
|
|
)
|
|
};
|
|
|
|
/** 可选的项目介绍:shadcn Button 打开 Dialog,正文按 Markdown 渲染 */
|
|
export const ProjectIntroDialog = ({
|
|
title,
|
|
intro = ""
|
|
}: ProjectIntroDialogProps) => {
|
|
const body = intro.trim() || "暂无介绍";
|
|
|
|
return (
|
|
<Dialog>
|
|
<DialogTrigger
|
|
render={
|
|
<Button
|
|
variant="outline"
|
|
className="h-auto rounded-full border-border px-5 py-2.5 text-xs font-medium uppercase tracking-[0.15em]"
|
|
/>
|
|
}
|
|
>
|
|
Intro
|
|
</DialogTrigger>
|
|
<DialogContent className="max-w-2xl p-6">
|
|
<DialogTitle>{title}</DialogTitle>
|
|
<DialogDescription className="sr-only">{title} 项目介绍</DialogDescription>
|
|
<div
|
|
className="mt-3 max-h-[70vh] overflow-y-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden text-sm leading-relaxed text-muted-foreground [&_a]:text-foreground [&_a]:underline [&_a]:underline-offset-4 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-xs [&_h1]:mb-2 [&_h1]:text-base [&_h1]:font-medium [&_h1]:text-foreground [&_h2]:mt-5 [&_h2]:mb-2 [&_h2]:text-sm [&_h2]:font-medium [&_h2]:text-foreground [&_h3]:mt-4 [&_h3]:mb-2 [&_h3]:text-sm [&_h3]:font-medium [&_h3]:text-foreground [&_ol]:mb-3 [&_ol]:list-decimal [&_ol]:space-y-1 [&_ol]:pl-5 [&_p]:mb-3 [&_p]:last:mb-0 [&_pre]:mb-3 [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:bg-muted [&_pre]:p-3 [&_strong]:font-medium [&_strong]:text-foreground [&_ul]:mb-3 [&_ul]:list-disc [&_ul]:space-y-1 [&_ul]:pl-5"
|
|
>
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkGfm]}
|
|
components={markdownComponents}
|
|
>
|
|
{body}
|
|
</ReactMarkdown>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
};
|