Skip to content

Commit 57dc7b0

Browse files
feat(Message): Add components that behave well on white background (#691)
Added styles so components behave well on primary ChatBot background color (white). The specific components are the attachment label, table, loading indicator, and inline code block.
1 parent 9ae1deb commit 57dc7b0

14 files changed

Lines changed: 117 additions & 18 deletions

File tree

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/WhiteEmbeddedChatbot.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ const MessageLoading = () => (
9797
export default MessageLoading;
9898
9999
~~~
100+
101+
Here is a table:
102+
103+
| Version | GA date | User role
104+
|-|-|-|
105+
| 2.5 | September 30, 2024 | Administrator |
106+
| 2.5 | June 27, 2023 | Editor |
107+
| 3.0 | April 1, 2025 | Administrator
100108
`;
101109

102110
// It's important to set a date and timestamp prop since the Message components re-render.
@@ -111,7 +119,8 @@ const initialMessages: MessageProps[] = [
111119
name: 'User',
112120
avatar: userAvatar,
113121
timestamp: date.toLocaleString(),
114-
avatarProps: { isBordered: true }
122+
avatarProps: { isBordered: true },
123+
isPrimary: true
115124
},
116125
{
117126
id: '2',
@@ -131,7 +140,9 @@ const initialMessages: MessageProps[] = [
131140
download: { onClick: () => console.log('Download') },
132141
// eslint-disable-next-line no-console
133142
listen: { onClick: () => console.log('Listen') }
134-
}
143+
},
144+
isPrimary: true,
145+
attachments: [{ name: 'auth-operator.yml', id: '1' }]
135146
}
136147
];
137148

@@ -220,7 +231,8 @@ export const EmbeddedChatbotDemo: FunctionComponent = () => {
220231
name: 'User',
221232
avatar: userAvatar,
222233
timestamp: date.toLocaleString(),
223-
avatarProps: { isBordered: true }
234+
avatarProps: { isBordered: true },
235+
isPrimary: true
224236
});
225237
newMessages.push({
226238
id: generateId(),
@@ -229,7 +241,8 @@ export const EmbeddedChatbotDemo: FunctionComponent = () => {
229241
name: 'Bot',
230242
avatar: patternflyAvatar,
231243
isLoading: true,
232-
timestamp: date.toLocaleString()
244+
timestamp: date.toLocaleString(),
245+
isPrimary: true
233246
});
234247
setMessages(newMessages);
235248
// make announcement to assistive devices that new messages have been added
@@ -261,7 +274,8 @@ export const EmbeddedChatbotDemo: FunctionComponent = () => {
261274
// eslint-disable-next-line no-console
262275
listen: { onClick: () => console.log('Listen') }
263276
},
264-
timestamp: date.toLocaleString()
277+
timestamp: date.toLocaleString(),
278+
isPrimary: true
265279
});
266280
setMessages(loadedMessages);
267281
// make announcement to assistive devices that new message has loaded
399 Bytes
Loading

packages/module/src/FileDetailsLabel/FileDetailsLabel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { PropsWithChildren } from 'react';
2-
import { Button, Label } from '@patternfly/react-core';
2+
import { Button, Label, LabelProps } from '@patternfly/react-core';
33
import FileDetails from '../FileDetails';
44
import { Spinner } from '@patternfly/react-core';
55
import { TimesIcon } from '@patternfly/react-icons';
66

7-
export interface FileDetailsLabelProps {
7+
export interface FileDetailsLabelProps extends Omit<LabelProps, 'onClose' | 'onClick'> {
88
/** Name of file, including extension */
99
fileName: string;
1010
/** Unique id of file */

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@
8181
--pf-chatbot-message-text-inline-code-font-size: var(--pf-t--global--font--size--body--default);
8282
background-color: var(--pf-t--global--background--color--tertiary--default);
8383
font-size: var(--pf-chatbot-message-text-inline-code-font-size);
84+
85+
&.pf-m-primary {
86+
background-color: var(--pf-t--global--background--color--secondary--default);
87+
}
8488
}
8589

8690
.pf-chatbot__message-code-toggle {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export interface CodeBlockMessageProps {
3939
collapsedText?: string;
4040
/** Custom actions added to header of code block, after any default actions such as the "copy" action. */
4141
customActions?: React.ReactNode;
42+
/** Sets background colors to be appropriate on primary chatbot background */
43+
isPrimary?: boolean;
4244
}
4345

4446
const DEFAULT_EXPANDED_TEXT = 'Show less';
@@ -54,6 +56,7 @@ const CodeBlockMessage = ({
5456
expandedText = DEFAULT_EXPANDED_TEXT,
5557
collapsedText = DEFAULT_COLLAPSED_TEXT,
5658
customActions,
59+
isPrimary,
5760
...props
5861
}: CodeBlockMessageProps) => {
5962
const [copied, setCopied] = useState(false);
@@ -108,7 +111,7 @@ const CodeBlockMessage = ({
108111

109112
if (!String(children).includes('\n')) {
110113
return (
111-
<code {...props} className="pf-chatbot__message-inline-code">
114+
<code {...props} className={`pf-chatbot__message-inline-code ${isPrimary ? 'pf-m-primary' : ''}`}>
112115
{children}
113116
</code>
114117
);

packages/module/src/Message/Message.test.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,4 +1093,41 @@ describe('Message', () => {
10931093
expect(screen.getByText('Thought for 3 seconds')).toBeTruthy();
10941094
expect(screen.getByText("Here's why I said this.")).toBeTruthy();
10951095
});
1096+
it('should handle isPrimary correctly for inline code when it is true', () => {
1097+
const { container } = render(<Message avatar="./img" role="user" name="User" content={INLINE_CODE} isPrimary />);
1098+
expect(container.querySelector('.pf-m-primary')).toBeTruthy();
1099+
});
1100+
it('should handle isPrimary correctly for inline code when it is false', () => {
1101+
const { container } = render(<Message avatar="./img" role="user" name="User" content={INLINE_CODE} />);
1102+
expect(container.querySelector('.pf-m-primary')).toBeFalsy();
1103+
});
1104+
it('should handle isPrimary correctly for table when it is true', () => {
1105+
const { container } = render(<Message avatar="./img" role="user" name="User" content={TABLE} isPrimary />);
1106+
expect(container.querySelector('.pf-m-primary')).toBeTruthy();
1107+
});
1108+
it('should handle isPrimary correctly for table when it is false', () => {
1109+
const { container } = render(<Message avatar="./img" role="user" name="User" content={TABLE} />);
1110+
expect(container.querySelector('.pf-m-primary')).toBeFalsy();
1111+
});
1112+
it('should handle isPrimary correctly for loading when it is true', () => {
1113+
const { container } = render(<Message avatar="./img" role="user" name="User" content="" isPrimary isLoading />);
1114+
expect(container.querySelector('.pf-m-primary')).toBeTruthy();
1115+
});
1116+
it('should handle isPrimary correctly for loading when it is false', () => {
1117+
const { container } = render(<Message avatar="./img" role="user" name="User" content="" isLoading />);
1118+
1119+
expect(container.querySelector('.pf-m-primary')).toBeFalsy();
1120+
});
1121+
it('should handle isPrimary correctly for attachments when it is true', () => {
1122+
const { container } = render(
1123+
<Message avatar="./img" role="user" name="User" content="" isPrimary attachments={[{ name: 'testAttachment' }]} />
1124+
);
1125+
expect(container.querySelector('.pf-m-outline')).toBeTruthy();
1126+
});
1127+
it('should handle isPrimary correctly for attachments when it is false', () => {
1128+
const { container } = render(
1129+
<Message avatar="./img" role="user" name="User" content="" attachments={[{ name: 'testAttachment' }]} />
1130+
);
1131+
expect(container.querySelector('.pf-m-outline')).toBeFalsy();
1132+
});
10961133
});

packages/module/src/Message/Message.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
189189
toolCall?: ToolCallProps;
190190
/** Whether user messages default to stripping out images in markdown */
191191
hasNoImagesInUserMessages?: boolean;
192+
/** Sets background colors to be appropriate on primary chatbot background */
193+
isPrimary?: boolean;
192194
}
193195

194196
export const MessageBase: FunctionComponent<MessageProps> = ({
@@ -236,6 +238,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
236238
remarkGfmProps,
237239
toolCall,
238240
hasNoImagesInUserMessages = true,
241+
isPrimary,
239242
...props
240243
}: MessageProps) => {
241244
const [messageText, setMessageText] = useState(content);
@@ -286,13 +289,13 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
286289
p: (props) => {
287290
// eslint-disable-next-line @typescript-eslint/no-unused-vars
288291
const { node, ...rest } = props;
289-
return <TextMessage component={ContentVariants.p} {...rest} />;
292+
return <TextMessage component={ContentVariants.p} {...rest} isPrimary={isPrimary} />;
290293
},
291294
code: ({ children, ...props }) => {
292295
// eslint-disable-next-line @typescript-eslint/no-unused-vars
293296
const { node, ...codeProps } = props;
294297
return (
295-
<CodeBlockMessage {...codeProps} {...codeBlockProps}>
298+
<CodeBlockMessage {...codeProps} {...codeBlockProps} isPrimary={isPrimary}>
296299
{children}
297300
</CodeBlockMessage>
298301
);
@@ -348,7 +351,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
348351
return <ListItemMessage {...rest} />;
349352
},
350353
// table requires node attribute for calculating headers for mobile breakpoint
351-
table: (props) => <TableMessage {...props} {...tableProps} />,
354+
table: (props) => <TableMessage {...props} {...tableProps} isPrimary={isPrimary} />,
352355
tbody: (props) => {
353356
// eslint-disable-next-line @typescript-eslint/no-unused-vars
354357
const { node, ...rest } = props;
@@ -416,7 +419,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
416419

417420
const renderMessage = () => {
418421
if (isLoading) {
419-
return <MessageLoading loadingWord={loadingWord} />;
422+
return <MessageLoading loadingWord={loadingWord} isPrimary={isPrimary} />;
420423
}
421424
if (isEditable) {
422425
return (
@@ -522,6 +525,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
522525
closeButtonAriaLabel={attachment.closeButtonAriaLabel}
523526
languageTestId={attachment.languageTestId}
524527
spinnerTestId={attachment.spinnerTestId}
528+
variant={isPrimary ? 'outline' : undefined}
525529
/>
526530
</div>
527531
))}

packages/module/src/Message/MessageLoading.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,8 @@
5050
background-color: rgba(41, 41, 41, 0.25);
5151
}
5252
}
53+
54+
&.pf-m-primary {
55+
background-color: var(--pf-t--global--background--color--secondary--default);
56+
}
5357
}

packages/module/src/Message/MessageLoading.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// Chatbot Main - Message - Processing
33
// ============================================================================
44

5-
const MessageLoading = ({ loadingWord }) => (
6-
<div className="pf-chatbot__message-loading">
5+
const MessageLoading = ({ loadingWord, isPrimary }) => (
6+
<div className={`pf-chatbot__message-loading ${isPrimary ? 'pf-m-primary' : ''}`}>
77
<span className="pf-chatbot__message-loading-dots">
88
<span className="pf-v6-screen-reader">{loadingWord}</span>
99
</span>

0 commit comments

Comments
 (0)