@patternfly/chatbot 6.4.0-prerelease.2 → 6.4.0-prerelease.20

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 (186) hide show
  1. package/dist/cjs/Chatbot/Chatbot.js +1 -7
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  4. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +22 -2
  5. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +15 -9
  6. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +40 -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/DeepThinking/DeepThinking.d.ts +18 -0
  16. package/dist/cjs/DeepThinking/DeepThinking.js +18 -0
  17. package/dist/cjs/DeepThinking/DeepThinking.test.d.ts +1 -0
  18. package/dist/cjs/DeepThinking/DeepThinking.test.js +48 -0
  19. package/dist/cjs/DeepThinking/index.d.ts +2 -0
  20. package/dist/cjs/DeepThinking/index.js +23 -0
  21. package/dist/cjs/FilePreview/FilePreview.d.ts +26 -0
  22. package/dist/cjs/FilePreview/FilePreview.js +26 -0
  23. package/dist/cjs/FilePreview/FilePreview.test.d.ts +1 -0
  24. package/dist/cjs/FilePreview/FilePreview.test.js +97 -0
  25. package/dist/cjs/FilePreview/index.d.ts +2 -0
  26. package/dist/cjs/FilePreview/index.js +23 -0
  27. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.js +3 -3
  28. package/dist/cjs/Message/LinkMessage/LinkMessage.d.ts +2 -1
  29. package/dist/cjs/Message/LinkMessage/LinkMessage.js +7 -3
  30. package/dist/cjs/Message/ListMessage/ListItemMessage.d.ts +1 -1
  31. package/dist/cjs/Message/ListMessage/ListItemMessage.js +16 -1
  32. package/dist/cjs/Message/Message.d.ts +15 -0
  33. package/dist/cjs/Message/Message.js +129 -32
  34. package/dist/cjs/Message/Message.test.js +71 -0
  35. package/dist/cjs/Message/SuperscriptMessage/SuperscriptMessage.d.ts +3 -0
  36. package/dist/cjs/Message/SuperscriptMessage/SuperscriptMessage.js +5 -0
  37. package/dist/cjs/Message/UserFeedback/UserFeedback.d.ts +15 -1
  38. package/dist/cjs/Message/UserFeedback/UserFeedback.js +4 -4
  39. package/dist/cjs/Message/UserFeedback/UserFeedback.test.js +44 -0
  40. package/dist/cjs/MessageBar/MessageBar.js +19 -4
  41. package/dist/cjs/MessageBox/JumpButton.d.ts +5 -0
  42. package/dist/cjs/MessageBox/JumpButton.js +1 -1
  43. package/dist/cjs/MessageBox/JumpButton.test.js +4 -4
  44. package/dist/cjs/MessageBox/MessageBox.d.ts +9 -0
  45. package/dist/cjs/MessageBox/MessageBox.js +2 -2
  46. package/dist/cjs/MessageBox/MessageBox.test.js +2 -2
  47. package/dist/cjs/SourcesCard/SourcesCard.d.ts +13 -1
  48. package/dist/cjs/SourcesCard/SourcesCard.js +6 -6
  49. package/dist/cjs/SourcesCard/SourcesCard.test.js +49 -0
  50. package/dist/cjs/ToolResponse/ToolResponse.d.ts +30 -0
  51. package/dist/cjs/ToolResponse/ToolResponse.js +18 -0
  52. package/dist/cjs/ToolResponse/ToolResponse.test.d.ts +1 -0
  53. package/dist/cjs/ToolResponse/ToolResponse.test.js +60 -0
  54. package/dist/cjs/ToolResponse/index.d.ts +2 -0
  55. package/dist/cjs/ToolResponse/index.js +23 -0
  56. package/dist/cjs/index.d.ts +6 -0
  57. package/dist/cjs/index.js +10 -1
  58. package/dist/css/main.css +273 -17
  59. package/dist/css/main.css.map +1 -1
  60. package/dist/dynamic/DeepThinking/package.json +1 -0
  61. package/dist/dynamic/FilePreview/package.json +1 -0
  62. package/dist/dynamic/ToolResponse/package.json +1 -0
  63. package/dist/esm/Chatbot/Chatbot.js +1 -7
  64. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  65. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  66. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +22 -2
  67. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +17 -11
  68. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +41 -3
  69. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
  70. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
  71. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
  72. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.js +22 -0
  73. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
  74. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +20 -0
  75. package/dist/esm/ChatbotHeader/index.d.ts +1 -0
  76. package/dist/esm/ChatbotHeader/index.js +1 -0
  77. package/dist/esm/DeepThinking/DeepThinking.d.ts +18 -0
  78. package/dist/esm/DeepThinking/DeepThinking.js +14 -0
  79. package/dist/esm/DeepThinking/DeepThinking.test.d.ts +1 -0
  80. package/dist/esm/DeepThinking/DeepThinking.test.js +43 -0
  81. package/dist/esm/DeepThinking/index.d.ts +2 -0
  82. package/dist/esm/DeepThinking/index.js +2 -0
  83. package/dist/esm/FilePreview/FilePreview.d.ts +26 -0
  84. package/dist/esm/FilePreview/FilePreview.js +21 -0
  85. package/dist/esm/FilePreview/FilePreview.test.d.ts +1 -0
  86. package/dist/esm/FilePreview/FilePreview.test.js +92 -0
  87. package/dist/esm/FilePreview/index.d.ts +2 -0
  88. package/dist/esm/FilePreview/index.js +2 -0
  89. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.js +5 -5
  90. package/dist/esm/Message/LinkMessage/LinkMessage.d.ts +2 -1
  91. package/dist/esm/Message/LinkMessage/LinkMessage.js +7 -3
  92. package/dist/esm/Message/ListMessage/ListItemMessage.d.ts +1 -1
  93. package/dist/esm/Message/ListMessage/ListItemMessage.js +16 -1
  94. package/dist/esm/Message/Message.d.ts +15 -0
  95. package/dist/esm/Message/Message.js +129 -32
  96. package/dist/esm/Message/Message.test.js +71 -0
  97. package/dist/esm/Message/SuperscriptMessage/SuperscriptMessage.d.ts +3 -0
  98. package/dist/esm/Message/SuperscriptMessage/SuperscriptMessage.js +3 -0
  99. package/dist/esm/Message/UserFeedback/UserFeedback.d.ts +15 -1
  100. package/dist/esm/Message/UserFeedback/UserFeedback.js +4 -4
  101. package/dist/esm/Message/UserFeedback/UserFeedback.test.js +45 -1
  102. package/dist/esm/MessageBar/MessageBar.js +19 -4
  103. package/dist/esm/MessageBox/JumpButton.d.ts +5 -0
  104. package/dist/esm/MessageBox/JumpButton.js +1 -1
  105. package/dist/esm/MessageBox/JumpButton.test.js +4 -4
  106. package/dist/esm/MessageBox/MessageBox.d.ts +9 -0
  107. package/dist/esm/MessageBox/MessageBox.js +2 -2
  108. package/dist/esm/MessageBox/MessageBox.test.js +2 -2
  109. package/dist/esm/SourcesCard/SourcesCard.d.ts +13 -1
  110. package/dist/esm/SourcesCard/SourcesCard.js +6 -6
  111. package/dist/esm/SourcesCard/SourcesCard.test.js +50 -1
  112. package/dist/esm/ToolResponse/ToolResponse.d.ts +30 -0
  113. package/dist/esm/ToolResponse/ToolResponse.js +14 -0
  114. package/dist/esm/ToolResponse/ToolResponse.test.d.ts +1 -0
  115. package/dist/esm/ToolResponse/ToolResponse.test.js +55 -0
  116. package/dist/esm/ToolResponse/index.d.ts +2 -0
  117. package/dist/esm/ToolResponse/index.js +2 -0
  118. package/dist/esm/index.d.ts +6 -0
  119. package/dist/esm/index.js +6 -0
  120. package/dist/tsconfig.tsbuildinfo +1 -1
  121. package/package.json +7 -6
  122. package/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx +101 -3
  123. package/patternfly-docs/content/extensions/chatbot/examples/Messages/FilePreview.tsx +33 -0
  124. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDeepThinking.tsx +17 -0
  125. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithFeedback.tsx +111 -85
  126. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithSources.tsx +70 -0
  127. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolResponse.tsx +135 -0
  128. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +28 -4
  129. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +107 -2
  130. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx +616 -3
  131. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotConversationEditing.tsx +202 -0
  132. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderBasic.tsx +17 -3
  133. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +36 -5
  134. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +12 -2
  135. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +22 -3
  136. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +1 -1
  137. package/patternfly-docs/patternfly-docs.config.js +1 -1
  138. package/src/Chatbot/Chatbot.scss +9 -2
  139. package/src/Chatbot/Chatbot.tsx +18 -31
  140. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +5 -1
  141. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +28 -10
  142. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +132 -3
  143. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +80 -33
  144. package/src/ChatbotHeader/ChatbotHeaderMenu.test.tsx +1 -1
  145. package/src/ChatbotHeader/ChatbotHeaderMenu.tsx +2 -2
  146. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.test.tsx +25 -0
  147. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.tsx +64 -0
  148. package/src/ChatbotHeader/index.ts +1 -0
  149. package/src/ChatbotModal/ChatbotModal.scss +1 -1
  150. package/src/DeepThinking/DeepThinking.scss +24 -0
  151. package/src/DeepThinking/DeepThinking.test.tsx +61 -0
  152. package/src/DeepThinking/DeepThinking.tsx +68 -0
  153. package/src/DeepThinking/index.ts +3 -0
  154. package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +6 -9
  155. package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +6 -9
  156. package/src/FilePreview/FilePreview.scss +22 -0
  157. package/src/FilePreview/FilePreview.test.tsx +112 -0
  158. package/src/FilePreview/FilePreview.tsx +58 -0
  159. package/src/FilePreview/index.ts +3 -0
  160. package/src/Message/CodeBlockMessage/CodeBlockMessage.scss +2 -1
  161. package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +6 -5
  162. package/src/Message/LinkMessage/LinkMessage.tsx +6 -2
  163. package/src/Message/ListMessage/ListItemMessage.tsx +5 -1
  164. package/src/Message/ListMessage/ListMessage.scss +17 -0
  165. package/src/Message/Message.scss +44 -0
  166. package/src/Message/Message.test.tsx +90 -0
  167. package/src/Message/Message.tsx +171 -46
  168. package/src/Message/SuperscriptMessage/SuperscriptMessage.scss +8 -0
  169. package/src/Message/SuperscriptMessage/SuperscriptMessage.tsx +13 -0
  170. package/src/Message/TextMessage/TextMessage.scss +46 -5
  171. package/src/Message/UserFeedback/UserFeedback.test.tsx +107 -0
  172. package/src/Message/UserFeedback/UserFeedback.tsx +41 -6
  173. package/src/MessageBar/MessageBar.tsx +23 -3
  174. package/src/MessageBox/JumpButton.test.tsx +4 -4
  175. package/src/MessageBox/JumpButton.tsx +20 -4
  176. package/src/MessageBox/MessageBox.test.tsx +2 -2
  177. package/src/MessageBox/MessageBox.tsx +22 -1
  178. package/src/SourcesCard/SourcesCard.scss +17 -0
  179. package/src/SourcesCard/SourcesCard.test.tsx +93 -0
  180. package/src/SourcesCard/SourcesCard.tsx +116 -80
  181. package/src/ToolResponse/ToolResponse.scss +36 -0
  182. package/src/ToolResponse/ToolResponse.test.tsx +78 -0
  183. package/src/ToolResponse/ToolResponse.tsx +95 -0
  184. package/src/ToolResponse/index.ts +3 -0
  185. package/src/index.ts +9 -0
  186. package/src/main.scss +3 -0
