@patternfly/chatbot 6.5.0-prerelease.2 → 6.5.0-prerelease.4

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 (40) hide show
  1. package/dist/cjs/AttachMenu/AttachMenu.d.ts +7 -1
  2. package/dist/cjs/AttachMenu/AttachMenu.js +2 -2
  3. package/dist/cjs/ChatbotFooter/ChatbotFooter.d.ts +5 -2
  4. package/dist/cjs/ChatbotFooter/ChatbotFooter.js +2 -2
  5. package/dist/cjs/ChatbotFooter/ChatbotFooter.test.js +5 -1
  6. package/dist/cjs/Message/Message.d.ts +1 -0
  7. package/dist/cjs/Message/Message.js +4 -1
  8. package/dist/cjs/MessageBar/MessageBar.d.ts +9 -1
  9. package/dist/cjs/MessageBar/MessageBar.js +3 -3
  10. package/dist/cjs/MessageBar/MessageBar.test.js +37 -0
  11. package/dist/cjs/__mocks__/rehype-highlight.d.ts +2 -0
  12. package/dist/cjs/__mocks__/rehype-highlight.js +4 -0
  13. package/dist/css/main.css +6 -0
  14. package/dist/css/main.css.map +1 -1
  15. package/dist/esm/AttachMenu/AttachMenu.d.ts +7 -1
  16. package/dist/esm/AttachMenu/AttachMenu.js +2 -2
  17. package/dist/esm/ChatbotFooter/ChatbotFooter.d.ts +5 -2
  18. package/dist/esm/ChatbotFooter/ChatbotFooter.js +2 -2
  19. package/dist/esm/ChatbotFooter/ChatbotFooter.test.js +5 -1
  20. package/dist/esm/Message/Message.d.ts +1 -0
  21. package/dist/esm/Message/Message.js +4 -1
  22. package/dist/esm/MessageBar/MessageBar.d.ts +9 -1
  23. package/dist/esm/MessageBar/MessageBar.js +3 -3
  24. package/dist/esm/MessageBar/MessageBar.test.js +37 -0
  25. package/dist/esm/__mocks__/rehype-highlight.d.ts +2 -0
  26. package/dist/esm/__mocks__/rehype-highlight.js +2 -0
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +2 -1
  29. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +12 -4
  30. package/patternfly-docs/content/extensions/chatbot/examples/demos/WhiteEmbeddedChatbot.tsx +437 -0
  31. package/patternfly-docs/patternfly-docs.config.js +1 -0
  32. package/src/AttachMenu/AttachMenu.tsx +16 -3
  33. package/src/ChatbotFooter/ChatbotFooter.scss +4 -0
  34. package/src/ChatbotFooter/ChatbotFooter.test.tsx +10 -1
  35. package/src/ChatbotFooter/ChatbotFooter.tsx +10 -3
  36. package/src/Message/Message.tsx +4 -1
  37. package/src/MessageBar/MessageBar.scss +4 -0
  38. package/src/MessageBar/MessageBar.test.tsx +62 -1
  39. package/src/MessageBar/MessageBar.tsx +24 -2
  40. package/src/__mocks__/rehype-highlight.ts +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/chatbot",
3
- "version": "6.5.0-prerelease.2",
3
+ "version": "6.5.0-prerelease.4",
4
4
  "description": "This library provides React components based on PatternFly 6 that can be used to build chatbots.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -43,6 +43,7 @@
43
43
  "posthog-js": "^1.194.4",
44
44
  "react-markdown": "^9.0.1",
45
45
  "rehype-external-links": "^3.0.0",
46
+ "rehype-highlight": "^7.0.0",
46
47
  "rehype-sanitize": "^6.0.0",
47
48
  "rehype-unwrap-images": "^1.0.0",
48
49
  "remark-gfm": "^4.0.0",
@@ -131,15 +131,23 @@ This demo displays a ChatBot in a static, inline drawer. This demo includes:
131
131
 
