@patternfly/chatbot 2.2.0-prerelease.31 → 2.2.0-prerelease.33

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 (38) hide show
  1. package/dist/cjs/Chatbot/Chatbot.d.ts +2 -1
  2. package/dist/cjs/Chatbot/Chatbot.js +1 -0
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +1 -1
  4. package/dist/cjs/ChatbotHeader/ChatbotHeaderTitle.d.ts +3 -1
  5. package/dist/cjs/ChatbotHeader/ChatbotHeaderTitle.js +4 -2
  6. package/dist/cjs/ChatbotHeader/ChatbotHeaderTitle.test.js +15 -7
  7. package/dist/cjs/Message/UserFeedback/UserFeedback.d.ts +1 -1
  8. package/dist/cjs/Message/UserFeedback/UserFeedback.js +2 -3
  9. package/dist/cjs/Message/UserFeedback/UserFeedback.test.js +0 -13
  10. package/dist/css/main.css +33 -3
  11. package/dist/css/main.css.map +1 -1
  12. package/dist/esm/Chatbot/Chatbot.d.ts +2 -1
  13. package/dist/esm/Chatbot/Chatbot.js +1 -0
  14. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +1 -1
  15. package/dist/esm/ChatbotHeader/ChatbotHeaderTitle.d.ts +3 -1
  16. package/dist/esm/ChatbotHeader/ChatbotHeaderTitle.js +4 -2
  17. package/dist/esm/ChatbotHeader/ChatbotHeaderTitle.test.js +15 -7
  18. package/dist/esm/Message/UserFeedback/UserFeedback.d.ts +1 -1
  19. package/dist/esm/Message/UserFeedback/UserFeedback.js +2 -3
  20. package/dist/esm/Message/UserFeedback/UserFeedback.test.js +0 -13
  21. package/package.json +4 -2
  22. package/patternfly-docs/content/extensions/chatbot/examples/Analytics/Analytics.md +67 -62
  23. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +11 -0
  24. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotInDrawer.tsx +453 -0
  25. package/src/Chatbot/Chatbot.scss +19 -0
  26. package/src/Chatbot/Chatbot.tsx +2 -1
  27. package/src/ChatbotContent/ChatbotContent.scss +1 -0
  28. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +2 -0
  29. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +1 -1
  30. package/src/ChatbotFooter/ChatbotFooter.scss +9 -0
  31. package/src/ChatbotHeader/ChatbotHeader.scss +2 -1
  32. package/src/ChatbotHeader/ChatbotHeaderTitle.test.tsx +23 -7
  33. package/src/ChatbotHeader/ChatbotHeaderTitle.tsx +7 -2
  34. package/src/Message/UserFeedback/UserFeedback.test.tsx +0 -21
  35. package/src/Message/UserFeedback/UserFeedback.tsx +1 -5
  36. package/src/MessageBar/MessageBar.scss +3 -3
  37. package/src/MessageBox/MessageBox.scss +1 -0
  38. package/src/SourcesCard/SourcesCard.scss +6 -0