@@ -6,20 +6,20 @@ import userEvent from '@testing-library/user-event';
6
6
  describe('JumpButton', () => {
7
7
  it('should render top button correctly', () => {
8
8
  render(<JumpButton position="top" onClick={jest.fn()} />);
9
- expect(screen.getByRole('button', { name: /Jump top/i })).toBeTruthy();
9
+ expect(screen.getByRole('button', { name: /Back to top/i })).toBeTruthy();
10
10
  });
11
11
  it('should render bottom button correctly', () => {
12
12
  render(<JumpButton position="bottom" onClick={jest.fn()} />);
13
- expect(screen.getByRole('button', { name: /Jump bottom/i })).toBeTruthy();
13
+ expect(screen.getByRole('button', { name: /Back to bottom/i })).toBeTruthy();
14
14
  });
15
15
  it('should call onClick appropriately', async () => {
16
16
  const spy = jest.fn();
17
17
  render(<JumpButton position="bottom" onClick={spy} />);
18
- await userEvent.click(screen.getByRole('button', { name: /Jump bottom/i }));
18
+ await userEvent.click(screen.getByRole('button', { name: /Back to bottom/i }));
19
19
  expect(spy).toHaveBeenCalledTimes(1);
20
20
  });
21
21
  it('should be hidden if isHidden prop is used', async () => {
22
22
  render(<JumpButton position="bottom" onClick={jest.fn()} isHidden />);
23
- expect(screen.queryByRole('button', { name: /Jump bottom/i })).toBeFalsy();
23
+ expect(screen.queryByRole('button', { name: /Back to bottom/i })).toBeFalsy();
24
24
  });
25
25
  });
