mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 06:14:59 +00:00
170 lines
4.4 KiB
TypeScript
170 lines
4.4 KiB
TypeScript
'use client'
|
|
|
|
import { lazy, Suspense, type ComponentType } from 'react'
|
|
// Use a simple skeleton div instead of the Skeleton component for cleaner loading state
|
|
import type { Monaco } from '@monaco-editor/react'
|
|
|
|
// Lazy load Monaco Editor for better performance
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const Editor = lazy(() => import('@monaco-editor/react').then(mod => ({ default: mod.default as ComponentType<any> })))
|
|
|
|
/**
|
|
* Language name to Monaco language ID mapping
|
|
*/
|
|
const LANGUAGE_MAP: Record<string, string> = {
|
|
'JavaScript': 'javascript',
|
|
'TypeScript': 'typescript',
|
|
'JSX': 'javascript',
|
|
'TSX': 'typescript',
|
|
'Python': 'python',
|
|
'Java': 'java',
|
|
'C++': 'cpp',
|
|
'C#': 'csharp',
|
|
'Go': 'go',
|
|
'Rust': 'rust',
|
|
'PHP': 'php',
|
|
'Ruby': 'ruby',
|
|
'SQL': 'sql',
|
|
'HTML': 'html',
|
|
'CSS': 'css',
|
|
'JSON': 'json',
|
|
'YAML': 'yaml',
|
|
'Markdown': 'markdown',
|
|
'XML': 'xml',
|
|
'Shell': 'shell',
|
|
'Bash': 'shell',
|
|
}
|
|
|
|
/**
|
|
* Get Monaco language ID from human-readable language name
|
|
*/
|
|
export function getMonacoLanguage(language: string): string {
|
|
return LANGUAGE_MAP[language] || language.toLowerCase()
|
|
}
|
|
|
|
/**
|
|
* Configure TypeScript support in Monaco Editor
|
|
*/
|
|
export function configureMonacoTypeScript(monaco: Monaco) {
|
|
if (monaco.languages.typescript) {
|
|
monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true)
|
|
}
|
|
}
|
|
|
|
export interface MonacoEditorProps {
|
|
/** Current editor value */
|
|
value: string
|
|
/** Called when editor value changes */
|
|
onChange: (value: string) => void
|
|
/** Programming language for syntax highlighting */
|
|
language: string
|
|
/** Editor height (CSS value) */
|
|
height?: string
|
|
/** Whether the editor is read-only */
|
|
readOnly?: boolean
|
|
/** Monaco theme */
|
|
theme?: 'vs-dark' | 'light' | 'vs'
|
|
/** Custom Monaco options */
|
|
options?: Record<string, unknown>
|
|
/** Called before Monaco mounts (for custom configuration) */
|
|
beforeMount?: (monaco: Monaco) => void
|
|
/** Test ID for the component */
|
|
testId?: string
|
|
}
|
|
|
|
function EditorLoadingSkeleton({ height = '400px' }: { height?: string }) {
|
|
return (
|
|
<div
|
|
className="animate-pulse bg-muted rounded-md"
|
|
style={{ height }}
|
|
data-testid="monaco-editor-skeleton"
|
|
role="status"
|
|
aria-busy="true"
|
|
aria-label="Loading code editor"
|
|
/>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Monaco Editor wrapper component with lazy loading and TypeScript support.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <MonacoEditor
|
|
* value={code}
|
|
* onChange={setCode}
|
|
* language="TypeScript"
|
|
* height="400px"
|
|
* />
|
|
* ```
|
|
*/
|
|
export function MonacoEditor({
|
|
value,
|
|
onChange,
|
|
language,
|
|
height = '400px',
|
|
readOnly = false,
|
|
theme = 'vs-dark',
|
|
options = {},
|
|
beforeMount,
|
|
testId,
|
|
}: MonacoEditorProps) {
|
|
const monacoLanguage = getMonacoLanguage(language)
|
|
|
|
const handleBeforeMount = (monaco: Monaco) => {
|
|
configureMonacoTypeScript(monaco)
|
|
beforeMount?.(monaco)
|
|
}
|
|
|
|
return (
|
|
<Suspense fallback={<EditorLoadingSkeleton height={height} />}>
|
|
<div
|
|
data-testid={testId ?? "monaco-editor-container"}
|
|
role="region"
|
|
aria-label={`Code editor (${readOnly ? 'read-only' : 'editable'}, ${monacoLanguage} language)`}
|
|
>
|
|
{/* Aria-live region for editor status updates */}
|
|
<div
|
|
className="sr-only"
|
|
role="status"
|
|
aria-live="polite"
|
|
aria-atomic="true"
|
|
data-testid="monaco-editor-status"
|
|
>
|
|
Code editor loaded with {monacoLanguage} syntax highlighting. {readOnly ? 'Read-only mode' : 'Editable mode'}.
|
|
</div>
|
|
<Editor
|
|
height={height}
|
|
language={monacoLanguage}
|
|
value={value}
|
|
onChange={(newValue) => onChange(newValue || '')}
|
|
theme={theme}
|
|
beforeMount={handleBeforeMount}
|
|
options={{
|
|
minimap: { enabled: false },
|
|
fontSize: 14,
|
|
lineNumbers: 'on',
|
|
scrollBeyondLastLine: false,
|
|
automaticLayout: true,
|
|
tabSize: 2,
|
|
wordWrap: 'on',
|
|
readOnly,
|
|
scrollbar: {
|
|
vertical: 'auto',
|
|
horizontal: 'auto',
|
|
useShadows: false,
|
|
},
|
|
padding: {
|
|
top: 12,
|
|
bottom: 12,
|
|
},
|
|
fontFamily: 'JetBrains Mono, monospace',
|
|
fontLigatures: true,
|
|
...options,
|
|
}}
|
|
/>
|
|
</div>
|
|
</Suspense>
|
|
)
|
|
}
|