**Docs and Code Enhancements:** Add CodeBlock component with copy functionality and syntax highlighting. Introduce /docs page as the central hub for design system documentation. Update MarkdownContent to support improved heading styles, enhanced links, optimized images with Next.js Image, and upgraded table, blockquote, and list styling for better readability and usability.
This commit is contained in:
81
frontend/src/components/docs/CodeBlock.tsx
Normal file
81
frontend/src/components/docs/CodeBlock.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
/**
|
||||
* CodeBlock Component
|
||||
* Syntax-highlighted code block with copy functionality
|
||||
* This file is excluded from coverage as it's a documentation component
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Check, Copy } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface CodeBlockProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export function CodeBlock({ children, className, title }: CodeBlockProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async () => {
|
||||
const code = extractTextFromChildren(children);
|
||||
await navigator.clipboard.writeText(code);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="group relative my-6">
|
||||
{title && (
|
||||
<div className="flex items-center justify-between rounded-t-lg border border-b-0 bg-muted/50 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',
|
||||
title && 'rounded-t-none',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute right-2 top-2 h-8 w-8 opacity-0 transition-opacity 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" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function extractTextFromChildren(children: React.ReactNode): string {
|
||||
if (typeof children === 'string') {
|
||||
return children;
|
||||
}
|
||||
|
||||
if (Array.isArray(children)) {
|
||||
return children.map(extractTextFromChildren).join('');
|
||||
}
|
||||
|
||||
if (children && typeof children === 'object' && 'props' in children) {
|
||||
return extractTextFromChildren((children as { props: { children: React.ReactNode } }).props.children);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
@@ -6,13 +6,15 @@
|
||||
* This file is excluded from coverage as it's a documentation component
|
||||
*/
|
||||
|
||||
import Image from 'next/image';
|
||||
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 { CodeBlock } from './CodeBlock';
|
||||
import { cn } from '@/lib/utils';
|
||||
import 'highlight.js/styles/github-dark.css';
|
||||
import 'highlight.js/styles/atom-one-dark.css';
|
||||
|
||||
interface MarkdownContentProps {
|
||||
content: string;
|
||||
@@ -30,10 +32,10 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
[rehypeAutolinkHeadings, { behavior: 'wrap' }],
|
||||
]}
|
||||
components={{
|
||||
// Headings
|
||||
// Headings - improved spacing and visual hierarchy
|
||||
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"
|
||||
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"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -41,7 +43,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
),
|
||||
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"
|
||||
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"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -49,7 +51,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
),
|
||||
h3: ({ children, ...props }) => (
|
||||
<h3
|
||||
className="scroll-mt-20 text-2xl font-semibold tracking-tight mb-3 mt-8 first:mt-0"
|
||||
className="scroll-mt-20 text-2xl font-semibold tracking-tight mb-4 mt-10 first:mt-0 text-foreground"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -57,16 +59,16 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
),
|
||||
h4: ({ children, ...props }) => (
|
||||
<h4
|
||||
className="scroll-mt-20 text-xl font-semibold tracking-tight mb-2 mt-6 first:mt-0"
|
||||
className="scroll-mt-20 text-xl font-semibold tracking-tight mb-3 mt-8 first:mt-0 text-foreground"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</h4>
|
||||
),
|
||||
|
||||
// Paragraphs and text
|
||||
// Paragraphs and text - improved readability
|
||||
p: ({ children, ...props }) => (
|
||||
<p className="leading-7 mb-4 text-foreground" {...props}>
|
||||
<p className="leading-relaxed mb-6 text-foreground/90 text-base" {...props}>
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
@@ -76,40 +78,40 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
</strong>
|
||||
),
|
||||
em: ({ children, ...props }) => (
|
||||
<em className="italic" {...props}>
|
||||
<em className="italic text-foreground/80" {...props}>
|
||||
{children}
|
||||
</em>
|
||||
),
|
||||
|
||||
// Links
|
||||
// Links - more prominent with better hover state
|
||||
a: ({ children, href, ...props }) => (
|
||||
<a
|
||||
href={href}
|
||||
className="font-medium text-primary underline underline-offset-4 hover:text-primary/80 transition-colors"
|
||||
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>
|
||||
),
|
||||
|
||||
// Lists
|
||||
// Lists - improved spacing and hierarchy
|
||||
ul: ({ children, ...props }) => (
|
||||
<ul className="my-4 ml-6 list-disc space-y-2" {...props}>
|
||||
<ul className="my-6 ml-6 list-disc space-y-3 marker:text-primary/60" {...props}>
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
ol: ({ children, ...props }) => (
|
||||
<ol className="my-4 ml-6 list-decimal space-y-2" {...props}>
|
||||
<ol className="my-6 ml-6 list-decimal space-y-3 marker:text-primary/60 marker:font-semibold" {...props}>
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children, ...props }) => (
|
||||
<li className="leading-7 text-foreground" {...props}>
|
||||
<li className="leading-relaxed text-foreground/90 pl-2" {...props}>
|
||||
{children}
|
||||
</li>
|
||||
),
|
||||
|
||||
// Code blocks
|
||||
// Code blocks - enhanced with copy button and better styling
|
||||
code: ({ inline, className, children, ...props }: {
|
||||
inline?: boolean;
|
||||
className?: string;
|
||||
@@ -118,7 +120,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
if (inline) {
|
||||
return (
|
||||
<code
|
||||
className="relative rounded bg-muted px-[0.4rem] py-[0.2rem] font-mono text-sm font-medium text-foreground border"
|
||||
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}
|
||||
>
|
||||
{children}
|
||||
@@ -128,7 +130,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
return (
|
||||
<code
|
||||
className={cn(
|
||||
'block font-mono text-sm',
|
||||
'block font-mono text-sm leading-relaxed',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -138,27 +140,24 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
);
|
||||
},
|
||||
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}
|
||||
>
|
||||
<CodeBlock {...props}>
|
||||
{children}
|
||||
</pre>
|
||||
</CodeBlock>
|
||||
),
|
||||
|
||||
// Blockquotes
|
||||
// Blockquotes - enhanced callout styling
|
||||
blockquote: ({ children, ...props }) => (
|
||||
<blockquote
|
||||
className="mt-6 border-l-4 border-primary pl-4 italic text-muted-foreground"
|
||||
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>
|
||||
),
|
||||
|
||||
// Tables
|
||||
// Tables - improved styling with better borders and hover states
|
||||
table: ({ children, ...props }) => (
|
||||
<div className="my-6 w-full overflow-x-auto">
|
||||
<div className="my-8 w-full overflow-x-auto rounded-lg border">
|
||||
<table
|
||||
className="w-full border-collapse text-sm"
|
||||
{...props}
|
||||
@@ -168,21 +167,21 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
</div>
|
||||
),
|
||||
thead: ({ children, ...props }) => (
|
||||
<thead className="border-b bg-muted/50" {...props}>
|
||||
<thead className="bg-muted/80 border-b-2 border-border" {...props}>
|
||||
{children}
|
||||
</thead>
|
||||
),
|
||||
tbody: ({ children, ...props }) => (
|
||||
<tbody {...props}>{children}</tbody>
|
||||
<tbody className="divide-y divide-border" {...props}>{children}</tbody>
|
||||
),
|
||||
tr: ({ children, ...props }) => (
|
||||
<tr className="border-b transition-colors hover:bg-muted/30" {...props}>
|
||||
<tr className="transition-colors hover:bg-muted/40" {...props}>
|
||||
{children}
|
||||
</tr>
|
||||
),
|
||||
th: ({ children, ...props }) => (
|
||||
<th
|
||||
className="px-4 py-3 text-left font-semibold [&[align=center]]:text-center [&[align=right]]:text-right"
|
||||
className="px-5 py-3.5 text-left font-semibold text-foreground [&[align=center]]:text-center [&[align=right]]:text-right"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -190,27 +189,35 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
),
|
||||
td: ({ children, ...props }) => (
|
||||
<td
|
||||
className="px-4 py-3 [&[align=center]]:text-center [&[align=right]]:text-right"
|
||||
className="px-5 py-3.5 text-foreground/80 [&[align=center]]:text-center [&[align=right]]:text-right"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</td>
|
||||
),
|
||||
|
||||
// Horizontal rule
|
||||
// Horizontal rule - more prominent
|
||||
hr: ({ ...props }) => (
|
||||
<hr className="my-8 border-t border-border" {...props} />
|
||||
<hr className="my-12 border-t-2 border-border/50" {...props} />
|
||||
),
|
||||
|
||||
// Images
|
||||
img: ({ src, alt, ...props }) => (
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="rounded-lg border my-6"
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
// Images - optimized with Next.js Image component
|
||||
img: ({ src, alt }) => {
|
||||
if (!src || typeof src !== 'string') return null;
|
||||
|
||||
return (
|
||||
<div className="my-8 relative w-full overflow-hidden rounded-lg border shadow-md">
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt || ''}
|
||||
width={1200}
|
||||
height={675}
|
||||
className="w-full h-auto"
|
||||
style={{ objectFit: 'contain' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
|
||||
Reference in New Issue
Block a user