@@ -4,7 +4,7 @@
4
4
  import type { FunctionComponent } from 'react';
5
5
 
6
6
  // Import PatternFly components
7
- import { Button, Tooltip, Icon } from '@patternfly/react-core';
7
+ import { Button, Tooltip, Icon, TooltipProps, ButtonProps } from '@patternfly/react-core';
8
8
 
9
9
  import { ArrowUpIcon } from '@patternfly/react-icons/dist/esm/icons/arrow-up-icon';
10
10
  import { ArrowDownIcon } from '@patternfly/react-icons/dist/esm/icons/arrow-down-icon';
@@ -16,16 +16,32 @@ export interface JumpButtonProps {
16
16
  onClick: () => void;
17
17
  /** Flag to change the visibilty of the button */
18
18
  isHidden?: boolean;
19
+ /** Additional props passed to jump buttons */
20
+ jumpButtonProps?: ButtonProps;
21
+ /** Additional props passed to tooltip */
22
+ jumpButtonTooltipProps?: TooltipProps;
19
23
  }
20
24
 
21
- const JumpButton: FunctionComponent<JumpButtonProps> = ({ position, isHidden, onClick }: JumpButtonProps) =>
25
+ const JumpButton: FunctionComponent<JumpButtonProps> = ({
26
+ position,
27
+ isHidden,
28
+ onClick,
29
+ jumpButtonProps,
30
+ jumpButtonTooltipProps
31
+ }: JumpButtonProps) =>
22
32
  isHidden ? null : (
23
- <Tooltip id={`pf-chatbot__tooltip--jump-${position}`} content={`Back to ${position}`} position="top">
33
+ <Tooltip
34
+ id={`pf-chatbot__tooltip--jump-${position}`}
35
+ content={`Back to ${position}`}
36
+ position="top"
37
+ {...jumpButtonTooltipProps}
38
+ >
24
39
  <Button
25
40
  variant="plain"
26
41
  className={`pf-chatbot__jump pf-chatbot__jump--${position}`}
27
- aria-label={`Jump ${position}`}
42
+ aria-label={`Back to ${position}`}
28
43
  onClick={onClick}
44
+ {...jumpButtonProps}
29
45
  >
30
46
  <Icon iconSize="lg" isInline>
31
47
  {position === 'top' ? <ArrowUpIcon /> : <ArrowDownIcon />}
@@ -61,7 +61,7 @@ describe('MessageBox', () => {
61
61
  });
62
62
 
63
63
  await waitFor(() => {
64
- userEvent.click(screen.getByRole('button', { name: /Jump bottom/i }));
64
+ userEvent.click(screen.getByRole('button', { name: /Back to bottom/i }));
65
65
  expect(spy).toHaveBeenCalled();
66
66
  });
67
67
  });
