11// ============================================================================
22// Chatbot Main - Message - Content - Code Block
33// ============================================================================
4- import { useState , useRef , useId , useCallback , useEffect } from 'react' ;
5- import SyntaxHighlighter from 'react-syntax-highlighter' ;
6- import { obsidian } from 'react-syntax-highlighter/dist/esm/styles/hljs' ;
7- // Import PatternFly components
8- import {
9- CodeBlock ,
10- CodeBlockAction ,
11- CodeBlockCode ,
12- Button ,
13- Tooltip ,
14- ExpandableSection ,
15- ExpandableSectionToggle ,
16- ExpandableSectionProps ,
17- ExpandableSectionToggleProps ,
18- ExpandableSectionVariant
19- } from '@patternfly/react-core' ;
4+ import { useState } from 'react' ;
5+ import { ExpandableSectionProps , ExpandableSection } from '@patternfly/react-core' ;
206
21- import { CheckIcon } from '@patternfly/react-icons/dist/esm/icons/check-icon' ;
22- import { CopyIcon } from '@patternfly/react-icons/dist/esm/icons/copy-icon' ;
23- import { ExpandableSectionForSyntaxHighlighter } from './ExpandableSectionForSyntaxHighlighter' ;
7+ import { CodeEditor , CodeEditorProps , Language } from '@patternfly/react-code-editor' ;
248
259export interface CodeBlockMessageProps {
2610 /** Content rendered in code block */
2711 children ?: React . ReactNode ;
28- /** Aria label applied to code block */
29- 'aria-label' ?: string ;
3012 /** Class name applied to code block */
3113 className ?: string ;
3214 /** Whether code block is expandable */
3315 isExpandable ?: boolean ;
3416 /** Additional props passed to expandable section if isExpandable is applied */
3517 expandableSectionProps ?: Omit < ExpandableSectionProps , 'ref' > ;
36- /** Additional props passed to expandable toggle if isExpandable is applied */
37- expandableSectionToggleProps ?: ExpandableSectionToggleProps ;
18+ /** Additional props passed to code editor */
19+ codeEditorProps ?: Omit < CodeEditorProps , 'ref' > ;
3820 /** Link text applied to expandable toggle when expanded */
3921 expandedText ?: string ;
4022 /** Link text applied to expandable toggle when collapsed */
@@ -47,24 +29,17 @@ const DEFAULT_COLLAPSED_TEXT = 'Show more';
4729const CodeBlockMessage = ( {
4830 children,
4931 className,
50- 'aria-label' : ariaLabel ,
5132 isExpandable = false ,
5233 expandableSectionProps,
53- expandableSectionToggleProps,
5434 expandedText = DEFAULT_EXPANDED_TEXT ,
5535 collapsedText = DEFAULT_COLLAPSED_TEXT ,
36+ codeEditorProps,
5637 ...props
5738} : CodeBlockMessageProps ) => {
58- const [ copied , setCopied ] = useState ( false ) ;
5939 const [ isExpanded , setIsExpanded ] = useState ( false ) ;
6040
61- const buttonRef = useRef ( ) ;
62- const tooltipID = useId ( ) ;
63- const toggleId = useId ( ) ;
64- const contentId = useId ( ) ;
65- const codeBlockRef = useRef < HTMLDivElement > ( null ) ;
66-
67- const language = / l a n g u a g e - ( \w + ) / . exec ( className || '' ) ?. [ 1 ] ;
41+ const languageString = / l a n g u a g e - ( \w + ) / . exec ( className || '' ) ?. [ 1 ] ;
42+ const language = languageString ? Language [ languageString ] : undefined ;
6843
6944 // Get custom toggle text from data attributes if available - for use with rehype plugins
7045 const customExpandedText = props [ 'data-expanded-text' ] ;
@@ -84,27 +59,10 @@ const CodeBlockMessage = ({
8459 ) ;
8560 }
8661
87- const onToggle = ( isExpanded ) => {
62+ const onToggle = ( _event : React . MouseEvent , isExpanded : boolean ) => {
8863 setIsExpanded ( isExpanded ) ;
8964 } ;
9065
91- // Handle clicking copy button
92- const handleCopy = useCallback ( ( event , text ) => {
93- navigator . clipboard . writeText ( text . toString ( ) ) ;
94- setCopied ( true ) ;
95- } , [ ] ) ;
96-
97- // Reset copied state
98- useEffect ( ( ) => {
99- if ( copied ) {
100- const timer = setTimeout ( ( ) => {
101- setCopied ( false ) ;
102- } , 3000 ) ;
103-
104- return ( ) => clearTimeout ( timer ) ;
105- }
106- } ) ;
107-
10866 if ( ! String ( children ) . includes ( '\n' ) ) {
10967 return (
11068 < code { ...props } className = "pf-chatbot__message-inline-code" >
@@ -113,82 +71,35 @@ const CodeBlockMessage = ({
11371 ) ;
11472 }
11573
116- // Setup code block header
117- const actions = (
118- < >
119- < CodeBlockAction >
120- { language && < div className = "pf-chatbot__message-code-block-language" > { language } </ div > }
121- < Button
122- ref = { buttonRef }
123- aria-label = { ariaLabel ?? 'Copy code' }
124- variant = "plain"
125- className = "pf-chatbot__button--copy"
126- onClick = { ( event ) => handleCopy ( event , children ) }
127- >
128- { copied ? < CheckIcon /> : < CopyIcon /> }
129- </ Button >
130- < Tooltip id = { tooltipID } content = "Copy" position = "top" triggerRef = { buttonRef } />
131- </ CodeBlockAction >
132- </ >
74+ const codeEditor = (
75+ < CodeEditor
76+ // Force remount on state change
77+ // Prevents bug where code editor is 1px high when expandable is opened, closed, and reopened
78+ key = { `code-editor-${ isExpanded } ` }
79+ code = { children ?. toString ( ) }
80+ isReadOnly
81+ isCopyEnabled
82+ isLanguageLabelVisible
83+ language = { language }
84+ height = "sizeToFit"
85+ { ...codeEditorProps }
86+ > </ CodeEditor >
13387 ) ;
13488
13589 return (
136- < div className = "pf-chatbot__message-code-block" ref = { codeBlockRef } >
137- < CodeBlock actions = { actions } >
138- < CodeBlockCode >
139- < >
140- { language ? (
141- // SyntaxHighlighter doesn't work with ExpandableSection because it targets the direct child
142- // Forked for now and adjusted to match what we need
143- < ExpandableSectionForSyntaxHighlighter
144- variant = { ExpandableSectionVariant . truncate }
145- isExpanded = { isExpanded }
146- isDetached
147- toggleId = { toggleId }
148- contentId = { contentId }
149- language = { language }
150- { ...expandableSectionProps }
151- >
152- < SyntaxHighlighter
153- { ...props }
154- language = { language }
155- style = { obsidian }
156- PreTag = "div"
157- CodeTag = "div"
158- wrapLongLines
159- >
160- { String ( children ) . replace ( / \n $ / , '' ) }
161- </ SyntaxHighlighter >
162- </ ExpandableSectionForSyntaxHighlighter >
163- ) : (
164- < ExpandableSection
165- variant = { ExpandableSectionVariant . truncate }
166- isExpanded = { isExpanded }
167- isDetached
168- toggleId = { toggleId }
169- contentId = { contentId }
170- { ...expandableSectionProps }
171- >
172- { children }
173- </ ExpandableSection >
174- ) }
175- </ >
176- </ CodeBlockCode >
177- { isExpandable && (
178- < ExpandableSectionToggle
179- isExpanded = { isExpanded }
180- onToggle = { onToggle }
181- direction = "up"
182- toggleId = { toggleId }
183- contentId = { contentId }
184- hasTruncatedContent
185- className = "pf-chatbot__message-code-toggle"
186- { ...expandableSectionToggleProps }
187- >
188- { isExpanded ? finalExpandedText : finalCollapsedText }
189- </ ExpandableSectionToggle >
190- ) }
191- </ CodeBlock >
90+ < div >
91+ { isExpandable ? (
92+ < ExpandableSection
93+ toggleText = { isExpanded ? finalExpandedText : finalCollapsedText }
94+ onToggle = { onToggle }
95+ isExpanded = { isExpanded }
96+ { ...expandableSectionProps }
97+ >
98+ { codeEditor }
99+ </ ExpandableSection >
100+ ) : (
101+ codeEditor
102+ ) }
192103 </ div >
193104 ) ;
194105} ;
0 commit comments