@patternfly/chatbot 6.4.1 → 6.5.0-prerelease.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/cjs/AttachMenu/AttachMenu.d.ts +8 -2
  2. package/dist/cjs/AttachMenu/AttachMenu.js +2 -2
  3. package/dist/cjs/ChatbotContent/ChatbotContent.d.ts +2 -0
  4. package/dist/cjs/ChatbotContent/ChatbotContent.js +2 -2
  5. package/dist/cjs/ChatbotContent/ChatbotContent.test.js +4 -0
  6. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +3 -1
  7. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +3 -3
  8. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +4 -0
  9. package/dist/cjs/ChatbotFooter/ChatbotFooter.d.ts +5 -2
  10. package/dist/cjs/ChatbotFooter/ChatbotFooter.js +2 -2
  11. package/dist/cjs/ChatbotFooter/ChatbotFooter.test.js +5 -1
  12. package/dist/cjs/CodeModal/CodeModal.js +40 -4
  13. package/dist/cjs/FileDetailsLabel/FileDetailsLabel.d.ts +2 -1
  14. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.d.ts +5 -1
  15. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.js +3 -3
  16. package/dist/cjs/Message/Message.d.ts +6 -19
  17. package/dist/cjs/Message/Message.js +10 -7
  18. package/dist/cjs/Message/Message.test.js +38 -0
  19. package/dist/cjs/Message/MessageLoading.d.ts +2 -1
  20. package/dist/cjs/Message/MessageLoading.js +1 -1
  21. package/dist/cjs/Message/TableMessage/TableMessage.d.ts +4 -1
  22. package/dist/cjs/Message/TableMessage/TableMessage.js +2 -2
  23. package/dist/cjs/Message/TextMessage/TextMessage.d.ts +4 -1
  24. package/dist/cjs/Message/TextMessage/TextMessage.js +2 -2
  25. package/dist/cjs/MessageBar/AttachButton.d.ts +2 -0
  26. package/dist/cjs/MessageBar/AttachButton.js +2 -2
  27. package/dist/cjs/MessageBar/AttachButton.test.js +4 -0
  28. package/dist/cjs/MessageBar/MessageBar.d.ts +16 -6
  29. package/dist/cjs/MessageBar/MessageBar.js +6 -5
  30. package/dist/cjs/MessageBar/MessageBar.test.js +62 -0
  31. package/dist/cjs/__mocks__/monaco-editor.d.ts +11 -0
  32. package/dist/cjs/__mocks__/monaco-editor.js +18 -0
  33. package/dist/cjs/__mocks__/rehype-highlight.d.ts +2 -0
  34. package/dist/cjs/__mocks__/rehype-highlight.js +4 -0
  35. package/dist/css/main.css +81 -10
  36. package/dist/css/main.css.map +1 -1
  37. package/dist/esm/AttachMenu/AttachMenu.d.ts +8 -2
  38. package/dist/esm/AttachMenu/AttachMenu.js +2 -2
  39. package/dist/esm/ChatbotContent/ChatbotContent.d.ts +2 -0
  40. package/dist/esm/ChatbotContent/ChatbotContent.js +2 -2
  41. package/dist/esm/ChatbotContent/ChatbotContent.test.js +4 -0
  42. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +3 -1
  43. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +3 -3
  44. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +4 -0
  45. package/dist/esm/ChatbotFooter/ChatbotFooter.d.ts +5 -2
  46. package/dist/esm/ChatbotFooter/ChatbotFooter.js +2 -2
  47. package/dist/esm/ChatbotFooter/ChatbotFooter.test.js +5 -1
  48. package/dist/esm/CodeModal/CodeModal.js +42 -6
  49. package/dist/esm/FileDetailsLabel/FileDetailsLabel.d.ts +2 -1
  50. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.d.ts +5 -1
  51. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.js +3 -3
  52. package/dist/esm/Message/Message.d.ts +6 -19
  53. package/dist/esm/Message/Message.js +10 -7
  54. package/dist/esm/Message/Message.test.js +39 -1
  55. package/dist/esm/Message/MessageLoading.d.ts +2 -1
  56. package/dist/esm/Message/MessageLoading.js +1 -1
  57. package/dist/esm/Message/TableMessage/TableMessage.d.ts +4 -1
  58. package/dist/esm/Message/TableMessage/TableMessage.js +2 -2
  59. package/dist/esm/Message/TextMessage/TextMessage.d.ts +4 -1
  60. package/dist/esm/Message/TextMessage/TextMessage.js +2 -2
  61. package/dist/esm/MessageBar/AttachButton.d.ts +2 -0
  62. package/dist/esm/MessageBar/AttachButton.js +2 -2
  63. package/dist/esm/MessageBar/AttachButton.test.js +4 -0
  64. package/dist/esm/MessageBar/MessageBar.d.ts +16 -6
  65. package/dist/esm/MessageBar/MessageBar.js +6 -5
  66. package/dist/esm/MessageBar/MessageBar.test.js +62 -0
  67. package/dist/esm/__mocks__/monaco-editor.d.ts +11 -0
  68. package/dist/esm/__mocks__/monaco-editor.js +18 -0
  69. package/dist/esm/__mocks__/rehype-highlight.d.ts +2 -0
  70. package/dist/esm/__mocks__/rehype-highlight.js +2 -0
  71. package/dist/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +5 -2
  73. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx +3 -1
  74. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithActions.tsx +14 -14
  75. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithSelection.tsx +14 -14
  76. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotMessageBarAttach.tsx +2 -2
  77. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotMessageBarIndicatorThinking.tsx +15 -0
  78. package/patternfly-docs/content/extensions/chatbot/examples/UI/Settings.tsx +1 -1
  79. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +10 -0
  80. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +12 -4
  81. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotAttachmentMenu.tsx +2 -2
  82. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +1 -1
  83. package/patternfly-docs/content/extensions/chatbot/examples/demos/WhiteEmbeddedChatbot.tsx +451 -0
  84. package/patternfly-docs/patternfly-docs.config.js +1 -0
  85. package/src/AttachMenu/AttachMenu.tsx +26 -11
  86. package/src/Chatbot/Chatbot.scss +23 -1
  87. package/src/ChatbotContent/ChatbotContent.scss +4 -0
  88. package/src/ChatbotContent/ChatbotContent.test.tsx +5 -0
  89. package/src/ChatbotContent/ChatbotContent.tsx +4 -1
  90. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx +5 -0
  91. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +7 -4
  92. package/src/ChatbotFooter/ChatbotFooter.scss +21 -0
  93. package/src/ChatbotFooter/ChatbotFooter.test.tsx +10 -1
  94. package/src/ChatbotFooter/ChatbotFooter.tsx +10 -3
  95. package/src/ChatbotHeader/ChatbotHeader.scss +19 -0
  96. package/src/CodeModal/CodeModal.tsx +58 -7
  97. package/src/FileDetailsLabel/FileDetailsLabel.tsx +2 -2
  98. package/src/Message/CodeBlockMessage/CodeBlockMessage.scss +7 -2
  99. package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +9 -2
  100. package/src/Message/Message.test.tsx +56 -1
  101. package/src/Message/Message.tsx +14 -26
  102. package/src/Message/MessageLoading.scss +7 -0
  103. package/src/Message/MessageLoading.tsx +2 -2
  104. package/src/Message/TableMessage/TableMessage.scss +4 -0
  105. package/src/Message/TableMessage/TableMessage.tsx +6 -2
  106. package/src/Message/TextMessage/TextMessage.scss +12 -0
  107. package/src/Message/TextMessage/TextMessage.tsx +11 -2
  108. package/src/Message/UserFeedback/UserFeedback.scss +2 -1
  109. package/src/MessageBar/AttachButton.test.tsx +4 -0
  110. package/src/MessageBar/AttachButton.tsx +4 -1
  111. package/src/MessageBar/MessageBar.scss +11 -5
  112. package/src/MessageBar/MessageBar.test.tsx +102 -1
  113. package/src/MessageBar/MessageBar.tsx +44 -11
  114. package/src/__mocks__/monaco-editor.ts +19 -0
  115. package/src/__mocks__/rehype-highlight.ts +3 -0