@@ -0,0 +1,453 @@
1
+ import React from 'react';
2
+
3
+ import {
4
+ Brand,
5
+ DropdownList,
6
+ DropdownItem,
7
+ Page,
8
+ Masthead,
9
+ MastheadMain,
10
+ MastheadBrand,
11
+ MastheadLogo,
12
+ PageSidebarBody,
13
+ PageSidebar,
14
+ MastheadToggle,
15
+ PageToggleButton,
16
+ SkipToContent,
17
+ Drawer,
18
+ DrawerContent,
19
+ DrawerContentBody,
20
+ DrawerPanelContent
21
+ } from '@patternfly/react-core';
22
+
23
+ import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
24
+ import ChatbotContent from '@patternfly/chatbot/dist/dynamic/ChatbotContent';
25
+ import ChatbotWelcomePrompt from '@patternfly/chatbot/dist/dynamic/ChatbotWelcomePrompt';
26
+ import ChatbotFooter, { ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic/ChatbotFooter';
27
+ import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar';
28
+ import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox';
29
+ import Message, { MessageProps } from '@patternfly/chatbot/dist/dynamic/Message';
30
+ import ChatbotConversationHistoryNav, {
31
+ Conversation
32
+ } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
33
+ import ChatbotHeader, {
34
+ ChatbotHeaderMenu,
35
+ ChatbotHeaderMain,
36
+ ChatbotHeaderTitle,
37
+ ChatbotHeaderActions,
38
+ ChatbotHeaderSelectorDropdown
39
+ } from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
40
+ import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg';
41
+ import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg';
42
+ import { BarsIcon } from '@patternfly/react-icons';
43
+ import userAvatar from '../Messages/user_avatar.svg';
44
+ import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
45
+
46
+ const footnoteProps = {
47
+ label: 'ChatBot uses AI. Check for mistakes.',
48
+ popover: {
49
+ title: 'Verify accuracy',
50
+ description: `While ChatBot strives for accuracy, there's always a possibility of errors. It's a good practice to verify critical information from reliable sources, especially if it's crucial for decision-making or actions.`,
51
+ bannerImage: {
52
+ src: 'https://cdn.dribbble.com/userupload/10651749/file/original-8a07b8e39d9e8bf002358c66fce1223e.gif',
53
+ alt: 'Example image for footnote popover'
54
+ },
55
+ cta: {
56
+ label: 'Got it',
57
+ onClick: () => {
58
+ alert('Do something!');
59
+ }
60
+ },
61
+ link: {
62
+ label: 'Learn more',
63
+ url: 'https://www.redhat.com/'
64
+ }
65
+ }
66
+ };
67
+
68
+ const markdown = `A paragraph with *emphasis* and **strong importance**.
69
+
70
+ > A block quote with ~strikethrough~ and a URL: https://reactjs.org.
71
+
72
+ Here is an inline code - \`() => void\`
73
+
74
+ Here is some YAML code:
75
+
76
+ ~~~yaml
77
+ apiVersion: helm.openshift.io/v1beta1/
78
+ kind: HelmChartRepository
79
+ metadata:
80
+ name: azure-sample-repo0oooo00ooo
81
+ spec:
82
+ connectionConfig:
83
+ url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docs
84
+ ~~~
85
+
86
+ Here is some JavaScript code:
87
+
88
+ ~~~js
89
+ import React from 'react';
90
+
91
+ const MessageLoading = () => (
92
+ <div className="pf-chatbot__message-loading">
93
+ <span className="pf-chatbot__message-loading-dots">
94
+ <span className="pf-v6-screen-reader">Loading message</span>
95
+ </span>
96
+ </div>
97
+ );
98
+
99
+ export default MessageLoading;
100
+
101
+ ~~~
102
+ `;
103
+
104
+ // It's important to set a date and timestamp prop since the Message components re-render.
105
+ // The timestamps re-render with them.
106
+ const date = new Date();
107
+
108
+ const initialMessages: MessageProps[] = [
109
+ {
110
+ id: '1',
111
+ role: 'user',
112
+ content: 'Hello, can you give me an example of what you can do?',
113
+ name: 'User',
114
+ avatar: userAvatar,
115
+ timestamp: date.toLocaleString(),
116
+ avatarProps: { isBordered: true }
117
+ },
118
+ {
119
+ id: '2',
120
+ role: 'bot',
121
+ content: markdown,
122
+ name: 'Bot',
123
+ avatar: patternflyAvatar,
124
+ timestamp: date.toLocaleString(),
125
+ actions: {
126
+ // eslint-disable-next-line no-console
127
+ positive: { onClick: () => console.log('Good response') },
128
+ // eslint-disable-next-line no-console
129
+ negative: { onClick: () => console.log('Bad response') },
130
+ // eslint-disable-next-line no-console
131
+ copy: { onClick: () => console.log('Copy') },
132
+ // eslint-disable-next-line no-console
133
+ share: { onClick: () => console.log('Share') },
134
+ // eslint-disable-next-line no-console
135
+ listen: { onClick: () => console.log('Listen') }
136
+ }
137
+ }
138
+ ];
139
+
140
+ const welcomePrompts = [
141
+ {
142
+ title: 'Topic 1',
143
+ message: 'Helpful prompt for Topic 1'
144
+ },
145
+ {
146
+ title: 'Topic 2',
147
+ message: 'Helpful prompt for Topic 2'
148
+ }
149
+ ];
150
+
151
+ const initialConversations = {
152
+ Today: [{ id: '1', text: 'Hello, can you give me an example of what you can do?' }],
153
+ 'This month': [
154
+ {
155
+ id: '2',
156
+ text: 'Enterprise Linux installation and setup'
157
+ },
158
+ { id: '3', text: 'Troubleshoot system crash' }
159
+ ],
160
+ March: [
161
+ { id: '4', text: 'Ansible security and updates' },
162
+ { id: '5', text: 'Red Hat certification' },
163
+ { id: '6', text: 'Lightspeed user documentation' }
164
+ ],
165
+ February: [
166
+ { id: '7', text: 'Crashing pod assistance' },
167
+ { id: '8', text: 'OpenShift AI pipelines' },
168
+ { id: '9', text: 'Updating subscription plan' },
169
+ { id: '10', text: 'Red Hat licensing options' }
170
+ ],
171
+ January: [
172
+ { id: '11', text: 'RHEL system performance' },
173
+ { id: '12', text: 'Manage user accounts' }
174
+ ]
175
+ };
176
+
177
+ export const EmbeddedChatbotDemo: React.FunctionComponent = () => {
178
+ const [messages, setMessages] = React.useState<MessageProps[]>(initialMessages);
179
+ const [selectedModel, setSelectedModel] = React.useState('Granite 7B');
180
+ const [isSendButtonDisabled, setIsSendButtonDisabled] = React.useState(false);
181
+ const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
182
+ const [conversations, setConversations] = React.useState<Conversation[] | { [key: string]: Conversation[] }>(
183
+ initialConversations
184
+ );
185
+ const [isSidebarOpen, setIsSidebarOpen] = React.useState(false);
186
+ const [announcement, setAnnouncement] = React.useState<string>();
187
+ const scrollToBottomRef = React.useRef<HTMLDivElement>(null);
188
+ const historyRef = React.useRef<HTMLButtonElement>(null);
189
+ const drawerRef = React.useRef<HTMLDivElement>();
190
+
191
+ const displayMode = ChatbotDisplayMode.drawer;
192
+ const isExpanded = true;
193
+
194
+ const onExpand = () => {
195
+ drawerRef.current && drawerRef.current.focus();
196
+ };
197
+
198
+ // Auto-scrolls to the latest message
199
+ React.useEffect(() => {
200
+ // don't scroll the first load - in this demo, we know we start with two messages
201
+ if (messages.length > 2) {
202
+ scrollToBottomRef.current?.scrollIntoView({ behavior: 'smooth' });
203
+ }
204
+ }, [messages]);
205
+
206
+ const onSelectModel = (
207
+ _event: React.MouseEvent<Element, MouseEvent> | undefined,
208
+ value: string | number | undefined
209
+ ) => {
210
+ setSelectedModel(value as string);
211
+ };
212
+
213
+ // you will likely want to come up with your own unique id function; this is for demo purposes only
214
+ const generateId = () => {
215
+ const id = Date.now() + Math.random();
216
+ return id.toString();
217
+ };
218
+
219
+ const handleSend = (message: string) => {
220
+ setIsSendButtonDisabled(true);
221
+ const newMessages: MessageProps[] = [];
222
+ // We can't use structuredClone since messages contains functions, but we can't mutate
223
+ // items that are going into state or the UI won't update correctly
224
+ messages.forEach((message) => newMessages.push(message));
225
+ // It's important to set a timestamp prop since the Message components re-render.
226
+ // The timestamps re-render with them.
227
+ const date = new Date();
228
+ newMessages.push({
229
+ id: generateId(),
230
+ role: 'user',
231
+ content: message,
232
+ name: 'User',
233
+ avatar: userAvatar,
234
+ timestamp: date.toLocaleString(),
235
+ avatarProps: { isBordered: true }
236
+ });
237
+ newMessages.push({
238
+ id: generateId(),
239
+ role: 'bot',
240
+ content: 'API response goes here',
241
+ name: 'Bot',
242
+ avatar: patternflyAvatar,
243
+ isLoading: true,
244
+ timestamp: date.toLocaleString()
245
+ });
246
+ setMessages(newMessages);
247
+ // make announcement to assistive devices that new messages have been added
248
+ setAnnouncement(`Message from User: ${message}. Message from Bot is loading.`);
249
+
250
+ // this is for demo purposes only; in a real situation, there would be an API response we would wait for
251
+ setTimeout(() => {
252
+ const loadedMessages: MessageProps[] = [];
253
+ // we can't use structuredClone since messages contains functions, but we can't mutate
254
+ // items that are going into state or the UI won't update correctly
255
+ newMessages.forEach((message) => loadedMessages.push(message));
256
+ loadedMessages.pop();
257
+ loadedMessages.push({
258
+ id: generateId(),
259
+ role: 'bot',
260
+ content: 'API response goes here',
261
+ name: 'Bot',
262
+ avatar: patternflyAvatar,
263
+ isLoading: false,
264
+ actions: {
265
+ // eslint-disable-next-line no-console
266
+ positive: { onClick: () => console.log('Good response') },
267
+ // eslint-disable-next-line no-console
268
+ negative: { onClick: () => console.log('Bad response') },
269
+ // eslint-disable-next-line no-console
270
+ copy: { onClick: () => console.log('Copy') },
271
+ // eslint-disable-next-line no-console
272
+ share: { onClick: () => console.log('Share') },
273
+ // eslint-disable-next-line no-console
274
+ listen: { onClick: () => console.log('Listen') }
275
+ },
276
+ timestamp: date.toLocaleString()
277
+ });
278
+ setMessages(loadedMessages);
279
+ // make announcement to assistive devices that new message has loaded
280
+ setAnnouncement(`Message from Bot: API response goes here`);
281
+ setIsSendButtonDisabled(false);
282
+ }, 5000);
283
+ };
284
+
285
+ const findMatchingItems = (targetValue: string) => {
286
+ let filteredConversations = Object.entries(initialConversations).reduce((acc, [key, items]) => {
287
+ const filteredItems = items.filter((item) => item.text.toLowerCase().includes(targetValue.toLowerCase()));
288
+ if (filteredItems.length > 0) {
289
+ acc[key] = filteredItems;
290
+ }
291
+ return acc;
292
+ }, {});
293
+
294
+ // append message if no items are found
295
+ if (Object.keys(filteredConversations).length === 0) {
296
+ filteredConversations = [{ id: '13', noIcon: true, text: 'No results found' }];
297
+ }
298
+ return filteredConversations;
299
+ };
300
+
301
+ const iconLogo = (
302
+ <>
303
+ <Brand className="show-light" src={PFIconLogoColor} alt="PatternFly" />
304
+ <Brand className="show-dark" src={PFIconLogoReverse} alt="PatternFly" />
305
+ </>
306
+ );
307
+ const masthead = (
308
+ <Masthead>
309
+ <MastheadMain>
310
+ <MastheadToggle>
311
+ <PageToggleButton
312
+ variant="plain"
313
+ aria-label="Global navigation"
314
+ isSidebarOpen={isSidebarOpen}
315
+ onSidebarToggle={() => setIsSidebarOpen(!isSidebarOpen)}
316
+ id="fill-nav-toggle"
317
+ >
318
+ <BarsIcon />
319
+ </PageToggleButton>
320
+ </MastheadToggle>
321
+ <MastheadBrand>
322
+ <MastheadLogo href="https://patternfly.org" target="_blank">
323
+ Logo
324
+ </MastheadLogo>
325
+ </MastheadBrand>
326
+ </MastheadMain>
327
+ </Masthead>
328
+ );
329
+
330
+ const sidebar = (
331
+ <PageSidebar isSidebarOpen={isSidebarOpen} id="fill-sidebar">
332
+ <PageSidebarBody>Navigation</PageSidebarBody>
333
+ </PageSidebar>
334
+ );
335
+
336
+ const skipToChatbot = (event: React.MouseEvent) => {
337
+ event.preventDefault();
338
+ if (historyRef.current) {
339
+ historyRef.current.focus();
340
+ }
341
+ };
342
+
343
+ const skipToContent = (
344
+ /* You can also add a SkipToContent for your main content here */
345
+ <SkipToContent href="#" onClick={skipToChatbot}>
346
+ Skip to chatbot
347
+ </SkipToContent>
348
+ );
349
+
350
+ const chatbot = (
351
+ <Chatbot displayMode={displayMode}>
352
+ <ChatbotConversationHistoryNav
353
+ displayMode={displayMode}
354
+ onDrawerToggle={() => {
355
+ setIsDrawerOpen(!isDrawerOpen);
356
+ setConversations(initialConversations);
357
+ }}
358
+ isDrawerOpen={isDrawerOpen}
359
+ setIsDrawerOpen={setIsDrawerOpen}
360
+ activeItemId="1"
361
+ // eslint-disable-next-line no-console
362
+ onSelectActiveItem={(e, selectedItem) => console.log(`Selected history item with id ${selectedItem}`)}
363
+ conversations={conversations}
364
+ onNewChat={() => {
365
+ setIsDrawerOpen(!isDrawerOpen);
366
+ setMessages([]);
367
+ setConversations(initialConversations);
368
+ }}
369
+ handleTextInputChange={(value: string) => {
370
+ if (value === '') {
371
+ setConversations(initialConversations);
372
+ }
373
+ // this is where you would perform search on the items in the drawer
374
+ // and update the state
375
+ const newConversations: { [key: string]: Conversation[] } = findMatchingItems(value);
376
+ setConversations(newConversations);
377
+ }}
378
+ drawerContent={
379
+ <>
380
+ <ChatbotHeader>
381
+ <ChatbotHeaderMain>
382
+ <ChatbotHeaderMenu
383
+ ref={historyRef}
384
+ aria-expanded={isDrawerOpen}
385
+ onMenuToggle={() => setIsDrawerOpen(!isDrawerOpen)}
386
+ />
387
+ <ChatbotHeaderTitle>{iconLogo}</ChatbotHeaderTitle>
388
+ </ChatbotHeaderMain>
389
+ <ChatbotHeaderActions>
390
+ <ChatbotHeaderSelectorDropdown value={selectedModel} onSelect={onSelectModel}>
391
+ <DropdownList>
392
+ <DropdownItem value="Granite 7B" key="granite">
393
+ Granite 7B
394
+ </DropdownItem>
395
+ <DropdownItem value="Llama 3.0" key="llama">
396
+ Llama 3.0
397
+ </DropdownItem>
398
+ <DropdownItem value="Mistral 3B" key="mistral">
399
+ Mistral 3B
400
+ </DropdownItem>
401
+ </DropdownList>
402
+ </ChatbotHeaderSelectorDropdown>
403
+ </ChatbotHeaderActions>
404
+ </ChatbotHeader>
405
+ <ChatbotContent>
406
+ {/* Update the announcement prop on MessageBox whenever a new message is sent
407
+ so that users of assistive devices receive sufficient context */}
408
+ <MessageBox announcement={announcement}>
409
+ <ChatbotWelcomePrompt
410
+ title="Hello, Chatbot User"
411
+ description="How may I help you today?"
412
+ prompts={welcomePrompts}
413
+ />
414
+ {/* This code block enables scrolling to the top of the last message.
415
+ You can instead choose to move the div with scrollToBottomRef on it below
416
+ the map of messages, so that users are forced to scroll to the bottom.
417
+ If you are using streaming, you will want to take a different approach;
418
+ see: https://github.com/patternfly/chatbot/issues/201#issuecomment-2400725173 */}
419
+ {messages.map((message, index) => {
420
+ if (index === messages.length - 1) {
421
+ return (
422
+ <>
423
+ <div ref={scrollToBottomRef}></div>
424
+ <Message key={message.id} {...message} />
425
+ </>
426
+ );
427
+ }
428
+ return <Message key={message.id} {...message} />;
429
+ })}
430
+ </MessageBox>
431
+ </ChatbotContent>
432
+ <ChatbotFooter>
433
+ <MessageBar onSendMessage={handleSend} hasMicrophoneButton isSendButtonDisabled={isSendButtonDisabled} />
434
+ <ChatbotFootnote {...footnoteProps} />
435
+ </ChatbotFooter>
436
+ </>
437
+ }
438
+ ></ChatbotConversationHistoryNav>
439
+ </Chatbot>
440
+ );
441
+
442
+ const panelContent = <DrawerPanelContent>{chatbot}</DrawerPanelContent>;
443
+
444
+ return (
445
+ <Drawer isExpanded={isExpanded} isInline onExpand={onExpand}>
446
+ <DrawerContent panelContent={panelContent}>
447
+ <DrawerContentBody>
448
+ <Page skipToContent={skipToContent} masthead={masthead} sidebar={sidebar} isContentFilled></Page>
449
+ </DrawerContentBody>
450
+ </DrawerContent>
451
+ </Drawer>
452
+ );
453
+ };
@@ -106,3 +106,22 @@
106
106
  .pf-chatbot-container--fullscreen {
107
107
  border-radius: unset;
108
108
  }
109
+
110
+ // ============================================================================
111
+ // Chatbot Display Mode - Drawer
112
+ // ============================================================================
113
+ .pf-chatbot--drawer {
114
+ inset-block-end: 0;
115
+ inset-inline-end: 0;
116
+ padding: 0;
117
+ width: 100%;
118
+ height: 100%;
119
+ border-radius: 0;
120
+ box-shadow: none;
121
+ border-left: var(--pf-t--global--border--width--divider--default) solid;
122
+ border-color: var(--pf-t--global--border--color--default);
123
+
124
+ .pf-chatbot-container {
125
+ border-radius: var(--pf-t--global--border--radius--sharp);
126
+ }
127
+ }
@@ -23,7 +23,8 @@ export enum ChatbotDisplayMode {
23
23
  default = 'default',
24
24
  embedded = 'embedded',
25
25
  docked = 'docked',
26
- fullscreen = 'fullscreen'
26
+ fullscreen = 'fullscreen',
27
+ drawer = 'drawer'
27
28
  }
28
29
 
29
30
  const ChatbotBase: React.FunctionComponent<ChatbotProps> = ({
@@ -18,6 +18,7 @@
18
18
  // Chatbot Display Mode - Fullscreen and Embedded
19
19
  // ============================================================================
20
20
  @media screen and (min-width: 64rem) {
21
+ .pf-chatbot--drawer,
21
22
  .pf-chatbot--fullscreen,
22
23
  .pf-chatbot--embedded {
23
24
  .pf-chatbot__content {
@@ -155,6 +155,7 @@
155
155
  // ============================================================================
156
156
  // Chatbot Display Mode - Fullscreen
157
157
  // ============================================================================
158
+ .pf-chatbot--drawer,
158
159
  .pf-chatbot--fullscreen {
159
160
  .pf-chatbot__history.pf-v6-c-drawer {
160
161
  height: 100vh;
@@ -181,6 +182,7 @@
181
182
  }
182
183
 
183
184
  .pf-chatbot--docked,
185
+ .pf-chatbot--drawer,
184
186
  .pf-chatbot--embedded,
185
187
  .pf-chatbot--fullscreen {
186
188
  .pf-chatbot__history {
@@ -285,7 +285,7 @@ export const ChatbotConversationHistoryNav: React.FunctionComponent<ChatbotConve
285
285
  <DrawerContentBody {...drawerContentBodyProps}>
286
286
  <>
287
287
  <div
288
- className={`${isDrawerOpen && (displayMode === ChatbotDisplayMode.default || displayMode === ChatbotDisplayMode.docked) ? 'pf-v6-c-backdrop pf-chatbot__drawer-backdrop' : undefined} `}
288
+ className={`${isDrawerOpen && (displayMode === ChatbotDisplayMode.default || displayMode === ChatbotDisplayMode.docked || displayMode === ChatbotDisplayMode.drawer) ? 'pf-v6-c-backdrop pf-chatbot__drawer-backdrop' : undefined} `}
289
289
  ></div>
290
290
  {drawerContent}
291
291
  </>
@@ -48,3 +48,12 @@
48
48
  padding: var(--pf-t--global--spacer--sm) var(--pf-t--global--spacer--lg);
49
49
  }
50
50
  }
51
+
52
+ // ============================================================================
53
+ // Chatbot Display Mode - Drawer
54
+ // ============================================================================
55
+ .pf-chatbot--drawer {
56
+ .pf-chatbot__footer-container {
57
+ padding: var(--pf-t--global--spacer--sm) var(--pf-t--global--spacer--lg);
58
+ }
59
+ }
@@ -70,8 +70,9 @@
70
70
  }
71
71
 
72
72
  // ============================================================================
73
- // Chatbot Display Mode - Docked
73
+ // Chatbot Display Mode - Docked and Drawer
74
74
  // ============================================================================
75
+ .pf-chatbot--drawer,
75
76
  .pf-chatbot--docked {
76
77
  .pf-chatbot__header {
77
78
  background-color: var(--pf-t--chatbot--background);
@@ -17,27 +17,27 @@ describe('ChatbotHeaderTitle', () => {
17
17
  });
18
18
 
19
19
  it('should render title for default display mode', () => {
20
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.default} showOnDefault={'Default header title'} />);
20
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.default} showOnDefault="Default header title" />);
21
21
  expect(screen.getByText('Default header title')).toBeTruthy();
22
22
  });
23
23
 
24
24
  it('should render title for docked display mode', () => {
25
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.docked} showOnDocked={'Docked header title'} />);
25
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.docked} showOnDocked="Docked header title" />);
26
26
  expect(screen.getByText('Docked header title')).toBeTruthy();
27
27
  });
