forked from cardosofelipe/fast-next-template
Refactor Markdown rendering and code blocks styling
- Enhanced Markdown heading hierarchy with subtle anchors and improved spacing. - Improved styling for links, blockquotes, tables, and horizontal rules using reusable components (`Alert`, `Badge`, `Table`, `Separator`). - Standardized code block background, button transitions, and copy-to-clipboard feedback. - Refined readability and visual hierarchy of text elements across Markdown content.
This commit is contained in:
@@ -30,16 +30,18 @@ export function CodeBlock({ children, className, title }: CodeBlockProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group relative my-6">
|
<div className="group relative my-6 rounded-lg border bg-[#282c34] text-slate-50">
|
||||||
{title && (
|
{title && (
|
||||||
<div className="flex items-center justify-between rounded-t-lg border border-b-0 bg-muted/50 px-4 py-2">
|
<div className="flex items-center justify-between rounded-t-lg border-b bg-muted/10 px-4 py-2">
|
||||||
<span className="text-xs font-medium text-muted-foreground">{title}</span>
|
<span className="text-xs font-medium text-muted-foreground">{title}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={cn('relative', title && 'rounded-t-none')}>
|
<div className={cn('relative', title && 'rounded-t-none')}>
|
||||||
<pre
|
<pre
|
||||||
className={cn(
|
className={cn(
|
||||||
'overflow-x-auto rounded-lg border bg-slate-950 p-4 font-mono text-sm',
|
'overflow-x-auto p-4 font-mono text-sm leading-relaxed',
|
||||||
|
// Force transparent background for hljs to avoid double background
|
||||||
|
'[&_.hljs]:!bg-transparent [&_code]:!bg-transparent',
|
||||||
title && 'rounded-t-none',
|
title && 'rounded-t-none',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
@@ -49,14 +51,14 @@ export function CodeBlock({ children, className, title }: CodeBlockProps) {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="absolute right-2 top-2 h-8 w-8 opacity-0 transition-opacity group-hover:opacity-100"
|
className="absolute right-2 top-2 h-8 w-8 text-muted-foreground opacity-0 transition-all hover:bg-muted/20 hover:text-foreground group-hover:opacity-100"
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
aria-label="Copy code"
|
aria-label="Copy code"
|
||||||
>
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
<Check className="h-4 w-4 text-green-500" />
|
<Check className="h-4 w-4 text-green-500" />
|
||||||
) : (
|
) : (
|
||||||
<Copy className="h-4 w-4 text-muted-foreground" />
|
<Copy className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,18 @@ import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
|||||||
import { CodeBlock } from './CodeBlock';
|
import { CodeBlock } from './CodeBlock';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import 'highlight.js/styles/atom-one-dark.css';
|
import 'highlight.js/styles/atom-one-dark.css';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/components/ui/table';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Info } from 'lucide-react';
|
||||||
|
|
||||||
interface MarkdownContentProps {
|
interface MarkdownContentProps {
|
||||||
content: string;
|
content: string;
|
||||||
@@ -23,19 +35,35 @@ interface MarkdownContentProps {
|
|||||||
|
|
||||||
export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||||
return (
|
return (
|
||||||
<div className={cn('prose prose-neutral dark:prose-invert max-w-none', className)}>
|
<div className={cn('max-w-none text-foreground', className)}>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
rehypePlugins={[
|
rehypePlugins={[
|
||||||
rehypeHighlight,
|
rehypeHighlight,
|
||||||
rehypeSlug,
|
rehypeSlug,
|
||||||
[rehypeAutolinkHeadings, { behavior: 'wrap' }],
|
[
|
||||||
|
rehypeAutolinkHeadings,
|
||||||
|
{
|
||||||
|
behavior: 'append',
|
||||||
|
properties: {
|
||||||
|
className: ['subtle-anchor'],
|
||||||
|
ariaHidden: true,
|
||||||
|
tabIndex: -1,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: 'element',
|
||||||
|
tagName: 'span',
|
||||||
|
properties: { className: ['icon', 'icon-link'] },
|
||||||
|
children: [{ type: 'text', value: '#' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
]}
|
]}
|
||||||
components={{
|
components={{
|
||||||
// Headings - improved spacing and visual hierarchy
|
// Headings - improved spacing and visual hierarchy
|
||||||
h1: ({ children, ...props }) => (
|
h1: ({ children, ...props }) => (
|
||||||
<h1
|
<h1
|
||||||
className="scroll-mt-20 text-4xl font-bold tracking-tight mb-8 mt-12 first:mt-0 border-b-2 border-primary/20 pb-4 text-foreground"
|
className="group scroll-mt-20 text-4xl font-bold tracking-tight mb-8 mt-12 first:mt-0 border-b-2 border-primary/20 pb-4 text-foreground flex items-center gap-2"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -43,7 +71,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|||||||
),
|
),
|
||||||
h2: ({ children, ...props }) => (
|
h2: ({ children, ...props }) => (
|
||||||
<h2
|
<h2
|
||||||
className="scroll-mt-20 text-3xl font-semibold tracking-tight mb-6 mt-12 first:mt-0 border-b border-border pb-3 text-foreground"
|
className="group scroll-mt-20 text-3xl font-semibold tracking-tight mb-6 mt-12 first:mt-0 border-b border-border pb-3 text-foreground flex items-center gap-2"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -51,7 +79,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|||||||
),
|
),
|
||||||
h3: ({ children, ...props }) => (
|
h3: ({ children, ...props }) => (
|
||||||
<h3
|
<h3
|
||||||
className="scroll-mt-20 text-2xl font-semibold tracking-tight mb-4 mt-10 first:mt-0 text-foreground"
|
className="group scroll-mt-20 text-2xl font-semibold tracking-tight mb-4 mt-10 first:mt-0 text-foreground flex items-center gap-2"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -59,12 +87,28 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|||||||
),
|
),
|
||||||
h4: ({ children, ...props }) => (
|
h4: ({ children, ...props }) => (
|
||||||
<h4
|
<h4
|
||||||
className="scroll-mt-20 text-xl font-semibold tracking-tight mb-3 mt-8 first:mt-0 text-foreground"
|
className="group scroll-mt-20 text-xl font-semibold tracking-tight mb-3 mt-8 first:mt-0 text-foreground flex items-center gap-2"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</h4>
|
</h4>
|
||||||
),
|
),
|
||||||
|
h5: ({ children, ...props }) => (
|
||||||
|
<h5
|
||||||
|
className="group scroll-mt-20 text-lg font-semibold tracking-tight mb-3 mt-8 first:mt-0 text-foreground flex items-center gap-2"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</h5>
|
||||||
|
),
|
||||||
|
h6: ({ children, ...props }) => (
|
||||||
|
<h6
|
||||||
|
className="group scroll-mt-20 text-base font-semibold tracking-tight mb-3 mt-8 first:mt-0 text-foreground flex items-center gap-2"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</h6>
|
||||||
|
),
|
||||||
|
|
||||||
// Paragraphs and text - improved readability
|
// Paragraphs and text - improved readability
|
||||||
p: ({ children, ...props }) => (
|
p: ({ children, ...props }) => (
|
||||||
@@ -84,15 +128,32 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Links - more prominent with better hover state
|
// Links - more prominent with better hover state
|
||||||
a: ({ children, href, ...props }) => (
|
a: ({ children, href, className, ...props }) => {
|
||||||
|
// Check if this is an anchor link generated by rehype-autolink-headings
|
||||||
|
const isAnchor = className?.includes('subtle-anchor');
|
||||||
|
|
||||||
|
if (isAnchor) {
|
||||||
|
return (
|
||||||
<a
|
<a
|
||||||
href={href}
|
href={href}
|
||||||
className="font-medium text-primary underline decoration-primary/30 underline-offset-4 hover:decoration-primary/60 hover:text-primary/90 transition-all"
|
className={cn("opacity-0 group-hover:opacity-100 transition-opacity text-muted-foreground hover:text-primary ml-2 no-underline", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
),
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
className={cn("font-medium text-primary underline decoration-primary/30 underline-offset-4 hover:decoration-primary/60 hover:text-primary/90 transition-all", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
// Lists - improved spacing and hierarchy
|
// Lists - improved spacing and hierarchy
|
||||||
ul: ({ children, ...props }) => (
|
ul: ({ children, ...props }) => (
|
||||||
@@ -127,12 +188,12 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|||||||
}) => {
|
}) => {
|
||||||
if (inline) {
|
if (inline) {
|
||||||
return (
|
return (
|
||||||
<code
|
<Badge
|
||||||
className="relative rounded-md bg-primary/10 border border-primary/20 px-1.5 py-0.5 font-mono text-sm font-medium text-primary"
|
variant="secondary"
|
||||||
{...props}
|
className="font-mono text-sm font-medium px-1.5 py-0.5 h-auto rounded-md"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</code>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -143,58 +204,42 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|||||||
},
|
},
|
||||||
pre: ({ children, ...props }) => <CodeBlock {...props}>{children}</CodeBlock>,
|
pre: ({ children, ...props }) => <CodeBlock {...props}>{children}</CodeBlock>,
|
||||||
|
|
||||||
// Blockquotes - enhanced callout styling
|
// Blockquotes - enhanced callout styling using Alert
|
||||||
blockquote: ({ children, ...props }) => (
|
blockquote: ({ children }) => (
|
||||||
<blockquote
|
<Alert className="my-8 border-l-4 border-l-primary/50 bg-primary/5">
|
||||||
className="my-8 border-l-4 border-primary/50 bg-primary/5 pl-6 pr-4 py-4 italic text-foreground/80 rounded-r-lg"
|
<Info className="h-4 w-4" />
|
||||||
{...props}
|
<AlertDescription className="italic text-foreground/80 ml-2">
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</blockquote>
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
),
|
),
|
||||||
|
|
||||||
// Tables - improved styling with better borders and hover states
|
// Tables - improved styling with better borders and hover states
|
||||||
table: ({ children, ...props }) => (
|
table: ({ children, ...props }) => (
|
||||||
<div className="my-8 w-full overflow-x-auto rounded-lg border">
|
<div className="my-8 w-full overflow-x-auto rounded-lg border">
|
||||||
<table className="w-full border-collapse text-sm" {...props}>
|
<Table {...props}>{children}</Table>
|
||||||
{children}
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
thead: ({ children, ...props }) => (
|
thead: ({ children, ...props }) => (
|
||||||
<thead className="bg-muted/80 border-b-2 border-border" {...props}>
|
<TableHeader className="bg-muted/80" {...props}>
|
||||||
{children}
|
{children}
|
||||||
</thead>
|
</TableHeader>
|
||||||
),
|
|
||||||
tbody: ({ children, ...props }) => (
|
|
||||||
<tbody className="divide-y divide-border" {...props}>
|
|
||||||
{children}
|
|
||||||
</tbody>
|
|
||||||
),
|
|
||||||
tr: ({ children, ...props }) => (
|
|
||||||
<tr className="transition-colors hover:bg-muted/40" {...props}>
|
|
||||||
{children}
|
|
||||||
</tr>
|
|
||||||
),
|
),
|
||||||
|
tbody: ({ children, ...props }) => <TableBody {...props}>{children}</TableBody>,
|
||||||
|
tr: ({ children, ...props }) => <TableRow {...props}>{children}</TableRow>,
|
||||||
th: ({ children, ...props }) => (
|
th: ({ children, ...props }) => (
|
||||||
<th
|
<TableHead className="font-semibold text-foreground" {...props}>
|
||||||
className="px-5 py-3.5 text-left font-semibold text-foreground [&[align=center]]:text-center [&[align=right]]:text-right"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</th>
|
</TableHead>
|
||||||
),
|
),
|
||||||
td: ({ children, ...props }) => (
|
td: ({ children, ...props }) => (
|
||||||
<td
|
<TableCell className="text-foreground/80" {...props}>
|
||||||
className="px-5 py-3.5 text-foreground/80 [&[align=center]]:text-center [&[align=right]]:text-right"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</td>
|
</TableCell>
|
||||||
),
|
),
|
||||||
|
|
||||||
// Horizontal rule - more prominent
|
// Horizontal rule - more prominent
|
||||||
hr: ({ ...props }) => <hr className="my-12 border-t-2 border-border/50" {...props} />,
|
hr: ({ ...props }) => <Separator className="my-12" {...props} />,
|
||||||
|
|
||||||
// Images - optimized with Next.js Image component
|
// Images - optimized with Next.js Image component
|
||||||
img: ({ src, alt }) => {
|
img: ({ src, alt }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user