-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathCodeBlockMessage.tsx
More file actions
193 lines (176 loc) · 6.17 KB
/
Copy pathCodeBlockMessage.tsx
File metadata and controls
193 lines (176 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// ============================================================================
// 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 { css } from '@patternfly/react-styles';
import { RhUiCopyFillIcon } from '@patternfly/react-icons';
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<ExpandableSectionProps, 'ref'>;
/** 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<HTMLDivElement>(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 (
<code {...props} className={`pf-chatbot__message-inline-code ${isPrimary ? 'pf-m-primary' : ''}`}>
{children}
</code>
);
}
// Setup code block header
const actions = (
<>
<CodeBlockAction className="pf-chatbot__message-code-block-default-action">
{language && <div className="pf-chatbot__message-code-block-language">{language}</div>}
<Button
ref={buttonRef}
aria-label={ariaLabel ?? 'Copy code'}
variant="plain"
className="pf-chatbot__button--copy"
onClick={(event) => handleCopy(event, children)}
>
{copied ? <CheckIcon /> : <RhUiCopyFillIcon />}
</Button>
<Tooltip id={tooltipID} content="Copy" position="top" triggerRef={buttonRef} />
</CodeBlockAction>
{customActions}
</>
);
return (
<div className={css('pf-chatbot__message-code-block', shouldRetainStyles && 'pf-m-markdown')} ref={codeBlockRef}>
<CodeBlock actions={actions}>
<CodeBlockCode>
<>
{isExpandable ? (
<ExpandableSection
variant={ExpandableSectionVariant.truncate}
isExpanded={isExpanded}
isDetached
toggleId={toggleId}
contentId={contentId}
{...expandableSectionProps}
>
{children}
</ExpandableSection>
) : (
children
)}
</>
</CodeBlockCode>
{isExpandable && (
<ExpandableSectionToggle
isExpanded={isExpanded}
onToggle={onToggle}
direction="up"
toggleId={toggleId}
contentId={contentId}
hasTruncatedContent
className="pf-chatbot__message-code-toggle"
{...expandableSectionToggleProps}
>
{isExpanded ? finalExpandedText : finalCollapsedText}
</ExpandableSectionToggle>
)}
</CodeBlock>
</div>
);
};
export default CodeBlockMessage;