@patternfly/chatbot 6.3.0 → 6.4.0-prerelease.2

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 (94) hide show
  1. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +5 -1
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +3 -3
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +17 -0
  4. package/dist/cjs/FileDropZone/FileDropZone.d.ts +1 -2
  5. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.js +1 -10
  6. package/dist/cjs/Message/Message.d.ts +4 -2
  7. package/dist/cjs/Message/Message.js +4 -4
  8. package/dist/cjs/Message/Message.test.js +26 -0
  9. package/dist/cjs/Message/MessageInput.d.ts +3 -1
  10. package/dist/cjs/Message/MessageInput.js +2 -2
  11. package/dist/cjs/MessageBar/AttachButton.d.ts +2 -2
  12. package/dist/cjs/MessageBar/MessageBar.d.ts +2 -2
  13. package/dist/cjs/MessageBox/MessageBox.js +1 -1
  14. package/dist/cjs/MessageDivider/MessageDivider.d.ts +9 -0
  15. package/dist/cjs/MessageDivider/MessageDivider.js +23 -0
  16. package/dist/cjs/MessageDivider/MessageDivider.test.d.ts +1 -0
  17. package/dist/cjs/MessageDivider/MessageDivider.test.js +29 -0
  18. package/dist/cjs/MessageDivider/index.d.ts +2 -0
  19. package/dist/cjs/MessageDivider/index.js +23 -0
  20. package/dist/cjs/ResponseActions/ResponseActions.d.ts +1 -0
  21. package/dist/cjs/ResponseActions/ResponseActions.js +4 -4
  22. package/dist/cjs/ResponseActions/ResponseActions.test.js +6 -1
  23. package/dist/cjs/index.d.ts +2 -0
  24. package/dist/cjs/index.js +4 -1
  25. package/dist/css/main.css +56 -55
  26. package/dist/css/main.css.map +1 -1
  27. package/dist/dynamic/MessageDivider/package.json +1 -0
  28. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +5 -1
  29. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +5 -5
  30. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +17 -0
  31. package/dist/esm/FileDropZone/FileDropZone.d.ts +1 -2
  32. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.js +1 -7
  33. package/dist/esm/Message/Message.d.ts +4 -2
  34. package/dist/esm/Message/Message.js +4 -4
  35. package/dist/esm/Message/Message.test.js +26 -0
  36. package/dist/esm/Message/MessageInput.d.ts +3 -1
  37. package/dist/esm/Message/MessageInput.js +2 -2
  38. package/dist/esm/MessageBar/AttachButton.d.ts +2 -2
  39. package/dist/esm/MessageBar/MessageBar.d.ts +2 -2
  40. package/dist/esm/MessageBox/MessageBox.js +1 -1
  41. package/dist/esm/MessageDivider/MessageDivider.d.ts +9 -0
  42. package/dist/esm/MessageDivider/MessageDivider.js +21 -0
  43. package/dist/esm/MessageDivider/MessageDivider.test.d.ts +1 -0
  44. package/dist/esm/MessageDivider/MessageDivider.test.js +24 -0
  45. package/dist/esm/MessageDivider/index.d.ts +2 -0
  46. package/dist/esm/MessageDivider/index.js +2 -0
  47. package/dist/esm/ResponseActions/ResponseActions.d.ts +1 -0
  48. package/dist/esm/ResponseActions/ResponseActions.js +5 -5
  49. package/dist/esm/ResponseActions/ResponseActions.test.js +6 -1
  50. package/dist/esm/index.d.ts +2 -0
  51. package/dist/esm/index.js +2 -0
  52. package/dist/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +5 -3
  54. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDividers.tsx +24 -0
  55. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +15 -1
  56. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +39 -7
  57. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +9 -0
  58. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +196 -0
  59. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +9 -1
  60. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +33 -1
  61. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotDisplayMode.tsx +486 -0
  62. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +565 -0
  63. package/src/Chatbot/Chatbot.scss +1 -1
  64. package/src/ChatbotContent/ChatbotContent.scss +1 -1
  65. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +14 -2
  66. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +58 -0
  67. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +29 -12
  68. package/src/ChatbotFooter/ChatbotFooter.scss +1 -1
  69. package/src/ChatbotHeader/ChatbotHeader.scss +3 -3
  70. package/src/ChatbotToggle/ChatbotToggle.scss +2 -2
  71. package/src/FileDropZone/FileDropZone.tsx +2 -2
  72. package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +3 -27
  73. package/src/Message/Message.scss +9 -7
  74. package/src/Message/Message.test.tsx +35 -0
  75. package/src/Message/Message.tsx +8 -4
  76. package/src/Message/MessageInput.tsx +5 -1
  77. package/src/MessageBar/AttachButton.tsx +2 -2
  78. package/src/MessageBar/MessageBar.tsx +2 -2
  79. package/src/MessageBar/SendButton.scss +3 -3
  80. package/src/MessageBox/JumpButton.scss +1 -1
  81. package/src/MessageBox/MessageBox.tsx +1 -1
  82. package/src/MessageDivider/MessageDivider.scss +45 -0
  83. package/src/MessageDivider/MessageDivider.test.tsx +24 -0
  84. package/src/MessageDivider/MessageDivider.tsx +35 -0
  85. package/src/MessageDivider/index.ts +3 -0
  86. package/src/ResponseActions/ResponseActions.test.tsx +6 -1
  87. package/src/ResponseActions/ResponseActions.tsx +24 -3
  88. package/src/index.ts +3 -0
  89. package/src/main.scss +1 -52
  90. package/dist/cjs/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.d.ts +0 -62
  91. package/dist/cjs/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.js +0 -139
  92. package/dist/esm/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.d.ts +0 -62
  93. package/dist/esm/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.js +0 -133
  94. package/src/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.tsx +0 -223
