"use client"; import { Editor, type Monaco, type OnMount } from "@monaco-editor/react"; import { Button } from "@workspace/ui/components/button"; import { cn } from "@workspace/ui/lib/utils"; import { useSize } from "ahooks"; import { EyeIcon, EyeOff, FullscreenIcon, MinimizeIcon } from "lucide-react"; import DraculaTheme from "monaco-themes/themes/Dracula.json" with { type: "json", }; import { useEffect, useRef, useState } from "react"; export interface MonacoEditorProps { value?: string; onChange?: (value: string | undefined) => void; onBlur?: (value: string | undefined) => void; title?: string; description?: string; placeholder?: string; render?: (value?: string) => React.ReactNode; onMount?: OnMount; beforeMount?: (monaco: Monaco) => void; language?: string; className?: string; showLineNumbers?: boolean; readOnly?: boolean; } // eslint-disable-next-line @typescript-eslint/no-explicit-any function debounce void>(func: T, delay: number) { let timeoutId: ReturnType; return (...args: Parameters) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => func(...args), delay); }; } export function MonacoEditor({ value: propValue, onChange, onBlur, title = "Editor Title", description, placeholder = "Start typing...", render, onMount, beforeMount, language = "markdown", className, showLineNumbers = false, readOnly = false, }: MonacoEditorProps) { const [internalValue, setInternalValue] = useState( propValue ); const [isFullscreen, setIsFullscreen] = useState(false); const [isPreviewVisible, setIsPreviewVisible] = useState(false); const ref = useRef(null); const size = useSize(ref); useEffect(() => { // Only update internalValue if propValue has actually changed and is different from current value if (propValue !== internalValue) { setInternalValue(propValue); } }, [propValue]); const debouncedOnChange = useRef( debounce((newValue: string | undefined) => { if (onChange) { onChange(newValue); } }, 300) ).current; const handleEditorDidMount: OnMount = (editor, monaco) => { if (onMount) onMount(editor, monaco); editor.onDidChangeModelContent(() => { const newValue = editor.getValue(); setInternalValue(newValue); debouncedOnChange(newValue); }); editor.onDidBlurEditorWidget(() => { if (onBlur) { onBlur(editor.getValue()); } }); }; const toggleFullscreen = () => setIsFullscreen(!isFullscreen); const togglePreview = () => setIsPreviewVisible(!isPreviewVisible); return (

{title}

{description}

{render && ( )}
{ monaco.editor.defineTheme("transparentTheme", { base: DraculaTheme.base as "vs" | "vs-dark" | "hc-black", inherit: DraculaTheme.inherit, rules: DraculaTheme.rules, colors: { ...DraculaTheme.colors, "editor.background": "#00000000", }, }); if (beforeMount) { beforeMount(monaco); } }} className="" language={language} onChange={(newValue) => { setInternalValue(newValue); debouncedOnChange(newValue); }} onMount={handleEditorDidMount} options={{ automaticLayout: true, contextmenu: false, folding: false, fontSize: 14, formatOnPaste: true, formatOnType: true, glyphMargin: false, lineNumbers: showLineNumbers ? "on" : "off", minimap: { enabled: false }, overviewRulerLanes: 0, renderLineHighlight: "none", scrollBeyondLastLine: false, scrollbar: { useShadows: false, vertical: "auto", }, tabSize: 2, wordWrap: "off", readOnly, }} theme="transparentTheme" value={internalValue} /> {!internalValue?.trim() && placeholder && (
                  {placeholder}
                
)}
{render && isPreviewVisible && (
{render(internalValue)}
)}
); }