@patternfly/chatbot 6.3.2 → 6.4.0-prerelease.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
  4. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +27 -4
  5. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +8 -14
  6. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +53 -2
  7. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
  8. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
  9. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
  10. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.js +25 -0
  11. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
  12. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +22 -0
  13. package/dist/cjs/ChatbotHeader/index.d.ts +1 -0
  14. package/dist/cjs/ChatbotHeader/index.js +1 -0
  15. package/dist/cjs/FileDropZone/FileDropZone.d.ts +1 -2
  16. package/dist/cjs/Message/Message.d.ts +9 -2
  17. package/dist/cjs/Message/Message.js +40 -34
  18. package/dist/cjs/Message/Message.test.js +37 -0
  19. package/dist/cjs/Message/MessageInput.d.ts +3 -1
  20. package/dist/cjs/Message/MessageInput.js +2 -2
  21. package/dist/cjs/MessageBar/AttachButton.d.ts +2 -2
  22. package/dist/cjs/MessageBar/MessageBar.d.ts +2 -2
  23. package/dist/cjs/MessageBox/JumpButton.d.ts +5 -0
  24. package/dist/cjs/MessageBox/JumpButton.js +1 -1
  25. package/dist/cjs/MessageBox/JumpButton.test.js +4 -4
  26. package/dist/cjs/MessageBox/MessageBox.d.ts +9 -0
  27. package/dist/cjs/MessageBox/MessageBox.js +2 -2
  28. package/dist/cjs/MessageBox/MessageBox.test.js +2 -2
  29. package/dist/cjs/MessageDivider/MessageDivider.d.ts +9 -0
  30. package/dist/cjs/MessageDivider/MessageDivider.js +23 -0
  31. package/dist/cjs/MessageDivider/MessageDivider.test.d.ts +1 -0
  32. package/dist/cjs/MessageDivider/MessageDivider.test.js +29 -0
  33. package/dist/cjs/MessageDivider/index.d.ts +2 -0
  34. package/dist/cjs/MessageDivider/index.js +23 -0
  35. package/dist/cjs/ResponseActions/ResponseActions.d.ts +1 -0
  36. package/dist/cjs/ResponseActions/ResponseActions.js +4 -4
  37. package/dist/cjs/ResponseActions/ResponseActions.test.js +6 -1
  38. package/dist/cjs/index.d.ts +2 -0
  39. package/dist/cjs/index.js +4 -1
  40. package/dist/css/main.css +103 -81
  41. package/dist/css/main.css.map +1 -1
  42. package/dist/dynamic/MessageDivider/package.json +1 -0
  43. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  44. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  45. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
  46. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +27 -4
  47. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +10 -16
  48. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +54 -3
  49. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
  50. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
  51. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
  52. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.js +22 -0
  53. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
  54. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +20 -0
  55. package/dist/esm/ChatbotHeader/index.d.ts +1 -0
  56. package/dist/esm/ChatbotHeader/index.js +1 -0
  57. package/dist/esm/FileDropZone/FileDropZone.d.ts +1 -2
  58. package/dist/esm/Message/Message.d.ts +9 -2
  59. package/dist/esm/Message/Message.js +40 -34
  60. package/dist/esm/Message/Message.test.js +37 -0
  61. package/dist/esm/Message/MessageInput.d.ts +3 -1
  62. package/dist/esm/Message/MessageInput.js +2 -2
  63. package/dist/esm/MessageBar/AttachButton.d.ts +2 -2
  64. package/dist/esm/MessageBar/MessageBar.d.ts +2 -2
  65. package/dist/esm/MessageBox/JumpButton.d.ts +5 -0
  66. package/dist/esm/MessageBox/JumpButton.js +1 -1
  67. package/dist/esm/MessageBox/JumpButton.test.js +4 -4
  68. package/dist/esm/MessageBox/MessageBox.d.ts +9 -0
  69. package/dist/esm/MessageBox/MessageBox.js +2 -2
  70. package/dist/esm/MessageBox/MessageBox.test.js +2 -2
  71. package/dist/esm/MessageDivider/MessageDivider.d.ts +9 -0
  72. package/dist/esm/MessageDivider/MessageDivider.js +21 -0
  73. package/dist/esm/MessageDivider/MessageDivider.test.d.ts +1 -0
  74. package/dist/esm/MessageDivider/MessageDivider.test.js +24 -0
  75. package/dist/esm/MessageDivider/index.d.ts +2 -0
  76. package/dist/esm/MessageDivider/index.js +2 -0
  77. package/dist/esm/ResponseActions/ResponseActions.d.ts +1 -0
  78. package/dist/esm/ResponseActions/ResponseActions.js +5 -5
  79. package/dist/esm/ResponseActions/ResponseActions.test.js +6 -1
  80. package/dist/esm/index.d.ts +2 -0
  81. package/dist/esm/index.js +2 -0
  82. package/dist/tsconfig.tsbuildinfo +1 -1
  83. package/package.json +9 -4
  84. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDividers.tsx +24 -0
  85. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +18 -1
  86. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +39 -7
  87. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx +401 -3
  88. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotConversationEditing.tsx +202 -0
  89. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderBasic.tsx +17 -3
  90. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +45 -5
  91. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +206 -0
  92. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +30 -4
  93. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +33 -1
  94. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotDisplayMode.tsx +486 -0
  95. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +565 -0
  96. package/src/Chatbot/Chatbot.scss +1 -1
  97. package/src/ChatbotContent/ChatbotContent.scss +1 -1
  98. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx +6 -6
  99. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +5 -2
  100. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +70 -32
  101. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +176 -3
  102. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +110 -60
  103. package/src/ChatbotFooter/ChatbotFooter.scss +1 -1
  104. package/src/ChatbotHeader/ChatbotHeader.scss +3 -3
  105. package/src/ChatbotHeader/ChatbotHeaderMenu.test.tsx +1 -1
  106. package/src/ChatbotHeader/ChatbotHeaderMenu.tsx +2 -2
  107. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.test.tsx +25 -0
  108. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.tsx +64 -0
  109. package/src/ChatbotHeader/index.ts +1 -0
  110. package/src/ChatbotModal/ChatbotModal.scss +1 -1
  111. package/src/ChatbotToggle/ChatbotToggle.scss +2 -2
  112. package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +6 -9
  113. package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +6 -9
  114. package/src/FileDropZone/FileDropZone.tsx +2 -2
  115. package/src/Message/Message.scss +9 -7
  116. package/src/Message/Message.test.tsx +54 -0
  117. package/src/Message/Message.tsx +70 -50
  118. package/src/Message/MessageInput.tsx +5 -1
  119. package/src/MessageBar/AttachButton.tsx +2 -2
  120. package/src/MessageBar/MessageBar.tsx +2 -2
  121. package/src/MessageBar/SendButton.scss +3 -3
  122. package/src/MessageBox/JumpButton.scss +1 -1
  123. package/src/MessageBox/JumpButton.test.tsx +4 -4
  124. package/src/MessageBox/JumpButton.tsx +20 -4
  125. package/src/MessageBox/MessageBox.test.tsx +2 -2
  126. package/src/MessageBox/MessageBox.tsx +23 -2
  127. package/src/MessageDivider/MessageDivider.scss +45 -0
  128. package/src/MessageDivider/MessageDivider.test.tsx +24 -0
  129. package/src/MessageDivider/MessageDivider.tsx +35 -0
  130. package/src/MessageDivider/index.ts +3 -0
  131. package/src/ResponseActions/ResponseActions.test.tsx +6 -1
  132. package/src/ResponseActions/ResponseActions.tsx +24 -3
  133. package/src/index.ts +3 -0
  134. package/src/main.scss +1 -52
