55import type { FunctionComponent , MouseEvent } from 'react' ;
66import { useState , useEffect , useRef } from 'react' ;
77import path from 'path-browserify' ;
8- import * as monaco from 'monaco-editor' ;
9- import { loader } from '@monaco-editor/react' ;
108
119// Import PatternFly components
1210import { CodeEditor } from '@patternfly/react-code-editor' ;
1311import {
12+ Bullseye ,
1413 Button ,
1514 getResizeObserver ,
1615 ModalBody ,
1716 ModalFooter ,
1817 ModalHeader ,
18+ Spinner ,
1919 Stack ,
2020 StackItem
2121} from '@patternfly/react-core' ;
2222import FileDetails , { extensionToLanguage } from '../FileDetails' ;
2323import { ChatbotDisplayMode } from '../Chatbot' ;
2424import ChatbotModal from '../ChatbotModal/ChatbotModal' ;
2525
26- // Configure Monaco loader to use the npm package instead of CDN
27- loader . config ( { monaco } ) ;
26+ // Try to lazy load - some consumers need to be below a certain bundle size, but can't use the CDN and don't have webpack
27+ let monacoInstance : typeof import ( 'monaco-editor' ) | null = null ;
28+ const loadMonaco = async ( ) => {
29+ if ( ! monacoInstance ) {
30+ const [ monaco , { loader } ] = await Promise . all ( [
31+ import ( 'monaco-editor' ) ,
32+ import ( '@monaco-editor/react' )
33+ ] ) ;
34+ monacoInstance = monaco ;
35+ loader . config ( { monaco } ) ;
36+ }
37+ return monacoInstance ;
38+ } ;
2839
2940export interface CodeModalProps {
3041 /** Class applied to code editor */
@@ -63,6 +74,8 @@ export interface CodeModalProps {
6374 modalBodyClassName ?: string ;
6475 /** Class applied to modal footer */
6576 modalFooterClassName ?: string ;
77+ /** Aria label applied to spinner when loading Monaco */
78+ spinnerAriaLabel ?: string ;
6679}
6780
6881export const CodeModal : FunctionComponent < CodeModalProps > = ( {
@@ -84,13 +97,32 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
8497 modalHeaderClassName,
8598 modalBodyClassName,
8699 modalFooterClassName,
100+ spinnerAriaLabel = 'Loading' ,
87101 ...props
88102} : CodeModalProps ) => {
89103 const [ newCode , setNewCode ] = useState ( code ) ;
90- const [ editorInstance , setEditorInstance ] = useState < monaco . editor . IStandaloneCodeEditor | null > ( null ) ;
104+ const [ editorInstance , setEditorInstance ] = useState < any > ( null ) ;
91105 const [ isEditorReady , setIsEditorReady ] = useState ( false ) ;
106+ const [ isMonacoLoading , setIsMonacoLoading ] = useState ( false ) ;
107+ const [ isMonacoLoaded , setIsMonacoLoaded ] = useState ( false ) ;
92108 const containerRef = useRef < HTMLDivElement > ( null ) ;
93109
110+ useEffect ( ( ) => {
111+ if ( isModalOpen && ! isMonacoLoaded && ! isMonacoLoading ) {
112+ setIsMonacoLoading ( true ) ;
113+ loadMonaco ( )
114+ . then ( ( ) => {
115+ setIsMonacoLoaded ( true ) ;
116+ setIsMonacoLoading ( false ) ;
117+ } )
118+ . catch ( ( error ) => {
119+ // eslint-disable-next-line no-console
120+ console . error ( 'Failed to load Monaco editor:' , error ) ;
121+ setIsMonacoLoading ( false ) ;
122+ } ) ;
123+ }
124+ } , [ isModalOpen , isMonacoLoaded , isMonacoLoading ] ) ;
125+
94126 useEffect ( ( ) => {
95127 if ( ! isModalOpen || ! isEditorReady || ! editorInstance || ! containerRef . current ) {
96128 return ;
@@ -166,27 +198,31 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
166198 < FileDetails fileName = { fileName } />
167199 </ StackItem >
168200 < div className = "pf-v6-l-stack__item pf-chatbot__code-modal-editor" ref = { containerRef } >
169- < CodeEditor
170- isDarkTheme
171- isLineNumbersVisible = { isLineNumbersVisible }
172- isLanguageLabelVisible
173- isCopyEnabled = { isCopyEnabled }
174- isReadOnly = { isReadOnly }
175- code = { newCode }
176- language = { extensionToLanguage [ path . extname ( fileName ) . slice ( 1 ) ] }
177- onEditorDidMount = { onEditorDidMount }
178- onCodeChange = { onCodeChange }
179- className = { codeEditorClassName }
180- isFullHeight
181- options = { {
182- glyphMargin : false ,
183- folding : false ,
184- // prevents Monaco from handling resizing itself
185- // was causing ResizeObserver issues
186- automaticLayout : false
187- } }
188- { ...props }
189- />
201+ { isMonacoLoading ? (
202+ < Bullseye > < Spinner aria-label = { spinnerAriaLabel } /> </ Bullseye >
203+ ) : isMonacoLoaded ? (
204+ < CodeEditor
205+ isDarkTheme
206+ isLineNumbersVisible = { isLineNumbersVisible }
207+ isLanguageLabelVisible
208+ isCopyEnabled = { isCopyEnabled }
209+ isReadOnly = { isReadOnly }
210+ code = { newCode }
211+ language = { extensionToLanguage [ path . extname ( fileName ) . slice ( 1 ) ] }
212+ onEditorDidMount = { onEditorDidMount }
213+ onCodeChange = { onCodeChange }
214+ className = { codeEditorClassName }
215+ isFullHeight
216+ options = { {
217+ glyphMargin : false ,
218+ folding : false ,
219+ // prevents Monaco from handling resizing itself
220+ // was causing ResizeObserver issues
221+ automaticLayout : false
222+ } }
223+ { ...props }
224+ />
225+ ) : null }
190226 </ div >
191227 </ Stack >
192228 </ ModalBody >
0 commit comments