@@ -11,6 +11,10 @@
11
11
  flex-direction: column;
12
12
  row-gap: var(--pf-chatbot__footer--RowGap);
13
13
  position: relative; // this is so focus ring on parent chatbot doesn't include footer
14
+
15
+ &.pf-m-primary {
16
+ background-color: var(--pf-t--global--background--color--primary--default);
17
+ }
14
18
  }
15
19
  .pf-chatbot__footer-container {
16
20
  padding: 0 var(--pf-t--global--spacer--lg) var(--pf-t--global--spacer--lg) var(--pf-t--global--spacer--lg);
@@ -62,3 +66,20 @@
62
66
  padding: 0 var(--pf-t--global--spacer--sm) var(--pf-t--global--spacer--sm) var(--pf-t--global--spacer--sm);
63
67
  row-gap: var(--pf-t--global--spacer--sm);
64
68
  }
69
+
70
+ // ============================================================================
71
+ // High contrast
72
+ // ============================================================================
73
+ :root:where(.pf-v6-theme-high-contrast) {
74
+ // Chatbot Display Mode - Fullscreen and Embedded
75
+ @media screen and (min-width: 64rem) {
76
+ .pf-chatbot--embedded,
77
+ .pf-chatbot--fullscreen {
78
+ .pf-chatbot__footer {
79
+ .pf-v6-c-divider {
80
+ display: var(--pf-v6-hidden-visible--Display);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
@@ -15,10 +15,19 @@ describe('ChatbotFooter', () => {
15
15
 
16
16
  it('should handle isCompact', () => {
17
17
  render(
18
- <ChatbotFooter className="custom-class" isCompact data-testid="footer">
18
+ <ChatbotFooter isCompact data-testid="footer">
19
19
  Chatbot Content
20
20
  </ChatbotFooter>
21
21
  );
22
22
  expect(screen.getByTestId('footer')).toHaveClass('pf-m-compact');
23
23
  });
24
+
25
+ it('should handle isPrimary', () => {
26
+ render(
27
+ <ChatbotFooter isPrimary data-testid="footer">
28
+ Chatbot Content
29
+ </ChatbotFooter>
30
+ );
31
+ expect(screen.getByTestId('footer')).toHaveClass('pf-m-primary');
32
+ });
24
33
  });
@@ -13,20 +13,27 @@ import type { HTMLProps, FunctionComponent } from 'react';
13
13
  import { Divider } from '@patternfly/react-core';
14
14
 
15
15
  export interface ChatbotFooterProps extends HTMLProps<HTMLDivElement> {
16
- /** Children for the Footer that supports MessageBar and FootNote components*/
16
+ /** Children for the footer - supports MessageBar and FootNote components*/
17
17
  children?: React.ReactNode;
18
- /** Custom classname for the Footer component */
18
+ /** Custom classname for the footer component */
19
19
  className?: string;
20
+ /** Sets footer to compact styling. */
20
21
  isCompact?: boolean;
22
+ /** Sets background color to primary */
23
+ isPrimary?: boolean;
21
24
  }
22
25
 
23
26
  export const ChatbotFooter: FunctionComponent<ChatbotFooterProps> = ({
24
27
  children,
25
28
  className,
26
29
  isCompact,
30
+ isPrimary,
27
31
  ...props
28
32
  }: ChatbotFooterProps) => (
29
- <div className={`pf-chatbot__footer ${isCompact ? 'pf-m-compact' : ''} ${className ?? ''}`} {...props}>
33
+ <div
34
+ className={`pf-chatbot__footer ${isCompact ? 'pf-m-compact' : ''} ${isPrimary ? 'pf-m-primary' : ''} ${className ?? ''}`}
35
+ {...props}
36
+ >
30
37
  <Divider />
31
38
  <div className="pf-chatbot__footer-container">{children}</div>
32
39
  </div>
@@ -91,6 +91,10 @@
91
91
  align-items: center;
92
92
  justify-content: center;
93
93
 
94
+ &::before {
95
+ border-radius: inherit;
96
+ }
97
+
94
98
  .pf-v6-c-button__icon,
95
99
  .pf-v6-c-menu-toggle__icon,
96
100
  .pf-v6-c-icon__content {
@@ -162,3 +166,18 @@
162
166
  .pf-chatbot__header .pf-chatbot__actions .pf-v6-c-menu-toggle.pf-m-secondary.pf-m-compact {
163
167
  width: initial;
164
168
  }
169
+
170
+ // ============================================================================
171
+ // High contrast
172
+ // ============================================================================
173
+ :root:where(.pf-v6-theme-high-contrast) {
174
+ // Chatbot Display Mode - Fullscreen and Embedded
175
+ @media screen and (min-width: 64rem) {
176
+ .pf-chatbot--fullscreen,
177
+ .pf-chatbot--embedded {
178
+ .pf-chatbot__header__divider {
179
+ display: var(--pf-v6-hidden-visible--Display);
180
+ }
181
+ }
182
+ }
183
+ }
@@ -1,17 +1,31 @@
1
1
  // ============================================================================
2
2
  // Code Modal - Chatbot Modal with Code Editor
3
3
  // ============================================================================
4
+
4
5
  import type { FunctionComponent, MouseEvent } from 'react';
5
- import { useState } from 'react';
6
+ import { useState, useEffect, useRef } from 'react';
6
7
  import path from 'path-browserify';
8
+ import * as monaco from 'monaco-editor';
9
+ import { loader } from '@monaco-editor/react';
7
10
 
8
11
  // Import PatternFly components
9
12
  import { CodeEditor } from '@patternfly/react-code-editor';
10
- import { Button, ModalBody, ModalFooter, ModalHeader, Stack, StackItem } from '@patternfly/react-core';
13
+ import {
14
+ Button,
15
+ getResizeObserver,
16
+ ModalBody,
17
+ ModalFooter,
18
+ ModalHeader,
19
+ Stack,
20
+ StackItem
21
+ } from '@patternfly/react-core';
11
22
  import FileDetails, { extensionToLanguage } from '../FileDetails';
12
23
  import { ChatbotDisplayMode } from '../Chatbot';
13
24
  import ChatbotModal from '../ChatbotModal/ChatbotModal';
14
25
 
26
+ // Configure Monaco loader to use the npm package instead of CDN
27
+ loader.config({ monaco });
28
+
15
29
  export interface CodeModalProps {
16
30
  /** Class applied to code editor */
17
31
  codeEditorControlClassName?: string;
@@ -73,6 +87,34 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
73
87
  ...props
74
88
  }: CodeModalProps) => {
75
89
  const [newCode, setNewCode] = useState(code);
90
+ const [editorInstance, setEditorInstance] = useState<monaco.editor.IStandaloneCodeEditor | null>(null);
91
+ const [isEditorReady, setIsEditorReady] = useState(false);
92
+ const containerRef = useRef<HTMLDivElement>(null);
93
+
94
+ useEffect(() => {
95
+ if (!isModalOpen || !isEditorReady || !editorInstance || !containerRef.current) {
96
+ return;
97
+ }
98
+
99
+ const handleResize = () => {
100
+ if (editorInstance && isEditorReady && isModalOpen) {
101
+ try {
102
+ window.requestAnimationFrame(() => {
103
+ editorInstance.layout();
104
+ });
105
+ } catch (error) {
106
+ // eslint-disable-next-line no-console
107
+ console.error('ChatBot code modal layout error:', error);
108
+ }
109
+ }
110
+ };
111
+
112
+ const observer = getResizeObserver(containerRef.current, handleResize);
113
+
114
+ return () => {
115
+ observer();
116
+ };
117
+ }, [editorInstance, isEditorReady, isModalOpen]);
76
118
 
77
119
  const handlePrimaryAction = (_event: MouseEvent | MouseEvent | KeyboardEvent) => {
78
120
  handleModalToggle(_event);
@@ -89,9 +131,15 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
89
131
  };
90
132
 
91
133
  const onEditorDidMount = (editor, monaco) => {
92
- editor.layout();
93
- editor.focus();
134
+ setEditorInstance(editor);
135
+
94
136
  monaco.editor.getModels()[0].updateOptions({ tabSize: 5 });
137
+
138
+ if (containerRef.current) {
139
+ setIsEditorReady(true);
140
+ editor.layout();
141
+ editor.focus();
142
+ }
95
143
  };
96
144
 
97
145
  const onCodeChange = (value: string) => {
@@ -117,7 +165,7 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
117
165
  <StackItem className="pf-chatbot__code-modal-file-details">
118
166
  <FileDetails fileName={fileName} />
119
167
  </StackItem>
120
- <StackItem className="pf-chatbot__code-modal-editor">
168
+ <div className="pf-v6-l-stack__item pf-chatbot__code-modal-editor" ref={containerRef}>
121
169
  <CodeEditor
122
170
  isDarkTheme
123
171
  isLineNumbersVisible={isLineNumbersVisible}
@@ -132,11 +180,14 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
132
180
  isFullHeight
133
181
  options={{
134
182
  glyphMargin: false,
135
- folding: false
183
+ folding: false,
184
+ // prevents Monaco from handling resizing itself
185
+ // was causing ResizeObserver issues
186
+ automaticLayout: false
136
187
  }}
137
188
  {...props}
138
189
  />
139
- </StackItem>
190
+ </div>
140
191
  </Stack>
141
192
  </ModalBody>
142
193
  <ModalFooter className={modalFooterClassName}>
@@ -1,10 +1,10 @@
1
1
  import { PropsWithChildren } from 'react';
2
- import { Button, Label } from '@patternfly/react-core';
2
+ import { Button, Label, LabelProps } from '@patternfly/react-core';
3
3
  import FileDetails from '../FileDetails';
4
4
  import { Spinner } from '@patternfly/react-core';
5
5
  import { TimesIcon } from '@patternfly/react-icons';
6
6
 
7
- export interface FileDetailsLabelProps {
7
+ export interface FileDetailsLabelProps extends Omit<LabelProps, 'onClose' | 'onClick'> {
8
8
  /** Name of file, including extension */
9
9
  fileName: string;
10
10
  /** Unique id of file */
@@ -30,7 +30,8 @@
30
30
  font-family: var(--pf-t--global--font--family--body);
31
31
  }
32
32
 
33
- .pf-v6-c-code-block__actions-item {
33
+ // we are overriding some default PatternFly positioning here - it's only necessary for the first two items
34
+ .pf-chatbot__message-code-block-default-action {
34
35
  display: flex;
35
36
  align-items: center;
36
37
  justify-content: space-between;
@@ -39,7 +40,7 @@
39
40
  font-weight: var(--pf-t--global--font--weight--body--bold);
40
41
  }
41
42
 
42
- .pf-chatbot__button--copy.pf-v6-c-button {
43
+ .pf-v6-c-code-block__actions-item > .pf-v6-c-button.pf-m-plain {
43
44
  color: var(--pf-t--color--white); // same in light + dark theme
44
45
 
45
46
  &:hover,
@@ -80,6 +81,10 @@
80
81
  --pf-chatbot-message-text-inline-code-font-size: var(--pf-t--global--font--size--body--default);
81
82
  background-color: var(--pf-t--global--background--color--tertiary--default);
82
83
  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
+ }
83
88
  }
84
89
 
85
90
  .pf-chatbot__message-code-toggle {
@@ -37,6 +37,10 @@ export interface CodeBlockMessageProps {
37
37
  expandedText?: string;
38
38
  /** Link text applied to expandable toggle when collapsed */
39
39
  collapsedText?: string;
40
+ /** Custom actions added to header of code block, after any default actions such as the "copy" action. */
41
+ customActions?: React.ReactNode;
42
+ /** Sets background colors to be appropriate on primary chatbot background */
43
+ isPrimary?: boolean;
40
44
  }
41
45
 
42
46
  const DEFAULT_EXPANDED_TEXT = 'Show less';
@@ -51,6 +55,8 @@ const CodeBlockMessage = ({
51
55
  expandableSectionToggleProps,
52
56
  expandedText = DEFAULT_EXPANDED_TEXT,
53
57
  collapsedText = DEFAULT_COLLAPSED_TEXT,
58
+ customActions,
59
+ isPrimary,
54
60
  ...props
55
61
  }: CodeBlockMessageProps) => {
56
62
  const [copied, setCopied] = useState(false);
@@ -105,7 +111,7 @@ const CodeBlockMessage = ({
105
111
 
106
112
  if (!String(children).includes('\n')) {
107
113
  return (
108
- <code {...props} className="pf-chatbot__message-inline-code">
114
+ <code {...props} className={`pf-chatbot__message-inline-code ${isPrimary ? 'pf-m-primary' : ''}`}>
109
115
  {children}
110
116
  </code>
111
117
  );
@@ -114,7 +120,7 @@ const CodeBlockMessage = ({
114
120
  // Setup code block header
115
121
  const actions = (
116
122
  <>
117
- <CodeBlockAction>
123
+ <CodeBlockAction className="pf-chatbot__message-code-block-default-action">
118
124
  {language && <div className="pf-chatbot__message-code-block-language">{language}</div>}
119
125
  <Button
120
126
  ref={buttonRef}
@@ -127,6 +133,7 @@ const CodeBlockMessage = ({
127
133
  </Button>
128
134
  <Tooltip id={tooltipID} content="Copy" position="top" triggerRef={buttonRef} />
129
135
  </CodeBlockAction>
136
+ {customActions}
130
137
  </>
131
138
  );
132
139
 
@@ -6,7 +6,7 @@ import userEvent from '@testing-library/user-event';
6
6
  import { monitorSampleAppQuickStart } from './QuickStarts/monitor-sampleapp-quickstart';
7
7
  import { monitorSampleAppQuickStartWithImage } from './QuickStarts/monitor-sampleapp-quickstart-with-image';
8
8
  import rehypeExternalLinks from '../__mocks__/rehype-external-links';
9
- import { AlertActionLink } from '@patternfly/react-core';
9
+ import { AlertActionLink, Button, CodeBlockAction } from '@patternfly/react-core';
10
10
  import { DeepThinkingProps } from '../DeepThinking';
11
11
 
12
12
  const ALL_ACTIONS = [
@@ -612,6 +612,24 @@ describe('Message', () => {
612
612
  );
613
613
  expect(screen.getByRole('button', { name: 'test' })).toBeTruthy();
614
614
  });
615
+ it('should be able to add custom actions to CodeMessage', () => {
616
+ render(
617
+ <Message
618
+ avatar="./img"
619
+ role="user"
620
+ name="User"
621
+ content={CODE_MESSAGE}
622
+ codeBlockProps={{
623
+ customActions: (
624
+ <CodeBlockAction>
625
+ <Button>New custom action</Button>
626
+ </CodeBlockAction>
627
+ )
628
+ }}
629
+ />
630
+ );
631
+ expect(screen.getByRole('button', { name: /New custom action/i })).toBeTruthy();
632
+ });
615
633
  it('should handle hasRoundAvatar correctly when it is true', () => {
616
634
  render(<Message avatar="./img" role="user" name="User" content="Hi" hasRoundAvatar />);
617
635
  expect(screen.getByRole('img')).toBeTruthy();
@@ -1075,4 +1093,41 @@ describe('Message', () => {
1075
1093
  expect(screen.getByText('Thought for 3 seconds')).toBeTruthy();
1076
1094
  expect(screen.getByText("Here's why I said this.")).toBeTruthy();
1077
1095
  });
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
+ });
1078
1133
  });
@@ -11,8 +11,6 @@ import {
11
11
  AvatarProps,
12
12
  ButtonProps,
13
13
  ContentVariants,
14
- ExpandableSectionProps,
15
- ExpandableSectionToggleProps,
16
14
  FormProps,
17
15
  Label,
18
16
  LabelGroupProps,
@@ -20,7 +18,7 @@ import {
20
18
  Truncate
21
19
  } from '@patternfly/react-core';
22
20
  import MessageLoading from './MessageLoading';
23
- import CodeBlockMessage from './CodeBlockMessage/CodeBlockMessage';
21
+ import CodeBlockMessage, { CodeBlockMessageProps } from './CodeBlockMessage/CodeBlockMessage';
24
22
  import TextMessage from './TextMessage/TextMessage';
25
23
  import FileDetailsLabel from '../FileDetailsLabel/FileDetailsLabel';
26
24
  import ResponseActions, { ActionProps } from '../ResponseActions/ResponseActions';
@@ -44,6 +42,9 @@ import ImageMessage from './ImageMessage/ImageMessage';
44
42
  import rehypeUnwrapImages from 'rehype-unwrap-images';
45
43
  import rehypeExternalLinks from 'rehype-external-links';
46
44
  import rehypeSanitize from 'rehype-sanitize';
45
+ import rehypeHighlight from 'rehype-highlight';
46
+ // see the full list of styles here: https://highlightjs.org/examples
47
+ import 'highlight.js/styles/vs2015.css';
47
48
  import { PluggableList } from 'unified';
48
49
  import LinkMessage from './LinkMessage/LinkMessage';
49
50
  import ErrorMessage from './ErrorMessage/ErrorMessage';
@@ -114,24 +115,7 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
114
115
  /** Label for the English "Loading message," displayed to screenreaders when loading a message */
115
116
  loadingWord?: string;
116
117
  /** Props for code blocks */
117
- codeBlockProps?: {
118
- /** Aria label applied to code blocks */
119
- 'aria-label'?: string;
120
- /** Class name applied to code blocks */
121
- className?: string;
122
- /** Whether code blocks are expandable */
123
- isExpandable?: boolean;
124
- /** Length of text initially shown in expandable code blocks; defaults to 10 characters */
125
- maxLength?: number;
126
- /** Additional props passed to expandable section if isExpandable is applied */
127
- expandableSectionProps?: Omit<ExpandableSectionProps, 'ref'>;
128
- /** Additional props passed to expandable toggle if isExpandable is applied */
129
- expandableSectionToggleProps?: ExpandableSectionToggleProps;
130
- /** Link text applied to expandable toggle when expanded */
131
- expandedText?: string;
132
- /** Link text applied to expandable toggle when collapsed */
133
- collapsedText?: string;
134
- };
118
+ codeBlockProps?: CodeBlockMessageProps;
135
119
  /** Props for quick responses */
136
120
  quickResponses?: QuickResponse[];
137
121
  /** Props for quick responses container */
@@ -205,6 +189,8 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
205
189
  toolCall?: ToolCallProps;
206
190
  /** Whether user messages default to stripping out images in markdown */
207
191
  hasNoImagesInUserMessages?: boolean;
192
+ /** Sets background colors to be appropriate on primary chatbot background */
193
+ isPrimary?: boolean;
208
194
  }
209
195
 
210
196
  export const MessageBase: FunctionComponent<MessageProps> = ({
@@ -252,6 +238,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
252
238
  remarkGfmProps,
253
239
  toolCall,
254
240
  hasNoImagesInUserMessages = true,
241
+ isPrimary,
255
242
  ...props
256
243
  }: MessageProps) => {
257
244
  const [messageText, setMessageText] = useState(content);
@@ -261,7 +248,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
261
248
  }, [content]);
262
249
 
263
250
  const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
264
- let rehypePlugins: PluggableList = [rehypeUnwrapImages, rehypeMoveImagesOutOfParagraphs];
251
+ let rehypePlugins: PluggableList = [rehypeUnwrapImages, rehypeMoveImagesOutOfParagraphs, rehypeHighlight];
265
252
  if (openLinkInNewTab) {
266
253
  rehypePlugins = rehypePlugins.concat([[rehypeExternalLinks, { target: '_blank' }, rehypeSanitize]]);
267
254
  }
@@ -302,13 +289,13 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
302
289
  p: (props) => {
303
290
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
304
291
  const { node, ...rest } = props;
305
- return <TextMessage component={ContentVariants.p} {...rest} />;
292
+ return <TextMessage component={ContentVariants.p} {...rest} isPrimary={isPrimary} />;
306
293
  },
307
294
  code: ({ children, ...props }) => {
308
295
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
309
296
  const { node, ...codeProps } = props;
310
297
  return (
311
- <CodeBlockMessage {...codeProps} {...codeBlockProps}>
298
+ <CodeBlockMessage {...codeProps} {...codeBlockProps} isPrimary={isPrimary}>
312
299
  {children}
313
300
  </CodeBlockMessage>
314
301
  );
@@ -364,7 +351,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
364
351
  return <ListItemMessage {...rest} />;
365
352
  },
366
353
  // table requires node attribute for calculating headers for mobile breakpoint
367
- table: (props) => <TableMessage {...props} {...tableProps} />,
354
+ table: (props) => <TableMessage {...props} {...tableProps} isPrimary={isPrimary} />,
368
355
  tbody: (props) => {
369
356
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
370
357
  const { node, ...rest } = props;
@@ -432,7 +419,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
432
419
 
433
420
  const renderMessage = () => {
434
421
  if (isLoading) {
435
- return <MessageLoading loadingWord={loadingWord} />;
422
+ return <MessageLoading loadingWord={loadingWord} isPrimary={isPrimary} />;
436
423
  }
437
424
  if (isEditable) {
438
425
  return (
@@ -538,6 +525,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
538
525
  closeButtonAriaLabel={attachment.closeButtonAriaLabel}
539
526
  languageTestId={attachment.languageTestId}
540
527
  spinnerTestId={attachment.spinnerTestId}
528
+ variant={isPrimary ? 'outline' : undefined}
541
529
  />
542
530
  </div>
543
531
  ))}
@@ -6,6 +6,9 @@
6
6
  padding: var(--pf-t--global--spacer--sm);
7
7
  background-color: var(--pf-t--global--background--color--tertiary--default);
8
8
  border-radius: var(--pf-t--global--border--radius--small);
9
+ // for high contrast support
10
+ border: var(--pf-t--global--border--width--high-contrast--regular) solid
11
+ var(--pf-t--global--border--color--high-contrast);
9
12
 
10
13
  &-dots {
11
14
  position: relative;
@@ -50,4 +53,8 @@
50
53
  background-color: rgba(41, 41, 41, 0.25);
51
54
  }
52
55
  }
56
+
57
+ &.pf-m-primary {
58
+ background-color: var(--pf-t--global--background--color--secondary--default);
59
+ }
53
60
  }
@@ -2,8 +2,8 @@
2
2
  // Chatbot Main - Message - Processing
3
3
  // ============================================================================
4
4
 
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' : ''}`}>
7
7
  <span className="pf-chatbot__message-loading-dots">
8
8
  <span className="pf-v6-screen-reader">{loadingWord}</span>
9
9
  </span>
@@ -8,6 +8,10 @@
8
8
  border-block-start: 0;
9
9
  }
10
10
 
11
+ &.pf-m-primary {
12
+ --pf-v6-c-table--BackgroundColor: var(--pf-t--global--background--color--secondary--default) !important;
13
+ }
14
+
11
15
  tbody {
12
16
  border-radius: var(--pf-t--global--border--radius--small);
13
17
  }
@@ -19,7 +19,11 @@ export interface TableNode {
19
19
  type: string;
20
20
  }
21
21
 
22
- const TableMessage = ({ children, ...props }: Omit<TableProps, 'ref'> & ExtraProps) => {
22
+ export interface TableMessageProps {
23
+ isPrimary?: boolean;
24
+ }
25
+
26
+ const TableMessage = ({ children, isPrimary, ...props }: Omit<TableProps, 'ref'> & ExtraProps & TableMessageProps) => {
23
27
  const { className, ...rest } = props;
24
28
 
25
29
  // This allows us to parse the nested data we get back from the 3rd party Markdown parser
@@ -72,7 +76,7 @@ const TableMessage = ({ children, ...props }: Omit<TableProps, 'ref'> & ExtraPro
72
76
  <Table
73
77
  aria-label={props['aria-label']}
74
78
  gridBreakPoint="grid"
75
- className={`pf-chatbot__message-table ${className ? className : ''}`}
79
+ className={`pf-chatbot__message-table ${isPrimary ? 'pf-m-primary' : ''} ${className ? className : ''}`}
76
80
  {...rest}
77
81
  >
78
82
  {modifyChildren(children)}
@@ -47,6 +47,12 @@
47
47
  white-space: nowrap;
48
48
  width: 1px;
49
49
  }
50
+
51
+ &.pf-m-primary {
52
+ code {
53
+ background-color: var(--pf-t--global--background--color--secondary--default);
54
+ }
55
+ }
50
56
  }
51
57
 
52
58
  // ============================================================================
@@ -71,6 +77,12 @@ li[id*='user-content-fn-']:has(> span > span > .pf-chatbot__message-text + .pf-c
71
77
  margin-block-end: var(--pf-t--global--spacer--md);
72
78
  }
73
79
 
80
+ .pf-chatbot__message-text.footnotes {
81
+ .data-footnote-backref {
82
+ width: fit-content;
83
+ }
84
+ }
85
+
74
86
  .pf-chatbot__message--user {
75
87
  .pf-chatbot__message-text {
76
88
  background-color: var(--pf-t--global--color--brand--default);
@@ -5,8 +5,17 @@
5
5
  import { ExtraProps } from 'react-markdown';
6
6
  import { Content, ContentProps } from '@patternfly/react-core';
7
7
 
8
- const TextMessage = ({ component, children, ...props }: Omit<ContentProps, 'ref'> & ExtraProps) => (
9
- <span className="pf-chatbot__message-text">
8
+ export interface TextMessageProps {
9
+ isPrimary?: boolean;
10
+ }
11
+
12
+ const TextMessage = ({
13
+ component,
14
+ children,
15
+ isPrimary,
16
+ ...props
17
+ }: Omit<ContentProps, 'ref'> & ExtraProps & TextMessageProps) => (
18
+ <span className={`pf-chatbot__message-text ${isPrimary ? 'pf-m-primary' : ''}`}>
10
19
  <Content component={component} {...props}>
11
20
  {children}
12
21
  </Content>
@@ -1,7 +1,8 @@
1
1
  // shared
2
2
  .pf-chatbot__feedback-card {
3
3
  box-shadow: var(--pf-t--global--box-shadow--sm);
4
- --pf-v6-c-card--BorderWidth: 0;
4
+ // we want to override to 0 usually and assume the default border for high contrast support
5
+ --pf-v6-c-card--BorderWidth: var(--pf-t--global--border--width--high-contrast--regular);
5
6
  max-width: 27.5rem; // fixme address mobile vs desktop
6
7
  }
7
8
 
@@ -173,4 +173,8 @@ describe('Attach button', () => {
173
173
  expect(validator).toHaveBeenCalledWith(file);
174
174
  expect(onAttachRejected).toHaveBeenCalled();
175
175
  });
176
+ it('should handle icon prop', () => {
177
+ render(<AttachButton icon={<img alt="" src="" />} />);
178
+ expect(screen.getByRole('img')).toBeVisible();
179
+ });
176
180
  });