bolt.diy/app/shared/components/ui/CodeBlock.tsx
KevIsDev 4d3222ee96 refactor: reorganize project structure by moving files to a more dev friendly setup
- Move stores/utils/types to their relative directories (i.e chat stores in chat directory)
- Move utility files to shared/utils
- Move component files to shared/components
- Move type definitions to shared/types
- Move stores to shared/stores
- Update import paths across the project
2025-06-16 15:33:59 +01:00

104 lines
3.9 KiB
TypeScript

import React, { useState } from 'react';
import { classNames } from '~/shared/utils/classNames';
import { motion } from 'framer-motion';
import { FileIcon } from './FileIcon';
import { Tooltip } from './Tooltip';
interface CodeBlockProps {
code: string;
language?: string;
filename?: string;
showLineNumbers?: boolean;
highlightLines?: number[];
maxHeight?: string;
className?: string;
onCopy?: () => void;
}
export function CodeBlock({
code,
language,
filename,
showLineNumbers = true,
highlightLines = [],
maxHeight = '400px',
className,
onCopy,
}: CodeBlockProps) {
const [copied, setCopied] = useState(false);
const handleCopy = () => {
navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
onCopy?.();
};
const lines = code.split('\n');
return (
<div
className={classNames(
'rounded-lg overflow-hidden border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor-dark',
'bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3',
className,
)}
>
{/* Header */}
<div className="flex items-center justify-between px-4 py-2 bg-bolt-elements-background-depth-3 dark:bg-bolt-elements-background-depth-4 border-b border-bolt-elements-borderColor dark:border-bolt-elements-borderColor-dark">
<div className="flex items-center gap-2">
{filename && (
<>
<FileIcon filename={filename} size="sm" />
<span className="text-xs font-medium text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark">
{filename}
</span>
</>
)}
{language && !filename && (
<span className="text-xs font-medium text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark uppercase">
{language}
</span>
)}
</div>
<Tooltip content={copied ? 'Copied!' : 'Copy code'}>
<motion.button
onClick={handleCopy}
className="p-1.5 rounded-md text-bolt-elements-textTertiary hover:text-bolt-elements-textSecondary dark:text-bolt-elements-textTertiary-dark dark:hover:text-bolt-elements-textSecondary-dark hover:bg-bolt-elements-background-depth-2 dark:hover:bg-bolt-elements-background-depth-3 transition-colors"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
{copied ? <span className="i-ph:check w-4 h-4 text-green-500" /> : <span className="i-ph:copy w-4 h-4" />}
</motion.button>
</Tooltip>
</div>
{/* Code content */}
<div className={classNames('overflow-auto', 'font-mono text-sm', 'custom-scrollbar')} style={{ maxHeight }}>
<table className="min-w-full border-collapse">
<tbody>
{lines.map((line, index) => (
<tr
key={index}
className={classNames(
highlightLines.includes(index + 1) ? 'bg-purple-500/10 dark:bg-purple-500/20' : '',
'hover:bg-bolt-elements-background-depth-3 dark:hover:bg-bolt-elements-background-depth-4',
)}
>
{showLineNumbers && (
<td className="py-1 pl-4 pr-2 text-right select-none text-bolt-elements-textTertiary dark:text-bolt-elements-textTertiary-dark border-r border-bolt-elements-borderColor dark:border-bolt-elements-borderColor-dark">
<span className="inline-block min-w-[1.5rem] text-xs">{index + 1}</span>
</td>
)}
<td className="py-1 pl-4 pr-4 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark whitespace-pre">
{line || ' '}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}