Clean up MCP Monaco editor (#21643)

Rebuild the MCP setup block on the shared Monaco editor with theme-driven colors, padding, and auto-height; add the settings description line-height token; revert the clipboard util to the shared navigator.clipboard implementation; and consolidate the code editor's auto-height to a single disposed, reactive effect.
This commit is contained in:
Thomas des Francs
2026-06-16 13:57:44 +02:00
committed by GitHub
parent ce92a1f1e1
commit a67fdf2dac
14 changed files with 393 additions and 149 deletions
@@ -4,14 +4,27 @@ import { BASE_CODE_EDITOR_THEME_ID } from '@ui/input/code-editor/constants/BaseC
import { getBaseCodeEditorTheme } from '@ui/input/code-editor/theme/utils/getBaseCodeEditorTheme';
import { ResizeHandle } from '@ui/layout/resize-handle/components/ResizeHandle';
import { useResizeHandle } from '@ui/layout/resize-handle/hooks/useResizeHandle';
import { ThemeContext } from '@ui/theme-constants';
import { ThemeContext, type ThemeType } from '@ui/theme-constants';
import { type editor } from 'monaco-editor';
import { type KeyboardEvent, useContext, useState } from 'react';
import { type KeyboardEvent, useContext, useEffect, useState } from 'react';
import { isDefined } from '@ui/utilities/utils/isDefined';
import styles from './CodeEditor.module.scss';
type CodeEditorVariant = 'default' | 'with-header' | 'borderless';
type CodeEditorContentPadding = 'default' | 'comfortable';
const setCodeEditorTheme = (
monaco: Monaco,
theme: ThemeType,
colorScheme: 'light' | 'dark',
) => {
monaco.editor.defineTheme(
BASE_CODE_EDITOR_THEME_ID,
getBaseCodeEditorTheme(theme, colorScheme),
);
monaco.editor.setTheme(BASE_CODE_EDITOR_THEME_ID);
};
type CodeEditorProps = Pick<
EditorProps,
@@ -23,6 +36,8 @@ type CodeEditorProps = Pick<
isLoading?: boolean;
transparentBackground?: boolean;
resizable?: boolean;
contentPadding?: CodeEditorContentPadding;
autoHeight?: boolean;
};
export const CodeEditor = ({
@@ -38,13 +53,18 @@ export const CodeEditor = ({
isLoading = false,
options,
resizable = false,
contentPadding = 'default',
autoHeight = false,
}: CodeEditorProps) => {
const { theme } = useContext(ThemeContext);
const { theme, colorScheme } = useContext(ThemeContext);
const [monaco, setMonaco] = useState<Monaco | undefined>(undefined);
const [editor, setEditor] = useState<
editor.IStandaloneCodeEditor | undefined
>(undefined);
const [isEditorFocused, setIsEditorFocused] = useState(false);
const [autoHeightContentHeight, setAutoHeightContentHeight] = useState<
number | undefined
>(undefined);
const numericHeight = typeof height === 'number' ? height : 450;
const {
@@ -56,7 +76,28 @@ export const CodeEditor = ({
initialSize: numericHeight,
});
const currentHeight = resizable ? resizableHeight : height;
const shouldAutoHeight = autoHeight && !resizable;
const codeEditorPadding =
typeof theme.spacingMultiplicator === 'number'
? theme.spacingMultiplicator * 4
: undefined;
const currentHeight = shouldAutoHeight
? (autoHeightContentHeight ?? height)
: resizable
? resizableHeight
: height;
const contentPaddingOptions = {
...(contentPadding === 'comfortable'
? {
lineDecorationsWidth: codeEditorPadding,
}
: {}),
padding: {
bottom: codeEditorPadding,
top: codeEditorPadding,
},
};
const { padding: _callerPadding, ...editorOptions } = options ?? {};
const setModelMarkers = (
editor: editor.IStandaloneCodeEditor | undefined,
@@ -78,6 +119,43 @@ export const CodeEditor = ({
}
};
useEffect(() => {
if (!isDefined(monaco)) {
return;
}
setCodeEditorTheme(monaco, theme, colorScheme);
}, [colorScheme, monaco, theme]);
// Drive the container height from Monaco's content height; the editor's
// default automaticLayout then re-fits the canvas (no manual layout needed).
useEffect(() => {
if (!shouldAutoHeight || !isDefined(editor)) {
setAutoHeightContentHeight(undefined);
return;
}
const updateAutoHeight = () => {
const nextHeight = editor.getContentHeight();
if (!Number.isFinite(nextHeight) || nextHeight <= 0) {
return;
}
setAutoHeightContentHeight((currentHeight) =>
currentHeight === nextHeight ? currentHeight : nextHeight,
);
};
updateAutoHeight();
const disposable = editor.onDidContentSizeChange(updateAutoHeight);
return () => {
disposable.dispose();
};
}, [editor, shouldAutoHeight]);
return isLoading ? (
<div
className={styles.editorLoader}
@@ -115,11 +193,7 @@ export const CodeEditor = ({
setMonaco(monaco);
setEditor(editor);
monaco.editor.defineTheme(
BASE_CODE_EDITOR_THEME_ID,
getBaseCodeEditorTheme(theme),
);
monaco.editor.setTheme(BASE_CODE_EDITOR_THEME_ID);
setCodeEditorTheme(monaco, theme, colorScheme);
editor.onDidFocusEditorWidget(() => {
setIsEditorFocused(true);
@@ -143,6 +217,10 @@ export const CodeEditor = ({
options={{
formatOnPaste: true,
formatOnType: true,
fontFamily: theme.code.font.family,
bracketPairColorization: {
enabled: false,
},
overviewRulerLanes: 0,
scrollBeyondLastLine: false,
scrollbar: {
@@ -152,7 +230,8 @@ export const CodeEditor = ({
minimap: {
enabled: false,
},
...options,
...editorOptions,
...contentPaddingOptions,
}}
/>
</div>
@@ -3,6 +3,10 @@ import { type editor } from 'monaco-editor';
import { isDefined } from '@ui/utilities/utils/isDefined';
const convertColorToHex = (color: string): string => {
if (color.trim() === 'transparent') {
return '#00000000';
}
const displayP3Match = color.match(
/color\(display-p3\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s+\/\s+([\d.]+))?\)/,
);
@@ -33,35 +37,81 @@ const convertColorToHex = (color: string): string => {
export const getBaseCodeEditorTheme = (
theme: ThemeType,
colorScheme: 'light' | 'dark',
): editor.IStandaloneThemeData => {
return {
base: 'vs',
base: colorScheme === 'dark' ? 'vs-dark' : 'vs',
inherit: true,
rules: [
{
token: '',
foreground: convertColorToHex(theme.code.text.gray),
fontStyle: 'bold',
foreground: convertColorToHex(theme.font.color.secondary),
},
{
token: 'keyword',
foreground: convertColorToHex(theme.code.text.sky),
foreground: convertColorToHex(theme.color.pink11),
},
{
token: 'delimiter',
foreground: convertColorToHex(theme.code.text.gray),
token: 'keyword.control',
foreground: convertColorToHex(theme.color.pink11),
},
{
token: 'keyword.json',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'number',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'number.json',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'regexp',
foreground: convertColorToHex(theme.color.orange11),
},
{
token: 'type',
foreground: convertColorToHex(theme.color.green11),
},
{
token: 'attribute.name',
foreground: convertColorToHex(theme.color.blue11),
},
{
token: 'tag',
foreground: convertColorToHex(theme.color.pink11),
},
{
token: 'string',
foreground: convertColorToHex(theme.code.text.pink),
foreground: convertColorToHex(theme.color.green11),
},
{
token: 'string.key.json',
foreground: convertColorToHex(theme.color.blue11),
},
{
token: 'delimiter',
foreground: convertColorToHex(theme.font.color.light),
},
{
token: 'delimiter.bracket.json',
foreground: convertColorToHex(theme.font.color.light),
},
{
token: 'string.value.json',
foreground: convertColorToHex(theme.color.green11),
},
{
token: 'comment',
foreground: convertColorToHex(theme.code.text.orange),
foreground: convertColorToHex(theme.font.color.light),
fontStyle: 'italic',
},
],
colors: {
'editor.background': '#00000000',
'editor.background': convertColorToHex('transparent'),
'editor.foreground': convertColorToHex(theme.font.color.secondary),
'editorCursor.foreground': convertColorToHex(theme.font.color.primary),
'editorLineNumber.foreground': convertColorToHex(
theme.font.color.extraLight,
@@ -72,6 +122,22 @@ export const getBaseCodeEditorTheme = (
'editor.lineHighlightBackground': convertColorToHex(
theme.background.tertiary,
),
'editor.selectionBackground': convertColorToHex(
theme.background.transparent.blue,
),
'editor.inactiveSelectionBackground': convertColorToHex(
theme.background.transparent.light,
),
'editorIndentGuide.background1': convertColorToHex(
theme.border.color.light,
),
'editorIndentGuide.activeBackground1': convertColorToHex(
theme.border.color.medium,
),
'editorBracketMatch.background': convertColorToHex(
theme.background.transparent.light,
),
'editorBracketMatch.border': convertColorToHex(theme.border.color.medium),
},
};
};