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:
Felipe Cardoso
2025-11-24 18:58:01 +01:00
parent d0b717a128
commit 372af25aaa
2 changed files with 105 additions and 58 deletions

View File

@@ -30,16 +30,18 @@ export function CodeBlock({ children, className, title }: CodeBlockProps) {
};
return (
<div className="group relative my-6">
<div className="group relative my-6 rounded-lg border bg-[#282c34] text-slate-50">
{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>
</div>
)}
<div className={cn('relative', title && 'rounded-t-none')}>
<pre
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',
className
)}
@@ -49,14 +51,14 @@ export function CodeBlock({ children, className, title }: CodeBlockProps) {
<Button
variant="ghost"
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}
aria-label="Copy code"
>
{copied ? (
<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>
</div>

View File

@@ -15,6 +15,18 @@ import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import { CodeBlock } from './CodeBlock';
import { cn } from '@/lib/utils';
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 {
content: string;
@@ -23,19 +35,35 @@ interface MarkdownContentProps {
export function MarkdownContent({ content, className }: MarkdownContentProps) {
return (
<div className={cn('prose prose-neutral dark:prose-invert max-w-none', className)}>
<div className={cn('max-w-none text-foreground', className)}>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[
rehypeHighlight,
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={{
// Headings - improved spacing and visual hierarchy
h1: ({ children, ...props }) => (
<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}
>
{children}
@@ -43,7 +71,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
),
h2: ({ children, ...props }) => (
<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}
>
{children}
@@ -51,7 +79,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
),
h3: ({ children, ...props }) => (
<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}
>
{children}
@@ -59,12 +87,28 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
),
h4: ({ children, ...props }) => (
<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}
>
{children}
</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
p: ({ children, ...props }) => (
@@ -84,15 +128,32 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
),
// Links - more prominent with better hover state
a: ({ children, href, ...props }) => (
<a
href={href}
className="font-medium text-primary underline decoration-primary/30 underline-offset-4 hover:decoration-primary/60 hover:text-primary/90 transition-all"
{...props}
>
{children}
</a>
),
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
href={href}
className={cn("opacity-0 group-hover:opacity-100 transition-opacity text-muted-foreground hover:text-primary ml-2 no-underline", className)}
{...props}
>
{children}
</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
ul: ({ children, ...props }) => (
@@ -127,12 +188,12 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
}) => {
if (inline) {
return (
<code
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"
{...props}
<Badge
variant="secondary"
className="font-mono text-sm font-medium px-1.5 py-0.5 h-auto rounded-md"
>
{children}
</code>
</Badge>
);
}
return (
@@ -143,58 +204,42 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
},
pre: ({ children, ...props }) => <CodeBlock {...props}>{children}</CodeBlock>,
// Blockquotes - enhanced callout styling
blockquote: ({ children, ...props }) => (
<blockquote
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"
{...props}
>
{children}
</blockquote>
// Blockquotes - enhanced callout styling using Alert
blockquote: ({ children }) => (
<Alert className="my-8 border-l-4 border-l-primary/50 bg-primary/5">
<Info className="h-4 w-4" />
<AlertDescription className="italic text-foreground/80 ml-2">
{children}
</AlertDescription>
</Alert>
),
// Tables - improved styling with better borders and hover states
table: ({ children, ...props }) => (
<div className="my-8 w-full overflow-x-auto rounded-lg border">
<table className="w-full border-collapse text-sm" {...props}>
{children}
</table>
<Table {...props}>{children}</Table>
</div>
),
thead: ({ children, ...props }) => (
<thead className="bg-muted/80 border-b-2 border-border" {...props}>
<TableHeader className="bg-muted/80" {...props}>
{children}
</thead>
),
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>
</TableHeader>
),
tbody: ({ children, ...props }) => <TableBody {...props}>{children}</TableBody>,
tr: ({ children, ...props }) => <TableRow {...props}>{children}</TableRow>,
th: ({ children, ...props }) => (
<th
className="px-5 py-3.5 text-left font-semibold text-foreground [&[align=center]]:text-center [&[align=right]]:text-right"
{...props}
>
<TableHead className="font-semibold text-foreground" {...props}>
{children}
</th>
</TableHead>
),
td: ({ children, ...props }) => (
<td
className="px-5 py-3.5 text-foreground/80 [&[align=center]]:text-center [&[align=right]]:text-right"
{...props}
>
<TableCell className="text-foreground/80" {...props}>
{children}
</td>
</TableCell>
),
// 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
img: ({ src, alt }) => {