28
28
 
29
29
  it('should fallback to default title when docked display mode title is not configured', () => {
30
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.docked} showOnDefault={'Default header title'} />);
30
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.docked} showOnDefault="Default header title" />);
31
31
  expect(screen.getByText('Default header title')).toBeTruthy();
32
32
  });
33
33
 
34
34
  it('should render title for embedded display mode', () => {
35
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.embedded} showOnEmbedded={'Embedded header title'} />);
35
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.embedded} showOnEmbedded="Embedded header title" />);
36
36
  expect(screen.getByText('Embedded header title')).toBeTruthy();
37
37
  });
38
38
 
39
39
  it('should fallback to default title when embedded display mode title is not configured', () => {
40
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.embedded} showOnDefault={'Default header title'} />);
40
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.embedded} showOnDefault="Default header title" />);
41
41
  expect(screen.getByText('Default header title')).toBeTruthy();
42
42
  });
43
43
 
@@ -45,7 +45,7 @@ describe('ChatbotHeaderTitle', () => {
45
45
  render(
46
46
  <ChatbotHeaderTitle
47
47
  displayMode={ChatbotDisplayMode.fullscreen}
48
- showOnFullScreen={'Fullscreen header title'}
48
+ showOnFullScreen="Fullscreen header title"
49
49
  className="custom-header-class"
50
50
  />
51
51
  );
@@ -53,7 +53,23 @@ describe('ChatbotHeaderTitle', () => {
53
53
  });
54
54
 
55
55
  it('should fallback to default title when fullscreen display mode title is not configured', () => {
56
- render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.fullscreen} showOnDefault={'Default header title'} />);
56
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.fullscreen} showOnDefault="Default header title" />);
57
+ expect(screen.getByText('Default header title')).toBeTruthy();
58
+ });
59
+
60
+ it('should render title for drawer display mode', () => {
61
+ render(
62
+ <ChatbotHeaderTitle
63
+ displayMode={ChatbotDisplayMode.drawer}
64
+ showOnDrawer="Drawer header title"
65
+ className="custom-header-class"
66
+ />
67
+ );
68
+ expect(screen.getByText('Drawer header title')).toBeTruthy();
69
+ });
70
+
71
+ it('should fallback to default title when drawer display mode title is not configured', () => {
72
+ render(<ChatbotHeaderTitle displayMode={ChatbotDisplayMode.drawer} showOnDefault="Default header title" />);
57
73
  expect(screen.getByText('Default header title')).toBeTruthy();
58
74
  });