132
132
  ```
133
133
 
134
+ ### Primary color background
135
+
136
+ This demo displays an embedded ChatBot with a [primary background color](/design-foundations/colors#background-colors). This example includes the same features as the [Embedded ChatBot demo](/patternfly-ai/chatbot/overview/demo/#embedded-chatbot)—the only differences are that the background color is adjusted via the `isPrimary` prop and some of the sample Messages have changed. You can use the same logic to adjust the background color in any ChatBot layout.
137
+
138
+ ```js file="./WhiteEmbeddedChatbot.tsx" isFullscreen
139
+
140
+ ```
141
+
134
142
  ### Display mode switcher
135
143
 
136
144
  This demo showcases how the ChatBot can be rendered in different display modes to suit various application layouts. It demonstrates how to dynamically change the page structure in response to the user's selection. This demo includes:
137
145
 
138
146
  1. The ability to switch between overlay, drawer, and fullscreen modes using the [`<ChatbotHeaderOptionsDropdown>`](/patternfly-ai/chatbot/ui#header-options) in the header.
139
147
  2. A conditional page layout that renders the ChatBot for each display mode option:
140
- - **Overlay:** As a floating window on top of the page content.
141
- - **Drawer:** Inside an inline PatternFly `<Drawer>` as a side panel.
142
- - **Fullscreen:** As a top-level component that covers the entire screen for an embedded experience.
148
+ - **Overlay:** As a floating window on top of the page content.
149
+ - **Drawer:** Inside an inline PatternFly `<Drawer>` as a side panel.
150
+ - **Fullscreen:** As a top-level component that covers the entire screen for an embedded experience.
143
151
  3. Logic to show or hide the `<ChatbotToggle>` button, which is only present in the default overlay mode.
144
152
  4. A [basic ChatBot](#basic-chatbot) with a header, welcome prompt, and message bar to populate the different layouts.
145
153
 
@@ -170,7 +178,7 @@ Your code structure should look like this:
170
178
 
171
179
  ### Chat transcripts
172
180
 
173
- This demo illustrates how you could add downloadable transcripts to your ChatBot, which outline conversation details in a Markdown file. This approach allows users to easily share information from a conversation with others.
181
+ This demo illustrates how you could add downloadable transcripts to your ChatBot, which outline conversation details in a Markdown file. This approach allows users to easily share information from a conversation with others.
174
182
 
175
183
  A message transcript includes details from a single chat message. To download a sample message transcript in this demo, click the "Download" action under a bot message.
176
184
 
@@ -0,0 +1,437 @@
1
+ import { useEffect, useRef, useState, FunctionComponent, MouseEvent } from 'react';
2
+
3
+ import {
4
+ Bullseye,
5
+ Brand,
6
+ DropdownList,
7
+ DropdownItem,
8
+ Page,
9
+ Masthead,
10
+ MastheadMain,
11
+ MastheadBrand,
12
+ MastheadLogo,
13
+ PageSidebarBody,
14
+ PageSidebar,
15
+ MastheadToggle,
16
+ PageToggleButton,
17
+ SkipToContent
18
+ } from '@patternfly/react-core';
19
+
20
+ import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
21
+ import ChatbotContent from '@patternfly/chatbot/dist/dynamic/ChatbotContent';
22
+ import ChatbotWelcomePrompt from '@patternfly/chatbot/dist/dynamic/ChatbotWelcomePrompt';
23
+ import ChatbotFooter, { ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic/ChatbotFooter';
24
+ import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar';
25
+ import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox';
26
+ import Message, { MessageProps } from '@patternfly/chatbot/dist/dynamic/Message';
27
+ import ChatbotConversationHistoryNav, {
28
+ Conversation
29
+ } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
30
+ import ChatbotHeader, {
31
+ ChatbotHeaderMenu,
32
+ ChatbotHeaderMain,
33
+ ChatbotHeaderTitle,
34
+ ChatbotHeaderActions,
35
+ ChatbotHeaderSelectorDropdown
36
+ } from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
37
+
38
+ import PFHorizontalLogoColor from '../UI/PF-HorizontalLogo-Color.svg';
39
+ import PFHorizontalLogoReverse from '../UI/PF-HorizontalLogo-Reverse.svg';
40
+ import { BarsIcon } from '@patternfly/react-icons';
41
+ import userAvatar from '../Messages/user_avatar.svg';
42
+ import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
43
+ import '@patternfly/react-core/dist/styles/base.css';
44
+ import '@patternfly/chatbot/dist/css/main.css';
45
+
46
+ const footnoteProps = {
47
+ label: 'ChatBot uses AI. Check for mistakes.',
48
+ popover: {
49
+ title: 'Verify information',
50
+ description: `While ChatBot strives for accuracy, AI is experimental and can make mistakes. We cannot guarantee that all information provided by ChatBot is up to date or without error. You should always verify responses using reliable sources, especially for crucial information and decision making.`,
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: 'Dismiss',
57
+ onClick: () => {
58
+ alert('Do something!');
59
+ }
60
+ },
61
+ link: {
62
+ label: 'View AI policy',
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
+ const MessageLoading = () => (
90
+ <div className="pf-chatbot__message-loading">
91
+ <span className="pf-chatbot__message-loading-dots">
92
+ <span className="pf-v6-screen-reader">Loading message</span>
93
+ </span>
94
+ </div>
95
+ );
96
+
97
+ export default MessageLoading;
98
+
99
+ ~~~
100
+ `;
101
+
102
+ // It's important to set a date and timestamp prop since the Message components re-render.
103
+ // The timestamps re-render with them.
104
+ const date = new Date();
105
+
106
+ const initialMessages: MessageProps[] = [
107
+ {
108
+ id: '1',
109
+ role: 'user',
110
+ content: 'Hello, can you give me an example of what you can do?',
111
+ name: 'User',
112
+ avatar: userAvatar,
113
+ timestamp: date.toLocaleString(),
114
+ avatarProps: { isBordered: true }
115
+ },
116
+ {
117
+ id: '2',
118
+ role: 'bot',
119
+ content: markdown,
120
+ name: 'Bot',
121
+ avatar: patternflyAvatar,
122
+ timestamp: date.toLocaleString(),
123
+ actions: {
124
+ // eslint-disable-next-line no-console
125
+ positive: { onClick: () => console.log('Good response') },
126
+ // eslint-disable-next-line no-console
127
+ negative: { onClick: () => console.log('Bad response') },
128
+ // eslint-disable-next-line no-console
129
+ copy: { onClick: () => console.log('Copy') },
130
+ // eslint-disable-next-line no-console
131
+ download: { onClick: () => console.log('Download') },
132
+ // eslint-disable-next-line no-console
133
+ listen: { onClick: () => console.log('Listen') }
134
+ }
135
+ }
136
+ ];
137
+
138
+ const welcomePrompts = [
139
+ {
140
+ title: 'Set up account',
141
+ message: 'Choose the necessary settings and preferences for your account.'
142
+ },
143
+ {
144
+ title: 'Troubleshoot issue',
145
+ message: 'Find documentation and instructions to resolve your issue.'
146
+ }
147
+ ];
148
+
149
+ const initialConversations = {
150
+ Today: [{ id: '1', text: 'Hello, can you give me an example of what you can do?' }],
151
+ 'This month': [
152
+ {
153
+ id: '2',
154
+ text: 'Enterprise Linux installation and setup'
155
+ },
156
+ { id: '3', text: 'Troubleshoot system crash' }
157
+ ],
158
+ March: [
159
+ { id: '4', text: 'Ansible security and updates' },
160
+ { id: '5', text: 'Red Hat certification' },
161
+ { id: '6', text: 'Lightspeed user documentation' }
162
+ ],
163
+ February: [
164
+ { id: '7', text: 'Crashing pod assistance' },
165
+ { id: '8', text: 'OpenShift AI pipelines' },
166
+ { id: '9', text: 'Updating subscription plan' },
167
+ { id: '10', text: 'Red Hat licensing options' }
168
+ ],
169
+ January: [
170
+ { id: '11', text: 'RHEL system performance' },
171
+ { id: '12', text: 'Manage user accounts' }
172
+ ]
173
+ };
174
+
175
+ export const EmbeddedChatbotDemo: FunctionComponent = () => {
176
+ const [messages, setMessages] = useState<MessageProps[]>(initialMessages);
177
+ const [selectedModel, setSelectedModel] = useState('Granite 7B');
178
+ const [isSendButtonDisabled, setIsSendButtonDisabled] = useState(false);
179
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
180
+ const [conversations, setConversations] = useState<Conversation[] | { [key: string]: Conversation[] }>(
181
+ initialConversations
182
+ );
183
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
184
+ const [announcement, setAnnouncement] = useState<string>();
185
+ const scrollToBottomRef = useRef<HTMLDivElement>(null);
186
+ const historyRef = useRef<HTMLButtonElement>(null);
187
+
188
+ const displayMode = ChatbotDisplayMode.embedded;
189
+ // Auto-scrolls to the latest message
190
+ useEffect(() => {
191
+ // don't scroll the first load - in this demo, we know we start with two messages
192
+ if (messages.length > 2) {
193
+ scrollToBottomRef.current?.scrollIntoView({ behavior: 'smooth' });
194
+ }
195
+ }, [messages]);
196
+
197
+ const onSelectModel = (_event: MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
198
+ setSelectedModel(value as string);
199
+ };
200
+
201
+ // you will likely want to come up with your own unique id function; this is for demo purposes only
202
+ const generateId = () => {
203
+ const id = Date.now() + Math.random();
204
+ return id.toString();
205
+ };
206
+
207
+ const handleSend = (message: string) => {
208
+ setIsSendButtonDisabled(true);
209
+ const newMessages: MessageProps[] = [];
210
+ // We can't use structuredClone since messages contains functions, but we can't mutate
211
+ // items that are going into state or the UI won't update correctly
212
+ messages.forEach((message) => newMessages.push(message));
213
+ // It's important to set a timestamp prop since the Message components re-render.
214
+ // The timestamps re-render with them.
215
+ const date = new Date();
216
+ newMessages.push({
217
+ id: generateId(),
218
+ role: 'user',
219
+ content: message,
220
+ name: 'User',
221
+ avatar: userAvatar,
222
+ timestamp: date.toLocaleString(),
223
+ avatarProps: { isBordered: true }
224
+ });
225
+ newMessages.push({
226
+ id: generateId(),
227
+ role: 'bot',
228
+ content: 'API response goes here',
229
+ name: 'Bot',
230
+ avatar: patternflyAvatar,
231
+ isLoading: true,
232
+ timestamp: date.toLocaleString()
233
+ });
234
+ setMessages(newMessages);
235
+ // make announcement to assistive devices that new messages have been added
236
+ setAnnouncement(`Message from User: ${message}. Message from Bot is loading.`);
237
+
238
+ // this is for demo purposes only; in a real situation, there would be an API response we would wait for
239
+ setTimeout(() => {
240
+ const loadedMessages: MessageProps[] = [];
241
+ // we can't use structuredClone since messages contains functions, but we can't mutate
242
+ // items that are going into state or the UI won't update correctly
243
+ newMessages.forEach((message) => loadedMessages.push(message));
244
+ loadedMessages.pop();
245
+ loadedMessages.push({
246
+ id: generateId(),
247
+ role: 'bot',
248
+ content: 'API response goes here',
249
+ name: 'Bot',
250
+ avatar: patternflyAvatar,
251
+ isLoading: false,
252
+ actions: {
253
+ // eslint-disable-next-line no-console
254
+ positive: { onClick: () => console.log('Good response') },
255
+ // eslint-disable-next-line no-console
256
+ negative: { onClick: () => console.log('Bad response') },
257
+ // eslint-disable-next-line no-console
258
+ copy: { onClick: () => console.log('Copy') },
259
+ // eslint-disable-next-line no-console
260
+ download: { onClick: () => console.log('Download') },
261
+ // eslint-disable-next-line no-console
262
+ listen: { onClick: () => console.log('Listen') }
263
+ },
264
+ timestamp: date.toLocaleString()
265
+ });
266
+ setMessages(loadedMessages);
267
+ // make announcement to assistive devices that new message has loaded
268
+ setAnnouncement(`Message from Bot: API response goes here`);
269
+ setIsSendButtonDisabled(false);
270
+ }, 5000);
271
+ };
272
+
273
+ const findMatchingItems = (targetValue: string) => {
274
+ let filteredConversations = Object.entries(initialConversations).reduce((acc, [key, items]) => {
275
+ const filteredItems = items.filter((item) => item.text.toLowerCase().includes(targetValue.toLowerCase()));
276
+ if (filteredItems.length > 0) {
277
+ acc[key] = filteredItems;
278
+ }
279
+ return acc;
280
+ }, {});
281
+
282
+ // append message if no items are found
283
+ if (Object.keys(filteredConversations).length === 0) {
284
+ filteredConversations = [{ id: '13', noIcon: true, text: 'No results found' }];
285
+ }
286
+ return filteredConversations;
287
+ };
288
+
289
+ const horizontalLogo = (
290
+ <Bullseye>
291
+ <Brand className="show-light" src={PFHorizontalLogoColor} alt="PatternFly" />
292
+ <Brand className="show-dark" src={PFHorizontalLogoReverse} alt="PatternFly" />
293
+ </Bullseye>
294
+ );
295
+
296
+ const masthead = (
297
+ <Masthead>
298
+ <MastheadMain>
299
+ <MastheadToggle>
300
+ <PageToggleButton
301
+ variant="plain"
302
+ aria-label="Global navigation"
303
+ isSidebarOpen={isSidebarOpen}
304
+ onSidebarToggle={() => setIsSidebarOpen(!isSidebarOpen)}
305
+ id="fill-nav-toggle"
306
+ >
307
+ <BarsIcon />
308
+ </PageToggleButton>
309
+ </MastheadToggle>
310
+ <MastheadBrand>
311
+ <MastheadLogo href="https://patternfly.org" target="_blank">
312
+ Logo
313
+ </MastheadLogo>
314
+ </MastheadBrand>
315
+ </MastheadMain>
316
+ </Masthead>
317
+ );
318
+
319
+ const sidebar = (
320
+ <PageSidebar isSidebarOpen={isSidebarOpen} id="fill-sidebar">
321
+ <PageSidebarBody>Navigation</PageSidebarBody>
322
+ </PageSidebar>
323
+ );
324
+
325
+ const skipToChatbot = (event: MouseEvent) => {
326
+ event.preventDefault();
327
+ if (historyRef.current) {
328
+ historyRef.current.focus();
329
+ }
330
+ };
331
+
332
+ const skipToContent = (
333
+ /* You can also add a SkipToContent for your main content here */
334
+ <SkipToContent href="#" onClick={skipToChatbot}>
335
+ Skip to chatbot
336
+ </SkipToContent>
337
+ );
338
+
339
+ return (
340
+ <Page skipToContent={skipToContent} masthead={masthead} sidebar={sidebar} isContentFilled>
341
+ <Chatbot displayMode={displayMode}>
342
+ <ChatbotConversationHistoryNav
343
+ displayMode={displayMode}
344
+ onDrawerToggle={() => {
345
+ setIsDrawerOpen(!isDrawerOpen);
346
+ setConversations(initialConversations);
347
+ }}
348
+ isDrawerOpen={isDrawerOpen}
349
+ setIsDrawerOpen={setIsDrawerOpen}
350
+ activeItemId="1"
351
+ // eslint-disable-next-line no-console
352
+ onSelectActiveItem={(e, selectedItem) => console.log(`Selected history item with id ${selectedItem}`)}
353
+ conversations={conversations}
354
+ onNewChat={() => {
355
+ setIsDrawerOpen(!isDrawerOpen);
356
+ setMessages([]);
357
+ setConversations(initialConversations);
358
+ }}
359
+ handleTextInputChange={(value: string) => {
360
+ if (value === '') {
361
+ setConversations(initialConversations);
362
+ }
363
+ // this is where you would perform search on the items in the drawer
364
+ // and update the state
365
+ const newConversations: { [key: string]: Conversation[] } = findMatchingItems(value);
366
+ setConversations(newConversations);
367
+ }}
368
+ drawerContent={
369
+ <>
370
+ <ChatbotHeader>
371
+ <ChatbotHeaderMain>
372
+ <ChatbotHeaderMenu
373
+ ref={historyRef}
374
+ aria-expanded={isDrawerOpen}
375
+ onMenuToggle={() => setIsDrawerOpen(!isDrawerOpen)}
376
+ />
377
+ <ChatbotHeaderTitle>{horizontalLogo}</ChatbotHeaderTitle>
378
+ </ChatbotHeaderMain>
379
+ <ChatbotHeaderActions>
380
+ <ChatbotHeaderSelectorDropdown value={selectedModel} onSelect={onSelectModel}>
381
+ <DropdownList>
382
+ <DropdownItem value="Granite 7B" key="granite">
383
+ Granite 7B
384
+ </DropdownItem>
385
+ <DropdownItem value="Llama 3.0" key="llama">
386
+ Llama 3.0
387
+ </DropdownItem>
388
+ <DropdownItem value="Mistral 3B" key="mistral">
389
+ Mistral 3B
390
+ </DropdownItem>
391
+ </DropdownList>
392
+ </ChatbotHeaderSelectorDropdown>
393
+ </ChatbotHeaderActions>
394
+ </ChatbotHeader>
395
+ <ChatbotContent isPrimary>
396
+ {/* Update the announcement prop on MessageBox whenever a new message is sent
397
+ so that users of assistive devices receive sufficient context */}
398
+ <MessageBox announcement={announcement}>
399
+ <ChatbotWelcomePrompt
400
+ title="Hi, ChatBot User!"
401
+ description="How can I help you today?"
402
+ prompts={welcomePrompts}
403
+ />
404
+ {/* This code block enables scrolling to the top of the last message.
405
+ You can instead choose to move the div with scrollToBottomRef on it below
406
+ the map of messages, so that users are forced to scroll to the bottom.
407
+ If you are using streaming, you will want to take a different approach;
408
+ see: https://github.com/patternfly/chatbot/issues/201#issuecomment-2400725173 */}
409
+ {messages.map((message, index) => {
410
+ if (index === messages.length - 1) {
411
+ return (
412
+ <>
413
+ <div ref={scrollToBottomRef}></div>
414
+ <Message key={message.id} {...message} />
415
+ </>
416
+ );
417
+ }
418
+ return <Message key={message.id} {...message} />;
419
+ })}
420
+ </MessageBox>
421
+ </ChatbotContent>
422
+ <ChatbotFooter isPrimary>
423
+ <MessageBar
424
+ isPrimary
425
+ onSendMessage={handleSend}
426
+ hasMicrophoneButton
427
+ isSendButtonDisabled={isSendButtonDisabled}
428
+ />
429
+ <ChatbotFootnote {...footnoteProps} />
430
+ </ChatbotFooter>
431
+ </>
432
+ }
433
+ ></ChatbotConversationHistoryNav>
434
+ </Chatbot>
435
+ </Page>
436
+ );
437
+ };
@@ -4,5 +4,6 @@ module.exports = {
4
4
  topNavItems: [],
5
5
  hasThemeSwitcher: true,
6
6
  hasRTLSwitcher: true,
7
+ hasHighContrastSwitcher: true,
7
8
  port: 8006
8
9
  };