@@ -85,7 +85,7 @@ describe('MessageBox', () => {
85
85
  region.dispatchEvent(new Event('scroll'));
86
86
  });
87
87
  await waitFor(() => {
88
- userEvent.click(screen.getByRole('button', { name: /Jump top/i }));
88
+ userEvent.click(screen.getByRole('button', { name: /Back to top/i }));
89
89
  expect(spy).toHaveBeenCalled();
90
90
  });
91
91
  });
@@ -18,6 +18,7 @@ import {
18
18
  WheelEventHandler
19
19
  } from 'react';
20
20
  import JumpButton from './JumpButton';
21
+ import { ButtonProps, TooltipProps } from '@patternfly/react-core';
21
22
 
22
23
  export interface MessageBoxProps extends HTMLProps<HTMLDivElement> {
23
24
  /** Content that can be announced, such as a new message, for screen readers */
@@ -38,6 +39,14 @@ export interface MessageBoxProps extends HTMLProps<HTMLDivElement> {
38
39
  onScrollToBottomClick?: () => void;
39
40
  /** Flag to enable automatic scrolling when new messages are added */
40
41
  enableSmartScroll?: boolean;
42
+ /** Props passed to top jump button */
43
+ jumpButtonTopProps?: ButtonProps;
44
+ /** Props passed to bottom jump button */
45
+ jumpButtonBottomProps?: ButtonProps;
46
+ /** Props passed to top jump button tooltip */
47
+ jumpButtonTopTooltipProps?: TooltipProps;
48
+ /** Props passed to top jump button tooltip */
49
+ jumpButtonBottomTooltipProps?: TooltipProps;
41
50
  }
42
51
 
43
52
  export interface MessageBoxHandle extends HTMLDivElement {
@@ -60,6 +69,10 @@ export const MessageBox = forwardRef(
60
69
  onScrollToTopClick,
61
70
  onScrollToBottomClick,
62
71
  enableSmartScroll = false,
72
+ jumpButtonTopProps,
73
+ jumpButtonBottomProps,
74
+ jumpButtonBottomTooltipProps,
75
+ jumpButtonTopTooltipProps,
63
76
  ...props
64
77
  }: MessageBoxProps,
65
78
  ref: ForwardedRef<MessageBoxHandle | null>
@@ -305,7 +318,13 @@ export const MessageBox = forwardRef(
305
318
 
306
319
  return (
307
320
  <>
308
- <JumpButton position="top" isHidden={isOverflowing && atTop} onClick={scrollToTop} />
321
+ <JumpButton
322
+ position="top"
323
+ isHidden={isOverflowing && atTop}
324
+ onClick={scrollToTop}
325
+ jumpButtonProps={jumpButtonTopProps}
326
+ jumpButtonTooltipProps={jumpButtonTopTooltipProps}
327
+ />
309
328
  <div
310
329
  role="region"
311
330
  tabIndex={0}
@@ -324,6 +343,8 @@ export const MessageBox = forwardRef(
324
343
  position="bottom"
325
344
  isHidden={isOverflowing && atBottom}
326
345
  onClick={() => scrollToBottom({ resumeSmartScroll: true })}
346
+ jumpButtonProps={jumpButtonBottomProps}
347
+ jumpButtonTooltipProps={jumpButtonBottomTooltipProps}
327
348
  />
328
349
  </>
329
350
  );
@@ -16,6 +16,17 @@
16
16
  box-shadow: var(--pf-t--global--box-shadow--sm);
17
17
  }
18
18
 
19
+ .pf-chatbot__compact-sources-card-body {
20
+ --pf-v6-c-card--child--PaddingBlockEnd: var(--pf-t--global--spacer--xs);
21
+ }
22
+
23
+ .pf-chatbot__sources-card-subtitle,
24
+ .pf-chatbot__sources-card-subtle {
25
+ color: var(--pf-t--global--text--color--subtle);
26
+ font-size: var(--pf-t--global--font--size--body--sm);
27
+ font-weight: var(--pf-t--global--font--weight--body--default);
28
+ }
29
+
19
30
  .pf-chatbot__sources-card-body-text {
20
31
  display: block;
21
32
  display: -webkit-box;
@@ -27,6 +38,12 @@
27
38
  text-overflow: ellipsis;
28
39
  }
29
40
 
41
+ .pf-chatbot__sources-card-title-container {
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: var(--pf-t--global--spacer--xs);
45
+ }
46
+
30
47
  .pf-chatbot__sources-card-footer-container {
31
48
  border-top: var(--pf-t--global--border--width--regular) solid var(--pf-t--global--border--color--default);
32
49
  padding: var(--pf-t--global--spacer--sm) var(--pf-t--global--spacer--md) var(--pf-t--global--spacer--sm)
@@ -256,4 +256,97 @@ describe('SourcesCard', () => {
256
256
  );
257
257
  expect(screen.getByRole('link', { name: /How to make an apple pie/i })).toHaveClass('test');
258
258
  });
259
+
260
+ it('should apply cardTitleProps appropriately', () => {
261
+ render(
262
+ <SourcesCard
263
+ cardTitleProps={{ 'data-testid': 'card-title', className: 'test' } as any}
264
+ sources={[{ title: 'How to make an apple pie', link: '' }]}
265
+ />
266
+ );
267
+ expect(screen.getByTestId('card-title')).toHaveClass('test');
268
+ });
269
+
270
+ it('should apply cardBodyProps appropriately', () => {
271
+ render(
272
+ <SourcesCard
273
+ cardBodyProps={
274
+ { 'data-testid': 'card-body', body: 'To make an apple pie, you must first...', className: 'test' } as any
275
+ }
276
+ sources={[{ title: 'How to make an apple pie', link: '', body: 'To make an apple pie, you must first...' }]}
277
+ />
278
+ );
279
+ expect(screen.getByTestId('card-body')).toHaveClass('test');
280
+ });
281
+
282
+ it('should apply cardFooterProps appropriately', () => {
283
+ render(
284
+ <SourcesCard
285
+ cardFooterProps={{ 'data-testid': 'card-footer', className: 'test' } as any}
286
+ sources={[
287
+ { title: 'How to make an apple pie', link: '' },
288
+ { title: 'How to make cookies', link: '' }
289
+ ]}
290
+ />
291
+ );
292
+ expect(screen.getByTestId('card-footer')).toHaveClass('test');
293
+ });
294
+
295
+ it('should apply truncateProps appropriately', () => {
296
+ render(
297
+ <SourcesCard
298
+ sources={[
299
+ {
300
+ title: 'How to make an apple pie',
301
+ link: '',
302
+ truncateProps: { 'data-testid': 'card-truncate', className: 'test' } as any
303
+ }
304
+ ]}
305
+ />
306
+ );
307
+ expect(screen.getByTestId('card-truncate')).toHaveClass('test');
308
+ });
309
+
310
+ it('should apply custom footer appropriately when there is one source', () => {
311
+ render(
312
+ <SourcesCard sources={[{ title: 'How to make an apple pie', link: '', footer: <>I am a custom footer</> }]} />
313
+ );
314
+ expect(screen.getByText('I am a custom footer'));
315
+ expect(screen.queryByText('1/1')).toBeFalsy();
316
+ });
317
+
318
+ it('should apply custom footer appropriately when are multiple sources', () => {
319
+ render(
320
+ <SourcesCard
321
+ sources={[
322
+ { title: 'How to make an apple pie', link: '', footer: <>I am a custom footer</> },
323
+ { title: 'How to bake bread', link: '' }
324
+ ]}
325
+ />
326
+ );
327
+ expect(screen.getByText('I am a custom footer'));
328
+ // does not show navigation bar
329
+ expect(screen.queryByText('1/2')).toBeFalsy();
330
+ });
331
+
332
+ it('should apply footer props to custom footer appropriately', () => {
333
+ render(
334
+ <SourcesCard
335
+ cardFooterProps={{ 'data-testid': 'card-footer', className: 'test' } as any}
336
+ sources={[{ title: 'How to make an apple pie', link: '', footer: <>I am a custom footer</> }]}
337
+ />
338
+ );
339
+ expect(screen.getByText('I am a custom footer'));
340
+ expect(screen.getByTestId('card-footer')).toHaveClass('test');
341
+ });
342
+
343
+ it('should apply subtitle appropriately', () => {
344
+ render(
345
+ <SourcesCard
346
+ sources={[{ title: 'How to make an apple pie', link: '', subtitle: 'You must first create the universe' }]}
347
+ />
348
+ );
349
+ expect(screen.getByText('How to make an apple pie'));
350
+ expect(screen.getByText('You must first create the universe'));
351
+ });
259
352
  });
@@ -10,14 +10,18 @@ import {
10
10
  ButtonVariant,
11
11
  Card,
12
12
  CardBody,
13
+ CardBodyProps,
13
14
  CardFooter,
15
+ CardFooterProps,
14
16
  CardProps,
15
17
  CardTitle,
18
+ CardTitleProps,
16
19
  ExpandableSection,
17
20
  ExpandableSectionVariant,
18
21
  Icon,
19
22
  pluralize,
20
- Truncate
23
+ Truncate,
24
+ TruncateProps
21
25
  } from '@patternfly/react-core';
22
26
  import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
23
27
 
@@ -34,6 +38,8 @@ export interface SourcesCardProps extends CardProps {
34
38
  sources: {
35
39
  /** Title of sources card */
36
40
  title?: string;
41
+ /** Subtitle of sources card */
42
+ subtitle?: string;
37
43
  /** Link to source */
38
44
  link: string;
39
45
  /** Body of sources card */
@@ -46,6 +52,10 @@ export interface SourcesCardProps extends CardProps {
46
52
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
47
53
  /** Any additional props applied to the title of the Sources card */
48
54
  titleProps?: ButtonProps;
55
+ /** Custom footer applied to the Sources card */
56
+ footer?: React.ReactNode;
57
+ /** Additional props passed to Truncate component */
58
+ truncateProps?: TruncateProps;
49
59
  }[];
50
60
  /** Label for the English word "source" */
51
61
  sourceWord?: string;
@@ -65,6 +75,12 @@ export interface SourcesCardProps extends CardProps {
65
75
  showMoreWords?: string;
66
76
  /** Label for English words "show less" */
67
77
  showLessWords?: string;
78
+ /** Additional props passed to card title */
79
+ cardTitleProps?: CardTitleProps;
80
+ /** Additional props passed to card body */
81
+ cardBodyProps?: CardBodyProps;
82
+ /** Additional props passed to card footer */
83
+ cardFooterProps?: CardFooterProps;
68
84
  }
69
85
 
70
86
  const SourcesCard: FunctionComponent<SourcesCardProps> = ({
@@ -82,6 +98,9 @@ const SourcesCard: FunctionComponent<SourcesCardProps> = ({
82
98
  showMoreWords = 'show more',
83
99
  showLessWords = 'show less',
84
100
  isCompact,
101
+ cardTitleProps,
102
+ cardBodyProps,
103
+ cardFooterProps,
85
104
  ...props
86
105
  }: SourcesCardProps) => {
87
106
  const [page, setPage] = useState(1);
@@ -96,9 +115,9 @@ const SourcesCard: FunctionComponent<SourcesCardProps> = ({
96
115
  onSetPage && onSetPage(_evt, newPage);
97
116
  };
98
117
 
99
- const renderTitle = (title?: string) => {
118
+ const renderTitle = (title?: string, truncateProps?: TruncateProps) => {
100
119
  if (title) {
101
- return <Truncate content={title} />;
120
+ return <Truncate content={title} {...truncateProps} />;
102
121
  }
103
122
  return `Source ${page}`;
104
123
  };
@@ -107,24 +126,32 @@ const SourcesCard: FunctionComponent<SourcesCardProps> = ({
107
126
  <div className="pf-chatbot__source">
108
127
  <span>{pluralize(sources.length, sourceWord, sourceWordPlural)}</span>
109
128
  <Card isCompact={isCompact} className="pf-chatbot__sources-card" {...props}>
110
- <CardTitle className="pf-chatbot__sources-card-title">
111
- <Button
112
- component="a"
113
- variant={ButtonVariant.link}
114
- href={sources[page - 1].link}
115
- icon={sources[page - 1].isExternal ? <ExternalLinkSquareAltIcon /> : undefined}
116
- iconPosition="end"
117
- isInline
118
- rel={sources[page - 1].isExternal ? 'noreferrer' : undefined}
119
- target={sources[page - 1].isExternal ? '_blank' : undefined}
120
- onClick={sources[page - 1].onClick ?? undefined}
121
- {...sources[page - 1].titleProps}
122
- >
123
- {renderTitle(sources[page - 1].title)}
124
- </Button>
129
+ <CardTitle className="pf-chatbot__sources-card-title" {...cardTitleProps}>
130
+ <div className="pf-chatbot__sources-card-title-container">
131
+ <Button
132
+ component="a"
133
+ variant={ButtonVariant.link}
134
+ href={sources[page - 1].link}
135
+ icon={sources[page - 1].isExternal ? <ExternalLinkSquareAltIcon /> : undefined}
136
+ iconPosition="end"
137
+ isInline
138
+ rel={sources[page - 1].isExternal ? 'noreferrer' : undefined}
139
+ target={sources[page - 1].isExternal ? '_blank' : undefined}
140
+ onClick={sources[page - 1].onClick ?? undefined}
141
+ {...sources[page - 1].titleProps}
142
+ >
143
+ {renderTitle(sources[page - 1].title, sources[page - 1].truncateProps)}
144
+ </Button>
145
+ {sources[page - 1].subtitle && (
146
+ <span className="pf-chatbot__sources-card-subtitle">{sources[page - 1].subtitle}</span>
147
+ )}
148
+ </div>
125
149
  </CardTitle>
126
150
  {sources[page - 1].body && (
127
- <CardBody className={`pf-chatbot__sources-card-body`}>
151
+ <CardBody
152
+ className={`pf-chatbot__sources-card-body ${sources[page - 1].footer ? 'pf-chatbot__compact-sources-card-body' : undefined}`}
153
+ {...cardBodyProps}
154
+ >
128
155
  {sources[page - 1].hasShowMore ? (
129
156
  // prevents extra VO announcements of button text - parent Message has aria-live
130
157
  <div aria-live="off">
@@ -143,68 +170,77 @@ const SourcesCard: FunctionComponent<SourcesCardProps> = ({
143
170
  )}
144
171
  </CardBody>
145
172
  )}
146
- {sources.length > 1 && (
147
- <CardFooter className="pf-chatbot__sources-card-footer-container">
148
- <div className="pf-chatbot__sources-card-footer">
149
- <nav className={`pf-chatbot__sources-card-footer-buttons ${className}`} aria-label={paginationAriaLabel}>
150
- <Button
151
- variant={ButtonVariant.plain}
152
- isDisabled={isDisabled || page === 1}
153
- data-action="previous"
154
- onClick={(event) => {
155
- const newPage = page >= 1 ? page - 1 : 1;
156
- onPreviousClick && onPreviousClick(event, newPage);
157
- handleNewPage(event, newPage);
158
- }}
159
- aria-label={toPreviousPageAriaLabel}
160
- >
161
- <Icon iconSize="lg">
162
- {/* these are inline because the viewBox that works in a round icon is different than the PatternFly default */}
163
- <svg
164
- className="pf-v6-svg"
165
- viewBox="0 0 280 500"
166
- fill="currentColor"
167
- aria-hidden="true"
168
- role="img"
169
- width="1em"
170
- height="1em"
171
- >
172
- <path d="M31.7 239l136-136c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9L127.9 256l96.4 96.4c9.4 9.4 9.4 24.6 0 33.9L201.7 409c-9.4 9.4-24.6 9.4-33.9 0l-136-136c-9.5-9.4-9.5-24.6-.1-34z"></path>
173
- </svg>
174
- </Icon>
175
- </Button>
176
- <span aria-hidden="true">
177
- {page}/{sources.length}
178
- </span>
179
- <Button
180
- variant={ButtonVariant.plain}
181
- isDisabled={isDisabled || page === sources.length}
182
- aria-label={toNextPageAriaLabel}
183
- data-action="next"
184
- onClick={(event) => {
185
- const newPage = page + 1 <= sources.length ? page + 1 : sources.length;
186
- onNextClick && onNextClick(event, newPage);
187
- handleNewPage(event, newPage);
188
- }}
189
- >
190
- <Icon isInline iconSize="lg">
191
- {/* these are inline because the viewBox that works in a round icon is different than the PatternFly default */}
192
- <svg
193
- className="pf-v6-svg"
194
- viewBox="0 0 180 500"
195
- fill="currentColor"
196
- aria-hidden="true"
197
- role="img"
198
- width="1em"
199
- height="1em"
200
- >
201
- <path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"></path>
202
- </svg>
203
- </Icon>
204
- </Button>
205
- </nav>
206
- </div>
173
+ {sources[page - 1].footer ? (
174
+ <CardFooter className="pf-chatbot__sources-card-footer" {...cardFooterProps}>
175
+ {sources[page - 1].footer}
207
176
  </CardFooter>
177
+ ) : (
178
+ sources.length > 1 && (
179
+ <CardFooter className="pf-chatbot__sources-card-footer-container" {...cardFooterProps}>
180
+ <div className="pf-chatbot__sources-card-footer">
181
+ <nav
182
+ className={`pf-chatbot__sources-card-footer-buttons ${className}`}
183
+ aria-label={paginationAriaLabel}
184
+ >
185
+ <Button
186
+ variant={ButtonVariant.plain}
187
+ isDisabled={isDisabled || page === 1}
188
+ data-action="previous"
189
+ onClick={(event) => {
190
+ const newPage = page >= 1 ? page - 1 : 1;
191
+ onPreviousClick && onPreviousClick(event, newPage);
192
+ handleNewPage(event, newPage);
193
+ }}
194
+ aria-label={toPreviousPageAriaLabel}
195
+ >
196
+ <Icon iconSize="lg">
197
+ {/* these are inline because the viewBox that works in a round icon is different than the PatternFly default */}
198
+ <svg
199
+ className="pf-v6-svg"
200
+ viewBox="0 0 280 500"
201
+ fill="currentColor"
202
+ aria-hidden="true"
203
+ role="img"
204
+ width="1em"
205
+ height="1em"
206
+ >
207
+ <path d="M31.7 239l136-136c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9L127.9 256l96.4 96.4c9.4 9.4 9.4 24.6 0 33.9L201.7 409c-9.4 9.4-24.6 9.4-33.9 0l-136-136c-9.5-9.4-9.5-24.6-.1-34z"></path>
208
+ </svg>
209
+ </Icon>
210
+ </Button>
211
+ <span aria-hidden="true">
212
+ {page}/{sources.length}
213
+ </span>
214
+ <Button
215
+ variant={ButtonVariant.plain}
216
+ isDisabled={isDisabled || page === sources.length}
217
+ aria-label={toNextPageAriaLabel}
218
+ data-action="next"
219
+ onClick={(event) => {
220
+ const newPage = page + 1 <= sources.length ? page + 1 : sources.length;
221
+ onNextClick && onNextClick(event, newPage);
222
+ handleNewPage(event, newPage);
223
+ }}
224
+ >
225
+ <Icon isInline iconSize="lg">
226
+ {/* these are inline because the viewBox that works in a round icon is different than the PatternFly default */}
227
+ <svg
228
+ className="pf-v6-svg"
229
+ viewBox="0 0 180 500"
230
+ fill="currentColor"
231
+ aria-hidden="true"
232
+ role="img"
233
+ width="1em"
234
+ height="1em"
235
+ >
236
+ <path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"></path>
237
+ </svg>
238
+ </Icon>
239
+ </Button>
240
+ </nav>
241
+ </div>
242
+ </CardFooter>
243
+ )
208
244
  )}
209
245
  </Card>
210
246
  </div>
@@ -0,0 +1,36 @@
1
+ .pf-chatbot__tool-response {
2
+ --pf-v6-c-card--BorderColor: var(--pf-t--global--border--color--control--read-only);
3
+ overflow: unset;
4
+ }
5
+
6
+ .pf-chatbot__tool-response-expandable-section {
7
+ --pf-v6-c-expandable-section--Gap: var(--pf-t--global--spacer--xs);
8
+ }
9
+
10
+ .pf-chatbot__tool-response-section {
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: var(--pf-t--global--spacer--xs);
14
+ }
15
+
16
+ .pf-chatbot__tool-response-subheading {
17
+ font-size: var(--pf-t--global--font--size--body--sm);
18
+ font-weight: var(--pf-t--global--font--weight--body--default);
19
+ color: var(--pf-t--global--text--color--subtle);
20
+ }
21
+
22
+ .pf-chatbot__tool-response-body {
23
+ color: var(--pf-t--global--text--color--subtle);
24
+ margin-block-end: var(--pf-t--global--spacer--xs);
25
+ }
26
+
27
+ .pf-chatbot__tool-response-card {
28
+ --pf-v6-c-card--BorderColor: var(--pf-t--global--border--color--control--read-only);
29
+ --pf-v6-c-card--first-child--PaddingBlockStart: var(--pf-t--global--spacer--sm);
30
+ --pf-v6-c-card__title--not--last-child--PaddingBlockEnd: var(--pf-t--global--spacer--sm);
31
+ --pf-v6-c-card--c-divider--child--PaddingBlockStart: var(--pf-t--global--spacer--sm);
32
+
33
+ .pf-v6-c-divider {
34
+ --pf-v6-c-divider--Color: var(--pf-t--global--border--color--control--read-only);
35
+ }
36
+ }
@@ -0,0 +1,78 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import '@testing-library/jest-dom';
3
+ import ToolResponse from './ToolResponse';
4
+
5
+ describe('ToolResponse', () => {
6
+ const defaultProps = {
7
+ toggleContent: 'Tool response: toolName',
8
+ cardTitle: 'Title',
9
+ cardBody: 'Body'
10
+ };
11
+
12
+ it('should render with required props only', () => {
13
+ render(<ToolResponse {...defaultProps} />);
14
+ expect(screen.getByText('Title')).toBeTruthy();
15
+ expect(screen.getByText('Body')).toBeTruthy();
16
+ expect(screen.getByText('Tool response: toolName')).toBeTruthy();
17
+ });
18
+
19
+ it('should render subheading when provided', () => {
20
+ const subheading = 'Tool execution result';
21
+ render(<ToolResponse {...defaultProps} subheading={subheading} />);
22
+ expect(screen.getByText(subheading)).toBeTruthy();
23
+ });
24
+
25
+ it('should render body content when provided', () => {
26
+ const body = 'This is the tool response body content';
27
+ render(<ToolResponse {...defaultProps} body={body} />);
28
+ expect(screen.getByText(body)).toBeTruthy();
29
+ });
30
+
31
+ it('should render with complex content including React elements', () => {
32
+ const body = (
33
+ <div>
34
+ <p>Complex body content</p>
35
+ <ul>
36
+ <li>Item 1</li>
37
+ <li>Item 2</li>
38
+ </ul>
39
+ </div>
40
+ );
41
+ const cardTitle = <strong>API Response</strong>;
42
+ const cardBody = (
43
+ <div>
44
+ <code>{"{ status: 'success' }"}</code>
45
+ </div>
46
+ );
47
+
48
+ render(<ToolResponse {...defaultProps} body={body} cardTitle={cardTitle} cardBody={cardBody} />);
49
+ expect(screen.getByText('Complex body content')).toBeTruthy();
50
+ expect(screen.getByText('Item 1')).toBeTruthy();
51
+ expect(screen.getByText('Item 2')).toBeTruthy();
52
+ expect(screen.getByText('API Response')).toBeTruthy();
53
+ expect(screen.getByText("{ status: 'success' }")).toBeTruthy();
54
+ });
55
+
56
+ it('should apply custom className from cardProps', () => {
57
+ const { container } = render(
58
+ <ToolResponse {...defaultProps} cardProps={{ className: 'custom-tool-response-class' }} />
59
+ );
60
+ expect(container.querySelector('.custom-tool-response-class')).toBeTruthy();
61
+ });
62
+
63
+ it('should pass through expandableSectionProps', () => {
64
+ render(<ToolResponse {...defaultProps} expandableSectionProps={{ className: 'custom-expandable-class' }} />);
65
+ expect(document.querySelector('.custom-expandable-class')).toBeTruthy();
66
+ });
67
+
68
+ it('should pass through toolResponseCardProps', () => {
69
+ render(<ToolResponse {...defaultProps} toolResponseCardProps={{ className: 'custom-card-class' }} />);
70
+ expect(document.querySelector('.custom-card-class')).toBeTruthy();
71
+ });
72
+
73
+ it('should not render subheading span when subheading is not provided', () => {
74
+ const { container } = render(<ToolResponse {...defaultProps} />);
75
+ const subheadingContainer = container.querySelector('.pf-chatbot__tool-response-subheading');
76
+ expect(subheadingContainer).toBeFalsy();
77
+ });
78
+ });