@patternfly/chatbot 6.3.2 → 6.4.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 (133) 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 +15 -1
  86. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +39 -7
  87. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotConversationEditing.tsx +202 -0
  88. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderBasic.tsx +17 -3
  89. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +45 -5
  90. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +206 -0
  91. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +30 -4
  92. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +33 -1
  93. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotDisplayMode.tsx +486 -0
  94. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +565 -0
  95. package/src/Chatbot/Chatbot.scss +1 -1
  96. package/src/ChatbotContent/ChatbotContent.scss +1 -1
  97. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx +6 -6
  98. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +5 -2
  99. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +70 -32
  100. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +176 -3
  101. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +110 -60
  102. package/src/ChatbotFooter/ChatbotFooter.scss +1 -1
  103. package/src/ChatbotHeader/ChatbotHeader.scss +3 -3
  104. package/src/ChatbotHeader/ChatbotHeaderMenu.test.tsx +1 -1
  105. package/src/ChatbotHeader/ChatbotHeaderMenu.tsx +2 -2
  106. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.test.tsx +25 -0
  107. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.tsx +64 -0
  108. package/src/ChatbotHeader/index.ts +1 -0
  109. package/src/ChatbotModal/ChatbotModal.scss +1 -1
  110. package/src/ChatbotToggle/ChatbotToggle.scss +2 -2
  111. package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +6 -9
  112. package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +6 -9
  113. package/src/FileDropZone/FileDropZone.tsx +2 -2
  114. package/src/Message/Message.scss +9 -7
  115. package/src/Message/Message.test.tsx +54 -0
  116. package/src/Message/Message.tsx +70 -50
  117. package/src/Message/MessageInput.tsx +5 -1
  118. package/src/MessageBar/AttachButton.tsx +2 -2
  119. package/src/MessageBar/MessageBar.tsx +2 -2
  120. package/src/MessageBar/SendButton.scss +3 -3
  121. package/src/MessageBox/JumpButton.scss +1 -1
  122. package/src/MessageBox/JumpButton.test.tsx +4 -4
  123. package/src/MessageBox/JumpButton.tsx +20 -4
  124. package/src/MessageBox/MessageBox.test.tsx +2 -2
  125. package/src/MessageBox/MessageBox.tsx +23 -2
  126. package/src/MessageDivider/MessageDivider.scss +45 -0
  127. package/src/MessageDivider/MessageDivider.test.tsx +24 -0
  128. package/src/MessageDivider/MessageDivider.tsx +35 -0
  129. package/src/MessageDivider/index.ts +3 -0
  130. package/src/ResponseActions/ResponseActions.test.tsx +6 -1
  131. package/src/ResponseActions/ResponseActions.tsx +24 -3
  132. package/src/index.ts +3 -0
  133. package/src/main.scss +1 -52
@@ -13,7 +13,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
13
13
  import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
14
14
  import ChatbotConversationHistoryNav from './ChatbotConversationHistoryNav';
15
15
  import { EmptyStateStatus, Spinner } from '@patternfly/react-core';
16
- import { OutlinedCommentsIcon, SearchIcon } from '@patternfly/react-icons';
16
+ import { BellIcon, OutlinedCommentsIcon, SearchIcon } from '@patternfly/react-icons';
17
17
  const ERROR = {
18
18
  bodyText: (_jsxs(_Fragment, { children: ["To try again, check your connection and reload this page. If the issue persists,", ' ', _jsx("a", { href: "", children: "contact the support team" }), "."] })),
19
19
  buttonText: 'Reload',
@@ -65,6 +65,10 @@ describe('ChatbotConversationHistoryNav', () => {
65
65
  render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), reverseButtonOrder: true, conversations: initialConversations }));
66
66
  expect(screen.getByTestId('chatbot-nav-drawer-actions')).toHaveClass('pf-v6-c-drawer__actions--reversed');
67
67
  });
68
+ it('should disable new chat button', () => {
69
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), reverseButtonOrder: true, conversations: initialConversations, newChatButtonProps: { isDisabled: true }, onNewChat: jest.fn() }));
70
+ expect(screen.getByRole('button', { name: 'New chat' })).toBeDisabled();
71
+ });
68
72
  it('should not apply the reversed class when reverseButtonOrder is false', () => {
69
73
  render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), reverseButtonOrder: false, conversations: initialConversations }));
70
74
  expect(screen.getByTestId('chatbot-nav-drawer-actions')).not.toHaveClass('pf-v6-c-drawer__actions--reversed');
@@ -143,7 +147,7 @@ describe('ChatbotConversationHistoryNav', () => {
143
147
  })).toBeTruthy();
144
148
  expect(screen.getByRole('button', { name: /Close drawer panel/i })).toBeTruthy();
145
149
  expect(screen.getByRole('button', { name: /Loading... Reload/i })).toBeTruthy();
146
- expect(screen.getByRole('textbox', { name: /Filter menu items/i })).toBeTruthy();
150
+ expect(screen.getByRole('textbox', { name: /Search previous conversations/i })).toBeTruthy();
147
151
  expect(screen.getByRole('heading', { name: /Could not load chat history/i })).toBeTruthy();
148
152
  });
149
153
  it('should accept errorState without button', () => {
@@ -153,7 +157,7 @@ describe('ChatbotConversationHistoryNav', () => {
153
157
  })).toBeTruthy();
154
158
  expect(screen.getByRole('button', { name: /Close drawer panel/i })).toBeTruthy();
155
159
  expect(screen.queryByRole('button', { name: /Loading... Reload/i })).toBeFalsy();
156
- expect(screen.getByRole('textbox', { name: /Filter menu items/i })).toBeTruthy();
160
+ expect(screen.getByRole('textbox', { name: /Search previous conversations/i })).toBeTruthy();
157
161
  expect(screen.getByRole('heading', { name: /Could not load chat history/i })).toBeTruthy();
158
162
  });
159
163
  it('should show loading state over error state if both are supplied', () => {
@@ -176,4 +180,51 @@ describe('ChatbotConversationHistoryNav', () => {
176
180
  render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), reverseButtonOrder: false, handleTextInputChange: jest.fn(), conversations: initialConversations, noResultsState: NO_RESULTS, isCompact: true, "data-testid": "drawer" }));
177
181
  expect(screen.getByTestId('drawer')).toHaveClass('pf-m-compact');
