mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +00:00
110 lines
2.9 KiB
TypeScript
110 lines
2.9 KiB
TypeScript
'use client';
|
|
|
|
import { Button } from '../ui/button';
|
|
import { cn } from '../../src/lib/utils';
|
|
import { CheckIcon, CopyIcon } from 'lucide-react';
|
|
import { type ComponentProps, createContext, type HTMLAttributes, useContext, useState } from 'react';
|
|
|
|
type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
|
|
code: string;
|
|
language?: string;
|
|
showLineNumbers?: boolean;
|
|
};
|
|
|
|
type CodeBlockContextType = {
|
|
code: string;
|
|
};
|
|
|
|
const CodeBlockContext = createContext<CodeBlockContextType>({
|
|
code: '',
|
|
});
|
|
|
|
export const CodeBlock = ({
|
|
code,
|
|
language,
|
|
showLineNumbers = false,
|
|
className,
|
|
children,
|
|
...props
|
|
}: CodeBlockProps) => {
|
|
const lines = code.split('\n');
|
|
|
|
return (
|
|
<CodeBlockContext.Provider value={{ code }}>
|
|
<div
|
|
className={cn(
|
|
'group relative w-full overflow-hidden rounded-md border bg-background text-foreground',
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
<div className="relative">
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full border-collapse">
|
|
<tbody>
|
|
{lines.map((line, i) => (
|
|
<tr key={i} className="border-0">
|
|
{showLineNumbers && (
|
|
<td className="w-10 select-none pr-4 text-right align-top text-muted-foreground text-xs">
|
|
{i + 1}
|
|
</td>
|
|
)}
|
|
<td className="p-0">
|
|
<pre className="m-0 p-0 text-sm whitespace-pre font-mono">
|
|
<code className="text-sm">{line || '\u00A0'}</code>
|
|
</pre>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{children && <div className="absolute top-2 right-2 flex items-center gap-2">{children}</div>}
|
|
</div>
|
|
</div>
|
|
</CodeBlockContext.Provider>
|
|
);
|
|
};
|
|
|
|
export type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {
|
|
onCopy?: () => void;
|
|
onError?: (error: Error) => void;
|
|
timeout?: number;
|
|
};
|
|
|
|
export const CodeBlockCopyButton = ({
|
|
onCopy,
|
|
onError,
|
|
timeout = 2000,
|
|
children,
|
|
className,
|
|
...props
|
|
}: CodeBlockCopyButtonProps) => {
|
|
const [isCopied, setIsCopied] = useState(false);
|
|
const { code } = useContext(CodeBlockContext);
|
|
|
|
const copyToClipboard = async () => {
|
|
if (typeof window === 'undefined' || !navigator?.clipboard?.writeText) {
|
|
onError?.(new Error('Clipboard API not available'));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await navigator.clipboard.writeText(code);
|
|
setIsCopied(true);
|
|
onCopy?.();
|
|
setTimeout(() => setIsCopied(false), timeout);
|
|
} catch (error) {
|
|
onError?.(error as Error);
|
|
}
|
|
};
|
|
|
|
const Icon = isCopied ? CheckIcon : CopyIcon;
|
|
|
|
return (
|
|
<Button className={cn('shrink-0', className)} onClick={copyToClipboard} size="icon" variant="ghost" {...props}>
|
|
{children ?? <Icon size={14} />}
|
|
</Button>
|
|
);
|
|
};
|