@@ -32,10 +32,13 @@ import {
32
32
  DrawerActionsProps,
33
33
  DrawerCloseButtonProps,
34
34
  DrawerPanelBodyProps,
35
- SkeletonProps
35
+ SkeletonProps,
36
+ Title,
37
+ Icon,
38
+ ButtonProps
36
39
  } from '@patternfly/react-core';
37
40
 
38
- import { OutlinedCommentAltIcon } from '@patternfly/react-icons';
41
+ import { OutlinedClockIcon, OutlinedCommentAltIcon } from '@patternfly/react-icons';
39
42
  import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
40
43
  import ConversationHistoryDropdown from './ChatbotConversationHistoryDropdown';
41
44
  import LoadingState from './LoadingState';
@@ -74,6 +77,8 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
74
77
  onSelectActiveItem?: (event?: React.MouseEvent, itemId?: string | number) => void;
75
78
  /** Items shown in conversation history */
76
79
  conversations: Conversation[] | { [key: string]: Conversation[] };
80
+ /** Additional button props for new chat button. */
81
+ newChatButtonProps?: ButtonProps;
77
82
  /** Text shown in blue button */
78
83
  newChatButtonText?: string;
79
84
  /** Callback function for when blue button is clicked. Omit to hide blue "new chat button" */
@@ -120,6 +125,8 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
120
125
  noResultsState?: HistoryEmptyStateProps;
121
126
  /** Sets drawer to compact styling. */
122
127
  isCompact?: boolean;
128
+ /** Display title */
129
+ title?: string;
123
130
  }
124
131
 
125
132
  export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversationHistoryNavProps> = ({
@@ -132,6 +139,7 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
132
139
  newChatButtonText = 'New chat',
133
140
  drawerContent,
134
141
  onNewChat,
142
+ newChatButtonProps,
135
143
  searchInputPlaceholder = 'Search previous conversations...',
136
144
  searchInputAriaLabel = 'Filter menu items',
137
145
  handleTextInputChange,
@@ -152,6 +160,7 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
152
160
  emptyState,
153
161
  noResultsState,
154
162
  isCompact,
163
+ title = 'Chat history',
155
164
  ...props
156
165
  }: ChatbotConversationHistoryNavProps) => {
157
166
  const drawerRef = useRef<HTMLDivElement>(null);
@@ -238,15 +247,6 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
238
247
 
239
248
  const renderDrawerContent = () => (
240
249
  <>
241
- {handleTextInputChange && (
242
- <div className="pf-chatbot__input">
243
- <SearchInput
244
- aria-label={searchInputAriaLabel}
245
- onChange={(_event, value) => handleTextInputChange(value)}
246
- placeholder={searchInputPlaceholder}
247
- />
248
- </div>
249
- )}
250
250
  <DrawerPanelBody {...drawerPanelBodyProps}>{renderMenuContent()}</DrawerPanelBody>
251
251
  </>
252
252
  );
@@ -262,12 +262,29 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
262
262
  >
263
263
  <DrawerCloseButton onClick={onDrawerToggle} {...drawerCloseButtonProps} />
264
264
  {onNewChat && (
265
- <Button size={isCompact ? 'sm' : undefined} onClick={onNewChat}>
265
+ <Button size={isCompact ? 'sm' : undefined} onClick={onNewChat} {...newChatButtonProps}>
266
266
  {newChatButtonText}
267
267
  </Button>
268
268
  )}
269
269
  </DrawerActions>
270
270
  </DrawerHead>
271
+ <div className="pf-chatbot__title-container">
272
+ <Title headingLevel="h3">
273
+ <Icon size="lg" className="pf-chatbot__title-icon">
274
+ <OutlinedClockIcon />
275
+ </Icon>
276
+ {title}
277
+ </Title>
278
+ {!isLoading && handleTextInputChange && (
279
+ <div className="pf-chatbot__input">
280
+ <SearchInput
281
+ aria-label={searchInputAriaLabel}
282
+ onChange={(_event, value) => handleTextInputChange(value)}
283
+ placeholder={searchInputPlaceholder}
284
+ />
285
+ </div>
286
+ )}
287
+ </div>
271
288
  {isLoading ? <LoadingState {...loadingState} /> : renderDrawerContent()}
272
289
  </>
273
290
  );
