import { cn } from '@/lib/utils'; import React, { lazy, Suspense, useCallback, useEffect } from 'react'; import { Spinner } from '../spinner/spinner'; import { useTheme } from '@/hooks/use-theme'; import { useMonaco } from '@monaco-editor/react'; import { useToast } from '@/components/toast/use-toast'; import { Button } from '../button/button'; import type { LucideIcon } from 'lucide-react'; import { Copy, CopyCheck } from 'lucide-react'; import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip'; import { useTranslation } from 'react-i18next'; import { DarkTheme } from './themes/dark'; import { LightTheme } from './themes/light'; import './config.ts'; export const Editor = lazy(() => import('./code-editor').then((module) => ({ default: module.Editor, })) ); export const DiffEditor = lazy(() => import('./code-editor').then((module) => ({ default: module.DiffEditor, })) ); type EditorType = typeof Editor; export interface CodeSnippetAction { label: string; icon: LucideIcon; onClick: () => void; className?: string; } export interface CodeSnippetProps { className?: string; code: string; codeToCopy?: string; language?: 'sql' | 'shell' | 'dbml'; loading?: boolean; autoScroll?: boolean; isComplete?: boolean; editorProps?: React.ComponentProps; actions?: CodeSnippetAction[]; actionsTooltipSide?: 'top' | 'right' | 'bottom' | 'left'; allowCopy?: boolean; } export const CodeSnippet: React.FC = React.memo( ({ className, code, codeToCopy, loading, language = 'sql', autoScroll = false, isComplete = true, editorProps, actions, actionsTooltipSide, allowCopy = true, }) => { const { t } = useTranslation(); const monaco = useMonaco(); const { effectiveTheme } = useTheme(); const { toast } = useToast(); const [isCopied, setIsCopied] = React.useState(false); const [tooltipOpen, setTooltipOpen] = React.useState(false); useEffect(() => { monaco?.editor?.defineTheme?.( effectiveTheme, effectiveTheme === 'dark' ? DarkTheme : LightTheme ); monaco?.editor?.setTheme?.(effectiveTheme); }, [monaco, effectiveTheme]); useEffect(() => { if (!isCopied) return; setTimeout(() => { setIsCopied(false); }, 1500); }, [isCopied]); useEffect(() => { if (monaco) { const editor = monaco.editor.getModels()[0]; if (editor && autoScroll) { const lineCount = editor.getLineCount(); monaco.editor.getEditors()[0]?.revealLine(lineCount); } } }, [code, monaco, autoScroll]); const copyToClipboard = useCallback(async () => { if (!navigator?.clipboard) { toast({ title: t('copy_to_clipboard_toast.unsupported.title'), variant: 'destructive', description: t( 'copy_to_clipboard_toast.unsupported.description' ), }); return; } try { await navigator.clipboard.writeText(codeToCopy ?? code); setIsCopied(true); } catch { setIsCopied(false); toast({ title: t('copy_to_clipboard_toast.failed.title'), variant: 'destructive', description: t( 'copy_to_clipboard_toast.failed.description' ), }); } }, [code, codeToCopy, t, toast]); return (
{loading ? ( ) : ( }> {isComplete ? (
{allowCopy ? ( {t( isCopied ? 'copied' : 'copy_to_clipboard' )} ) : null} {actions && actions.length > 0 && actions.map((action, index) => ( {action.label} ))}
) : null} } theme={effectiveTheme} {...editorProps} options={{ readOnly: true, automaticLayout: true, scrollBeyondLastLine: false, renderValidationDecorations: 'off', lineDecorationsWidth: 0, overviewRulerBorder: false, overviewRulerLanes: 0, hideCursorInOverviewRuler: true, contextmenu: false, ...editorProps?.options, guides: { indentation: false, ...editorProps?.options?.guides, }, scrollbar: { vertical: 'hidden', horizontal: 'hidden', alwaysConsumeMouseWheel: false, ...editorProps?.options?.scrollbar, }, minimap: { enabled: false, ...editorProps?.options?.minimap, }, }} /> {!isComplete ? (
) : null} )}
); } ); CodeSnippet.displayName = 'CodeSnippet';