From 554c6d580411132db1cadcd4c3319321da3f01fe Mon Sep 17 00:00:00 2001 From: logonoff Date: Mon, 27 Apr 2026 16:44:41 -0400 Subject: [PATCH] feat(CodeEditor): add isHighContrastTheme Adds a new prop, `isHighContrastTheme`, which switches the CodeEditor over to the monaco-provided `hc-black` and `hc-light` themes (based on the value of `isDarkTheme`) when enabled. A custom PatternFly variant of the hc-black and hc-light themes can come in a follow up, depending on the direction design wants to take on this --- .../src/components/CodeEditor/CodeEditor.tsx | 14 +++++++++-- .../CodeEditor/__test__/CodeEditor.test.tsx | 24 ++++++++++++++++++- .../__snapshots__/CodeEditor.test.tsx.snap | 2 ++ .../CodeEditor/examples/CodeEditor.md | 1 + .../CodeEditor/examples/CodeEditorBasic.tsx | 14 +++++++++++ .../examples/CodeEditorConfigurationModal.tsx | 11 +++++++++ .../demos/CodeEditorDemo/CodeEditorDemo.tsx | 20 ++++++++++++++-- 7 files changed, 81 insertions(+), 5 deletions(-) diff --git a/packages/react-code-editor/src/components/CodeEditor/CodeEditor.tsx b/packages/react-code-editor/src/components/CodeEditor/CodeEditor.tsx index ac3ac11aec6..b104a215a68 100644 --- a/packages/react-code-editor/src/components/CodeEditor/CodeEditor.tsx +++ b/packages/react-code-editor/src/components/CodeEditor/CodeEditor.tsx @@ -1,4 +1,4 @@ -import { HTMLProps, ReactNode, useEffect, useRef, useState } from 'react'; +import { HTMLProps, ReactNode, useEffect, useMemo, useRef, useState } from 'react'; import { css } from '@patternfly/react-styles'; import styles from '@patternfly/react-styles/css/components/CodeEditor/code-editor'; import fileUploadStyles from '@patternfly/react-styles/css/components/FileUpload/file-upload'; @@ -157,6 +157,8 @@ export interface CodeEditorProps extends Omit, 'onChan isCopyEnabled?: boolean; /** Flag indicating the editor is styled using monaco's dark theme. */ isDarkTheme?: boolean; + /** Flag indicating the editor is styled using monaco's high contrast themes. */ + isHighContrastTheme?: boolean; /** Flag that enables component to consume the available height of its container. If `height` prop is set to 100%, this will also become enabled. */ isFullHeight?: boolean; /** Flag indicating the editor has a plain header. */ @@ -286,6 +288,7 @@ export const CodeEditor = ({ height, isCopyEnabled = false, isDarkTheme = false, + isHighContrastTheme = false, isDownloadEnabled = false, isFullHeight = false, isHeaderPlain = false, @@ -462,6 +465,13 @@ export const CodeEditor = ({ headerMainContent || !!shortcutsPopoverProps.bodyContent; + const theme = useMemo(() => { + if (isHighContrastTheme) { + return isDarkTheme ? 'hc-black' : 'hc-light'; + } + return isDarkTheme ? 'pf-v6-theme-dark' : 'pf-v6-theme-light'; + }, [isHighContrastTheme, isDarkTheme]); + return ( {({ getRootProps, getInputProps, isDragActive, open }) => { @@ -579,7 +589,7 @@ export const CodeEditor = ({ onChange={onModelChange} onMount={editorDidMount} loading={loading} - theme={isDarkTheme ? 'pf-v6-theme-dark' : 'pf-v6-theme-light'} + theme={theme} {...editorProps} beforeMount={editorBeforeMount} /> diff --git a/packages/react-code-editor/src/components/CodeEditor/__test__/CodeEditor.test.tsx b/packages/react-code-editor/src/components/CodeEditor/__test__/CodeEditor.test.tsx index d6b7d21fe10..cf7f8b7d254 100644 --- a/packages/react-code-editor/src/components/CodeEditor/__test__/CodeEditor.test.tsx +++ b/packages/react-code-editor/src/components/CodeEditor/__test__/CodeEditor.test.tsx @@ -2,7 +2,9 @@ import { render, screen, act } from '@testing-library/react'; import { CodeEditor, Language } from '../CodeEditor'; import styles from '@patternfly/react-styles/css/components/CodeEditor/code-editor'; -jest.mock('@monaco-editor/react', () => jest.fn(() =>
)); +jest.mock('@monaco-editor/react', () => + jest.fn((props: any) =>
) +); test('Matches snapshot without props', () => { const { asFragment } = render(); @@ -72,3 +74,23 @@ test(`Renders with shortcuts when shortcutsPopoverButtonText is passed`, () => { }); expect(screen.getByText('shortcuts')).toBeInTheDocument(); }); + +test('Uses pf-v6-theme-light by default', () => { + render(); + expect(screen.getByTestId('mock-editor')).toHaveAttribute('data-theme', 'pf-v6-theme-light'); +}); + +test('Uses pf-v6-theme-dark when isDarkTheme is true', () => { + render(); + expect(screen.getByTestId('mock-editor')).toHaveAttribute('data-theme', 'pf-v6-theme-dark'); +}); + +test('Uses hc-light when isHighContrast is true', () => { + render(); + expect(screen.getByTestId('mock-editor')).toHaveAttribute('data-theme', 'hc-light'); +}); + +test('Uses hc-black when both isHighContrast and isDarkTheme are true', () => { + render(); + expect(screen.getByTestId('mock-editor')).toHaveAttribute('data-theme', 'hc-black'); +}); diff --git a/packages/react-code-editor/src/components/CodeEditor/__test__/__snapshots__/CodeEditor.test.tsx.snap b/packages/react-code-editor/src/components/CodeEditor/__test__/__snapshots__/CodeEditor.test.tsx.snap index 463c7e4e9f8..fd1515c08ab 100644 --- a/packages/react-code-editor/src/components/CodeEditor/__test__/__snapshots__/CodeEditor.test.tsx.snap +++ b/packages/react-code-editor/src/components/CodeEditor/__test__/__snapshots__/CodeEditor.test.tsx.snap @@ -164,6 +164,7 @@ exports[`Matches snapshot with control buttons enabled 1`] = ` >
@@ -196,6 +197,7 @@ exports[`Matches snapshot without props 1`] = ` >
diff --git a/packages/react-code-editor/src/components/CodeEditor/examples/CodeEditor.md b/packages/react-code-editor/src/components/CodeEditor/examples/CodeEditor.md index d08e97eb061..ce6c8ff4ff1 100644 --- a/packages/react-code-editor/src/components/CodeEditor/examples/CodeEditor.md +++ b/packages/react-code-editor/src/components/CodeEditor/examples/CodeEditor.md @@ -15,6 +15,7 @@ import MapIcon from '@patternfly/react-icons/dist/esm/icons/map-icon'; import MoonIcon from '@patternfly/react-icons/dist/esm/icons/moon-icon'; import PlayIcon from '@patternfly/react-icons/dist/esm/icons/play-icon'; import FontIcon from '@patternfly/react-icons/dist/esm/icons/font-icon'; +import AdjustIcon from '@patternfly/react-icons/dist/esm/icons/adjust-icon'; ## Examples diff --git a/packages/react-code-editor/src/components/CodeEditor/examples/CodeEditorBasic.tsx b/packages/react-code-editor/src/components/CodeEditor/examples/CodeEditorBasic.tsx index b31bc694360..348ba9856b9 100644 --- a/packages/react-code-editor/src/components/CodeEditor/examples/CodeEditorBasic.tsx +++ b/packages/react-code-editor/src/components/CodeEditor/examples/CodeEditorBasic.tsx @@ -4,6 +4,7 @@ import { Checkbox } from '@patternfly/react-core'; export const CodeEditorBasic: React.FunctionComponent = () => { const [isDarkTheme, setIsDarkTheme] = useState(false); + const [isHighContrastTheme, setIsHighContrastTheme] = useState(false); const [isLineNumbersVisible, setIsLineNumbersVisible] = useState(true); const [isReadOnly, setIsReadOnly] = useState(false); const [isMinimapVisible, setIsMinimapVisible] = useState(false); @@ -12,6 +13,10 @@ export const CodeEditorBasic: React.FunctionComponent = () => { setIsDarkTheme(checked); }; + const toggleHighContrastTheme = (checked) => { + setIsHighContrastTheme(checked); + }; + const toggleLineNumbers = (checked) => { setIsLineNumbersVisible(checked); }; @@ -43,6 +48,14 @@ export const CodeEditorBasic: React.FunctionComponent = () => { id="toggle-theme" name="toggle-theme" /> + toggleHighContrastTheme(checked)} + aria-label="high contrast theme checkbox" + id="toggle-high-contrast-theme" + name="toggle-high-contrast-theme" + /> { /> { const [isMinimapVisible, setIsMinimapVisible] = useState(true); const [isDarkTheme, setIsDarkTheme] = useState(false); + const [isHighContrastTheme, setIsHighContrastTheme] = useState(false); const [isLineNumbersVisible, setIsLineNumbersVisible] = useState(true); const [fontSize, setFontSize] = useState(14); @@ -181,6 +183,14 @@ export const CodeEditorConfigurationModal: React.FunctionComponent = () => { onChange={(_e, checked) => setIsDarkTheme(checked)} icon={} /> + setIsHighContrastTheme(checked)} + icon={} + /> { customControls={customControl} height="400px" isDarkTheme={isDarkTheme} + isHighContrastTheme={isHighContrastTheme} isLineNumbersVisible={isLineNumbersVisible} isMinimapVisible={isMinimapVisible} onChange={onChange} diff --git a/packages/react-integration/demo-app-ts/src/components/demos/CodeEditorDemo/CodeEditorDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/CodeEditorDemo/CodeEditorDemo.tsx index 65241fb2829..939ac62d01a 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/CodeEditorDemo/CodeEditorDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/CodeEditorDemo/CodeEditorDemo.tsx @@ -5,6 +5,7 @@ import PlayIcon from '@patternfly/react-icons/dist/esm/icons/play-icon'; interface CodeEditorDemoState { isDarkTheme: boolean; + isHighContrastTheme: boolean; isLineNumbersVisible: boolean; isReadOnly: boolean; isMinimapVisible: boolean; @@ -17,6 +18,7 @@ export class CodeEditorDemo extends Component { + this.setState({ + isHighContrastTheme: checked + }); + }; + toggleLineNumbers = (checked: boolean) => { this.setState({ isLineNumbersVisible: checked @@ -70,8 +78,7 @@ export class CodeEditorDemo extends Component} @@ -91,6 +98,14 @@ export class CodeEditorDemo extends Component + this.toggleHighContrastTheme(checked)} + aria-label="high contrast theme checkbox" + id="toggle-high-contrast-theme" + name="toggle-high-contrast-theme" + />