@@ -2,63 +2,101 @@
2
2
  // Chatbot Header - Menu
3
3
  // ============================================================================
4
4
  .pf-chatbot__history {
5
+ // hide from view but not assistive technologies
6
+ // https://css-tricks.com/inclusively-hidden/
7
+ .pf-chatbot__filter-announcement {
8
+ clip: rect(0 0 0 0);
9
+ clip-path: inset(50%);
10
+ height: 1px;
11
+ overflow: hidden;
12
+ position: absolute;
13
+ white-space: nowrap;
14
+ width: 1px;
15
+ }
16
+
5
17
  .pf-chatbot__drawer-backdrop {
6
18
  position: absolute;
7
19
  border-radius: var(--pf-t--global--border--radius--medium);
8
20
  }
9
- // Drawer input
21
+
22
+ // Drawer title
10
23
  // ----------------------------------------------------------------------------
11
- .pf-chatbot__input {
24
+ .pf-chatbot__heading-container {
12
25
  padding-inline-start: var(--pf-t--global--spacer--lg);
13
26
  padding-inline-end: var(--pf-t--global--spacer--lg);
27
+ display: flex;
28
+ flex-direction: column;
29
+ row-gap: var(--pf-t--global--spacer--sm);
30
+ }
31
+ .pf-chatbot__title {
32
+ font-size: var(--pf-v6-c-title--m-h3--FontSize);
33
+ font-weight: var(--pf-v6-c-title--m-h3--FontWeight);
34
+ line-height: var(--pf-v6-c-title--m-h3--LineHeight);
35
+ }
36
+ .pf-chatbot__title-container {
37
+ display: flex;
38
+ flex-direction: row;
39
+ align-items: baseline;
40
+ justify-content: flex-start;
41
+ gap: var(--pf-t--global--spacer--gap--text-to-element--default);
14
42
  }
15
-
16
43
  // Drawer menu
17
44
  // ----------------------------------------------------------------------------
18
- .pf-v6-c-menu {
19
- --pf-v6-c-menu--PaddingBlockStart: 0;
20
- --pf-v6-c-menu--BackgroundColor: var(--pf-t--global--background--color--floating--default);
21
- overflow: initial;
22
- position: relative;
23
- }
24
- .pf-v6-c-menu__item-main {
25
- --pf-v6-c-menu__item-main--ColumnGap: var(--pf-t--global--spacer--md);
45
+ .pf-chatbot__conversation-list {
46
+ --pf-v6-c-list--Gap: var(--pf-t--global--spacer--xs);
47
+
48
+ margin-block-start: var(--pf-t--global--spacer--md);
49
+ margin-block-end: var(--pf-t--global--spacer--md);
26
50
  }
27
- .pf-chatbot__menu-item-header > .pf-v6-c-menu__group-title {
51
+
52
+ .pf-chatbot__conversation-list-header {
28
53
  color: var(--pf-t--global--text--color--subtle);
29
54
  font-weight: var(--pf-t--global--font--weight--body--bold);
30
55
  font-size: var(--pf-t--global--icon--size--font--sm);
31
- --pf-v6-c-menu__group-title--PaddingInlineStart: var(--pf-t--global--spacer--sm);
32
- --pf-v6-c-menu__group-title--PaddingInlineEnd: var(--pf-t--global--spacer--sm);
56
+ padding-inline-start: var(--pf-t--global--spacer--sm);
57
+ padding-inline-end: var(--pf-t--global--spacer--sm);
33
58
  position: -webkit-sticky;
34
59
  position: sticky;
35
60
  top: 0;
36
61
  background-color: var(--pf-t--global--background--color--floating--default);
37
62
  z-index: var(--pf-t--global--z-index--md);
38
63
  }
39
- .pf-chatbot__menu-item {
40
- --pf-v6-c-menu__item--PaddingInlineStart: var(--pf-t--global--spacer--sm);
41
- --pf-v6-c-menu__item--PaddingInlineEnd: var(--pf-t--global--spacer--sm);
42
- padding-block-start: var(--pf-t--global--spacer--xs);
43
- padding-block-end: var(--pf-t--global--spacer--xs);
44
- color: var(--pf-t--global--text--color--regular);
45
- font-size: var(--pf-t--global--font--size--body--lg);
46
- font-weight: var(--pf-t--global--font--weight--body--default);
47
- border-radius: var(--pf-t--global--border--radius--small);
48
- }
49
- // allows focus state to have border radius
50
- .pf-v6-c-menu__list-item.pf-chatbot__menu-item {
51
- overflow: hidden;
64
+ .pf-chatbot__conversation-list-item {
65
+ & > span {
66
+ display: flex;
67
+ column-gap: var(--pf-t--global--spacer--sm);
68
+ }
69
+
70
+ & .pf-chatbot__conversation-history-item {
71
+ --pf-v6-c-button--JustifyContent: flex-start;
72
+ --pf-v6-c-button--FontSize: var(--pf-t--global--font--size--body--lg);
73
+ --pf-v6-c-button--m-link--Color: var(--pf-t--global--text--color--regular);
74
+ --pf-v6-c-button--m-link__icon--Color: var(--pf-t--global--icon--color--regular);
75
+ --pf-v6-c-button--m-link--hover--Color: var(--pf-t--global--text--color--regular--hover);
76
+ --pf-v6-c-button--m-link--hover__icon--Color: var(--pf-t--global--icon--color--regular);
77
+ --pf-v6-c-button--m-link--m-clicked--Color: var(--pf-t--global--text--color--regular--clicked);
78
+ --pf-v6-c-button--m-link--m-clicked__icon--Color: var(--pf-t--global--icon--color--regular);
79
+
80
+ column-gap: var(--pf-t--global--spacer--md);
81
+ flex-basis: 100%;
82
+
83
+ & .pf-v6-c-button__text {
84
+ overflow: hidden;
85
+ text-overflow: ellipsis;
86
+ white-space: nowrap;
87
+ }
88
+ }
52
89
  }
90
+
53
91
  .pf-chatbot__history-actions {
54
92
  transform: rotate(90deg);
55
93
  }
56
94
 
57
- .pf-chatbot__menu-item--active {
95
+ .pf-chatbot__conversation-list-item--active {
58
96
  background-color: var(--pf-t--global--background--color--action--plain--clicked);
59
97
  }
60
98
 
61
- button.pf-chatbot__menu-item--active {
99
+ button.pf-chatbot__conversation-list-item--active {
62
100
  background-color: initial;
63
101
  }
64
102
  }
@@ -75,7 +113,7 @@
75
113
  --pf-v6-c-drawer__panel--BackgroundColor: var(--pf-t--global--background--color--floating--default);
76
114
  --pf-v6-c-drawer__panel--PaddingBlockStart: var(--pf-t--global--spacer--lg);
77
115
  --pf-v6-c-drawer__panel--PaddingBlockEnd: var(--pf-t--global--spacer--lg);
78
- --pf-v6-c-drawer__panel--RowGap: var(--pf-t--global--spacer--lg);
116
+ --pf-v6-c-drawer__panel--RowGap: var(--pf-t--global--spacer--gap--group-to-group--vertical--default);
79
117
  overflow-x: hidden;
80
118
  overflow-y: hidden;
81
119
  }
@@ -221,8 +259,8 @@
221
259
  }
222
260
  }
223
261
 
224
- .pf-chatbot__menu-item {
225
- font-size: var(--pf-t--global--font--size--body--md);
262
+ .pf-chatbot__conversation-history-item {
263
+ --pf-v6-c-button--FontSize: var(--pf-t--global--font--size--body--md);
226
264
  }
227
265
 
228
266
  .pf-v6-c-drawer__head {
@@ -4,7 +4,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
4
4
  import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
5
5
  import ChatbotConversationHistoryNav, { Conversation } from './ChatbotConversationHistoryNav';
6
6
  import { EmptyStateStatus, Spinner } from '@patternfly/react-core';
7
- import { OutlinedCommentsIcon, SearchIcon } from '@patternfly/react-icons';
7
+ import { BellIcon, OutlinedCommentsIcon, SearchIcon } from '@patternfly/react-icons';
8
8
  import { ComponentType } from 'react';
9
9
 
10
10
  const ERROR = {
@@ -104,6 +104,23 @@ describe('ChatbotConversationHistoryNav', () => {
104
104
  expect(screen.getByTestId('chatbot-nav-drawer-actions')).toHaveClass('pf-v6-c-drawer__actions--reversed');
105
105
  });
106
106
 
107
+ it('should disable new chat button', () => {
108
+ render(
109
+ <ChatbotConversationHistoryNav
110
+ onDrawerToggle={onDrawerToggle}
111
+ isDrawerOpen={true}
112
+ displayMode={ChatbotDisplayMode.fullscreen}
113
+ setIsDrawerOpen={jest.fn()}
114
+ reverseButtonOrder
115
+ conversations={initialConversations}
116
+ newChatButtonProps={{ isDisabled: true }}
117
+ onNewChat={jest.fn()}
118
+ />
119
+ );
120
+
121
+ expect(screen.getByRole('button', { name: 'New chat' })).toBeDisabled();
122
+ });
123
+
107
124
  it('should not apply the reversed class when reverseButtonOrder is false', () => {
108
125
  render(
109
126
  <ChatbotConversationHistoryNav
@@ -331,7 +348,7 @@ describe('ChatbotConversationHistoryNav', () => {
331
348
  ).toBeTruthy();
332
349
  expect(screen.getByRole('button', { name: /Close drawer panel/i })).toBeTruthy();
333
350
  expect(screen.getByRole('button', { name: /Loading... Reload/i })).toBeTruthy();
334
- expect(screen.getByRole('textbox', { name: /Filter menu items/i })).toBeTruthy();
351
+ expect(screen.getByRole('textbox', { name: /Search previous conversations/i })).toBeTruthy();
335
352
  expect(screen.getByRole('heading', { name: /Could not load chat history/i })).toBeTruthy();
336
353
  });
337
354
 
@@ -355,7 +372,7 @@ describe('ChatbotConversationHistoryNav', () => {
355
372
  ).toBeTruthy();
356
373
  expect(screen.getByRole('button', { name: /Close drawer panel/i })).toBeTruthy();
357
374
  expect(screen.queryByRole('button', { name: /Loading... Reload/i })).toBeFalsy();
358
- expect(screen.getByRole('textbox', { name: /Filter menu items/i })).toBeTruthy();
375
+ expect(screen.getByRole('textbox', { name: /Search previous conversations/i })).toBeTruthy();
359
376
  expect(screen.getByRole('heading', { name: /Could not load chat history/i })).toBeTruthy();
360
377
  });
361
378
 
@@ -433,4 +450,160 @@ describe('ChatbotConversationHistoryNav', () => {
433
450
  );
434
451
  expect(screen.getByTestId('drawer')).toHaveClass('pf-m-compact');
435
452
  });
453
+
454
+ it('should display the default title', () => {
455
+ render(
456
+ <ChatbotConversationHistoryNav
457
+ onDrawerToggle={onDrawerToggle}
458
+ isDrawerOpen={true}
459
+ displayMode={ChatbotDisplayMode.fullscreen}
460
+ setIsDrawerOpen={jest.fn()}
461
+ conversations={initialConversations}
462
+ />
463
+ );
464
+ expect(screen.getByText('Chat history')).toBeInTheDocument();
465
+ });
466
+
467
+ it('should display the custom title', () => {
468
+ render(
469
+ <ChatbotConversationHistoryNav
470
+ title="PatternFly history"
471
+ onDrawerToggle={onDrawerToggle}
472
+ isDrawerOpen={true}
473
+ displayMode={ChatbotDisplayMode.fullscreen}
474
+ setIsDrawerOpen={jest.fn()}
475
+ conversations={initialConversations}
476
+ />
477
+ );
478
+ expect(screen.getByText('PatternFly history')).toBeInTheDocument();
479
+ });
480
+
481
+ it('should display the clock icon', () => {
482
+ const { container } = render(
483
+ <ChatbotConversationHistoryNav
484
+ onDrawerToggle={onDrawerToggle}
485
+ isDrawerOpen={true}
486
+ displayMode={ChatbotDisplayMode.fullscreen}
487
+ setIsDrawerOpen={jest.fn()}
488
+ conversations={initialConversations}
489
+ />
490
+ );
491
+ const iconElement = container.querySelector('.pf-chatbot__title-icon');
492
+ expect(iconElement).toBeInTheDocument();
493
+ });
494
+
495
+ it('Passes listTitleProps to Title', () => {
496
+ render(
497
+ <ChatbotConversationHistoryNav
498
+ onDrawerToggle={onDrawerToggle}
499
+ isDrawerOpen={true}
500
+ displayMode={ChatbotDisplayMode.fullscreen}
501
+ setIsDrawerOpen={jest.fn()}
502
+ conversations={{ Today: initialConversations }}
503
+ listTitleProps={{ className: 'test' }}
504
+ />
505
+ );
506
+ expect(screen.getByRole('heading', { name: /Today/i })).toHaveClass('test');
507
+ });
508
+
509
+ it('Overrides list title heading level when titleProps.headingLevel is passed', () => {
510
+ render(
511
+ <ChatbotConversationHistoryNav
512
+ onDrawerToggle={onDrawerToggle}
513
+ isDrawerOpen={true}
514
+ displayMode={ChatbotDisplayMode.fullscreen}
515
+ setIsDrawerOpen={jest.fn()}
516
+ conversations={{ Today: initialConversations }}
517
+ listTitleProps={{ headingLevel: 'h2' }}
518
+ />
519
+ );
520
+ expect(screen.queryByRole('heading', { name: /Today/i, level: 4 })).not.toBeInTheDocument();
521
+ expect(screen.getByRole('heading', { name: /Today/i, level: 2 })).toBeInTheDocument();
522
+ });
523
+
524
+ it('Passes listProps to List when conversations is an array', () => {
525
+ render(
526
+ <ChatbotConversationHistoryNav
527
+ onDrawerToggle={onDrawerToggle}
528
+ isDrawerOpen={true}
529
+ displayMode={ChatbotDisplayMode.fullscreen}
530
+ setIsDrawerOpen={jest.fn()}
531
+ conversations={initialConversations}
532
+ listProps={{ className: 'test' }}
533
+ />
534
+ );
535
+ expect(screen.getByRole('list')).toHaveClass('test');
536
+ });
537
+
538
+ it('Passes listProps to List when conversations is an object', () => {
539
+ render(
540
+ <ChatbotConversationHistoryNav
541
+ onDrawerToggle={onDrawerToggle}
542
+ isDrawerOpen={true}
543
+ displayMode={ChatbotDisplayMode.fullscreen}
544
+ setIsDrawerOpen={jest.fn()}
545
+ conversations={{ Today: initialConversations }}
546
+ listProps={{ Today: { className: 'test' } }}
547
+ />
548
+ );
549
+ expect(screen.getByRole('list')).toHaveClass('test');
550
+ });
551
+
552
+ it('Passes listItemProps to ListItem', () => {
553
+ render(
554
+ <ChatbotConversationHistoryNav
555
+ onDrawerToggle={onDrawerToggle}
556
+ isDrawerOpen={true}
557
+ displayMode={ChatbotDisplayMode.fullscreen}
558
+ setIsDrawerOpen={jest.fn()}
559
+ conversations={[{ id: '1', text: 'ChatBot documentation', listItemProps: { className: 'test' } }]}
560
+ />
561
+ );
562
+ expect(screen.getByRole('listitem')).toHaveClass('test');
563
+ });
564
+
565
+ it('should be able to spread search input props when searchInputProps is passed', () => {
566
+ render(
567
+ <ChatbotConversationHistoryNav
568
+ onDrawerToggle={onDrawerToggle}
569
+ isDrawerOpen={true}
570
+ displayMode={ChatbotDisplayMode.fullscreen}
571
+ setIsDrawerOpen={jest.fn()}
572
+ conversations={initialConversations}
573
+ handleTextInputChange={jest.fn()}
574
+ searchInputProps={{ value: 'I am a sample search' }}
575
+ />
576
+ );
577
+
578
+ expect(screen.getByRole('dialog', { name: /Chat history I am a sample search/i })).toBeInTheDocument();
579
+ });
580
+
581
+ it('overrides nav title heading level when navTitleProps.headingLevel is passed', () => {
582
+ render(
583
+ <ChatbotConversationHistoryNav
584
+ onDrawerToggle={onDrawerToggle}
585
+ isDrawerOpen={true}
586
+ displayMode={ChatbotDisplayMode.fullscreen}
587
+ setIsDrawerOpen={jest.fn()}
588
+ conversations={{ Today: initialConversations }}
589
+ navTitleProps={{ headingLevel: 'h1' }}
590
+ />
591
+ );
592
+ expect(screen.queryByRole('heading', { name: /Chat history/i, level: 2 })).not.toBeInTheDocument();
593
+ expect(screen.getByRole('heading', { name: /Chat history/i, level: 1 })).toBeInTheDocument();
594
+ });
595
+
596
+ it('overrides nav title icon when navTitleIcon is passed in', () => {
597
+ render(
598
+ <ChatbotConversationHistoryNav
599
+ onDrawerToggle={onDrawerToggle}
600
+ isDrawerOpen={true}
601
+ displayMode={ChatbotDisplayMode.fullscreen}
602
+ setIsDrawerOpen={jest.fn()}
603
+ conversations={initialConversations}
604
+ navTitleIcon={<BellIcon data-testid="bell" />}
605
+ />
606
+ );
607
+ expect(screen.getByTestId('bell')).toBeInTheDocument();
608
+ });
436
609
  });
@@ -8,6 +8,7 @@ import { useRef, Fragment } from 'react';
8
8
  // Import PatternFly components
9
9
  import {
10
10
  Button,
11
+ ButtonProps,
11
12
  Drawer,
12
13
  DrawerPanelContent,
13
14
  DrawerContent,
@@ -18,13 +19,10 @@ import {
18
19
  DrawerCloseButton,
19
20
  DrawerContentBody,
20
21
  SearchInput,
21
- Menu,
22
- MenuList,
23
- MenuGroup,
24
- MenuItem,
25
- MenuContent,
26
- MenuItemProps,
27
- MenuProps,
22
+ List,
23
+ ListItem,
24
+ ListItemProps,
25
+ Title,
28
26
  DrawerPanelContentProps,
29
27
  DrawerContentProps,
30
28
  DrawerContentBodyProps,
@@ -32,10 +30,15 @@ import {
32
30
  DrawerActionsProps,
33
31
  DrawerCloseButtonProps,
34
32
  DrawerPanelBodyProps,
35
- SkeletonProps
33
+ SkeletonProps,
34
+ Icon,
35
+ MenuProps, // Remove in next breaking change
36
+ TitleProps,
37
+ ListProps,
38
+ SearchInputProps
36
39
  } from '@patternfly/react-core';
37
40
 
38
- import { OutlinedCommentAltIcon } from '@patternfly/react-icons';
41
+ import { OutlinedClockIcon, OutlinedCommentAltIcon, PenToSquareIcon } from '@patternfly/react-icons';
39
42
  import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
40
43
  import ConversationHistoryDropdown from './ChatbotConversationHistoryDropdown';
41
44
  import LoadingState from './LoadingState';
@@ -58,8 +61,12 @@ export interface Conversation {
58
61
  label?: string;
59
62
  /** Callback for when user selects item. */
60
63
  onSelect?: (event?: React.MouseEvent, value?: string | number) => void;
61
- /** Additional props passed to conversation menu item */
62
- additionalProps?: MenuItemProps;
64
+ /** Additional props passed to conversation button item */
65
+ additionalProps?: ButtonProps;
66
+ /** Additional props passed to conversation list item */
67
+ listItemProps?: Omit<ListItemProps, 'children'>;
68
+ /** Custom dropdown ID to ensure uniqueness across demo instances */
69
+ dropdownId?: string;
63
70
  }
64
71
  export interface ChatbotConversationHistoryNavProps extends DrawerProps {
65
72
  /** Function called to toggle drawer */
@@ -74,6 +81,12 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
74
81
  onSelectActiveItem?: (event?: React.MouseEvent, itemId?: string | number) => void;
75
82
  /** Items shown in conversation history */
76
83
  conversations: Conversation[] | { [key: string]: Conversation[] };
84
+ /** Additional button props for new chat button. */
85
+ newChatButtonProps?: ButtonProps;
86
+ /** Additional props applied to all conversation list headers */
87
+ listTitleProps?: Partial<TitleProps>;
88
+ /** Additional props applied to conversation list. If conversations is an object, you should pass an object of ListProps for each group. */
89
+ listProps?: ListProps | { [key: string]: ListProps };
77
90
  /** Text shown in blue button */
78
91
  newChatButtonText?: string;
79
92
  /** Callback function for when blue button is clicked. Omit to hide blue "new chat button" */
@@ -84,6 +97,8 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
84
97
  searchInputPlaceholder?: string;
85
98
  /** Aria label for search input */
86
99
  searchInputAriaLabel?: string;
100
+ /** Additional props passed to search input */
101
+ searchInputProps?: SearchInputProps;
87
102
  /** A callback for when the input value changes. Omit to hide input field */
88
103
  handleTextInputChange?: (value: string) => void;
89
104
  /** Display mode of chatbot */
@@ -92,7 +107,7 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
92
107
  reverseButtonOrder?: boolean;
93
108
  /** Custom test id for the drawer actions */
94
109
  drawerActionsTestId?: string;
95
- /** Additional props applied to menu */
110
+ /** @deprecated Additional props applied to list container */
96
111
  menuProps?: MenuProps;
97
112
  /** Additional props applied to panel */
98
113
  drawerPanelContentProps?: DrawerPanelContentProps;
@@ -120,6 +135,14 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
120
135
  noResultsState?: HistoryEmptyStateProps;
121
136
  /** Sets drawer to compact styling. */
122
137
  isCompact?: boolean;
138
+ /** Display title */
139
+ title?: string;
140
+ /** Icon displayed in title */
141
+ navTitleIcon?: React.ReactNode;
142
+ /** Title header level */
143
+ navTitleProps?: Partial<TitleProps>;
144
+ /** Visually hidden text that gets announced by assistive technologies. Should be used to convey the result count when the search input value changes. */
145
+ searchInputScreenReaderText?: string;
123
146
  }
124
147
 
125
148
  export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversationHistoryNavProps> = ({
@@ -129,16 +152,19 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
129
152
  activeItemId,
130
153
  onSelectActiveItem,
131
154
  conversations,
155
+ listTitleProps,
156
+ listProps,
132
157
  newChatButtonText = 'New chat',
133
158
  drawerContent,
134
159
  onNewChat,
160
+ newChatButtonProps,
135
161
  searchInputPlaceholder = 'Search previous conversations...',
136
- searchInputAriaLabel = 'Filter menu items',
162
+ searchInputAriaLabel = 'Search previous conversations',
163
+ searchInputProps,
137
164
  handleTextInputChange,
138
165
  displayMode,
139
166
  reverseButtonOrder = false,
140
167
  drawerActionsTestId = 'chatbot-nav-drawer-actions',
141
- menuProps,
142
168
  drawerPanelContentProps,
143
169
  drawerContentProps,
144
170
  drawerContentBodyProps,
@@ -152,6 +178,10 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
152
178
  emptyState,
153
179
  noResultsState,
154
180
  isCompact,
181
+ title = 'Chat history',
182
+ navTitleProps,
183
+ navTitleIcon = <OutlinedClockIcon />,
184
+ searchInputScreenReaderText,
155
185
  ...props
156
186
  }: ChatbotConversationHistoryNavProps) => {
157
187
  const drawerRef = useRef<HTMLDivElement>(null);
@@ -161,55 +191,60 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
161
191
  };
162
192
 
163
193
  const getNavItem = (conversation: Conversation) => (
164
- <MenuItem
165
- className={`pf-chatbot__menu-item ${activeItemId && activeItemId === conversation.id ? 'pf-chatbot__menu-item--active' : ''}`}
166
- itemId={conversation.id}
194
+ <ListItem
195
+ className={`pf-chatbot__conversation-list-item ${activeItemId && activeItemId === conversation.id ? 'pf-chatbot__conversation-list-item--active' : ''}`}
167
196
  key={conversation.id}
168
- {...(conversation.noIcon ? {} : { icon: conversation.icon ?? <OutlinedCommentAltIcon /> })}
169
- /* eslint-disable indent */
170
- {...(conversation.menuItems
171
- ? {
172
- actions: (
173
- <ConversationHistoryDropdown
174
- menuClassName={conversation.menuClassName}
175
- onSelect={conversation.onSelect}
176
- menuItems={conversation.menuItems}
177
- label={conversation.label}
178
- />
179
- )
180
- }
181
- : {})}
182
- {...conversation.additionalProps}
197
+ {...conversation.listItemProps}
183
198
  /* eslint-enable indent */
184
199
  >
185
- {conversation.text}
186
- </MenuItem>
200
+ <>
201
+ <Button
202
+ className="pf-chatbot__conversation-history-item"
203
+ variant="link"
204
+ {...conversation.additionalProps}
205
+ {...(conversation.noIcon ? {} : { icon: conversation.icon ?? <OutlinedCommentAltIcon /> })}
206
+ onClick={(event) => onSelectActiveItem?.(event, conversation.id)}
207
+ >
208
+ {conversation.text}
209
+ </Button>
210
+ {conversation.menuItems && (
211
+ <ConversationHistoryDropdown
212
+ menuClassName={conversation.menuClassName}
213
+ onSelect={conversation.onSelect}
214
+ menuItems={conversation.menuItems}
215
+ label={conversation.label}
216
+ id={conversation.dropdownId}
217
+ />
218
+ )}
219
+ </>
220
+ </ListItem>
187
221
  );
188
222
 
189
- const buildMenu = () => {
223
+ const buildConversations = () => {
190
224
  if (Array.isArray(conversations)) {
191
- // Render for array of MenuItemObject
192
225
  return (
193
- <MenuList>
226
+ <List className="pf-chatbot__conversation-list" isPlain {...listProps}>
194
227
  {conversations.map((conversation) => (
195
228
  <Fragment key={conversation.id}>{getNavItem(conversation)}</Fragment>
196
229
  ))}
197
- </MenuList>
230
+ </List>
198
231
  );
199
232
  } else {
200
- // Render for object with NavItemObject arrays as values
201
233
  return (
202
- <>
234
+ <div>
203
235
  {Object.keys(conversations).map((navGroup) => (
204
- <MenuGroup className="pf-chatbot__menu-item-header" label={navGroup} key={navGroup}>
205
- <MenuList>
236
+ <section key={navGroup}>
237
+ <Title headingLevel="h4" className="pf-chatbot__conversation-list-header" {...listTitleProps}>
238
+ {navGroup}
239
+ </Title>
240
+ <List className="pf-chatbot__conversation-list" isPlain {...listProps?.[navGroup]}>
206
241
  {conversations[navGroup].map((conversation) => (
207
242
  <Fragment key={conversation.id}>{getNavItem(conversation)}</Fragment>
208
243
  ))}
209
- </MenuList>
210
- </MenuGroup>
244
+ </List>
245
+ </section>
211
246
  ))}
212
- </>
247
+ </div>
213
248
  );
214
249
  }
215
250
  };
@@ -229,24 +264,11 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
229
264
  if (noResultsState) {
230
265
  return <HistoryEmptyState {...noResultsState} />;
231
266
  }
232
- return (
233
- <Menu isPlain onSelect={onSelectActiveItem} activeItemId={activeItemId} {...menuProps}>
234
- <MenuContent>{buildMenu()}</MenuContent>
235
- </Menu>
236
- );
267
+ return <>{buildConversations()}</>;
237
268
  };
238
269
 
239
270
  const renderDrawerContent = () => (
240
271
  <>
241
- {handleTextInputChange && (
242
- <div className="pf-chatbot__input">
243
- <SearchInput
244
- aria-label={searchInputAriaLabel}
245
- onChange={(_event, value) => handleTextInputChange(value)}
246
- placeholder={searchInputPlaceholder}
247
- />
248
- </div>
249
- )}
250
272
  <DrawerPanelBody {...drawerPanelBodyProps}>{renderMenuContent()}</DrawerPanelBody>
251
273
  </>
252
274
  );
@@ -262,12 +284,40 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
262
284
  >
263
285
  <DrawerCloseButton onClick={onDrawerToggle} {...drawerCloseButtonProps} />
264
286
  {onNewChat && (
265
- <Button size={isCompact ? 'sm' : undefined} onClick={onNewChat}>
287
+ <Button
288
+ size={isCompact ? 'sm' : undefined}
289
+ onClick={onNewChat}
290
+ icon={<PenToSquareIcon />}
291
+ {...newChatButtonProps}
292
+ >
266
293
  {newChatButtonText}
267
294
  </Button>
268
295
  )}
269
296
  </DrawerActions>
270
297
  </DrawerHead>
298
+ <div className="pf-chatbot__heading-container">
299
+ <div className="pf-chatbot__title-container">
300
+ <Icon size="lg" className="pf-chatbot__title-icon">
301
+ {navTitleIcon}
302
+ </Icon>
303
+ <Title className="pf-chatbot__title" headingLevel="h2" {...navTitleProps}>
304
+ {title}
305
+ </Title>
306
+ </div>
307
+ {!isLoading && handleTextInputChange && (
308
+ <div className="pf-chatbot__input">
309
+ <SearchInput
310
+ aria-label={searchInputAriaLabel}
311
+ onChange={(_event, value) => handleTextInputChange(value)}
312
+ placeholder={searchInputPlaceholder}
313
+ {...searchInputProps}
314
+ />
315
+ {searchInputScreenReaderText && (
316
+ <div className="pf-chatbot__filter-announcement">{searchInputScreenReaderText}</div>
317
+ )}
318
+ </div>
319
+ )}
320
+ </div>
271
321
  {isLoading ? <LoadingState {...loadingState} /> : renderDrawerContent()}
272
322
  </>
273
323
  );
@@ -6,7 +6,7 @@
6
6
  // ============================================================================
7
7
  .pf-chatbot__footer {
8
8
  --pf-chatbot__footer--RowGap: var(--pf-t--global--spacer--md);
9
- background-color: var(--pf-t--chatbot--background);
9
+ background-color: var(--pf-t--global--background--color--secondary--default);
10
10
  display: flex;
11
11
  flex-direction: column;
12
12
  row-gap: var(--pf-chatbot__footer--RowGap);