tercul-frontend/client/src/components/ui/typography/code-block.tsx
mukimovd 66dd28e128 Implement core text formatting components enhancing content presentation
Adds typography components (Heading, Paragraph, BlockQuote, CodeBlock, Prose) with variants and updates COMPONENT-IMPLEMENTATION-TRACKER.md.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: bddfbb2b-6d6b-457b-b18c-05792cdaa035
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/39b5c689-6e8a-4d5a-9792-69cc81a56534/d046f5ac-7f62-419b-8fdb-893187a139f2.jpg
2025-05-10 21:10:41 +00:00

157 lines
3.9 KiB
TypeScript

import { forwardRef } from "react";
import { cn } from "@/lib/utils";
import { cva, type VariantProps } from "class-variance-authority";
/**
* Code Block component for displaying code snippets and inline code
*
* @example
* ```tsx
* <CodeBlock>const greeting = "Hello, world!";</CodeBlock>
*
* <CodeBlock variant="inline">const x = 10;</CodeBlock>
*
* <CodeBlock language="python" showLineNumbers>
* def hello_world():
* print("Hello, world!")
* </CodeBlock>
* ```
*/
const codeBlockVariants = cva(
"font-mono text-sm rounded-md",
{
variants: {
variant: {
default: "bg-muted text-foreground p-4 overflow-x-auto",
inline: "bg-muted px-1.5 py-0.5 text-primary-foreground",
terminal: "bg-black text-white p-4",
},
language: {
none: "",
js: "",
ts: "",
jsx: "",
tsx: "",
html: "",
css: "",
python: "",
ruby: "",
java: "",
csharp: "",
go: "",
rust: "",
php: "",
swift: "",
shell: "",
sql: "",
markdown: "",
json: "",
yaml: "",
},
},
defaultVariants: {
variant: "default",
language: "none",
},
}
);
export interface CodeBlockProps
extends React.HTMLAttributes<HTMLPreElement>,
VariantProps<typeof codeBlockVariants> {
/**
* Whether to show line numbers
*/
showLineNumbers?: boolean;
/**
* Whether this is a single line of code (uses inline styling)
*/
inline?: boolean;
/**
* Optional title for the code block
*/
title?: string;
}
const CodeBlock = forwardRef<HTMLPreElement, CodeBlockProps>(
({
className,
variant = "default",
language,
showLineNumbers = false,
inline = false,
title,
children,
...props
}, ref) => {
// If inline is true, override variant to "inline"
const actualVariant = inline ? "inline" : variant;
// If inline, render as inline code element
if (inline) {
return (
<code
className={cn(codeBlockVariants({ variant: actualVariant, language, className }))}
{...props}
>
{children}
</code>
);
}
// Process code content for line numbers if needed
let codeContent = children;
if (showLineNumbers && typeof children === 'string') {
const lines = children.toString().trim().split('\n');
const lineCount = lines.length;
const lineNumberWidth = lineCount.toString().length;
codeContent = (
<div className="flex">
<div className="flex-none pr-4 text-muted-foreground select-none border-r border-muted-foreground/20 mr-4">
{lines.map((_, idx) => (
<div key={idx} className="text-right">
{(idx + 1).toString().padStart(lineNumberWidth, ' ')}
</div>
))}
</div>
<div className="flex-1">
{lines.map((line, idx) => (
<div key={idx}>{line || ' '}</div>
))}
</div>
</div>
);
}
return (
<div className="relative my-6 rounded-md overflow-hidden">
{title && (
<div className="bg-muted-foreground/10 text-muted-foreground px-4 py-1.5 text-sm font-medium border-b border-border">
{title}
</div>
)}
<pre
className={cn(
codeBlockVariants({ variant: actualVariant, language, className }),
showLineNumbers && "pl-0"
)}
ref={ref}
{...props}
>
{language && (
<div className="absolute right-2 top-2 bg-muted-foreground/20 text-muted-foreground px-2 py-0.5 rounded text-xs uppercase">
{language}
</div>
)}
{codeContent}
</pre>
</div>
);
}
);
CodeBlock.displayName = "CodeBlock";
export { CodeBlock, codeBlockVariants };