Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions src/components/ui/typography/MarkdownRenderer/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use client'

import { FC, useState } from 'react'
import Icon from '@/components/ui/icon/Icon'

interface CodeBlockProps {
children?: React.ReactNode
className?: string
language?: string // Optional: specify the programming language
}

const CodeBlock: FC<CodeBlockProps> = ({ children, className, language }) => {
const [copied, setCopied] = useState(false)

const codeContent =
typeof children === 'string' ? children : String(children || '')
const langs = language || className?.replace(/language-/, '') || ''

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(codeContent)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch (err) {
console.error('Failed to copy:', err)
}
}

return (
<div className="border-border bg-background-secondary relative w-full overflow-hidden rounded-lg border">
{/* Header with language and copy button */}
<div className="bg-background-secondary/50 border-border flex items-center justify-between border-b px-4 py-3">
<span className="text-muted text-xs font-semibold uppercase">
{langs || 'code'}
</span>
<button
onClick={handleCopy}
className="hover:bg-background-secondary text-muted hover:text-primary flex items-center gap-2 rounded-md px-3 py-1 text-xs font-medium transition-all duration-200"
title="Copy code"
>
{copied ? (
<>
<Icon type="check" size="xs" />
<span>Copied!</span>
</>
) : (
<>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="size-4"
>
<path
d="M13.5 1.5H4.5C3.94772 1.5 3.5 1.94772 3.5 2.5V10.5C3.5 11.0523 3.94772 11.5 4.5 11.5H13.5C14.0523 11.5 14.5 11.0523 14.5 10.5V2.5C14.5 1.94772 14.0523 1.5 13.5 1.5Z"
stroke="currentColor"
strokeWidth="1"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.5 14.5H11.5C12.0523 14.5 12.5 14.0523 12.5 13.5V4"
stroke="currentColor"
strokeWidth="1"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span>Copy</span>
</>
)}
</button>
</div>

{/* Code content */}
<div className="overflow-x-auto">
<pre className="text-foreground p-4 text-sm leading-relaxed">
<code className="font-mono">{codeContent}</code>
</pre>
</div>
</div>
)
}

export default CodeBlock
30 changes: 23 additions & 7 deletions src/components/ui/typography/MarkdownRenderer/styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FC, useState } from 'react'
import Image from 'next/image'
import Link from 'next/link'
import { cn } from '@/lib/utils'
import CodeBlock from '@/components/ui/typography/MarkdownRenderer/CodeBlock'

import type {
UnorderedListProps,
Expand All @@ -27,7 +28,8 @@ import type {
TableBodyProps,
TableRowProps,
TableCellProps,
PreparedTextProps
PreparedTextProps,
PreProps
} from './types'

import { HEADING_SIZES } from '../Heading/constants'
Expand Down Expand Up @@ -116,14 +118,14 @@ const DeletedText = ({ className, ...props }: DeletedTextProps) => (

const HorizontalRule = ({ className, ...props }: HorizontalRuleProps) => (
<hr
className={cn(className, 'mx-auto w-48 border-b border-border')}
className={cn(className, 'border-border mx-auto w-48 border-b')}
{...filterProps(props)}
/>
)

const InlineCode: FC<PreparedTextProps> = ({ children }) => {
return (
<code className="relative whitespace-pre-wrap rounded-sm bg-background-secondary/50 p-1">
<code className="bg-background-secondary/50 relative whitespace-pre-wrap rounded-sm p-1">
{children}
</code>
)
Expand Down Expand Up @@ -183,7 +185,7 @@ const Img = ({ src, alt }: ImgProps) => {
return (
<div className="w-full max-w-xl">
{error ? (
<div className="flex h-40 flex-col items-center justify-center gap-2 rounded-md bg-secondary/50 text-muted">
<div className="bg-secondary/50 text-muted flex h-40 flex-col items-center justify-center gap-2 rounded-md">
<Paragraph className="text-primary">Image unavailable</Paragraph>
<Link
href={src}
Expand All @@ -209,7 +211,7 @@ const Img = ({ src, alt }: ImgProps) => {
}

const Table = ({ className, ...props }: TableProps) => (
<div className="w-full max-w-[560px] overflow-hidden rounded-md border border-border">
<div className="border-border w-full max-w-[560px] overflow-hidden rounded-md border">
<div className="w-full overflow-x-auto">
<table className={cn(className, 'w-full')} {...filterProps(props)} />
</div>
Expand All @@ -220,7 +222,7 @@ const TableHead = ({ className, ...props }: TableHeaderProps) => (
<thead
className={cn(
className,
'rounded-md border-b border-border bg-transparent p-2 text-left text-sm font-[600]'
'border-border rounded-md border-b bg-transparent p-2 text-left text-sm font-[600]'
)}
{...filterProps(props)}
/>
Expand All @@ -239,7 +241,7 @@ const TableBody = ({ className, ...props }: TableBodyProps) => (

const TableRow = ({ className, ...props }: TableRowProps) => (
<tr
className={cn(className, 'border-b border-border last:border-b-0')}
className={cn(className, 'border-border border-b last:border-b-0')}
{...filterProps(props)}
/>
)
Expand All @@ -250,6 +252,19 @@ const TableCell = ({ className, ...props }: TableCellProps) => (
{...filterProps(props)}
/>
)
const Pre: FC<PreProps> = ({ children }) => {
const codeElement =
children && Array.isArray(children) ? children[0] : children
const codeContent = codeElement?.props?.children || ''
const className = codeElement?.props?.className || ''
const language = className.replace(/language-/, '')

return (
<CodeBlock language={language} className={className}>
{codeContent}
</CodeBlock>
)
}

export const components = {
h1: Heading1,
Expand All @@ -272,6 +287,7 @@ export const components = {
a: AnchorLink,
img: Img,
p: Paragraph,
pre: Pre,
table: Table,
thead: TableHead,
th: TableHeadCell,
Expand Down
7 changes: 6 additions & 1 deletion src/components/ui/typography/MarkdownRenderer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ type TableCellProps = DetailedHTMLProps<
HTMLTableCellElement
>

type PreProps = {
children?: React.ReactNode
}

export type {
MarkdownRendererProps,
UnorderedListProps,
Expand All @@ -129,5 +133,6 @@ export type {
TableHeaderCellProps,
TableBodyProps,
TableRowProps,
TableCellProps
TableCellProps,
PreProps
}