59
75
  });
@@ -14,8 +14,10 @@ export interface ChatbotHeaderTitleProps {
14
14
  showOnFullScreen?: React.ReactNode | string;
15
15
  /** Content to display on docked screen */
16
16
  showOnDocked?: React.ReactNode | string;
17
- /** Content to display on overlay screen */
17
+ /** Content to display on embedded screen */
18
18
  showOnEmbedded?: React.ReactNode | string;
19
+ /** Content to display in drawer mode */
20
+ showOnDrawer?: React.ReactNode | string;
19
21
  /** Content to display by default; this will be shown if a case is not explicitly set */
20
22
  showOnDefault?: React.ReactNode | string;
21
23
  }
@@ -27,10 +29,11 @@ export const ChatbotHeaderTitle: React.FunctionComponent<ChatbotHeaderTitleProps
27
29
  showOnFullScreen,
28
30
  showOnDocked,
29
31
  showOnEmbedded,
32
+ showOnDrawer,
30
33
  showOnDefault
31
34
  }: ChatbotHeaderTitleProps) => {
32
35
  const renderChildren = () => {
33
- if (displayMode && (showOnDefault || showOnFullScreen || showOnEmbedded || showOnDocked)) {
36
+ if (displayMode) {
34
37
  /* eslint-disable indent */
35
38
  switch (displayMode) {
36
39
  case ChatbotDisplayMode.fullscreen:
@@ -39,6 +42,8 @@ export const ChatbotHeaderTitle: React.FunctionComponent<ChatbotHeaderTitleProps
39
42
  return showOnDocked ?? showOnDefault;
40
43
  case ChatbotDisplayMode.embedded:
41
44
  return showOnEmbedded ?? showOnDefault;
45
+ case ChatbotDisplayMode.drawer:
46
+ return showOnDrawer ?? showOnDefault;
42
47
  default:
43
48
  return showOnDefault;
44
49
  }
@@ -20,7 +20,6 @@ describe('UserFeedback', () => {
20
20
  expect(screen.getByRole('button', { name: /Resolved my issue/i })).toBeTruthy();
21
21
  expect(screen.getByRole('button', { name: /Submit/i })).toBeTruthy();
22
22
  expect(screen.getByRole('button', { name: 'Close feedback for message received at 12/12/12' })).toBeTruthy();
23
- expect(screen.getByRole('button', { name: /Cancel/i })).toBeTruthy();
24
23
  expect(screen.queryByRole('textbox', { name: /Provide optional additional feedback/i })).toBeFalsy();
25
24
  });
26
25
  it('should render different title correctly', () => {
@@ -125,26 +124,6 @@ describe('UserFeedback', () => {
125
124
  );
126
125
  expect(screen.getByRole('button', { name: /Ima button/i })).toBeTruthy();
127
126
  });
128
- it('should handle onClose correctly when cancel button is clicked', async () => {
129
- const spy = jest.fn();
130
- render(<UserFeedback onSubmit={jest.fn} quickResponses={MOCK_RESPONSES} onClose={spy} timestamp="12/12/12" />);
131
- const cancelButton = screen.getByRole('button', { name: 'Cancel' });
132
- expect(cancelButton).toBeTruthy();
133
- await userEvent.click(cancelButton);
134
- expect(spy).toHaveBeenCalledTimes(1);
135
- });
136
- it('should change cancel word correctly', () => {
137
- render(
138
- <UserFeedback
139
- onSubmit={jest.fn}
140
- quickResponses={MOCK_RESPONSES}
141
- onClose={jest.fn}
142
- cancelWord="Exit"
143
- timestamp="12/12/12"
144
- />
145
- );
146
- expect(screen.getByRole('button', { name: 'Exit' })).toBeTruthy();
147
- });
148
127
  it('should handle className', async () => {
149
128
  render(
150
129
  <UserFeedback