Skip to content

Commit 06c496d

Browse files
feat(CodeBlockMessage/Message): Add expandable code block (#556)
Co-authored-by: Erin Donehoo <[email protected]>
1 parent 359b293 commit 06c496d

File tree

7 files changed

+391
-12
lines changed

7 files changed

+391
-12
lines changed

packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ export const BotMessageExample: FunctionComponent = () => {
1515
const [variant, setVariant] = useState<string>('Code');
1616
const [isOpen, setIsOpen] = useState(false);
1717
const [selected, setSelected] = useState<string>('Message content type');
18+
const [isExpandable, setIsExpanded] = useState(false);
1819

1920
/* eslint-disable indent */
2021
const renderContent = () => {
2122
switch (variant) {
2223
case 'Code':
24+
case 'Expandable code':
2325
return code;
2426
case 'Heading':
2527
return heading;
@@ -166,6 +168,11 @@ _Italic text, formatted with single underscores_
166168
setVariant(value);
167169
setSelected(value as string);
168170
setIsOpen(false);
171+
if (value === 'Expandable code') {
172+
setIsExpanded(true);
173+
} else {
174+
setIsExpanded(false);
175+
}
169176
};
170177

171178
const onToggleClick = () => {
@@ -237,6 +244,7 @@ _Italic text, formatted with single underscores_
237244
>
238245
<SelectList>
239246
<SelectOption value="Code">Code</SelectOption>
247+
<SelectOption value="Expandable code">Expandable code</SelectOption>
240248
<SelectOption value="Inline code">Inline code</SelectOption>
241249
<SelectOption value="Heading">Heading</SelectOption>
242250
<SelectOption value="Block quotes">Block quotes</SelectOption>
@@ -259,6 +267,7 @@ _Italic text, formatted with single underscores_
259267
variant === 'Table' ? { 'aria-label': 'App information and user roles for bot messages' } : undefined
260268
}
261269
error={variant === 'Error' ? error : undefined}
270+
codeBlockProps={{ isExpandable, expandableSectionProps: { truncateMaxLines: isExpandable ? 1 : undefined } }}
262271
/>
263272
</>
264273
);

packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ export const UserMessageExample: FunctionComponent = () => {
1515
const [isEditable, setIsEditable] = useState<boolean>(true);
1616
const [isOpen, setIsOpen] = useState<boolean>(false);
1717
const [selected, setSelected] = useState<string>('Message content type');
18+
const [isExpandable, setIsExpanded] = useState(false);
1819

1920
/* eslint-disable indent */
2021
const renderContent = () => {
2122
switch (variant) {
2223
case 'Code':
24+
case 'Expandable code':
2325
return code;
2426
case 'Inline code':
2527
return inlineCode;
@@ -166,6 +168,11 @@ _Italic text, formatted with single underscores_
166168
setVariant(value);
167169
setSelected(value as string);
168170
setIsOpen(false);
171+
if (value === 'Expandable code') {
172+
setIsExpanded(true);
173+
} else {
174+
setIsExpanded(false);
175+
}
169176
};
170177

171178
const onToggleClick = () => {
@@ -215,6 +222,7 @@ _Italic text, formatted with single underscores_
215222
>
216223
<SelectList>
217224
<SelectOption value="Code">Code</SelectOption>
225+
<SelectOption value="Expandable code">Expandable code</SelectOption>
218226
<SelectOption value="Inline code">Inline code</SelectOption>
219227
<SelectOption value="Heading">Heading</SelectOption>
220228
<SelectOption value="Block quotes">Block quotes</SelectOption>
@@ -241,6 +249,7 @@ _Italic text, formatted with single underscores_
241249
error={variant === 'Error' ? error : undefined}
242250
onEditUpdate={() => setIsEditable(false)}
243251
onEditCancel={() => setIsEditable(false)}
252+
codeBlockProps={{ isExpandable, expandableSectionProps: { truncateMaxLines: isExpandable ? 1 : undefined } }}
244253
/>
245254
</>
246255
);

packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,10 @@
8080
background-color: var(--pf-t--global--background--color--tertiary--default);
8181
font-size: var(--pf-t--global--font--size--body--default);
8282
}
83+
84+
.pf-chatbot__message-code-toggle {
85+
.pf-v6-c-button.pf-m-link {
86+
--pf-v6-c-button--m-link--Color: var(--pf-t--global--color--nonstatus--blue--default);
87+
--pf-v6-c-button--hover--Color: var(--pf-t--global--color--nonstatus--blue--hover);
88+
}
89+
}

packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.tsx

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,68 @@ import { useState, useRef, useId, useCallback, useEffect } from 'react';
55
import SyntaxHighlighter from 'react-syntax-highlighter';
66
import { obsidian } from 'react-syntax-highlighter/dist/esm/styles/hljs';
77
// Import PatternFly components
8-
import { CodeBlock, CodeBlockAction, CodeBlockCode, Button, Tooltip } from '@patternfly/react-core';
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';
920

1021
import { CheckIcon } from '@patternfly/react-icons/dist/esm/icons/check-icon';
1122
import { CopyIcon } from '@patternfly/react-icons/dist/esm/icons/copy-icon';
12-
import { ExtraProps } from 'react-markdown';
23+
import { ExpandableSectionForSyntaxHighlighter } from './ExpandableSectionForSyntaxHighlighter';
24+
25+
export interface CodeBlockMessageProps {
26+
/** Content rendered in code block */
27+
children?: React.ReactNode;
28+
/** Aria label applied to code block */
29+
'aria-label'?: string;
30+
/** Class name applied to code block */
31+
className?: string;
32+
/** Whether code block is expandable */
33+
isExpandable?: boolean;
34+
/** Additional props passed to expandable section if isExpandable is applied */
35+
expandableSectionProps?: Omit<ExpandableSectionProps, 'ref'>;
36+
/** Additional props passed to expandable toggle if isExpandable is applied */
37+
expandableSectionToggleProps?: ExpandableSectionToggleProps;
38+
/** Link text applied to expandable toggle when expanded */
39+
expandedText?: string;
40+
/** Link text applied to expandable toggle when collapsed */
41+
collapsedText?: string;
42+
}
1343

1444
const CodeBlockMessage = ({
1545
children,
1646
className,
1747
'aria-label': ariaLabel,
48+
isExpandable = false,
49+
expandableSectionProps,
50+
expandableSectionToggleProps,
51+
expandedText = 'Show less',
52+
collapsedText = 'Show more',
1853
...props
19-
}: Omit<JSX.IntrinsicElements['code'], 'ref'> & ExtraProps) => {
54+
}: CodeBlockMessageProps) => {
2055
const [copied, setCopied] = useState(false);
56+
const [isExpanded, setIsExpanded] = useState(false);
2157

22-
const buttonRef = useRef(undefined);
58+
const buttonRef = useRef();
2359
const tooltipID = useId();
60+
const toggleId = useId();
61+
const contentId = useId();
62+
const codeBlockRef = useRef<HTMLDivElement>(null);
2463

2564
const language = /language-(\w+)/.exec(className || '')?.[1];
2665

66+
const onToggle = (isExpanded) => {
67+
setIsExpanded(isExpanded);
68+
};
69+
2770
// Handle clicking copy button
2871
const handleCopy = useCallback((event, text) => {
2972
navigator.clipboard.writeText(text.toString());
@@ -69,17 +112,61 @@ const CodeBlockMessage = ({
69112
);
70113

71114
return (
72-
<div className="pf-chatbot__message-code-block">
115+
<div className="pf-chatbot__message-code-block" ref={codeBlockRef}>
73116
<CodeBlock actions={actions}>
74117
<CodeBlockCode>
75-
{language ? (
76-
<SyntaxHighlighter {...props} language={language} style={obsidian} PreTag="div" CodeTag="div" wrapLongLines>
77-
{String(children).replace(/\n$/, '')}
78-
</SyntaxHighlighter>
79-
) : (
80-
<>{children}</>
81-
)}
118+
<>
119+
{language ? (
120+
// SyntaxHighlighter doesn't work with ExpandableSection because it targets the direct child
121+
// Forked for now and adjusted to match what we need
122+
<ExpandableSectionForSyntaxHighlighter
123+
variant={ExpandableSectionVariant.truncate}
124+
isExpanded={isExpanded}
125+
isDetached
126+
toggleId={toggleId}
127+
contentId={contentId}
128+
language={language}
129+
{...expandableSectionProps}
130+
>
131+
<SyntaxHighlighter
132+
{...props}
133+
language={language}
134+
style={obsidian}
135+
PreTag="div"
136+
CodeTag="div"
137+
wrapLongLines
138+
>
139+
{String(children).replace(/\n$/, '')}
140+
</SyntaxHighlighter>
141+
</ExpandableSectionForSyntaxHighlighter>
142+
) : (
143+
<ExpandableSection
144+
variant={ExpandableSectionVariant.truncate}
145+
isExpanded={isExpanded}
146+
isDetached
147+
toggleId={toggleId}
148+
contentId={contentId}
149+
{...expandableSectionProps}
150+
>
151+
{children}
152+
</ExpandableSection>
153+
)}
154+
</>
82155
</CodeBlockCode>
156+
{isExpandable && (
157+
<ExpandableSectionToggle
158+
isExpanded={isExpanded}
159+
onToggle={onToggle}
160+
direction="up"
161+
toggleId={toggleId}
162+
contentId={contentId}
163+
hasTruncatedContent
164+
className="pf-chatbot__message-code-toggle"
165+
{...expandableSectionToggleProps}
166+
>
167+
{isExpanded ? expandedText : collapsedText}
168+
</ExpandableSectionToggle>
169+
)}
83170
</CodeBlock>
84171
</div>
85172
);

0 commit comments

Comments
 (0)