@@ -11,7 +11,10 @@ import {
11
11
  DropdownProps,
12
12
  Dropdown,
13
13
  DropdownToggleProps,
14
- PopperOptions
14
+ PopperOptions,
15
+ MenuSearchInputProps,
16
+ SearchInputProps,
17
+ MenuSearchProps
15
18
  } from '@patternfly/react-core';
16
19
 
17
20
  export interface AttachMenuProps extends DropdownProps {
@@ -35,6 +38,12 @@ export interface AttachMenuProps extends DropdownProps {
35
38
  searchInputAriaLabel?: string;
36
39
  /** Toggle to be rendered */
37
40
  toggle: DropdownToggleProps | ((toggleRef: React.RefObject<any>) => React.ReactNode);
41
+ /** Additional props passed to MenuSearch component */
42
+ menuSearchProps?: Omit<MenuSearchProps, 'ref'>;
43
+ /** Additional props passed to MenuSearchInput component */
44
+ menuSearchInputProps?: Omit<MenuSearchInputProps, 'ref'>;
45
+ /** Additional props passed to SearchInput component */
46
+ searchInputProps?: SearchInputProps;
38
47
  }
39
48
 
40
49
  export const AttachMenu: FunctionComponent<AttachMenuProps> = ({
@@ -49,6 +58,9 @@ export const AttachMenu: FunctionComponent<AttachMenuProps> = ({
49
58
  searchInputPlaceholder,
50
59
  searchInputAriaLabel = 'Filter menu items',
51
60
  toggle,
61
+ menuSearchProps,
62
+ menuSearchInputProps,
63
+ searchInputProps,
52
64
  ...props
53
65
  }: AttachMenuProps) => (
54
66
  <Dropdown
@@ -61,12 +73,13 @@ export const AttachMenu: FunctionComponent<AttachMenuProps> = ({
61
73
  onSelect={onSelect}
62
74
  {...props}
63
75
  >
64
- <MenuSearch>
65
- <MenuSearchInput>
76
+ <MenuSearch {...menuSearchProps}>
77
+ <MenuSearchInput {...menuSearchInputProps}>
66
78
  <SearchInput
67
79
  aria-label={searchInputAriaLabel}
68
80
  onChange={(_event, value) => handleTextInputChange(value)}
69
81
  placeholder={searchInputPlaceholder}
82
+ {...searchInputProps}
70
83
  />
71
84
  </MenuSearchInput>
72
85
  </MenuSearch>
@@ -11,6 +11,10 @@
11
11
  flex-direction: column;
12
12
  row-gap: var(--pf-chatbot__footer--RowGap);
13
13
  position: relative; // this is so focus ring on parent chatbot doesn't include footer
14
+
15
+ &.pf-m-primary {
16
+ background-color: var(--pf-t--global--background--color--primary--default);
17
+ }
14
18
  }
15
19
  .pf-chatbot__footer-container {
16
20
  padding: 0 var(--pf-t--global--spacer--lg) var(--pf-t--global--spacer--lg) var(--pf-t--global--spacer--lg);
@@ -15,10 +15,19 @@ describe('ChatbotFooter', () => {
15
15
 
16
16
  it('should handle isCompact', () => {
17
17
  render(
18
- <ChatbotFooter className="custom-class" isCompact data-testid="footer">
18
+ <ChatbotFooter isCompact data-testid="footer">
19
19
  Chatbot Content
20
20
  </ChatbotFooter>
21
21
  );
22
22
  expect(screen.getByTestId('footer')).toHaveClass('pf-m-compact');
23
23
  });
24
+
25
+ it('should handle isPrimary', () => {
26
+ render(
27
+ <ChatbotFooter isPrimary data-testid="footer">
28
+ Chatbot Content
29
+ </ChatbotFooter>
30
+ );
31
+ expect(screen.getByTestId('footer')).toHaveClass('pf-m-primary');
32
+ });
24
33
  });
@@ -13,20 +13,27 @@ import type { HTMLProps, FunctionComponent } from 'react';
13
13
  import { Divider } from '@patternfly/react-core';
14
14
 
15
15
  export interface ChatbotFooterProps extends HTMLProps<HTMLDivElement> {
16
- /** Children for the Footer that supports MessageBar and FootNote components*/
16
+ /** Children for the footer - supports MessageBar and FootNote components*/
17
17
  children?: React.ReactNode;
18
- /** Custom classname for the Footer component */
18
+ /** Custom classname for the footer component */
19
19
  className?: string;
20
+ /** Sets footer to compact styling. */
20
21
  isCompact?: boolean;
22
+ /** Sets background color to primary */
23
+ isPrimary?: boolean;
21
24
  }
22
25
 
23
26
  export const ChatbotFooter: FunctionComponent<ChatbotFooterProps> = ({
24
27
  children,
25
28
  className,
26
29
  isCompact,
30
+ isPrimary,
27
31
  ...props
28
32
  }: ChatbotFooterProps) => (
29
- <div className={`pf-chatbot__footer ${isCompact ? 'pf-m-compact' : ''} ${className ?? ''}`} {...props}>
33
+ <div
34
+ className={`pf-chatbot__footer ${isCompact ? 'pf-m-compact' : ''} ${isPrimary ? 'pf-m-primary' : ''} ${className ?? ''}`}
35
+ {...props}
36
+ >
30
37
  <Divider />
31
38
  <div className="pf-chatbot__footer-container">{children}</div>
32
39
  </div>
@@ -42,6 +42,9 @@ import ImageMessage from './ImageMessage/ImageMessage';
42
42
  import rehypeUnwrapImages from 'rehype-unwrap-images';
43
43
  import rehypeExternalLinks from 'rehype-external-links';
44
44
  import rehypeSanitize from 'rehype-sanitize';
45
+ import rehypeHighlight from 'rehype-highlight';
46
+ // see the full list of styles here: https://highlightjs.org/examples
47
+ import 'highlight.js/styles/vs2015.css';
45
48
  import { PluggableList } from 'unified';
46
49
  import LinkMessage from './LinkMessage/LinkMessage';
47
50
  import ErrorMessage from './ErrorMessage/ErrorMessage';
@@ -242,7 +245,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
242
245
  }, [content]);
243
246
 
244
247
  const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
245
- let rehypePlugins: PluggableList = [rehypeUnwrapImages, rehypeMoveImagesOutOfParagraphs];
248
+ let rehypePlugins: PluggableList = [rehypeUnwrapImages, rehypeMoveImagesOutOfParagraphs, rehypeHighlight];
246
249
  if (openLinkInNewTab) {
247
250
  rehypePlugins = rehypePlugins.concat([[rehypeExternalLinks, { target: '_blank' }, rehypeSanitize]]);
248
251
  }
@@ -26,6 +26,10 @@
26
26
 
27
27
  overflow: hidden;
28
28
 
29
+ &.pf-m-primary {
30
+ box-shadow: inset 0 0 0 1px var(--pf-t--global--border--color--default);
31
+ }
32
+
29
33
  &:hover {
30
34
  box-shadow: inset 0 0 0 1px var(--pf-t--global--border--color--default);
31
35
  }