fix(ui): wrap codex config highlight editor (#1217)

This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-05-11 21:58:55 -04:00
1 parent a3e8e2e58c
commit ba761a0d1e
2 files changed
+26 -16

No files matched your search

+16 -12
View File
@@ -196,8 +196,8 @@ export function CodeEditor({
const layer = highlightLayerRef.current; const layer = highlightLayerRef.current;
if (!layer) return; if (!layer) return;
const { scrollLeft, scrollTop } = event.currentTarget; const { scrollTop } = event.currentTarget;
layer.style.transform = `translate(${-scrollLeft}px, ${-scrollTop}px)`; layer.style.transform = `translateY(${-scrollTop}px)`;
}, []); }, []);
return ( return (
@@ -218,12 +218,15 @@ export function CodeEditor({
data-slot="code-editor-surface" data-slot="code-editor-surface"
> >
<div <div
className={cn(isFillParent && 'scrollbar-editor min-h-0 flex-1 overflow-auto')} className={cn(
isFillParent && 'scrollbar-editor min-h-0 flex-1',
isFillParent && (exactText ? 'overflow-hidden' : 'overflow-auto')
)}
data-slot={isFillParent ? 'code-editor-viewport' : undefined} data-slot={isFillParent ? 'code-editor-viewport' : undefined}
> >
{exactText ? ( {exactText ? (
// Exact mode keeps the native textarea as the editable/copyable source of truth while // Exact mode keeps the native textarea as the editable/copyable source of truth while
// rendering Prism colors behind it with the same non-wrapping layout contract. // rendering Prism colors behind it with the same soft-wrap layout contract.
<div <div
className={cn( className={cn(
'relative w-full overflow-hidden', 'relative w-full overflow-hidden',
@@ -238,16 +241,16 @@ export function CodeEditor({
ref={highlightLayerRef} ref={highlightLayerRef}
aria-hidden="true" aria-hidden="true"
className={cn( className={cn(
'pointer-events-none absolute left-0 top-0 min-w-full p-3 font-mono text-sm leading-relaxed', 'pointer-events-none absolute inset-x-0 top-0 w-full p-3 font-mono text-sm leading-relaxed',
'whitespace-pre overflow-visible' 'whitespace-pre-wrap overflow-visible break-words'
)} )}
style={{ style={{
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
fontSize: '0.875rem', fontSize: '0.875rem',
minHeight, minHeight,
tabSize: 2, tabSize: 2,
overflowWrap: 'normal', overflowWrap: 'anywhere',
wordBreak: 'normal', wordBreak: 'break-word',
}} }}
data-slot="code-editor-highlight-layer" data-slot="code-editor-highlight-layer"
> >
@@ -259,14 +262,14 @@ export function CodeEditor({
if (!readonly) onChange(event.target.value); if (!readonly) onChange(event.target.value);
}} }}
readOnly={readonly} readOnly={readonly}
wrap="off" wrap="soft"
spellCheck={false} spellCheck={false}
onFocus={() => setIsFocused(true)} onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)} onBlur={() => setIsFocused(false)}
onScroll={syncHighlightScroll} onScroll={syncHighlightScroll}
className={cn( className={cn(
'absolute inset-0 z-10 block h-full w-full resize-none border-0 bg-transparent p-3 font-mono text-sm leading-relaxed', 'absolute inset-0 z-10 block h-full w-full resize-none border-0 bg-transparent p-3 font-mono text-sm leading-relaxed',
'whitespace-pre overflow-auto text-transparent caret-foreground selection:bg-primary/25 focus:outline-none', 'whitespace-pre-wrap overflow-y-auto overflow-x-hidden break-words text-transparent caret-foreground selection:bg-transparent focus:outline-none',
readonly && 'cursor-not-allowed' readonly && 'cursor-not-allowed'
)} )}
style={{ style={{
@@ -274,8 +277,9 @@ export function CodeEditor({
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
fontSize: '0.875rem', fontSize: '0.875rem',
tabSize: 2, tabSize: 2,
overflowWrap: 'normal', overflowWrap: 'anywhere',
wordBreak: 'normal', wordBreak: 'break-word',
WebkitTextFillColor: 'transparent',
}} }}
data-slot="code-editor-plain-textarea" data-slot="code-editor-plain-textarea"
/> />
@@ -68,7 +68,7 @@ describe('CodeEditor', () => {
expect(screen.getByText('Valid TOML')).toBeInTheDocument(); expect(screen.getByText('Valid TOML')).toBeInTheDocument();
}); });
it('renders raw TOML with syntax color while visible lines match copied text', () => { it('renders raw TOML with wrapping syntax color while copied text stays raw', () => {
const rawToml = const rawToml =
'model = "gpt-5.4"\n' + 'model = "gpt-5.4"\n' +
'[mcp_servers.playwright]\n' + '[mcp_servers.playwright]\n' +
@@ -92,17 +92,23 @@ describe('CodeEditor', () => {
expect(textarea).toBeInstanceOf(HTMLTextAreaElement); expect(textarea).toBeInstanceOf(HTMLTextAreaElement);
expect(textarea).toHaveValue(rawToml); expect(textarea).toHaveValue(rawToml);
expect(textarea).toHaveAttribute('wrap', 'off'); expect(textarea).toHaveAttribute('wrap', 'soft');
expect(textarea).toHaveClass('text-transparent'); expect(textarea).toHaveClass('text-transparent');
expect(textarea).toHaveClass('whitespace-pre-wrap');
expect(textarea).toHaveClass('overflow-x-hidden');
expect(highlightLayer).toBeInTheDocument(); expect(highlightLayer).toBeInTheDocument();
expect(highlightLayer).toHaveClass('whitespace-pre'); expect(highlightLayer).toHaveClass('whitespace-pre-wrap');
expect(highlightLayer).toHaveStyle({
overflowWrap: 'anywhere',
wordBreak: 'break-word',
});
expect(highlightedToken).toBeInTheDocument(); expect(highlightedToken).toBeInTheDocument();
expect(container.querySelector('pre')).not.toBeInTheDocument(); expect(container.querySelector('pre')).not.toBeInTheDocument();
if (textarea instanceof HTMLTextAreaElement && highlightLayer instanceof HTMLElement) { if (textarea instanceof HTMLTextAreaElement && highlightLayer instanceof HTMLElement) {
textarea.scrollLeft = 96; textarea.scrollLeft = 96;
textarea.scrollTop = 24; textarea.scrollTop = 24;
fireEvent.scroll(textarea); fireEvent.scroll(textarea);
expect(highlightLayer.style.transform).toBe('translate(-96px, -24px)'); expect(highlightLayer.style.transform).toBe('translateY(-24px)');
} }
expect(screen.getByText('Valid TOML')).toBeInTheDocument(); expect(screen.getByText('Valid TOML')).toBeInTheDocument();
}); });