// ============================================================================ // Chatbot Main - Message - Content - Code Block // ============================================================================ import { useState, useRef, useCallback, useEffect } from 'react'; // Import PatternFly components import { CodeBlock, CodeBlockAction, CodeBlockCode, Button, Tooltip, ExpandableSection, ExpandableSectionToggle, ExpandableSectionProps, ExpandableSectionToggleProps, ExpandableSectionVariant, getUniqueId } from '@patternfly/react-core'; import { CheckIcon } from '@patternfly/react-icons/dist/esm/icons/check-icon'; import { CopyIcon } from '@patternfly/react-icons/dist/esm/icons/copy-icon'; import { css } from '@patternfly/react-styles'; export interface CodeBlockMessageProps { /** Content rendered in code block */ children?: React.ReactNode; /** Aria label applied to code block */ 'aria-label'?: string; /** Class name applied to code block */ className?: string; /** Whether code block is expandable */ isExpandable?: boolean; /** Additional props passed to expandable section if isExpandable is applied */ expandableSectionProps?: Omit; /** Additional props passed to expandable toggle if isExpandable is applied */ expandableSectionToggleProps?: ExpandableSectionToggleProps; /** Link text applied to expandable toggle when expanded */ expandedText?: string; /** Link text applied to expandable toggle when collapsed */ collapsedText?: string; /** Custom actions added to header of code block, after any default actions such as the "copy" action. */ customActions?: React.ReactNode; /** Sets background colors to be appropriate on primary chatbot background */ isPrimary?: boolean; /** Flag indicating that the content should retain message styles when using Markdown. */ shouldRetainStyles?: boolean; } const DEFAULT_EXPANDED_TEXT = 'Show less'; const DEFAULT_COLLAPSED_TEXT = 'Show more'; const CodeBlockMessage = ({ children, className, 'aria-label': ariaLabel, isExpandable = false, expandableSectionProps, expandableSectionToggleProps, expandedText = DEFAULT_EXPANDED_TEXT, collapsedText = DEFAULT_COLLAPSED_TEXT, customActions, isPrimary, shouldRetainStyles, ...props }: CodeBlockMessageProps) => { const [copied, setCopied] = useState(false); const [isExpanded, setIsExpanded] = useState(false); const buttonRef = useRef(); const tooltipID = getUniqueId(); const toggleId = getUniqueId(); const contentId = getUniqueId(); const codeBlockRef = useRef(null); const language = /language-(\w+)/.exec(className || '')?.[1]; // Get custom toggle text from data attributes if available - for use with rehype plugins const customExpandedText = props['data-expanded-text']; const customCollapsedText = props['data-collapsed-text']; const finalExpandedText = customExpandedText || expandedText; const finalCollapsedText = customCollapsedText || collapsedText; if ( (customExpandedText && expandedText !== DEFAULT_EXPANDED_TEXT) || (customCollapsedText && collapsedText !== DEFAULT_COLLAPSED_TEXT) ) { // eslint-disable-next-line no-console console.error( 'Message:', 'Custom rehype plugins that rely on data-expanded-text or data-collapsed-text will override expandedText and collapsedText props if both are passed in.' ); } const onToggle = (isExpanded: boolean) => { setIsExpanded(isExpanded); }; // Handle clicking copy button const handleCopy = useCallback((_event: React.MouseEvent, text: React.ReactNode) => { let textToCopy = ''; if (typeof text === 'string') { textToCopy = text; } else { if (codeBlockRef.current) { const codeElement = codeBlockRef.current.querySelector('code'); textToCopy = codeElement?.textContent || ''; } } navigator.clipboard.writeText(textToCopy); setCopied(true); }, []); // Reset copied state useEffect(() => { if (copied) { const timer = setTimeout(() => { setCopied(false); }, 3000); return () => clearTimeout(timer); } }); if (!String(children).includes('\n')) { return ( {children} ); } // Setup code block header const actions = ( <> {language &&
{language}
}
{customActions} ); return (
<> {isExpandable ? ( {children} ) : ( children )} {isExpandable && ( {isExpanded ? finalExpandedText : finalCollapsedText} )}
); }; export default CodeBlockMessage;