@@ -6,7 +6,7 @@
6
6
  // ============================================================================
7
7
  .pf-chatbot__footer {
8
8
  --pf-chatbot__footer--RowGap: var(--pf-t--global--spacer--md);
9
- background-color: var(--pf-t--chatbot--background);
9
+ background-color: var(--pf-t--global--background--color--secondary--default);
10
10
  display: flex;
11
11
  flex-direction: column;
12
12
  row-gap: var(--pf-chatbot__footer--RowGap);
@@ -9,7 +9,7 @@
9
9
  grid-template-columns: 1fr auto;
10
10
  gap: var(--pf-t--global--spacer--sm);
11
11
  position: relative; // this is so focus ring on parent chatbot doesn't include header
12
- background-color: var(--pf-t--chatbot--background);
12
+ background-color: var(--pf-t--global--background--color--secondary--default);
13
13
  justify-content: space-between;
14
14
  padding: var(--pf-t--global--spacer--lg);
15
15
 
@@ -76,7 +76,7 @@
76
76
  .pf-chatbot--drawer,
77
77
  .pf-chatbot--docked {
78
78
  .pf-chatbot__header {
79
- background-color: var(--pf-t--chatbot--background);
79
+ background-color: var(--pf-t--global--background--color--secondary--default);
80
80
  }
81
81
  }
82
82
 
@@ -144,7 +144,7 @@
144
144
  .pf-chatbot.pf-m-compact {
145
145
  .pf-chatbot__header {
146
146
  gap: var(--pf-t--global--spacer--sm);
147
- padding: var(--pf-t--global--spacer--sm);
147
+ padding: var(--pf-t--global--spacer--md);
148
148
  }
149
149
 
150
150
  .pf-chatbot__header .pf-chatbot__title img {
@@ -6,12 +6,12 @@
6
6
  inset-block-end: var(--pf-t--global--spacer--md);
7
7
  inset-inline-end: var(--pf-t--global--spacer--md);
8
8
  background-color: var(--pf-t--global--background--color--inverse--default);
9
- --pf-v6-c-button__icon--Color: var(--pf-t--chatbot-toggle--color);
9
+ --pf-v6-c-button__icon--Color: var(--pf-t--global--icon--color--inverse);
10
10
  padding: var(--pf-t--global--spacer--md);
11
11
 
12
12
  &:hover,
13
13
  &:focus {
14
- background-color: var(--pf-t--chatbot-toggle--background--hover);
14
+ background-color: var(--pf-t--color--gray--70);
15
15
  }
16
16
 
17
17
  .pf-v6-c-button__icon {
@@ -1,9 +1,9 @@
1
- import { DropEvent, MultipleFileUpload, MultipleFileUploadMain } from '@patternfly/react-core';
1
+ import { MultipleFileUpload, MultipleFileUploadMain } from '@patternfly/react-core';
2
2
  import type { FunctionComponent } from 'react';
3
3
  import { useState } from 'react';
4
4
  import { ChatbotDisplayMode } from '../Chatbot';
5
5
  import { UploadIcon } from '@patternfly/react-icons';
6
- import { Accept, FileError, FileRejection } from 'react-dropzone/.';
6
+ import { Accept, DropEvent, FileError, FileRejection } from 'react-dropzone';
7
7
 
8
8
  export interface FileDropZoneProps {
9
9
  /** Content displayed when the drop zone is not currently in use */
@@ -2,8 +2,6 @@
2
2
  // Chatbot Main - Message - Content - Code Block
3
3
  // ============================================================================
4
4
  import { useState, useRef, useId, useCallback, useEffect } from 'react';
5
- import SyntaxHighlighter from 'react-syntax-highlighter';
6
- import { obsidian } from 'react-syntax-highlighter/dist/esm/styles/hljs';
7
5
  // Import PatternFly components
8
6
  import {
9
7
  CodeBlock,
@@ -20,7 +18,6 @@ import {
20
18
 
21
19
  import { CheckIcon } from '@patternfly/react-icons/dist/esm/icons/check-icon';
22
20
  import { CopyIcon } from '@patternfly/react-icons/dist/esm/icons/copy-icon';
23
- import { ExpandableSectionForSyntaxHighlighter } from './ExpandableSectionForSyntaxHighlighter';
24
21
 
25
22
  export interface CodeBlockMessageProps {
26
23
  /** Content rendered in code block */
@@ -137,30 +134,7 @@ const CodeBlockMessage = ({
137
134
  <CodeBlock actions={actions}>
138
135
  <CodeBlockCode>
139
136
  <>
140
- {language ? (
141
- // SyntaxHighlighter doesn't work with ExpandableSection because it targets the direct child
142
- // Forked for now and adjusted to match what we need
143
- <ExpandableSectionForSyntaxHighlighter
144
- variant={ExpandableSectionVariant.truncate}
145
- isExpanded={isExpanded}
146
- isDetached
147
- toggleId={toggleId}
148
- contentId={contentId}
149
- language={language}
150
- {...expandableSectionProps}
151
- >
152
- <SyntaxHighlighter
153
- {...props}
154
- language={language}
155
- style={obsidian}
156
- PreTag="div"
157
- CodeTag="div"
158
- wrapLongLines
159
- >
160
- {String(children).replace(/\n$/, '')}
161
- </SyntaxHighlighter>
162
- </ExpandableSectionForSyntaxHighlighter>
163
- ) : (
137
+ {isExpandable ? (
164
138
  <ExpandableSection
165
139
  variant={ExpandableSectionVariant.truncate}
166
140
  isExpanded={isExpanded}
@@ -171,6 +145,8 @@ const CodeBlockMessage = ({
171
145
  >
172
146
  {children}
173
147
  </ExpandableSection>
148
+ ) : (
149
+ children
174
150
  )}
175
151
  </>
176
152
  </CodeBlockCode>
@@ -5,7 +5,7 @@
5
5
  display: flex;
6
6
  align-items: flex-start;
7
7
  gap: var(--pf-t--global--spacer--lg);
8
- padding-bottom: var(--pf-t--global--spacer--2xl);
8
+ padding-bottom: var(--pf-t--global--spacer--xl);
9
9
 
10
10
  // Avatar
11
11
  // --------------------------------------------------------------------------
@@ -48,20 +48,22 @@
48
48
 
49
49
  // Author name
50
50
  .pf-chatbot__message-name {
51
- font-family: var(--pf-t--chatbot--heading--font-family);
51
+ font-family: var(
52
+ --pf-v6-c-content--heading--FontFamily,
53
+ redhatdisplayvf,
54
+ redhatdisplay,
55
+ helvetica,
56
+ arial,
57
+ sans-serif
58
+ );
52
59
  font-weight: 600;
53
60
  font-size: var(--pf-t--global--font--size--sm);
54
61
  }
55
62
 
56
63
  // Badge
57
64
  .pf-v6-c-label {
58
- --pf-v6-c-label--m-outline--BorderColor: var(--pf-t--global--border--color--on-secondary);
59
65
  --pf-v6-c-label--FontSize: var(--pf-t--global--font--size--xs);
60
66
  font-weight: var(--pf-t--global--font--weight--body--bold);
61
-
62
- .pf-v6-c-label__content {
63
- --pf-v6-c-label--Color: var(--pf-t--global--border--color--on-secondary);
64
- }
65
67
  }
66
68
 
67
69
  // Timestamp
@@ -12,6 +12,7 @@ const ALL_ACTIONS = [
12
12
  { label: /Good response/i },
13
13
  { label: /Bad response/i },
14
14
  { label: /Copy/i },
15
+ { label: /Edit/i },
15
16
  { label: /Share/i },
16
17
  { label: /Listen/i }
17
18
  ];
@@ -426,6 +427,8 @@ describe('Message', () => {
426
427
  // eslint-disable-next-line no-console
427
428
  copy: { onClick: () => console.log('Copy') },
428
429
  // eslint-disable-next-line no-console
430
+ edit: { onClick: () => console.log('Edit') },
431
+ // eslint-disable-next-line no-console
429
432
  share: { onClick: () => console.log('Share') },
430
433
  // eslint-disable-next-line no-console
431
434
  download: { onClick: () => console.log('Download') },
@@ -454,6 +457,8 @@ describe('Message', () => {
454
457
  // eslint-disable-next-line no-console
455
458
  copy: { onClick: () => console.log('Copy') },
456
459
  // eslint-disable-next-line no-console
460
+ edit: { onClick: () => console.log('Edit') },
461
+ // eslint-disable-next-line no-console
457
462
  share: { onClick: () => console.log('Share') },
458
463
  // eslint-disable-next-line no-console
459
464
  download: { onClick: () => console.log('Download') },
@@ -467,6 +472,36 @@ describe('Message', () => {
467
472
  expect(screen.queryByRole('button', { name: label })).toBeFalsy();
468
473
  });
469
474
  });
475
+ it('should not show actions if isEditable is true', async () => {
476
+ render(
477
+ <Message
478
+ avatar="./img"
479
+ role="bot"
480
+ name="Bot"
481
+ content="Hi"
482
+ isEditable
483
+ actions={{
484
+ // eslint-disable-next-line no-console
485
+ positive: { onClick: () => console.log('Good response') },
486
+ // eslint-disable-next-line no-console
487
+ negative: { onClick: () => console.log('Bad response') },
488
+ // eslint-disable-next-line no-console
489
+ copy: { onClick: () => console.log('Copy') },
490
+ // eslint-disable-next-line no-console
491
+ edit: { onClick: () => console.log('Edit') },
492
+ // eslint-disable-next-line no-console
493
+ share: { onClick: () => console.log('Share') },
494
+ // eslint-disable-next-line no-console
495
+ download: { onClick: () => console.log('Download') },
496
+ // eslint-disable-next-line no-console
497
+ listen: { onClick: () => console.log('Listen') }
498
+ }}
499
+ />
500
+ );
501
+ ALL_ACTIONS.forEach(({ label }) => {
502
+ expect(screen.queryByRole('button', { name: label })).toBeFalsy();
503
+ });
504
+ });
470
505
  it('should render unordered lists correctly', () => {
471
506
  render(<Message avatar="./img" role="user" name="User" content={UNORDERED_LIST} />);
472
507
  expect(screen.getByText('Here is an unordered list:')).toBeTruthy();
@@ -44,7 +44,7 @@ import ImageMessage from './ImageMessage/ImageMessage';
44
44
  import rehypeUnwrapImages from 'rehype-unwrap-images';
45
45
  import rehypeExternalLinks from 'rehype-external-links';
46
46
  import rehypeSanitize from 'rehype-sanitize';
47
- import { PluggableList } from 'react-markdown/lib';
47
+ import { PluggableList } from 'unified';
48
48
  import LinkMessage from './LinkMessage/LinkMessage';
49
49
  import ErrorMessage from './ErrorMessage/ErrorMessage';
50
50
  import MessageInput from './MessageInput';
@@ -99,7 +99,7 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
99
99
  isLoading?: boolean;
100
100
  /** Array of attachments attached to a message */
101
101
  attachments?: MessageAttachment[];
102
- /** Props for message actions, such as feedback (positive or negative), copy button, share, and listen */
102
+ /** Props for message actions, such as feedback (positive or negative), copy button, edit message, share, and listen */
103
103
  actions?: {
104
104
  [key: string]: ActionProps;
105
105
  };
@@ -179,6 +179,8 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
179
179
  onEditUpdate?: (event: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void;
180
180
  /** Callback functionf or when edit cancel update button is clicked */
181
181
  onEditCancel?: (event: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void;
182
+ /** Ref applied to editable message input */
183
+ inputRef?: Ref<HTMLTextAreaElement>;
182
184
  /** Props for edit form */
183
185
  editFormProps?: FormProps;
184
186
  /** Sets message to compact styling. */
@@ -219,6 +221,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
219
221
  cancelWord = 'Cancel',
220
222
  onEditUpdate,
221
223
  onEditCancel,
224
+ inputRef,
222
225
  editFormProps,
223
226
  isCompact,
224
227
  ...props
@@ -256,7 +259,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
256
259
  <>
257
260
  {beforeMainContent && <>{beforeMainContent}</>}
258
261
  <MessageInput
259
- content={content}
262
+ content={messageText}
260
263
  editPlaceholder={editPlaceholder}
261
264
  updateWord={updateWord}
262
265
  cancelWord={cancelWord}
@@ -265,6 +268,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
265
268
  setMessageText(value);
266
269
  }}
267
270
  onEditCancel={onEditCancel}
271
+ inputRef={inputRef}
268
272
  {...editFormProps}
269
273
  />
270
274
  </>
@@ -369,7 +373,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
369
373
  isCompact={isCompact}
370
374
  />
371
375
  )}
372
- {!isLoading && actions && <ResponseActions actions={actions} />}
376
+ {!isLoading && !isEditable && actions && <ResponseActions actions={actions} />}
373
377
  {userFeedbackForm && <UserFeedback {...userFeedbackForm} timestamp={dateString} isCompact={isCompact} />}
374
378
  {userFeedbackComplete && (
375
379
  <UserFeedbackComplete {...userFeedbackComplete} timestamp={dateString} isCompact={isCompact} />
@@ -1,7 +1,7 @@
1
1
  // ============================================================================
2
2
  // Chatbot Main - Message Input
3
3
  // ============================================================================
4
- import type { FormEvent, FunctionComponent } from 'react';
4
+ import type { FormEvent, FunctionComponent, Ref } from 'react';
5
5
  import { useState } from 'react';
6
6
  import { ActionGroup, Button, Form, FormProps, TextArea } from '@patternfly/react-core';
7
7
 
@@ -16,6 +16,8 @@ export interface MessageInputProps extends FormProps {
16
16
  onEditUpdate?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, value: string) => void;
17
17
  /** Callback functionf or when edit cancel update button is clicked */
18
18
  onEditCancel?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
19
+ /** Ref applied to editable message input */
20
+ inputRef?: Ref<HTMLTextAreaElement>;
19
21
  /** Message text */
20
22
  content?: string;
21
23
  }
@@ -26,6 +28,7 @@ const MessageInput: FunctionComponent<MessageInputProps> = ({
26
28
  cancelWord = 'Cancel',
27
29
  onEditUpdate,
28
30
  onEditCancel,
31
+ inputRef,
29
32
  content,
30
33
  ...props
31
34
  }: MessageInputProps) => {
@@ -43,6 +46,7 @@ const MessageInput: FunctionComponent<MessageInputProps> = ({
43
46
  onChange={onChange}
44
47
  aria-label={editPlaceholder}
45
48
  autoResize
49
+ ref={inputRef}
46
50
  />
47
51
  <ActionGroup className="pf-chatbot__message-edit-buttons">
48
52
  <Button variant="primary" onClick={(event) => onEditUpdate && onEditUpdate(event, messageText)}>
@@ -6,8 +6,8 @@ import type { Ref, FunctionComponent } from 'react';
6
6
  import { forwardRef } from 'react';
7
7
 
8
8
  // Import PatternFly components
9
- import { Button, ButtonProps, DropEvent, Icon, Tooltip, TooltipProps } from '@patternfly/react-core';
10
- import { Accept, DropzoneOptions, FileError, FileRejection, useDropzone } from 'react-dropzone';
9
+ import { Button, ButtonProps, Icon, Tooltip, TooltipProps } from '@patternfly/react-core';
10
+ import { Accept, DropEvent, DropzoneOptions, FileError, FileRejection, useDropzone } from 'react-dropzone';
11
11
  import { PaperclipIcon } from '@patternfly/react-icons/dist/esm/icons/paperclip-icon';
12
12
 
13
13
  export interface AttachButtonProps extends ButtonProps {
@@ -1,7 +1,7 @@
1
1
  import type { ChangeEvent, FunctionComponent, KeyboardEvent as ReactKeyboardEvent, Ref } from 'react';
2
2
  import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
3
- import { Accept, DropzoneOptions, FileError, FileRejection } from 'react-dropzone/.';
4
- import { ButtonProps, DropEvent, TextArea, TextAreaProps, TooltipProps } from '@patternfly/react-core';
3
+ import { Accept, DropEvent, DropzoneOptions, FileError, FileRejection } from 'react-dropzone';
4
+ import { ButtonProps, TextArea, TextAreaProps, TooltipProps } from '@patternfly/react-core';
5
5
 
6
6
  // Import Chatbot components
7
7
  import SendButton from './SendButton';
@@ -13,11 +13,11 @@
13
13
 
14
14
  &:hover,
15
15
  &:focus {
16
- background-color: var(--pf-t--chatbot--blue-icon--background--color--hover);
16
+ background-color: rgba(146, 197, 249, 0.25); // --pf-t--global--color--nonstatus--blue--default @ 25%;
17
17
  color: var(--pf-t--global--color--brand--hover);
18
18
 
19
19
  .pf-v6-c-button__icon {
20
- color: var(--pf-t--chatbot--blue-icon--fill--hover);
20
+ color: var(--pf-t--global--color--brand--hover);
21
21
  }
22
22
  }
23
23
  }
@@ -37,7 +37,7 @@
37
37
 
38
38
  .pf-v6-c-button.pf-chatbot__button--send:hover,
39
39
  .pf-v6-c-button.pf-chatbot__button--send:focus {
40
- background-color: var(--pf-t--chatbot--blue-icon--background--color--hover);
40
+ background-color: rgba(146, 197, 249, 0.25); // --pf-t--global--color--nonstatus--blue--default @ 25%;
41
41
  }
42
42
  }
43
43
 
@@ -14,7 +14,7 @@
14
14
  border-radius: var(--pf-t--global--border--radius--pill) !important;
15
15
  --pf-v6-c-button--MinWidth: 2rem !important;
16
16
  background-color: var(--pf-t--global--background--color--primary--default) !important;
17
- border: 1px solid var(--pf-t--chatbot--border) !important;
17
+ border: 1px solid var(--pf-t--global--border--color--default) !important;
18
18
  box-shadow: var(--pf-t--global--box-shadow--sm);
19
19
  color: var(--pf-t--global--icon--color--subtle) !important;
20
20
  transform: translate3d(-50%, 0, 0) !important;
@@ -310,7 +310,7 @@ export const MessageBox = forwardRef(
310
310
  role="region"
311
311
  tabIndex={0}
312
312
  aria-label={ariaLabel}
313
- className={`pf-chatbot__messagebox ${position === 'bottom' && 'pf-chatbot__messagebox--bottom'} ${className ?? ''}`}
313
+ className={`pf-chatbot__messagebox ${position === 'bottom' ? 'pf-chatbot__messagebox--bottom' : ''} ${className ?? ''}`}
314
314
  ref={messageBoxRef}
315
315
  {...props}
316
316
  {...(enableSmartScroll ? { ...smartScrollHandlers } : {})}
@@ -0,0 +1,45 @@
1
+ // ============================================================================
2
+ // Chatbot Main - Message Divider
3
+ // ============================================================================
4
+ .pf-chatbot__message-divider {
5
+ display: grid;
6
+ padding-block-end: var(--pf-t--global--spacer--xl);
7
+
8
+ .pf-v6-c-divider,
9
+ .pf-v6-c-label {
10
+ grid-row: 1 / 1;
11
+ grid-column: 1 / 1;
12
+ }
13
+
14
+ .pf-v6-c-label {
15
+ --pf-v6-c-label--BackgroundColor: var(--pf-t--global--background--color--tertiary--default);
16
+ --pf-v6-c-label--BorderColor: var(--pf-t--global--border--color--default);
17
+ --pf-v6-c-label--PaddingInlineStart: var(--pf-t--global--spacer--action--horizontal--compact);
18
+ --pf-v6-c-label--PaddingInlineEnd: var(--pf-t--global--spacer--action--horizontal--compact);
19
+
20
+ .pf-v6-c-label__text {
21
+ font-weight: var(--pf-t--global--font--weight--body--bold);
22
+ text-align: center;
23
+ }
24
+ }
25
+
26
+ &.pf-m-divider {
27
+ .pf-v6-c-label {
28
+ --pf-v6-c-label--BackgroundColor: var(--pf-t--global--background--color--secondary--default);
29
+ --pf-v6-c-label--MaxWidth: 75%;
30
+
31
+ justify-self: center;
32
+ }
33
+
34
+ .pf-v6-c-divider {
35
+ align-self: center;
36
+ }
37
+ }
38
+
39
+ &.pf-m-wrap {
40
+ .pf-v6-c-label,
41
+ .pf-v6-c-label__text {
42
+ white-space: normal;
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,24 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import '@testing-library/jest-dom';
3
+ import MessageDivider from './MessageDivider';
4
+
5
+ describe('MessageDivider', () => {
6
+ beforeEach(() => {
7
+ jest.clearAllMocks();
8
+ });
9
+ it('should render default correctly with variant = date and content = new Date().toLocaleDateString()', () => {
10
+ render(<MessageDivider data-testid="message-divider" />);
11
+ expect(screen.getByText(new Date().toLocaleDateString())).toBeInTheDocument();
12
+ expect(screen.getByTestId('message-divider')).toHaveClass('pf-m-divider');
13
+ });
14
+ it('should render inset variant correctly', () => {
15
+ render(<MessageDivider variant="inset" content="test" data-testid="message-divider" />);
16
+ expect(screen.getByText('test')).toBeInTheDocument();
17
+ expect(screen.getByTestId('message-divider')).toHaveClass('pf-m-divider');
18
+ });
19
+ it('should render fullWidth variant correctly', () => {
20
+ render(<MessageDivider variant="fullWidth" content="test" data-testid="message-divider" />);
21
+ expect(screen.getByText('test')).toBeInTheDocument();
22
+ expect(screen.getByTestId('message-divider')).not.toHaveClass('pf-m-divider');
23
+ });
24
+ });
@@ -0,0 +1,35 @@
1
+ // ============================================================================
2
+ // Chatbot Main - Message Divider
3
+ // ============================================================================
4
+ import type { FunctionComponent } from 'react';
5
+ import { Divider, Label } from '@patternfly/react-core';
6
+
7
+ export interface MessageDividerProps {
8
+ /** Variant of the divider */
9
+ variant?: 'inset' | 'fullWidth';
10
+ /** Content of the message divider */
11
+ content?: string;
12
+ }
13
+
14
+ const MessageDivider: FunctionComponent<MessageDividerProps> = ({
15
+ variant = 'inset',
16
+ content = new Date().toLocaleDateString(),
17
+ ...props
18
+ }: MessageDividerProps) => {
19
+ if (variant === 'inset') {
20
+ return (
21
+ <div className="pf-chatbot__message-divider pf-m-divider pf-m-wrap" {...props}>
22
+ <Divider />
23
+ <Label variant="outline">{content}</Label>
24
+ </div>
25
+ );
26
+ }
27
+
28
+ return (
29
+ <div className="pf-chatbot__message-divider pf-m-wrap" {...props}>
30
+ <Label>{content}</Label>
31
+ </div>
32
+ );
33
+ };
34
+
35
+ export default MessageDivider;
@@ -0,0 +1,3 @@
1
+ export { default } from './MessageDivider';
2
+
3
+ export * from './MessageDivider';
@@ -9,6 +9,7 @@ const ALL_ACTIONS = [
9
9
  { type: 'positive', label: 'Good response', clickedLabel: 'Response recorded' },
10
10
  { type: 'negative', label: 'Bad response', clickedLabel: 'Response recorded' },
11
11
  { type: 'copy', label: 'Copy', clickedLabel: 'Copied' },
12
+ { type: 'edit', label: 'Edit', clickedLabel: 'Editing' },
12
13
  { type: 'share', label: 'Share', clickedLabel: 'Shared' },
13
14
  { type: 'listen', label: 'Listen', clickedLabel: 'Listening' }
14
15
  ];
@@ -44,6 +45,7 @@ const ALL_ACTIONS_DATA_TEST = [
44
45
  { type: 'positive', label: 'Good response', dataTestId: 'positive' },
45
46
  { type: 'negative', label: 'Bad response', dataTestId: 'negative' },
46
47
  { type: 'copy', label: 'Copy', dataTestId: 'copy' },
48
+ { type: 'edit', label: 'Edit', dataTestId: 'edit' },
47
49
  { type: 'share', label: 'Share', dataTestId: 'share' },
48
50
  { type: 'download', label: 'Download', dataTestId: 'download' },
49
51
  { type: 'listen', label: 'Listen', dataTestId: 'listen' }
@@ -60,6 +62,7 @@ describe('ResponseActions', () => {
60
62
  positive: { onClick: jest.fn() },
61
63
  negative: { onClick: jest.fn() },
62
64
  copy: { onClick: jest.fn() },
65
+ edit: { onClick: jest.fn() },
63
66
  share: { onClick: jest.fn() },
64
67
  download: { onClick: jest.fn() },
65
68
  listen: { onClick: jest.fn() }
@@ -69,10 +72,11 @@ describe('ResponseActions', () => {
69
72
  const goodBtn = screen.getByRole('button', { name: 'Good response' });
70
73
  const badBtn = screen.getByRole('button', { name: 'Bad response' });
71
74
  const copyBtn = screen.getByRole('button', { name: 'Copy' });
75
+ const editBtn = screen.getByRole('button', { name: 'Edit' });
72
76
  const shareBtn = screen.getByRole('button', { name: 'Share' });
73
77
  const downloadBtn = screen.getByRole('button', { name: 'Download' });
74
78
  const listenBtn = screen.getByRole('button', { name: 'Listen' });
75
- const buttons = [goodBtn, badBtn, copyBtn, shareBtn, downloadBtn, listenBtn];
79
+ const buttons = [goodBtn, badBtn, copyBtn, editBtn, shareBtn, downloadBtn, listenBtn];
76
80
  buttons.forEach((button) => {
77
81
  expect(button).toBeTruthy();
78
82
  });
@@ -265,6 +269,7 @@ describe('ResponseActions', () => {
265
269
  { type: 'positive', ariaLabel: 'Thumbs up' },
266
270
  { type: 'negative', ariaLabel: 'Thumbs down' },
267
271
  { type: 'copy', ariaLabel: 'Copy the message' },
272
+ { type: 'edit', ariaLabel: 'Edit this message' },
268
273
  { type: 'share', ariaLabel: 'Share it with friends' },
269
274
  { type: 'download', ariaLabel: 'Download your cool message' },
270
275
  { type: 'listen', ariaLabel: 'Listen up' }