@patternfly/chatbot 6.3.2 → 6.4.0-prerelease.11

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 (134) hide show
  1. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
  4. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +27 -4
  5. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +8 -14
  6. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +53 -2
  7. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
  8. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
  9. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
  10. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.js +25 -0
  11. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
  12. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +22 -0
  13. package/dist/cjs/ChatbotHeader/index.d.ts +1 -0
  14. package/dist/cjs/ChatbotHeader/index.js +1 -0
  15. package/dist/cjs/FileDropZone/FileDropZone.d.ts +1 -2
  16. package/dist/cjs/Message/Message.d.ts +9 -2
  17. package/dist/cjs/Message/Message.js +40 -34
  18. package/dist/cjs/Message/Message.test.js +37 -0
  19. package/dist/cjs/Message/MessageInput.d.ts +3 -1
  20. package/dist/cjs/Message/MessageInput.js +2 -2
  21. package/dist/cjs/MessageBar/AttachButton.d.ts +2 -2
  22. package/dist/cjs/MessageBar/MessageBar.d.ts +2 -2
  23. package/dist/cjs/MessageBox/JumpButton.d.ts +5 -0
  24. package/dist/cjs/MessageBox/JumpButton.js +1 -1
  25. package/dist/cjs/MessageBox/JumpButton.test.js +4 -4
  26. package/dist/cjs/MessageBox/MessageBox.d.ts +9 -0
  27. package/dist/cjs/MessageBox/MessageBox.js +2 -2
  28. package/dist/cjs/MessageBox/MessageBox.test.js +2 -2
  29. package/dist/cjs/MessageDivider/MessageDivider.d.ts +9 -0
  30. package/dist/cjs/MessageDivider/MessageDivider.js +23 -0
  31. package/dist/cjs/MessageDivider/MessageDivider.test.d.ts +1 -0
  32. package/dist/cjs/MessageDivider/MessageDivider.test.js +29 -0
  33. package/dist/cjs/MessageDivider/index.d.ts +2 -0
  34. package/dist/cjs/MessageDivider/index.js +23 -0
  35. package/dist/cjs/ResponseActions/ResponseActions.d.ts +1 -0
  36. package/dist/cjs/ResponseActions/ResponseActions.js +4 -4
  37. package/dist/cjs/ResponseActions/ResponseActions.test.js +6 -1
  38. package/dist/cjs/index.d.ts +2 -0
  39. package/dist/cjs/index.js +4 -1
  40. package/dist/css/main.css +103 -81
  41. package/dist/css/main.css.map +1 -1
  42. package/dist/dynamic/MessageDivider/package.json +1 -0
  43. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  44. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  45. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
  46. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +27 -4
  47. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +10 -16
  48. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +54 -3
  49. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
  50. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
  51. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
  52. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.js +22 -0
  53. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
  54. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +20 -0
  55. package/dist/esm/ChatbotHeader/index.d.ts +1 -0
  56. package/dist/esm/ChatbotHeader/index.js +1 -0
  57. package/dist/esm/FileDropZone/FileDropZone.d.ts +1 -2
  58. package/dist/esm/Message/Message.d.ts +9 -2
  59. package/dist/esm/Message/Message.js +40 -34
  60. package/dist/esm/Message/Message.test.js +37 -0
  61. package/dist/esm/Message/MessageInput.d.ts +3 -1
  62. package/dist/esm/Message/MessageInput.js +2 -2
  63. package/dist/esm/MessageBar/AttachButton.d.ts +2 -2
  64. package/dist/esm/MessageBar/MessageBar.d.ts +2 -2
  65. package/dist/esm/MessageBox/JumpButton.d.ts +5 -0
  66. package/dist/esm/MessageBox/JumpButton.js +1 -1
  67. package/dist/esm/MessageBox/JumpButton.test.js +4 -4
  68. package/dist/esm/MessageBox/MessageBox.d.ts +9 -0
  69. package/dist/esm/MessageBox/MessageBox.js +2 -2
  70. package/dist/esm/MessageBox/MessageBox.test.js +2 -2
  71. package/dist/esm/MessageDivider/MessageDivider.d.ts +9 -0
  72. package/dist/esm/MessageDivider/MessageDivider.js +21 -0
  73. package/dist/esm/MessageDivider/MessageDivider.test.d.ts +1 -0
  74. package/dist/esm/MessageDivider/MessageDivider.test.js +24 -0
  75. package/dist/esm/MessageDivider/index.d.ts +2 -0
  76. package/dist/esm/MessageDivider/index.js +2 -0
  77. package/dist/esm/ResponseActions/ResponseActions.d.ts +1 -0
  78. package/dist/esm/ResponseActions/ResponseActions.js +5 -5
  79. package/dist/esm/ResponseActions/ResponseActions.test.js +6 -1
  80. package/dist/esm/index.d.ts +2 -0
  81. package/dist/esm/index.js +2 -0
  82. package/dist/tsconfig.tsbuildinfo +1 -1
  83. package/package.json +9 -4
  84. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDividers.tsx +24 -0
  85. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +18 -1
  86. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +39 -7
  87. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx +401 -3
  88. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotConversationEditing.tsx +202 -0
  89. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderBasic.tsx +17 -3
  90. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +45 -5
  91. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +206 -0
  92. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +30 -4
  93. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +33 -1
  94. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotDisplayMode.tsx +486 -0
  95. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +565 -0
  96. package/src/Chatbot/Chatbot.scss +1 -1
  97. package/src/ChatbotContent/ChatbotContent.scss +1 -1
  98. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx +6 -6
  99. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +5 -2
  100. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +70 -32
  101. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +176 -3
  102. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +110 -60
  103. package/src/ChatbotFooter/ChatbotFooter.scss +1 -1
  104. package/src/ChatbotHeader/ChatbotHeader.scss +3 -3
  105. package/src/ChatbotHeader/ChatbotHeaderMenu.test.tsx +1 -1
  106. package/src/ChatbotHeader/ChatbotHeaderMenu.tsx +2 -2
  107. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.test.tsx +25 -0
  108. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.tsx +64 -0
  109. package/src/ChatbotHeader/index.ts +1 -0
  110. package/src/ChatbotModal/ChatbotModal.scss +1 -1
  111. package/src/ChatbotToggle/ChatbotToggle.scss +2 -2
  112. package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +6 -9
  113. package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +6 -9
  114. package/src/FileDropZone/FileDropZone.tsx +2 -2
  115. package/src/Message/Message.scss +9 -7
  116. package/src/Message/Message.test.tsx +54 -0
  117. package/src/Message/Message.tsx +70 -50
  118. package/src/Message/MessageInput.tsx +5 -1
  119. package/src/MessageBar/AttachButton.tsx +2 -2
  120. package/src/MessageBar/MessageBar.tsx +2 -2
  121. package/src/MessageBar/SendButton.scss +3 -3
  122. package/src/MessageBox/JumpButton.scss +1 -1
  123. package/src/MessageBox/JumpButton.test.tsx +4 -4
  124. package/src/MessageBox/JumpButton.tsx +20 -4
  125. package/src/MessageBox/MessageBox.test.tsx +2 -2
  126. package/src/MessageBox/MessageBox.tsx +23 -2
  127. package/src/MessageDivider/MessageDivider.scss +45 -0
  128. package/src/MessageDivider/MessageDivider.test.tsx +24 -0
  129. package/src/MessageDivider/MessageDivider.tsx +35 -0
  130. package/src/MessageDivider/index.ts +3 -0
  131. package/src/ResponseActions/ResponseActions.test.tsx +6 -1
  132. package/src/ResponseActions/ResponseActions.tsx +24 -3
  133. package/src/index.ts +3 -0
  134. package/src/main.scss +1 -52