178
182
  });
183
+ it('should display the default title', () => {
184
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations }));
185
+ expect(screen.getByText('Chat history')).toBeInTheDocument();
186
+ });
187
+ it('should display the custom title', () => {
188
+ render(_jsx(ChatbotConversationHistoryNav, { title: "PatternFly history", onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations }));
189
+ expect(screen.getByText('PatternFly history')).toBeInTheDocument();
190
+ });
191
+ it('should display the clock icon', () => {
192
+ const { container } = render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations }));
193
+ const iconElement = container.querySelector('.pf-chatbot__title-icon');
194
+ expect(iconElement).toBeInTheDocument();
195
+ });
196
+ it('Passes listTitleProps to Title', () => {
197
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: { Today: initialConversations }, listTitleProps: { className: 'test' } }));
198
+ expect(screen.getByRole('heading', { name: /Today/i })).toHaveClass('test');
199
+ });
200
+ it('Overrides list title heading level when titleProps.headingLevel is passed', () => {
201
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: { Today: initialConversations }, listTitleProps: { headingLevel: 'h2' } }));
202
+ expect(screen.queryByRole('heading', { name: /Today/i, level: 4 })).not.toBeInTheDocument();
203
+ expect(screen.getByRole('heading', { name: /Today/i, level: 2 })).toBeInTheDocument();
204
+ });
205
+ it('Passes listProps to List when conversations is an array', () => {
206
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations, listProps: { className: 'test' } }));
207
+ expect(screen.getByRole('list')).toHaveClass('test');
208
+ });
209
+ it('Passes listProps to List when conversations is an object', () => {
210
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: { Today: initialConversations }, listProps: { Today: { className: 'test' } } }));
211
+ expect(screen.getByRole('list')).toHaveClass('test');
212
+ });
213
+ it('Passes listItemProps to ListItem', () => {
214
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: [{ id: '1', text: 'ChatBot documentation', listItemProps: { className: 'test' } }] }));
215
+ expect(screen.getByRole('listitem')).toHaveClass('test');
216
+ });
217
+ it('should be able to spread search input props when searchInputProps is passed', () => {
218
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations, handleTextInputChange: jest.fn(), searchInputProps: { value: 'I am a sample search' } }));
219
+ expect(screen.getByRole('dialog', { name: /Chat history I am a sample search/i })).toBeInTheDocument();
220
+ });
221
+ it('overrides nav title heading level when navTitleProps.headingLevel is passed', () => {
222
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: { Today: initialConversations }, navTitleProps: { headingLevel: 'h1' } }));
223
+ expect(screen.queryByRole('heading', { name: /Chat history/i, level: 2 })).not.toBeInTheDocument();
224
+ expect(screen.getByRole('heading', { name: /Chat history/i, level: 1 })).toBeInTheDocument();
225
+ });
226
+ it('overrides nav title icon when navTitleIcon is passed in', () => {
227
+ render(_jsx(ChatbotConversationHistoryNav, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations, navTitleIcon: _jsx(BellIcon, { "data-testid": "bell" }) }));
228
+ expect(screen.getByTestId('bell')).toBeInTheDocument();
229
+ });
179
230
  });
@@ -14,7 +14,7 @@ import { forwardRef } from 'react';
14
14
  import { Button, Icon, Tooltip } from '@patternfly/react-core';
15
15
  import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon';
