- add CodeBlock for read-only syntax-highlighted display (hljs, js/json) - add CodeEditor wrapping Monaco editor with theme sync, focus state, and error state - replace code textareas in snippet, credential, and step pages with CodeEditor - replace code <pre> blocks in snippet and credential detail pages with CodeBlock - make form cards full-width on pages with code editors - add resize:vertical support to CodeEditor wrapper with automaticLayout
84 lines
2.0 KiB
TypeScript
84 lines
2.0 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import Editor, { OnMount } from '@monaco-editor/react';
|
|
import styles from './CodeEditor.module.css';
|
|
|
|
export interface CodeEditorProps {
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
language?: string;
|
|
rows?: number;
|
|
error?: boolean;
|
|
}
|
|
|
|
function useMonacoTheme() {
|
|
const [theme, setTheme] = useState(() =>
|
|
document.documentElement.getAttribute('data-theme') === 'dark' ? 'vs-dark' : 'vs-light'
|
|
);
|
|
|
|
useEffect(() => {
|
|
const observer = new MutationObserver(() => {
|
|
setTheme(
|
|
document.documentElement.getAttribute('data-theme') === 'dark' ? 'vs-dark' : 'vs-light'
|
|
);
|
|
});
|
|
observer.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ['data-theme'],
|
|
});
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
return theme;
|
|
}
|
|
|
|
export function CodeEditor({
|
|
value,
|
|
onChange,
|
|
language = 'javascript',
|
|
rows = 6,
|
|
error = false,
|
|
}: CodeEditorProps) {
|
|
const theme = useMonacoTheme();
|
|
const [focused, setFocused] = useState(false);
|
|
const height = rows * 20 + 16;
|
|
|
|
const handleMount: OnMount = (editor) => {
|
|
editor.onDidFocusEditorWidget(() => setFocused(true));
|
|
editor.onDidBlurEditorWidget(() => setFocused(false));
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={[
|
|
styles.wrapper,
|
|
focused ? styles.focused : '',
|
|
error ? styles.hasError : '',
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ')}
|
|
style={{ height }}
|
|
>
|
|
<Editor
|
|
height="100%"
|
|
theme={theme}
|
|
language={language}
|
|
value={value}
|
|
onChange={(v) => onChange(v ?? '')}
|
|
onMount={handleMount}
|
|
options={{
|
|
minimap: { enabled: false },
|
|
scrollBeyondLastLine: false,
|
|
fontSize: 14,
|
|
lineHeight: 20,
|
|
wordWrap: 'on',
|
|
padding: { top: 8, bottom: 8 },
|
|
folding: false,
|
|
renderLineHighlight: 'line',
|
|
scrollbar: { vertical: 'auto', horizontal: 'hidden' },
|
|
automaticLayout: true,
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|