@@ -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 {
@@ -12,7 +12,7 @@ describe('ChatbotHeaderMenu', () => {
12
12
  it('should call onMenuToggle when ChatbotHeaderMenu button is clicked', () => {
13
13
  const onMenuToggle = jest.fn();
14
14
  render(<ChatbotHeaderMenu className="custom-header-menu" onMenuToggle={onMenuToggle} />);
15
- fireEvent.click(screen.getByRole('button', { name: 'Toggle menu' }));
15
+ fireEvent.click(screen.getByRole('button', { name: 'Chat history menu' }));
16
16
 
17
17
  expect(onMenuToggle).toHaveBeenCalled();
18
18
  });
@@ -25,9 +25,9 @@ const ChatbotHeaderMenuBase: FunctionComponent<ChatbotHeaderMenuProps> = ({
25
25
  className,
26
26
  onMenuToggle,
27
27
  tooltipProps,
28
- menuAriaLabel = 'Toggle menu',
28
+ menuAriaLabel = 'Chat history menu',
29
29
  innerRef,
30
- tooltipContent = 'Menu',
30
+ tooltipContent = 'Chat history menu',
31
31
  isCompact,
32
32
  ...props
33
33
  }: ChatbotHeaderMenuProps) => (
@@ -0,0 +1,25 @@
1
+ import { fireEvent, render, screen } from '@testing-library/react';
2
+ import { ChatbotHeaderNewChatButton } from './ChatbotHeaderNewChatButton';
3
+ import '@testing-library/jest-dom';
4
+
5
+ describe('ChatbotHeaderNewChatButton', () => {
6
+ it('should render ChatbotHeaderNewChatButton', () => {
7
+ const { container } = render(
8
+ <ChatbotHeaderNewChatButton className="custom-header-new-chat-button" onClick={jest.fn()} />
9
+ );
10
+
11
+ expect(container.querySelector('.custom-header-new-chat-button')).toBeTruthy();
12
+ });
13
+
14
+ it('should call onClick handler when new chat button is pressed', () => {
15
+ const onClick = jest.fn();
16
+ render(<ChatbotHeaderNewChatButton className="custom-header-new-chat-button" onClick={onClick} />);
17
+ fireEvent.click(screen.getByRole('button', { name: 'New chat' }));
18
+ expect(onClick).toHaveBeenCalled();
19
+ });
20
+
21
+ it('should render button with isCompact', () => {
22
+ render(<ChatbotHeaderNewChatButton data-testid="new-chat-button" onClick={jest.fn()} isCompact />);
23
+ expect(screen.getByTestId('new-chat-button')).toHaveClass('pf-m-compact');
24
+ });
25
+ });
@@ -0,0 +1,64 @@
1
+ import type { Ref, FunctionComponent } from 'react';
2
+ import { forwardRef } from 'react';
3
+
4
+ import { Button, ButtonProps, Icon, Tooltip, TooltipProps } from '@patternfly/react-core';
5
+ import { PenToSquareIcon } from '@patternfly/react-icons/dist/esm/icons/pen-to-square-icon';
6
+
7
+ export interface ChatbotHeaderNewChatButtonProps extends ButtonProps {
8
+ /** Callback function for when button is clicked */
9
+ onClick: () => void;
10
+ /** Custom classname for the header component */
11
+ className?: string;
12
+ /** Props spread to the PF Tooltip component wrapping the display mode dropdown */
13
+ tooltipProps?: TooltipProps;
14
+ /** Aria label for menu */
15
+ menuAriaLabel?: string;
16
+ /** Ref applied to menu */
17
+ innerRef?: React.Ref<HTMLButtonElement>;
18
+ /** Content used in tooltip */
19
+ tooltipContent?: string;
20
+ /** Sets button to compact styling. */
21
+ isCompact?: boolean;
22
+ }
23
+
24
+ const ChatbotHeaderNewChatButtonBase: FunctionComponent<ChatbotHeaderNewChatButtonProps> = ({
25
+ className,
26
+ onClick,
27
+ tooltipProps,
28
+ menuAriaLabel = 'New chat',
29
+ innerRef,
30
+ tooltipContent = 'New chat',
31
+ isCompact,
32
+ ...props
33
+ }: ChatbotHeaderNewChatButtonProps) => (
34
+ <div className={`pf-chatbot__menu${className ? ` ${className}` : ''}`}>
35
+ <Tooltip
36
+ content={tooltipContent}
37
+ position="bottom"
38
+ // prevents VO announcements of both aria label and tooltip
39
+ aria="none"
40
+ {...tooltipProps}
41
+ >
42
+ <Button
43
+ className={`pf-chatbot__button--toggle-menu ${isCompact ? 'pf-m-compact' : ''}`}
44
+ variant="plain"
45
+ onClick={onClick}
46
+ aria-label={menuAriaLabel}
47
+ ref={innerRef}
48
+ icon={
49
+ <Icon size={isCompact ? 'lg' : 'xl'} isInline>
50
+ <PenToSquareIcon />
51
+ </Icon>
52
+ }
53
+ size={isCompact ? 'sm' : undefined}
54
+ {...props}
55
+ />
56
+ </Tooltip>
57
+ </div>
58
+ );
59
+
60
+ export const ChatbotHeaderNewChatButton = forwardRef(
61
+ (props: ChatbotHeaderNewChatButtonProps, ref: Ref<HTMLButtonElement>) => (
62
+ <ChatbotHeaderNewChatButtonBase innerRef={ref} {...props} />
63
+ )
64
+ );
@@ -8,3 +8,4 @@ export * from './ChatbotHeaderTitle';
8
8
  export * from './ChatbotHeaderOptionsDropdown';
9
9
  export * from './ChatbotHeaderSelectorDropdown';
10
10
  export * from './ChatbotHeaderCloseButton';
11
+ export * from './ChatbotHeaderNewChatButton';
@@ -20,7 +20,7 @@
20
20
  padding-block-end: var(--pf-t--global--spacer--xl);
21
21
  }
22
22
  .pf-v6-c-modal-box__header {
23
- padding-block-end: var(--pf-t--global--spacer--lg);
23
+ padding-block-end: var(--pf-t--global--spacer--sm);
24
24
  }
25
25
  }
26
26
 
@@ -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 {
@@ -48,19 +48,16 @@ exports[`FileDetails should render file details 1`] = `
48
48
  <span
49
49
  class="pf-chatbot__code-fileName"
50
50
  >
51
- <div
52
- style="display: contents;"
51
+ <span
52
+ class="pf-v6-c-truncate"
53
+ tabindex="0"
53
54
  >
54
55
  <span
55
- class="pf-v6-c-truncate"
56
+ class="pf-v6-c-truncate__start"
56
57
  >
57
- <span
58
- class="pf-v6-c-truncate__start"
59
- >
60
- test
61
- </span>
58
+ test
62
59
  </span>
63
- </div>
60
+ </span>
64
61
  </span>
65
62
  </div>
66
63
  <div
@@ -60,19 +60,16 @@ exports[`FileDetailsLabel should render file details label 1`] = `
60
60
  <span
61
61
  class="pf-chatbot__code-fileName"
62
62
  >
63
- <div
64
- style="display: contents;"
63
+ <span
64
+ class="pf-v6-c-truncate"
65
+ tabindex="0"
65
66
  >
66
67
  <span
67
- class="pf-v6-c-truncate"
68
+ class="pf-v6-c-truncate__start"
68
69
  >
69
- <span
70
- class="pf-v6-c-truncate__start"
71
- >
72
- test
73
- </span>
70
+ test
74
71
  </span>
75
- </div>
72
+ </span>
76
73
  </span>
77
74
  </div>
78
75
  <div
@@ -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 */
@@ -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();
@@ -927,4 +962,23 @@ describe('Message', () => {
927
962
  const form = container.querySelector('form');
928
963
  expect(form).toHaveClass('test');
929
964
  });
965
+ it('should be able to disable markdown parsing', () => {
966
+ render(<Message avatar="./img" role="user" name="User" content={CODE_MESSAGE} isMarkdownDisabled />);
967
+ // this is looking for markdown syntax that is ordinarily stripped
968
+ expect(screen.getByText(/~~~yaml/i)).toBeTruthy();
969
+ });
970
+ it('should be able to pass props to react-markdown, such as disabling tags', () => {
971
+ render(
972
+ <Message
973
+ avatar="./img"
974
+ role="user"
975
+ name="User"
976
+ content={CODE_MESSAGE}
977
+ reactMarkdownProps={{ disallowedElements: ['code'] }}
978
+ />
979
+ );
980
+ expect(screen.getByText('Here is some YAML code:')).toBeTruthy();
981
+ // code block isn't rendering
982
+ expect(screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
983
+ });
930
984
  });
@@ -3,7 +3,7 @@
3
3
  // ============================================================================
4
4
  import { forwardRef, ReactNode, useEffect, useState } from 'react';
5
5
  import type { FunctionComponent, HTMLProps, MouseEvent as ReactMouseEvent, Ref } from 'react';
6
- import Markdown from 'react-markdown';
6
+ import Markdown, { Options } from 'react-markdown';
7
7
  import remarkGfm from 'remark-gfm';
8
8
  import {
9
9
  AlertProps,
@@ -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,10 +179,16 @@ 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. */
185
187
  isCompact?: boolean;
188
+ /** Disables markdown parsing for message, allowing only text input */
189
+ isMarkdownDisabled?: boolean;
190
+ /** Allows passing additional props down to markdown parser react-markdown, such as allowedElements and disallowedElements. See https://github.com/remarkjs/react-markdown?tab=readme-ov-file#options for options */
191
+ reactMarkdownProps?: Options;
186
192
  }
187
193
 
188
194
  export const MessageBase: FunctionComponent<MessageProps> = ({
@@ -219,8 +225,11 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
219
225
  cancelWord = 'Cancel',
220
226
  onEditUpdate,
221
227
  onEditCancel,
228
+ inputRef,
222
229
  editFormProps,
223
230
  isCompact,
231
+ isMarkdownDisabled,
232
+ reactMarkdownProps,
224
233
  ...props
225
234
  }: MessageProps) => {
226
235
  const [messageText, setMessageText] = useState(content);
@@ -247,6 +256,60 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
247
256
  const date = new Date();
248
257
  const dateString = timestamp ?? `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
249
258
 
259
+ const handleMarkdown = () => {
260
+ if (isMarkdownDisabled) {
261
+ return (
262
+ <TextMessage component={ContentVariants.p} {...props}>
263
+ {messageText}
264
+ </TextMessage>
265
+ );
266
+ }
267
+ return (
268
+ <Markdown
269
+ components={{
270
+ p: (props) => <TextMessage component={ContentVariants.p} {...props} />,
271
+ code: ({ children, ...props }) => (
272
+ <CodeBlockMessage {...props} {...codeBlockProps}>
273
+ {children}
274
+ </CodeBlockMessage>
275
+ ),
276
+ h1: (props) => <TextMessage component={ContentVariants.h1} {...props} />,
277
+ h2: (props) => <TextMessage component={ContentVariants.h2} {...props} />,
278
+ h3: (props) => <TextMessage component={ContentVariants.h3} {...props} />,
279
+ h4: (props) => <TextMessage component={ContentVariants.h4} {...props} />,
280
+ h5: (props) => <TextMessage component={ContentVariants.h5} {...props} />,
281
+ h6: (props) => <TextMessage component={ContentVariants.h6} {...props} />,
282
+ blockquote: (props) => <TextMessage component={ContentVariants.blockquote} {...props} />,
283
+ ul: (props) => <UnorderedListMessage {...props} />,
284
+ ol: (props) => <OrderedListMessage {...props} />,
285
+ li: (props) => <ListItemMessage {...props} />,
286
+ table: (props) => <TableMessage {...props} {...tableProps} />,
287
+ tbody: (props) => <TbodyMessage {...props} />,
288
+ thead: (props) => <TheadMessage {...props} />,
289
+ tr: (props) => <TrMessage {...props} />,
290
+ td: (props) => {
291
+ // Conflicts with Td type
292
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
293
+ const { width, ...rest } = props;
294
+ return <TdMessage {...rest} />;
295
+ },
296
+ th: (props) => <ThMessage {...props} />,
297
+ img: (props) => <ImageMessage {...props} />,
298
+ a: (props) => (
299
+ <LinkMessage href={props.href} rel={props.rel} target={props.target} {...linkProps}>
300
+ {props.children}
301
+ </LinkMessage>
302
+ )
303
+ }}
304
+ remarkPlugins={[remarkGfm]}
305
+ rehypePlugins={rehypePlugins}
306
+ {...reactMarkdownProps}
307
+ >
308
+ {messageText}
309
+ </Markdown>
310
+ );
311
+ };
312
+
250
313
  const renderMessage = () => {
251
314
  if (isLoading) {
252
315
  return <MessageLoading loadingWord={loadingWord} />;
@@ -256,7 +319,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
256
319
  <>
257
320
  {beforeMainContent && <>{beforeMainContent}</>}
258
321
  <MessageInput
259
- content={content}
322
+ content={messageText}
260
323
  editPlaceholder={editPlaceholder}
261
324
  updateWord={updateWord}
262
325
  cancelWord={cancelWord}
@@ -265,6 +328,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
265
328
  setMessageText(value);
266
329
  }}
267
330
  onEditCancel={onEditCancel}
331
+ inputRef={inputRef}
268
332
  {...editFormProps}
269
333
  />
270
334
  </>
@@ -273,51 +337,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
273
337
  return (
274
338
  <>
275
339
  {beforeMainContent && <>{beforeMainContent}</>}
276
- {error ? (
277
- <ErrorMessage {...error} />
278
- ) : (
279
- <Markdown
280
- components={{
281
- p: (props) => <TextMessage component={ContentVariants.p} {...props} />,
282
- code: ({ children, ...props }) => (
283
- <CodeBlockMessage {...props} {...codeBlockProps}>
284
- {children}
285
- </CodeBlockMessage>
286
- ),
287
- h1: (props) => <TextMessage component={ContentVariants.h1} {...props} />,
288
- h2: (props) => <TextMessage component={ContentVariants.h2} {...props} />,
289
- h3: (props) => <TextMessage component={ContentVariants.h3} {...props} />,
290
- h4: (props) => <TextMessage component={ContentVariants.h4} {...props} />,
291
- h5: (props) => <TextMessage component={ContentVariants.h5} {...props} />,
292
- h6: (props) => <TextMessage component={ContentVariants.h6} {...props} />,
293
- blockquote: (props) => <TextMessage component={ContentVariants.blockquote} {...props} />,
294
- ul: (props) => <UnorderedListMessage {...props} />,
295
- ol: (props) => <OrderedListMessage {...props} />,
296
- li: (props) => <ListItemMessage {...props} />,
297
- table: (props) => <TableMessage {...props} {...tableProps} />,
298
- tbody: (props) => <TbodyMessage {...props} />,
299
- thead: (props) => <TheadMessage {...props} />,
300
- tr: (props) => <TrMessage {...props} />,
301
- td: (props) => {
302
- // Conflicts with Td type
303
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
304
- const { width, ...rest } = props;
305
- return <TdMessage {...rest} />;
306
- },
307
- th: (props) => <ThMessage {...props} />,
308
- img: (props) => <ImageMessage {...props} />,
309
- a: (props) => (
310
- <LinkMessage href={props.href} rel={props.rel} target={props.target} {...linkProps}>
311
- {props.children}
312
- </LinkMessage>
313
- )
314
- }}
315
- remarkPlugins={[remarkGfm]}
316
- rehypePlugins={rehypePlugins}
317
- >
318
- {messageText}
319
- </Markdown>
320
- )}
340
+ {error ? <ErrorMessage {...error} /> : handleMarkdown()}
321
341
  </>
322
342
  );
323
343
  };
@@ -369,7 +389,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
369
389
  isCompact={isCompact}
370
390
  />
371
391
  )}
372
- {!isLoading && actions && <ResponseActions actions={actions} />}
392
+ {!isLoading && !isEditable && actions && <ResponseActions actions={actions} />}
373
393
  {userFeedbackForm && <UserFeedback {...userFeedbackForm} timestamp={dateString} isCompact={isCompact} />}
374
394
  {userFeedbackComplete && (
375
395
  <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;