16
16
  const ChatbotHeaderMenuBase = (_a) => {
17
- var { className, onMenuToggle, tooltipProps, menuAriaLabel = 'Toggle menu', innerRef, tooltipContent = 'Menu', isCompact } = _a, props = __rest(_a, ["className", "onMenuToggle", "tooltipProps", "menuAriaLabel", "innerRef", "tooltipContent", "isCompact"]);
17
+ var { className, onMenuToggle, tooltipProps, menuAriaLabel = 'Chat history menu', innerRef, tooltipContent = 'Chat history menu', isCompact } = _a, props = __rest(_a, ["className", "onMenuToggle", "tooltipProps", "menuAriaLabel", "innerRef", "tooltipContent", "isCompact"]);
18
18
  return (_jsx("div", { className: `pf-chatbot__menu ${className}`, children: _jsx(Tooltip, Object.assign({ content: tooltipContent, position: "bottom",
19
19
  // prevents VO announcements of both aria label and tooltip
20
20
  aria: "none" }, tooltipProps, { children: _jsx(Button, Object.assign({ className: `pf-chatbot__button--toggle-menu ${isCompact ? 'pf-m-compact' : ''}`, variant: "plain", onClick: onMenuToggle, "aria-label": menuAriaLabel, ref: innerRef, icon: _jsx(Icon, { size: isCompact ? 'lg' : 'xl', isInline: true, children: _jsx(BarsIcon, {}) }), size: isCompact ? 'sm' : undefined }, props)) })) }));
@@ -10,7 +10,7 @@ describe('ChatbotHeaderMenu', () => {
10
10
  it('should call onMenuToggle when ChatbotHeaderMenu button is clicked', () => {
11
11
  const onMenuToggle = jest.fn();
12
12
  render(_jsx(ChatbotHeaderMenu, { className: "custom-header-menu", onMenuToggle: onMenuToggle }));
13
- fireEvent.click(screen.getByRole('button', { name: 'Toggle menu' }));
13
+ fireEvent.click(screen.getByRole('button', { name: 'Chat history menu' }));
14
14
  expect(onMenuToggle).toHaveBeenCalled();
15
15
  });
16
16
  it('should handle isCompact', () => {
@@ -0,0 +1,18 @@
1
+ import { ButtonProps, TooltipProps } from '@patternfly/react-core';
2
+ export interface ChatbotHeaderNewChatButtonProps extends ButtonProps {
3
+ /** Callback function for when button is clicked */
4
+ onClick: () => void;
5
+ /** Custom classname for the header component */
6
+ className?: string;
7
+ /** Props spread to the PF Tooltip component wrapping the display mode dropdown */
8
+ tooltipProps?: TooltipProps;
9
+ /** Aria label for menu */
10
+ menuAriaLabel?: string;
11
+ /** Ref applied to menu */
12
+ innerRef?: React.Ref<HTMLButtonElement>;
13
+ /** Content used in tooltip */
14
+ tooltipContent?: string;
15
+ /** Sets button to compact styling. */
16
+ isCompact?: boolean;
17
+ }
18
+ export declare const ChatbotHeaderNewChatButton: import("react").ForwardRefExoticComponent<ChatbotHeaderNewChatButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,22 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ import { forwardRef } from 'react';
14
+ import { Button, Icon, Tooltip } from '@patternfly/react-core';
15
+ import { PenToSquareIcon } from '@patternfly/react-icons/dist/esm/icons/pen-to-square-icon';
16
+ const ChatbotHeaderNewChatButtonBase = (_a) => {
17
+ var { className, onClick, tooltipProps, menuAriaLabel = 'New chat', innerRef, tooltipContent = 'New chat', isCompact } = _a, props = __rest(_a, ["className", "onClick", "tooltipProps", "menuAriaLabel", "innerRef", "tooltipContent", "isCompact"]);
18
+ return (_jsx("div", { className: `pf-chatbot__menu${className ? ` ${className}` : ''}`, children: _jsx(Tooltip, Object.assign({ content: tooltipContent, position: "bottom",
19
+ // prevents VO announcements of both aria label and tooltip
20
+ aria: "none" }, tooltipProps, { children: _jsx(Button, Object.assign({ className: `pf-chatbot__button--toggle-menu ${isCompact ? 'pf-m-compact' : ''}`, variant: "plain", onClick: onClick, "aria-label": menuAriaLabel, ref: innerRef, icon: _jsx(Icon, { size: isCompact ? 'lg' : 'xl', isInline: true, children: _jsx(PenToSquareIcon, {}) }), size: isCompact ? 'sm' : undefined }, props)) })) }));
21
+ };
22
+ export const ChatbotHeaderNewChatButton = forwardRef((props, ref) => (_jsx(ChatbotHeaderNewChatButtonBase, Object.assign({ innerRef: ref }, props))));
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { fireEvent, render, screen } from '@testing-library/react';
3
+ import { ChatbotHeaderNewChatButton } from './ChatbotHeaderNewChatButton';
4
+ import '@testing-library/jest-dom';
5
+ describe('ChatbotHeaderNewChatButton', () => {
6
+ it('should render ChatbotHeaderNewChatButton', () => {
7
+ const { container } = render(_jsx(ChatbotHeaderNewChatButton, { className: "custom-header-new-chat-button", onClick: jest.fn() }));
8
+ expect(container.querySelector('.custom-header-new-chat-button')).toBeTruthy();
9
+ });
10
+ it('should call onClick handler when new chat button is pressed', () => {
11
+ const onClick = jest.fn();
12
+ render(_jsx(ChatbotHeaderNewChatButton, { className: "custom-header-new-chat-button", onClick: onClick }));
13
+ fireEvent.click(screen.getByRole('button', { name: 'New chat' }));
14
+ expect(onClick).toHaveBeenCalled();
15
+ });
16
+ it('should render button with isCompact', () => {
17
+ render(_jsx(ChatbotHeaderNewChatButton, { "data-testid": "new-chat-button", onClick: jest.fn(), isCompact: true }));
18
+ expect(screen.getByTestId('new-chat-button')).toHaveClass('pf-m-compact');
19
+ });
20
+ });
@@ -7,3 +7,4 @@ export * from './ChatbotHeaderTitle';
7
7
  export * from './ChatbotHeaderOptionsDropdown';
8
8
  export * from './ChatbotHeaderSelectorDropdown';
9
9
  export * from './ChatbotHeaderCloseButton';
10
+ export * from './ChatbotHeaderNewChatButton';
@@ -7,3 +7,4 @@ export * from './ChatbotHeaderTitle';
7
7
  export * from './ChatbotHeaderOptionsDropdown';
8
8
  export * from './ChatbotHeaderSelectorDropdown';
9
9
  export * from './ChatbotHeaderCloseButton';
10
+ export * from './ChatbotHeaderNewChatButton';
@@ -1,7 +1,6 @@
1
- import { DropEvent } from '@patternfly/react-core';
2
1
  import type { FunctionComponent } from 'react';
3
2
  import { ChatbotDisplayMode } from '../Chatbot';
4
- import { Accept, FileError, FileRejection } from 'react-dropzone/.';
3
+ import { Accept, DropEvent, FileError, FileRejection } from 'react-dropzone';
5
4
  export interface FileDropZoneProps {
6
5
  /** Content displayed when the drop zone is not currently in use */
7
6
  children?: React.ReactNode;
@@ -1,5 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
2
  import type { FunctionComponent, HTMLProps, MouseEvent as ReactMouseEvent, Ref } from 'react';
3
+ import { Options } from 'react-markdown';
3
4
  import { AlertProps, AvatarProps, ButtonProps, ExpandableSectionProps, ExpandableSectionToggleProps, FormProps, LabelGroupProps } from '@patternfly/react-core';
4
5
  import { ActionProps } from '../ResponseActions/ResponseActions';
5
6
  import { SourcesCardProps } from '../SourcesCard';
@@ -8,7 +9,7 @@ import QuickResponse from './QuickResponse/QuickResponse';
8
9
  import { UserFeedbackProps } from './UserFeedback/UserFeedback';
9
10
  import { UserFeedbackCompleteProps } from './UserFeedback/UserFeedbackComplete';
10
11
  import { TableProps } from '@patternfly/react-table';
11
- import { PluggableList } from 'react-markdown/lib';
12
+ import { PluggableList } from 'unified';
12
13
  export interface MessageAttachment {
13
14
  /** Name of file attached to the message */
14
15
  name: string;
@@ -54,7 +55,7 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
54
55
  isLoading?: boolean;
55
56
  /** Array of attachments attached to a message */
56
57
  attachments?: MessageAttachment[];
57
- /** Props for message actions, such as feedback (positive or negative), copy button, share, and listen */
58
+ /** Props for message actions, such as feedback (positive or negative), copy button, edit message, share, and listen */
58
59
  actions?: {
59
60
  [key: string]: ActionProps;
60
61
  };
@@ -134,10 +135,16 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
134
135
  onEditUpdate?: (event: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void;
135
136
  /** Callback functionf or when edit cancel update button is clicked */
136
137
  onEditCancel?: (event: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void;
138
+ /** Ref applied to editable message input */
139
+ inputRef?: Ref<HTMLTextAreaElement>;
137
140
  /** Props for edit form */
138
141
  editFormProps?: FormProps;
139
142
  /** Sets message to compact styling. */
140
143
  isCompact?: boolean;
144
+ /** Disables markdown parsing for message, allowing only text input */
145
+ isMarkdownDisabled?: boolean;
146
+ /** 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 */
147
+ reactMarkdownProps?: Options;
141
148
  }
142
149
  export declare const MessageBase: FunctionComponent<MessageProps>;
143
150
  declare const Message: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
@@ -45,7 +45,7 @@ import ErrorMessage from './ErrorMessage/ErrorMessage';
45
45
  import MessageInput from './MessageInput';
46
46
  import { rehypeMoveImagesOutOfParagraphs } from './Plugins/rehypeMoveImagesOutOfParagraphs';
47
47
  export const MessageBase = (_a) => {
48
- var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, editFormProps, isCompact } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "editFormProps", "isCompact"]);
48
+ var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, inputRef, editFormProps, isCompact, isMarkdownDisabled, reactMarkdownProps } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "inputRef", "editFormProps", "isCompact", "isMarkdownDisabled", "reactMarkdownProps"]);
49
49
  const [messageText, setMessageText] = useState(content);
50
50
  useEffect(() => {
51
51
  setMessageText(content);
@@ -67,48 +67,54 @@ export const MessageBase = (_a) => {
67
67
  // Keep timestamps consistent between Timestamp component and aria-label
68
68
  const date = new Date();
69
69
  const dateString = timestamp !== null && timestamp !== void 0 ? timestamp : `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
70
+ const handleMarkdown = () => {
71
+ if (isMarkdownDisabled) {
72
+ return (_jsx(TextMessage, Object.assign({ component: ContentVariants.p }, props, { children: messageText })));
73
+ }
74
+ return (_jsx(Markdown, Object.assign({ components: {
75
+ p: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.p }, props)),
76
+ code: (_a) => {
77
+ var { children } = _a, props = __rest(_a, ["children"]);
78
+ return (_jsx(CodeBlockMessage, Object.assign({}, props, codeBlockProps, { children: children })));
79
+ },
80
+ h1: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h1 }, props)),
81
+ h2: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h2 }, props)),
82
+ h3: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h3 }, props)),
83
+ h4: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h4 }, props)),
84
+ h5: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h5 }, props)),
85
+ h6: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h6 }, props)),
86
+ blockquote: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.blockquote }, props)),
87
+ ul: (props) => _jsx(UnorderedListMessage, Object.assign({}, props)),
88
+ ol: (props) => _jsx(OrderedListMessage, Object.assign({}, props)),
89
+ li: (props) => _jsx(ListItemMessage, Object.assign({}, props)),
90
+ table: (props) => _jsx(TableMessage, Object.assign({}, props, tableProps)),
91
+ tbody: (props) => _jsx(TbodyMessage, Object.assign({}, props)),
92
+ thead: (props) => _jsx(TheadMessage, Object.assign({}, props)),
93
+ tr: (props) => _jsx(TrMessage, Object.assign({}, props)),
94
+ td: (props) => {
95
+ // Conflicts with Td type
96
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
97
+ const { width } = props, rest = __rest(props, ["width"]);
98
+ return _jsx(TdMessage, Object.assign({}, rest));
99
+ },
100
+ th: (props) => _jsx(ThMessage, Object.assign({}, props)),
101
+ img: (props) => _jsx(ImageMessage, Object.assign({}, props)),
102
+ a: (props) => (_jsx(LinkMessage, Object.assign({ href: props.href, rel: props.rel, target: props.target }, linkProps, { children: props.children })))
103
+ }, remarkPlugins: [remarkGfm], rehypePlugins: rehypePlugins }, reactMarkdownProps, { children: messageText })));
104
+ };
70
105
  const renderMessage = () => {
71
106
  if (isLoading) {
72
107
  return _jsx(MessageLoading, { loadingWord: loadingWord });
73
108
  }
74
109
  if (isEditable) {
75
- return (_jsxs(_Fragment, { children: [beforeMainContent && _jsx(_Fragment, { children: beforeMainContent }), _jsx(MessageInput, Object.assign({ content: content, editPlaceholder: editPlaceholder, updateWord: updateWord, cancelWord: cancelWord, onEditUpdate: (event, value) => {
110
+ return (_jsxs(_Fragment, { children: [beforeMainContent && _jsx(_Fragment, { children: beforeMainContent }), _jsx(MessageInput, Object.assign({ content: messageText, editPlaceholder: editPlaceholder, updateWord: updateWord, cancelWord: cancelWord, onEditUpdate: (event, value) => {
76
111
  onEditUpdate && onEditUpdate(event);
77
112
  setMessageText(value);
78
- }, onEditCancel: onEditCancel }, editFormProps))] }));
113
+ }, onEditCancel: onEditCancel, inputRef: inputRef }, editFormProps))] }));
79
114
  }
80
- return (_jsxs(_Fragment, { children: [beforeMainContent && _jsx(_Fragment, { children: beforeMainContent }), error ? (_jsx(ErrorMessage, Object.assign({}, error))) : (_jsx(Markdown, { components: {
81
- p: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.p }, props)),
82
- code: (_a) => {
83
- var { children } = _a, props = __rest(_a, ["children"]);
84
- return (_jsx(CodeBlockMessage, Object.assign({}, props, codeBlockProps, { children: children })));
85
- },
86
- h1: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h1 }, props)),
87
- h2: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h2 }, props)),
88
- h3: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h3 }, props)),
89
- h4: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h4 }, props)),
90
- h5: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h5 }, props)),
91
- h6: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.h6 }, props)),
92
- blockquote: (props) => _jsx(TextMessage, Object.assign({ component: ContentVariants.blockquote }, props)),
93
- ul: (props) => _jsx(UnorderedListMessage, Object.assign({}, props)),
94
- ol: (props) => _jsx(OrderedListMessage, Object.assign({}, props)),
95
- li: (props) => _jsx(ListItemMessage, Object.assign({}, props)),
96
- table: (props) => _jsx(TableMessage, Object.assign({}, props, tableProps)),
97
- tbody: (props) => _jsx(TbodyMessage, Object.assign({}, props)),
98
- thead: (props) => _jsx(TheadMessage, Object.assign({}, props)),
99
- tr: (props) => _jsx(TrMessage, Object.assign({}, props)),
100
- td: (props) => {
101
- // Conflicts with Td type
102
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
103
- const { width } = props, rest = __rest(props, ["width"]);
104
- return _jsx(TdMessage, Object.assign({}, rest));
105
- },
106
- th: (props) => _jsx(ThMessage, Object.assign({}, props)),
107
- img: (props) => _jsx(ImageMessage, Object.assign({}, props)),
108
- a: (props) => (_jsx(LinkMessage, Object.assign({ href: props.href, rel: props.rel, target: props.target }, linkProps, { children: props.children })))
109
- }, remarkPlugins: [remarkGfm], rehypePlugins: rehypePlugins, children: messageText }))] }));
115
+ return (_jsxs(_Fragment, { children: [beforeMainContent && _jsx(_Fragment, { children: beforeMainContent }), error ? _jsx(ErrorMessage, Object.assign({}, error)) : handleMarkdown()] }));
110
116
  };
111
- return (_jsxs("section", Object.assign({ "aria-label": `Message from ${role} - ${dateString}`, className: `pf-chatbot__message pf-chatbot__message--${role}`, "aria-live": isLiveRegion ? 'polite' : undefined, "aria-atomic": isLiveRegion ? false : undefined, ref: innerRef }, props, { children: [_jsx(Avatar, Object.assign({ className: `pf-chatbot__message-avatar ${hasRoundAvatar ? 'pf-chatbot__message-avatar--round' : ''} ${avatarClassName ? avatarClassName : ''}`, src: avatar, alt: "" }, avatarProps)), _jsxs("div", { className: "pf-chatbot__message-contents", children: [_jsxs("div", { className: "pf-chatbot__message-meta", children: [name && (_jsx("span", { className: "pf-chatbot__message-name", children: _jsx(Truncate, { content: name }) })), role === 'bot' && (_jsx(Label, { variant: "outline", isCompact: true, children: botWord })), _jsx(Timestamp, { date: date, children: timestamp })] }), _jsxs("div", { className: "pf-chatbot__message-response", children: [_jsxs("div", { className: "pf-chatbot__message-and-actions", children: [renderMessage(), afterMainContent && _jsx(_Fragment, { children: afterMainContent }), !isLoading && sources && _jsx(SourcesCard, Object.assign({}, sources, { isCompact: isCompact })), quickStarts && quickStarts.quickStart && (_jsx(QuickStartTile, { quickStart: quickStarts.quickStart, onSelectQuickStart: quickStarts.onSelectQuickStart, minuteWord: quickStarts.minuteWord, minuteWordPlural: quickStarts.minuteWordPlural, prerequisiteWord: quickStarts.prerequisiteWord, prerequisiteWordPlural: quickStarts.prerequisiteWordPlural, quickStartButtonAriaLabel: quickStarts.quickStartButtonAriaLabel, isCompact: isCompact })), !isLoading && actions && _jsx(ResponseActions, { actions: actions }), userFeedbackForm && _jsx(UserFeedback, Object.assign({}, userFeedbackForm, { timestamp: dateString, isCompact: isCompact })), userFeedbackComplete && (_jsx(UserFeedbackComplete, Object.assign({}, userFeedbackComplete, { timestamp: dateString, isCompact: isCompact }))), !isLoading && quickResponses && (_jsx(QuickResponse, { quickResponses: quickResponses, quickResponseContainerProps: quickResponseContainerProps, isCompact: isCompact }))] }), attachments && (_jsx("div", { className: "pf-chatbot__message-attachments-container", children: attachments.map((attachment) => {
117
+ return (_jsxs("section", Object.assign({ "aria-label": `Message from ${role} - ${dateString}`, className: `pf-chatbot__message pf-chatbot__message--${role}`, "aria-live": isLiveRegion ? 'polite' : undefined, "aria-atomic": isLiveRegion ? false : undefined, ref: innerRef }, props, { children: [_jsx(Avatar, Object.assign({ className: `pf-chatbot__message-avatar ${hasRoundAvatar ? 'pf-chatbot__message-avatar--round' : ''} ${avatarClassName ? avatarClassName : ''}`, src: avatar, alt: "" }, avatarProps)), _jsxs("div", { className: "pf-chatbot__message-contents", children: [_jsxs("div", { className: "pf-chatbot__message-meta", children: [name && (_jsx("span", { className: "pf-chatbot__message-name", children: _jsx(Truncate, { content: name }) })), role === 'bot' && (_jsx(Label, { variant: "outline", isCompact: true, children: botWord })), _jsx(Timestamp, { date: date, children: timestamp })] }), _jsxs("div", { className: "pf-chatbot__message-response", children: [_jsxs("div", { className: "pf-chatbot__message-and-actions", children: [renderMessage(), afterMainContent && _jsx(_Fragment, { children: afterMainContent }), !isLoading && sources && _jsx(SourcesCard, Object.assign({}, sources, { isCompact: isCompact })), quickStarts && quickStarts.quickStart && (_jsx(QuickStartTile, { quickStart: quickStarts.quickStart, onSelectQuickStart: quickStarts.onSelectQuickStart, minuteWord: quickStarts.minuteWord, minuteWordPlural: quickStarts.minuteWordPlural, prerequisiteWord: quickStarts.prerequisiteWord, prerequisiteWordPlural: quickStarts.prerequisiteWordPlural, quickStartButtonAriaLabel: quickStarts.quickStartButtonAriaLabel, isCompact: isCompact })), !isLoading && !isEditable && actions && _jsx(ResponseActions, { actions: actions }), userFeedbackForm && _jsx(UserFeedback, Object.assign({}, userFeedbackForm, { timestamp: dateString, isCompact: isCompact })), userFeedbackComplete && (_jsx(UserFeedbackComplete, Object.assign({}, userFeedbackComplete, { timestamp: dateString, isCompact: isCompact }))), !isLoading && quickResponses && (_jsx(QuickResponse, { quickResponses: quickResponses, quickResponseContainerProps: quickResponseContainerProps, isCompact: isCompact }))] }), attachments && (_jsx("div", { className: "pf-chatbot__message-attachments-container", children: attachments.map((attachment) => {
112
118
  var _a;
113
119
  return (_jsx("div", { className: "pf-chatbot__message-attachment", children: _jsx(FileDetailsLabel, { fileName: attachment.name, fileId: attachment.id, onClose: attachment.onClose, onClick: attachment.onClick, isLoading: attachment.isLoading, closeButtonAriaLabel: attachment.closeButtonAriaLabel, languageTestId: attachment.languageTestId, spinnerTestId: attachment.spinnerTestId }) }, (_a = attachment.id) !== null && _a !== void 0 ? _a : attachment.name));
114
120
  }) })), !isLoading && endContent && _jsx(_Fragment, { children: endContent })] })] })] })));
@@ -21,6 +21,7 @@ const ALL_ACTIONS = [
21
21
  { label: /Good response/i },
22
22
  { label: /Bad response/i },
23
23
  { label: /Copy/i },
24
+ { label: /Edit/i },
24
25
  { label: /Share/i },
25
26
  { label: /Listen/i }
26
27
  ];
@@ -341,6 +342,8 @@ describe('Message', () => {
341
342
  // eslint-disable-next-line no-console
342
343
  copy: { onClick: () => console.log('Copy') },
343
344
  // eslint-disable-next-line no-console
345
+ edit: { onClick: () => console.log('Edit') },
346
+ // eslint-disable-next-line no-console
344
347
  share: { onClick: () => console.log('Share') },
345
348
  // eslint-disable-next-line no-console
346
349
  download: { onClick: () => console.log('Download') },
@@ -360,6 +363,8 @@ describe('Message', () => {
360
363
  // eslint-disable-next-line no-console
361
364
  copy: { onClick: () => console.log('Copy') },
362
365
  // eslint-disable-next-line no-console
366
+ edit: { onClick: () => console.log('Edit') },
367
+ // eslint-disable-next-line no-console
363
368
  share: { onClick: () => console.log('Share') },
364
369
  // eslint-disable-next-line no-console
365
370
  download: { onClick: () => console.log('Download') },
@@ -371,6 +376,27 @@ describe('Message', () => {
371
376
  expect(screen.queryByRole('button', { name: label })).toBeFalsy();
372
377
  });
373
378
  }));
379
+ it('should not show actions if isEditable is true', () => __awaiter(void 0, void 0, void 0, function* () {
380
+ render(_jsx(Message, { avatar: "./img", role: "bot", name: "Bot", content: "Hi", isEditable: true, actions: {
381
+ // eslint-disable-next-line no-console
382
+ positive: { onClick: () => console.log('Good response') },
383
+ // eslint-disable-next-line no-console
384
+ negative: { onClick: () => console.log('Bad response') },
385
+ // eslint-disable-next-line no-console
386
+ copy: { onClick: () => console.log('Copy') },
387
+ // eslint-disable-next-line no-console
388
+ edit: { onClick: () => console.log('Edit') },
389
+ // eslint-disable-next-line no-console
390
+ share: { onClick: () => console.log('Share') },
391
+ // eslint-disable-next-line no-console
392
+ download: { onClick: () => console.log('Download') },
393
+ // eslint-disable-next-line no-console
394
+ listen: { onClick: () => console.log('Listen') }
395
+ } }));
396
+ ALL_ACTIONS.forEach(({ label }) => {
397
+ expect(screen.queryByRole('button', { name: label })).toBeFalsy();
398
+ });
399
+ }));
374
400
  it('should render unordered lists correctly', () => {
375
401
  render(_jsx(Message, { avatar: "./img", role: "user", name: "User", content: UNORDERED_LIST }));
376
402
  expect(screen.getByText('Here is an unordered list:')).toBeTruthy();
@@ -708,4 +734,15 @@ describe('Message', () => {
708
734
  const form = container.querySelector('form');
709
735
  expect(form).toHaveClass('test');
710
736
  });
737
+ it('should be able to disable markdown parsing', () => {
738
+ render(_jsx(Message, { avatar: "./img", role: "user", name: "User", content: CODE_MESSAGE, isMarkdownDisabled: true }));
739
+ // this is looking for markdown syntax that is ordinarily stripped
740
+ expect(screen.getByText(/~~~yaml/i)).toBeTruthy();
741
+ });
742
+ it('should be able to pass props to react-markdown, such as disabling tags', () => {
743
+ render(_jsx(Message, { avatar: "./img", role: "user", name: "User", content: CODE_MESSAGE, reactMarkdownProps: { disallowedElements: ['code'] } }));
744
+ expect(screen.getByText('Here is some YAML code:')).toBeTruthy();
745
+ // code block isn't rendering
746
+ expect(screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
747
+ });
711
748
  });
@@ -1,4 +1,4 @@
1
- import type { FunctionComponent } from 'react';
1
+ import type { FunctionComponent, Ref } from 'react';
2
2
  import { FormProps } from '@patternfly/react-core';
3
3
  export interface MessageInputProps extends FormProps {
4
4
  /** Placeholder for edit input */
@@ -11,6 +11,8 @@ export interface MessageInputProps extends FormProps {
11
11
  onEditUpdate?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, value: string) => void;
12
12
  /** Callback functionf or when edit cancel update button is clicked */
13
13
  onEditCancel?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
14
+ /** Ref applied to editable message input */
15
+ inputRef?: Ref<HTMLTextAreaElement>;
14
16
  /** Message text */
15
17
  content?: string;
16
18
  }
@@ -13,11 +13,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import { useState } from 'react';
14
14
  import { ActionGroup, Button, Form, TextArea } from '@patternfly/react-core';
15
15
  const MessageInput = (_a) => {
16
- var { editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, content } = _a, props = __rest(_a, ["editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "content"]);
16
+ var { editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, inputRef, content } = _a, props = __rest(_a, ["editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "inputRef", "content"]);
17
17
  const [messageText, setMessageText] = useState(content !== null && content !== void 0 ? content : '');
18
18
  const onChange = (_event, value) => {
19
19
  setMessageText(value);
20
20
  };
21
- return (_jsxs(Form, Object.assign({}, props, { children: [_jsx(TextArea, { placeholder: editPlaceholder, value: messageText, onChange: onChange, "aria-label": editPlaceholder, autoResize: true }), _jsxs(ActionGroup, { className: "pf-chatbot__message-edit-buttons", children: [_jsx(Button, { variant: "primary", onClick: (event) => onEditUpdate && onEditUpdate(event, messageText), children: updateWord }), _jsx(Button, { variant: "secondary", onClick: onEditCancel, children: cancelWord })] })] })));
21
+ return (_jsxs(Form, Object.assign({}, props, { children: [_jsx(TextArea, { placeholder: editPlaceholder, value: messageText, onChange: onChange, "aria-label": editPlaceholder, autoResize: true, ref: inputRef }), _jsxs(ActionGroup, { className: "pf-chatbot__message-edit-buttons", children: [_jsx(Button, { variant: "primary", onClick: (event) => onEditUpdate && onEditUpdate(event, messageText), children: updateWord }), _jsx(Button, { variant: "secondary", onClick: onEditCancel, children: cancelWord })] })] })));
22
22
  };
23
23
  export default MessageInput;
@@ -1,5 +1,5 @@
1
- import { ButtonProps, DropEvent, TooltipProps } from '@patternfly/react-core';
2
- import { Accept, DropzoneOptions, FileError, FileRejection } from 'react-dropzone';
1
+ import { ButtonProps, TooltipProps } from '@patternfly/react-core';
2
+ import { Accept, DropEvent, DropzoneOptions, FileError, FileRejection } from 'react-dropzone';
3
3
  export interface AttachButtonProps extends ButtonProps {
4
4
  /** Callback for when button is clicked */
5
5
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
@@ -1,6 +1,6 @@
1
1
  import type { FunctionComponent } from 'react';
2
- import { Accept, DropzoneOptions, FileError, FileRejection } from 'react-dropzone/.';
3
- import { ButtonProps, DropEvent, TextAreaProps, TooltipProps } from '@patternfly/react-core';
2
+ import { Accept, DropEvent, DropzoneOptions, FileError, FileRejection } from 'react-dropzone';
3
+ import { ButtonProps, TextAreaProps, TooltipProps } from '@patternfly/react-core';
4
4
  import { ChatbotDisplayMode } from '../Chatbot';
5
5
  export interface MessageBarWithAttachMenuProps {
6
6
  /** Flag to enable whether attach menu is open */
@@ -1,4 +1,5 @@
1
1
  import type { FunctionComponent } from 'react';
2
+ import { TooltipProps, ButtonProps } from '@patternfly/react-core';
2
3
  export interface JumpButtonProps {
3
4
  /** Position of the Jump Button(top/bottom) */
4
5
  position: 'top' | 'bottom';
@@ -6,6 +7,10 @@ export interface JumpButtonProps {
6
7
  onClick: () => void;
7
8
  /** Flag to change the visibilty of the button */
8
9
  isHidden?: boolean;
10
+ /** Additional props passed to jump buttons */
11
+ jumpButtonProps?: ButtonProps;
12
+ /** Additional props passed to tooltip */
13
+ jumpButtonTooltipProps?: TooltipProps;
9
14
  }
10
15
  declare const JumpButton: FunctionComponent<JumpButtonProps>;
11
16
  export default JumpButton;
@@ -3,5 +3,5 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { Button, Tooltip, Icon } from '@patternfly/react-core';
4
4
  import { ArrowUpIcon } from '@patternfly/react-icons/dist/esm/icons/arrow-up-icon';
5
5
  import { ArrowDownIcon } from '@patternfly/react-icons/dist/esm/icons/arrow-down-icon';
6
- const JumpButton = ({ position, isHidden, onClick }) => isHidden ? null : (_jsx(Tooltip, { id: `pf-chatbot__tooltip--jump-${position}`, content: `Back to ${position}`, position: "top", children: _jsx(Button, { variant: "plain", className: `pf-chatbot__jump pf-chatbot__jump--${position}`, "aria-label": `Jump ${position}`, onClick: onClick, children: _jsx(Icon, { iconSize: "lg", isInline: true, children: position === 'top' ? _jsx(ArrowUpIcon, {}) : _jsx(ArrowDownIcon, {}) }) }) }));
6
+ const JumpButton = ({ position, isHidden, onClick, jumpButtonProps, jumpButtonTooltipProps }) => isHidden ? null : (_jsx(Tooltip, Object.assign({ id: `pf-chatbot__tooltip--jump-${position}`, content: `Back to ${position}`, position: "top" }, jumpButtonTooltipProps, { children: _jsx(Button, Object.assign({ variant: "plain", className: `pf-chatbot__jump pf-chatbot__jump--${position}`, "aria-label": `Back to ${position}`, onClick: onClick }, jumpButtonProps, { children: _jsx(Icon, { iconSize: "lg", isInline: true, children: position === 'top' ? _jsx(ArrowUpIcon, {}) : _jsx(ArrowDownIcon, {}) }) })) })));
7
7
  export default JumpButton;
@@ -15,20 +15,20 @@ import userEvent from '@testing-library/user-event';
15
15
  describe('JumpButton', () => {
16
16
  it('should render top button correctly', () => {
17
17
  render(_jsx(JumpButton, { position: "top", onClick: jest.fn() }));
18
- expect(screen.getByRole('button', { name: /Jump top/i })).toBeTruthy();
18
+ expect(screen.getByRole('button', { name: /Back to top/i })).toBeTruthy();
19
19
  });
20
20
  it('should render bottom button correctly', () => {
21
21
  render(_jsx(JumpButton, { position: "bottom", onClick: jest.fn() }));
22
- expect(screen.getByRole('button', { name: /Jump bottom/i })).toBeTruthy();
22
+ expect(screen.getByRole('button', { name: /Back to bottom/i })).toBeTruthy();
23
23
  });
24
24
  it('should call onClick appropriately', () => __awaiter(void 0, void 0, void 0, function* () {
25
25
  const spy = jest.fn();
26
26
  render(_jsx(JumpButton, { position: "bottom", onClick: spy }));
27
- yield userEvent.click(screen.getByRole('button', { name: /Jump bottom/i }));
27
+ yield userEvent.click(screen.getByRole('button', { name: /Back to bottom/i }));
28
28
  expect(spy).toHaveBeenCalledTimes(1);
29
29
  }));
30
30
  it('should be hidden if isHidden prop is used', () => __awaiter(void 0, void 0, void 0, function* () {
31
31
  render(_jsx(JumpButton, { position: "bottom", onClick: jest.fn(), isHidden: true }));
32
- expect(screen.queryByRole('button', { name: /Jump bottom/i })).toBeFalsy();
32
+ expect(screen.queryByRole('button', { name: /Back to bottom/i })).toBeFalsy();
33
33
  }));
34
34
  });
@@ -1,4 +1,5 @@
1
1
  import { HTMLProps, ReactNode } from 'react';
2
+ import { ButtonProps, TooltipProps } from '@patternfly/react-core';
2
3
  export interface MessageBoxProps extends HTMLProps<HTMLDivElement> {
3
4
  /** Content that can be announced, such as a new message, for screen readers */
4
5
  announcement?: string;
@@ -18,6 +19,14 @@ export interface MessageBoxProps extends HTMLProps<HTMLDivElement> {
18
19
  onScrollToBottomClick?: () => void;
19
20
  /** Flag to enable automatic scrolling when new messages are added */
20
21
  enableSmartScroll?: boolean;
22
+ /** Props passed to top jump button */
23
+ jumpButtonTopProps?: ButtonProps;
24
+ /** Props passed to bottom jump button */
25
+ jumpButtonBottomProps?: ButtonProps;
26
+ /** Props passed to top jump button tooltip */
27
+ jumpButtonTopTooltipProps?: TooltipProps;
28
+ /** Props passed to top jump button tooltip */
29
+ jumpButtonBottomTooltipProps?: TooltipProps;
21
30
  }
22
31
  export interface MessageBoxHandle extends HTMLDivElement {
23
32
  /** Scrolls to the top of the message box */
@@ -16,7 +16,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
16
16
  import { useState, useRef, useCallback, useEffect, forwardRef, useImperativeHandle } from 'react';
17
17
  import JumpButton from './JumpButton';
18
18
  export const MessageBox = forwardRef((_a, ref) => {
19
- var { announcement, ariaLabel = 'Scrollable message log', children, className, position = 'top', onScrollToTopClick, onScrollToBottomClick, enableSmartScroll = false } = _a, props = __rest(_a, ["announcement", "ariaLabel", "children", "className", "position", "onScrollToTopClick", "onScrollToBottomClick", "enableSmartScroll"]);
19
+ var { announcement, ariaLabel = 'Scrollable message log', children, className, position = 'top', onScrollToTopClick, onScrollToBottomClick, enableSmartScroll = false, jumpButtonTopProps, jumpButtonBottomProps, jumpButtonBottomTooltipProps, jumpButtonTopTooltipProps } = _a, props = __rest(_a, ["announcement", "ariaLabel", "children", "className", "position", "onScrollToTopClick", "onScrollToBottomClick", "enableSmartScroll", "jumpButtonTopProps", "jumpButtonBottomProps", "jumpButtonBottomTooltipProps", "jumpButtonTopTooltipProps"]);
20
20
  const [atTop, setAtTop] = useState(false);
21
21
  const [atBottom, setAtBottom] = useState(true);
22
22
  const [isOverflowing, setIsOverflowing] = useState(false);
@@ -200,6 +200,6 @@ export const MessageBox = forwardRef((_a, ref) => {
200
200
  onTouchMove,
201
201
  onTouchEnd
202
202
  };
203
- return (_jsxs(_Fragment, { children: [_jsx(JumpButton, { position: "top", isHidden: isOverflowing && atTop, onClick: scrollToTop }), _jsxs("div", Object.assign({ role: "region", tabIndex: 0, "aria-label": ariaLabel, className: `pf-chatbot__messagebox ${position === 'bottom' && 'pf-chatbot__messagebox--bottom'} ${className !== null && className !== void 0 ? className : ''}`, ref: messageBoxRef }, props, (enableSmartScroll ? Object.assign({}, smartScrollHandlers) : {}), { children: [children, _jsx("div", { className: "pf-chatbot__messagebox-announcement", "aria-live": "polite", children: announcement })] })), _jsx(JumpButton, { position: "bottom", isHidden: isOverflowing && atBottom, onClick: () => scrollToBottom({ resumeSmartScroll: true }) })] }));
203
+ return (_jsxs(_Fragment, { children: [_jsx(JumpButton, { position: "top", isHidden: isOverflowing && atTop, onClick: scrollToTop, jumpButtonProps: jumpButtonTopProps, jumpButtonTooltipProps: jumpButtonTopTooltipProps }), _jsxs("div", Object.assign({ role: "region", tabIndex: 0, "aria-label": ariaLabel, className: `pf-chatbot__messagebox ${position === 'bottom' ? 'pf-chatbot__messagebox--bottom' : ''} ${className !== null && className !== void 0 ? className : ''}`, ref: messageBoxRef }, props, (enableSmartScroll ? Object.assign({}, smartScrollHandlers) : {}), { children: [children, _jsx("div", { className: "pf-chatbot__messagebox-announcement", "aria-live": "polite", children: announcement })] })), _jsx(JumpButton, { position: "bottom", isHidden: isOverflowing && atBottom, onClick: () => scrollToBottom({ resumeSmartScroll: true }), jumpButtonProps: jumpButtonBottomProps, jumpButtonTooltipProps: jumpButtonBottomTooltipProps })] }));
204
204
  });
205
205
  export default MessageBox;
@@ -51,7 +51,7 @@ describe('MessageBox', () => {
51
51
  region.dispatchEvent(new Event('scroll'));
52
52
  });
53
53
  yield waitFor(() => {
54
- userEvent.click(screen.getByRole('button', { name: /Jump bottom/i }));
54
+ userEvent.click(screen.getByRole('button', { name: /Back to bottom/i }));
55
55
  expect(spy).toHaveBeenCalled();
56
56
  });
57
57
  }));
@@ -70,7 +70,7 @@ describe('MessageBox', () => {
70
70
  region.dispatchEvent(new Event('scroll'));
71
71
  });
72
72
  yield waitFor(() => {
73
- userEvent.click(screen.getByRole('button', { name: /Jump top/i }));
73
+ userEvent.click(screen.getByRole('button', { name: /Back to top/i }));
74
74
  expect(spy).toHaveBeenCalled();
75
75
  });
76
76
  }));
@@ -0,0 +1,9 @@
1
+ import type { FunctionComponent } from 'react';
2
+ export interface MessageDividerProps {
3
+ /** Variant of the divider */
4
+ variant?: 'inset' | 'fullWidth';
5
+ /** Content of the message divider */
6
+ content?: string;
7
+ }
8
+ declare const MessageDivider: FunctionComponent<MessageDividerProps>;
9
+ export default MessageDivider;