@patternfly/chatbot 6.9.0-prerelease.6 → 6.9.0-prerelease.7
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.
- package/dist/cjs/Chatbot/Chatbot.d.ts +2 -0
- package/dist/cjs/Chatbot/Chatbot.js +2 -2
- package/dist/cjs/MessageBar/MessageBar.d.ts +1 -1
- package/dist/cjs/MessageBar/MessageBar.js +3 -2
- package/dist/css/main.css +19 -0
- package/dist/css/main.css.map +1 -1
- package/dist/esm/Chatbot/Chatbot.d.ts +2 -0
- package/dist/esm/Chatbot/Chatbot.js +3 -3
- package/dist/esm/MessageBar/MessageBar.d.ts +1 -1
- package/dist/esm/MessageBar/MessageBar.js +3 -2
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md +35 -12
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.tsx +26 -19
- package/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx +698 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +14 -2
- package/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx +385 -0
- package/src/Canvas/Canvas.scss +12 -0
- package/src/Chatbot/Chatbot.scss +13 -1
- package/src/Chatbot/Chatbot.test.tsx +11 -0
- package/src/Chatbot/Chatbot.tsx +5 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +19 -13
- package/src/MessageBar/MessageBar.test.tsx +17 -0
- package/src/MessageBar/MessageBar.tsx +5 -3
|
@@ -37,6 +37,7 @@ import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox';
|
|
|
37
37
|
import Message from '@patternfly/chatbot/dist/dynamic/Message';
|
|
38
38
|
import Compare from '@patternfly/chatbot/dist/dynamic/Compare';
|
|
39
39
|
import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
|
|
40
|
+
import SettingsForm from '@patternfly/chatbot/dist/dynamic/Settings';
|
|
40
41
|
|
|
41
42
|
import ChatbotHeader, {
|
|
42
43
|
ChatbotHeaderMain,
|
|
@@ -55,9 +56,12 @@ import RhUiAddIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon';
|
|
|
55
56
|
import RhUiClipboardIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-clipboard-icon';
|
|
56
57
|
import RhUiCodeIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-code-icon';
|
|
57
58
|
import RhUiUploadIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-upload-icon';
|
|
58
|
-
import
|
|
59
|
-
import
|
|
59
|
+
import RhUiBuildFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-build-fill-icon';
|
|
60
|
+
import RhUiCopyFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-copy-fill-icon';
|
|
60
61
|
import RhUiImageFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon';
|
|
62
|
+
import RhUiEditFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon';
|
|
63
|
+
import RhUiSettingsFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon';
|
|
64
|
+
import RhMicronsCloseIcon from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon';
|
|
61
65
|
import {
|
|
62
66
|
Button,
|
|
63
67
|
Divider,
|
|
@@ -188,6 +192,14 @@ This demo showcases how the ChatBot can be rendered in different display modes t
|
|
|
188
192
|
|
|
189
193
|
```
|
|
190
194
|
|
|
195
|
+
### Fullscreen ChatBot with docked navigation
|
|
196
|
+
|
|
197
|
+
This demo shows a headerless fullscreen ChatBot with a vertical navigation rail for switching between chat history, a new chat, and settings.
|
|
198
|
+
|
|
199
|
+
```js file="./FullscreenDockedNav.tsx" isFullscreen
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
|
|
191
203
|
### Comparing ChatBots
|
|
192
204
|
|
|
193
205
|
To let users compare how different ChatBots respond to the same prompt, you can add multiple ChatBots within the same window. The following demo illustrates a comparison view pattern that allows users to toggle between different conversations in a single ChatBot window.
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { FunctionComponent, ReactNode, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Avatar,
|
|
4
|
+
Brand,
|
|
5
|
+
Button,
|
|
6
|
+
Divider,
|
|
7
|
+
Dropdown,
|
|
8
|
+
DropdownItem,
|
|
9
|
+
DropdownList,
|
|
10
|
+
Masthead,
|
|
11
|
+
MastheadBrand,
|
|
12
|
+
MastheadContent,
|
|
13
|
+
MastheadLogo,
|
|
14
|
+
MastheadMain,
|
|
15
|
+
MastheadToggle,
|
|
16
|
+
Flex,
|
|
17
|
+
MenuToggle,
|
|
18
|
+
Nav,
|
|
19
|
+
NavItem,
|
|
20
|
+
NavList,
|
|
21
|
+
PageToggleButton,
|
|
22
|
+
Switch,
|
|
23
|
+
Title,
|
|
24
|
+
Tooltip
|
|
25
|
+
} from '@patternfly/react-core';
|
|
26
|
+
import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
|
|
27
|
+
import ChatbotContent from '@patternfly/chatbot/dist/dynamic/ChatbotContent';
|
|
28
|
+
import ChatbotWelcomePrompt from '@patternfly/chatbot/dist/dynamic/ChatbotWelcomePrompt';
|
|
29
|
+
import ChatbotFooter, { ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic/ChatbotFooter';
|
|
30
|
+
import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar';
|
|
31
|
+
import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox';
|
|
32
|
+
import Message, { MessageProps } from '@patternfly/chatbot/dist/dynamic/Message';
|
|
33
|
+
import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
|
|
34
|
+
import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon';
|
|
35
|
+
import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon';
|
|
36
|
+
import { RhMicronsCloseIcon } from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon';
|
|
37
|
+
import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg';
|
|
38
|
+
import userAvatar from '../Messages/user_avatar.svg';
|
|
39
|
+
import '@patternfly/react-core/dist/styles/base.css';
|
|
40
|
+
import '@patternfly/chatbot/dist/css/main.css';
|
|
41
|
+
|
|
42
|
+
const initialMessages: MessageProps[] = [
|
|
43
|
+
{
|
|
44
|
+
id: '1',
|
|
45
|
+
role: 'user',
|
|
46
|
+
content: 'Show me how a fullscreen chatbot can use docked navigation.',
|
|
47
|
+
name: 'You',
|
|
48
|
+
avatar: userAvatar,
|
|
49
|
+
timestamp: new Date().toLocaleString(),
|
|
50
|
+
avatarProps: { isBordered: true }
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: '2',
|
|
54
|
+
role: 'bot',
|
|
55
|
+
content: 'Use the rail on the left to switch between the conversation, history, and a new chat.',
|
|
56
|
+
name: 'Bot',
|
|
57
|
+
timestamp: new Date().toLocaleString()
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const welcomePrompts = [
|
|
62
|
+
{
|
|
63
|
+
title: 'Topic 1',
|
|
64
|
+
message: 'Helpful prompt for Topic 1'
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
title: 'Topic 2',
|
|
68
|
+
message: 'Helpful prompt for Topic 2'
|
|
69
|
+
}
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const conversations = [
|
|
73
|
+
{ id: '1', text: 'Fullscreen chatbot navigation' },
|
|
74
|
+
{ id: '2', text: 'Review deployment options' }
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
interface PlainTextSettingsFormProps {
|
|
78
|
+
fields: { id: string; label: string; field: ReactNode }[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const PlainTextSettingsForm = ({ fields }: PlainTextSettingsFormProps) => (
|
|
82
|
+
<div className="pf-chatbot__settings-form-container">
|
|
83
|
+
<form className="pf-chatbot__settings-form">
|
|
84
|
+
{fields.map(({ id, label, field }) => (
|
|
85
|
+
<div className="pf-chatbot__settings-form-row" key={id}>
|
|
86
|
+
<div className="pf-chatbot__settings-label" id={`${id}-label`}>
|
|
87
|
+
{label}
|
|
88
|
+
</div>
|
|
89
|
+
{field}
|
|
90
|
+
</div>
|
|
91
|
+
))}
|
|
92
|
+
</form>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const hamburgerHoverStyles = `
|
|
97
|
+
.pf-chatbot__canvas-docked-nav .pf-v6-c-masthead__logo.pf-m-compact {
|
|
98
|
+
display: revert;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.pf-chatbot__canvas-docked-nav .pf-v6-c-nav.pf-m-docked .pf-v6-c-nav__link-text {
|
|
102
|
+
display: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.pf-chatbot__canvas-history-toggle.pf-v6-c-button.pf-m-hamburger:is(:hover, :focus-visible) {
|
|
106
|
+
--pf-v6-c-button--hamburger-icon--top--path: var(--pf-v6-c-button--hamburger-icon--top--collapse--path);
|
|
107
|
+
--pf-v6-c-button--hamburger-icon--arrow--path: var(--pf-v6-c-button--hamburger-icon--arrow--collapse--path);
|
|
108
|
+
--pf-v6-c-button--hamburger-icon--bottom--path: var(--pf-v6-c-button--hamburger-icon--bottom--collapse--path);
|
|
109
|
+
--pf-v6-c-button__icon--TransitionDelay: 0s;
|
|
110
|
+
--pf-v6-c-button__icon--ScaleX: var(--pf-v6-c-button--m-hamburger__icon--m-expand--ScaleX);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.pf-chatbot__canvas-history-toggle.pf-v6-c-button.pf-m-hamburger[aria-expanded="true"]:is(:hover, :focus-visible) {
|
|
114
|
+
--pf-v6-c-button__icon--ScaleX: var(--pf-v6-c-button--m-hamburger__icon--m-collapse--ScaleX);
|
|
115
|
+
}
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
const SettingsPanel = ({ onClose }) => {
|
|
119
|
+
const [openDropdown, setOpenDropdown] = useState<string>();
|
|
120
|
+
const [theme, setTheme] = useState('System');
|
|
121
|
+
const [language, setLanguage] = useState('Auto-detect');
|
|
122
|
+
const [voice, setVoice] = useState('Bot');
|
|
123
|
+
const [isAnalyticsShared, setIsAnalyticsShared] = useState(true);
|
|
124
|
+
const closeButtonRef = useRef<HTMLButtonElement>(null);
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
window.requestAnimationFrame(() => closeButtonRef.current?.focus());
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
const dropdownField = (id, label, value, options, onSelect) => (
|
|
131
|
+
<Dropdown
|
|
132
|
+
isOpen={openDropdown === id}
|
|
133
|
+
onSelect={(_event, selectedValue) => {
|
|
134
|
+
onSelect(String(selectedValue));
|
|
135
|
+
setOpenDropdown(undefined);
|
|
136
|
+
}}
|
|
137
|
+
onOpenChange={(isOpen: boolean) => setOpenDropdown(isOpen ? id : undefined)}
|
|
138
|
+
shouldFocusToggleOnSelect
|
|
139
|
+
shouldFocusFirstItemOnOpen
|
|
140
|
+
toggle={(toggleRef) => (
|
|
141
|
+
<MenuToggle
|
|
142
|
+
id={id}
|
|
143
|
+
ref={toggleRef}
|
|
144
|
+
onClick={() => setOpenDropdown(openDropdown === id ? undefined : id)}
|
|
145
|
+
isExpanded={openDropdown === id}
|
|
146
|
+
aria-label={`${value}, ${label}`}
|
|
147
|
+
>
|
|
148
|
+
{value}
|
|
149
|
+
</MenuToggle>
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
<DropdownList>
|
|
153
|
+
{options.map((option) => (
|
|
154
|
+
<DropdownItem value={option} key={option}>
|
|
155
|
+
{option}
|
|
156
|
+
</DropdownItem>
|
|
157
|
+
))}
|
|
158
|
+
</DropdownList>
|
|
159
|
+
</Dropdown>
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const fields = [
|
|
163
|
+
{
|
|
164
|
+
id: 'theme',
|
|
165
|
+
label: 'Theme',
|
|
166
|
+
field: dropdownField('theme', 'Theme', theme, ['System', 'Light', 'Dark'], setTheme)
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'language',
|
|
170
|
+
label: 'Language',
|
|
171
|
+
field: dropdownField('language', 'Language', language, ['Auto-detect', 'English'], setLanguage)
|
|
172
|
+
},
|
|
173
|
+
{ id: 'voice', label: 'Voice', field: dropdownField('voice', 'Voice', voice, ['Bot', 'User'], setVoice) },
|
|
174
|
+
{
|
|
175
|
+
id: 'analytics',
|
|
176
|
+
label: 'Share analytics',
|
|
177
|
+
field: (
|
|
178
|
+
<Switch
|
|
179
|
+
id="analytics"
|
|
180
|
+
aria-labelledby="analytics-label"
|
|
181
|
+
isChecked={isAnalyticsShared}
|
|
182
|
+
onChange={(_event, checked) => setIsAnalyticsShared(checked)}
|
|
183
|
+
/>
|
|
184
|
+
)
|
|
185
|
+
},
|
|
186
|
+
{ id: 'archived-chat', label: 'Archived chats', field: <Button id="archived-chat">Manage</Button> },
|
|
187
|
+
{ id: 'archive-all', label: 'Archived all chats', field: <Button id="archive-all">Archive all</Button> },
|
|
188
|
+
{
|
|
189
|
+
id: 'delete-all',
|
|
190
|
+
label: 'Delete all chats',
|
|
191
|
+
field: (
|
|
192
|
+
<Button id="delete-all" variant="danger">
|
|
193
|
+
Delete all
|
|
194
|
+
</Button>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div className="pf-v6-u-w-100">
|
|
201
|
+
<div className="pf-v6-u-w-100 pf-v6-u-mx-auto" style={{ maxWidth: '60rem' }}>
|
|
202
|
+
<Flex
|
|
203
|
+
justifyContent={{ default: 'justifyContentSpaceBetween' }}
|
|
204
|
+
alignItems={{ default: 'alignItemsCenter' }}
|
|
205
|
+
className="pf-v6-u-p-lg"
|
|
206
|
+
>
|
|
207
|
+
<Title headingLevel="h1" size="2xl">
|
|
208
|
+
Settings
|
|
209
|
+
</Title>
|
|
210
|
+
<Tooltip triggerRef={closeButtonRef} content="Close settings" position="bottom" aria="none" />
|
|
211
|
+
<Button
|
|
212
|
+
ref={closeButtonRef}
|
|
213
|
+
variant="plain"
|
|
214
|
+
icon={<RhMicronsCloseIcon />}
|
|
215
|
+
aria-label="Close settings"
|
|
216
|
+
onClick={onClose}
|
|
217
|
+
/>
|
|
218
|
+
</Flex>
|
|
219
|
+
<Divider />
|
|
220
|
+
<PlainTextSettingsForm fields={fields} />
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const FullscreenDockedNav: FunctionComponent = () => {
|
|
227
|
+
const [messages, setMessages] = useState<MessageProps[]>(initialMessages);
|
|
228
|
+
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
|
229
|
+
const [areSettingsOpen, setAreSettingsOpen] = useState(false);
|
|
230
|
+
const [announcement, setAnnouncement] = useState<string>();
|
|
231
|
+
const newChatRef = useRef<HTMLAnchorElement>(null);
|
|
232
|
+
const settingsRef = useRef<HTMLAnchorElement>(null);
|
|
233
|
+
|
|
234
|
+
const closeSettings = () => {
|
|
235
|
+
setAreSettingsOpen(false);
|
|
236
|
+
window.requestAnimationFrame(() => settingsRef.current?.focus());
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const startNewChat = () => {
|
|
240
|
+
setMessages([]);
|
|
241
|
+
setIsDrawerOpen(false);
|
|
242
|
+
setAreSettingsOpen(false);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const sendMessage = (content: string) => {
|
|
246
|
+
const message: MessageProps = {
|
|
247
|
+
id: Date.now().toString(),
|
|
248
|
+
role: 'user',
|
|
249
|
+
content,
|
|
250
|
+
name: 'You',
|
|
251
|
+
avatar: userAvatar,
|
|
252
|
+
timestamp: new Date().toLocaleString(),
|
|
253
|
+
avatarProps: { isBordered: true }
|
|
254
|
+
};
|
|
255
|
+
setMessages((currentMessages) => [...currentMessages, message]);
|
|
256
|
+
setAnnouncement(`Message from You: ${content}`);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const dockedNav = (
|
|
260
|
+
<>
|
|
261
|
+
<style>{hamburgerHoverStyles}</style>
|
|
262
|
+
<div className="pf-chatbot__canvas-docked-nav pf-v6-u-h-100 pf-v6-u-p-sm">
|
|
263
|
+
<Masthead variant="docked">
|
|
264
|
+
<MastheadMain>
|
|
265
|
+
<MastheadToggle>
|
|
266
|
+
<PageToggleButton
|
|
267
|
+
className="pf-chatbot__canvas-history-toggle"
|
|
268
|
+
aria-label="Chat history"
|
|
269
|
+
isHamburgerButton
|
|
270
|
+
isSidebarOpen={isDrawerOpen}
|
|
271
|
+
onSidebarToggle={() => setIsDrawerOpen((open) => !open)}
|
|
272
|
+
/>
|
|
273
|
+
</MastheadToggle>
|
|
274
|
+
<MastheadBrand>
|
|
275
|
+
<MastheadLogo isCompact>
|
|
276
|
+
<Brand src={PFIconLogoColor} alt="PatternFly" heights={{ default: '37px' }} />
|
|
277
|
+
</MastheadLogo>
|
|
278
|
+
</MastheadBrand>
|
|
279
|
+
</MastheadMain>
|
|
280
|
+
<MastheadContent>
|
|
281
|
+
<Divider />
|
|
282
|
+
<Nav
|
|
283
|
+
variant="docked"
|
|
284
|
+
aria-label="Chatbot navigation"
|
|
285
|
+
className="pf-v6-u-flex-1 pf-v6-u-align-content-space-between"
|
|
286
|
+
>
|
|
287
|
+
<NavList>
|
|
288
|
+
<NavItem
|
|
289
|
+
itemId="new-chat"
|
|
290
|
+
aria-label="New chat"
|
|
291
|
+
icon={<RhUiEditFillIcon />}
|
|
292
|
+
component="button"
|
|
293
|
+
preventDefault
|
|
294
|
+
anchorRef={newChatRef}
|
|
295
|
+
onClick={startNewChat}
|
|
296
|
+
>
|
|
297
|
+
New chat
|
|
298
|
+
</NavItem>
|
|
299
|
+
</NavList>
|
|
300
|
+
<NavList className="pf-v6-u-mt-auto">
|
|
301
|
+
<NavItem
|
|
302
|
+
itemId="settings"
|
|
303
|
+
aria-label="Settings"
|
|
304
|
+
icon={<RhUiSettingsFillIcon />}
|
|
305
|
+
component="button"
|
|
306
|
+
preventDefault
|
|
307
|
+
anchorRef={settingsRef}
|
|
308
|
+
onClick={() => {
|
|
309
|
+
setIsDrawerOpen(false);
|
|
310
|
+
setAreSettingsOpen(true);
|
|
311
|
+
}}
|
|
312
|
+
>
|
|
313
|
+
Settings
|
|
314
|
+
</NavItem>
|
|
315
|
+
</NavList>
|
|
316
|
+
</Nav>
|
|
317
|
+
<Tooltip aria="none" aria-live="off" triggerRef={newChatRef} content="New chat" />
|
|
318
|
+
<Tooltip aria="none" aria-live="off" triggerRef={settingsRef} content="Settings" />
|
|
319
|
+
<Avatar
|
|
320
|
+
className="pf-v6-u-mt-md pf-v6-u-mb-md pf-v6-u-mx-auto"
|
|
321
|
+
src={userAvatar}
|
|
322
|
+
alt="User profile"
|
|
323
|
+
size="md"
|
|
324
|
+
isBordered
|
|
325
|
+
/>
|
|
326
|
+
</MastheadContent>
|
|
327
|
+
</Masthead>
|
|
328
|
+
</div>
|
|
329
|
+
</>
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<Chatbot displayMode={ChatbotDisplayMode.fullscreen} dockedNav={dockedNav}>
|
|
334
|
+
<div className="pf-chatbot__canvas">
|
|
335
|
+
<ChatbotConversationHistoryNav
|
|
336
|
+
displayMode={ChatbotDisplayMode.fullscreen}
|
|
337
|
+
isDrawerOpen={isDrawerOpen}
|
|
338
|
+
setIsDrawerOpen={setIsDrawerOpen}
|
|
339
|
+
onDrawerToggle={() => setIsDrawerOpen((open) => !open)}
|
|
340
|
+
conversations={{ Today: conversations }}
|
|
341
|
+
activeItemId="1"
|
|
342
|
+
onNewChat={startNewChat}
|
|
343
|
+
drawerCloseButtonProps={{ 'aria-label': 'Close chat history' }}
|
|
344
|
+
drawerContent={
|
|
345
|
+
<div className="pf-chatbot__canvas">
|
|
346
|
+
{areSettingsOpen ? (
|
|
347
|
+
<SettingsPanel onClose={closeSettings} />
|
|
348
|
+
) : (
|
|
349
|
+
<div className="pf-chatbot__canvas-column">
|
|
350
|
+
<ChatbotContent>
|
|
351
|
+
<MessageBox
|
|
352
|
+
ariaLabel="Scrollable message log for ChatBot"
|
|
353
|
+
announcement={announcement}
|
|
354
|
+
position="bottom"
|
|
355
|
+
>
|
|
356
|
+
<ChatbotWelcomePrompt
|
|
357
|
+
title="Hello, Chatbot User"
|
|
358
|
+
description="How may I help you today?"
|
|
359
|
+
prompts={welcomePrompts}
|
|
360
|
+
/>
|
|
361
|
+
{messages.map((message) => (
|
|
362
|
+
<Message key={message.id} {...message} />
|
|
363
|
+
))}
|
|
364
|
+
</MessageBox>
|
|
365
|
+
</ChatbotContent>
|
|
366
|
+
<ChatbotFooter>
|
|
367
|
+
<MessageBar onSendMessage={sendMessage} attachButtonPosition="start" alwayShowSendButton />
|
|
368
|
+
<ChatbotFootnote
|
|
369
|
+
label="Always review AI-generated content prior to use."
|
|
370
|
+
popover={{
|
|
371
|
+
title: 'AI-generated content',
|
|
372
|
+
description: 'Always review AI-generated content prior to use.',
|
|
373
|
+
showClose: true
|
|
374
|
+
}}
|
|
375
|
+
/>
|
|
376
|
+
</ChatbotFooter>
|
|
377
|
+
</div>
|
|
378
|
+
)}
|
|
379
|
+
</div>
|
|
380
|
+
}
|
|
381
|
+
/>
|
|
382
|
+
</div>
|
|
383
|
+
</Chatbot>
|
|
384
|
+
);
|
|
385
|
+
};
|
package/src/Canvas/Canvas.scss
CHANGED
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
--pf-chatbot__canvas--BackgroundColor: var(--pf-t--global--background--color--glass--primary--default);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
.pf-chatbot__canvas-docked-nav {
|
|
21
|
+
border-inline-end: var(--pf-t--global--border--width--regular) solid var(--pf-t--global--border--color--subtle);
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
// Drawer
|
|
21
25
|
// ----------------------------------------------------------------------------
|
|
22
26
|
.pf-chatbot__canvas-drawer {
|
|
@@ -56,6 +60,14 @@
|
|
|
56
60
|
min-height: 0;
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
.pf-chatbot__canvas-chat-content {
|
|
64
|
+
padding-block-start: var(--pf-t--global--spacer--lg);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.pf-chatbot__canvas-chat-footer {
|
|
68
|
+
padding-block-end: var(--pf-t--global--spacer--md);
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
// Panel
|
|
60
72
|
// ----------------------------------------------------------------------------
|
|
61
73
|
.pf-chatbot__canvas-section {
|
package/src/Chatbot/Chatbot.scss
CHANGED
|
@@ -141,6 +141,19 @@
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// Chatbot With Docked Nav
|
|
146
|
+
// ============================================================================
|
|
147
|
+
.pf-chatbot--docked-nav {
|
|
148
|
+
.pf-chatbot-container {
|
|
149
|
+
flex-direction: row;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.pf-chatbot__header-container {
|
|
153
|
+
display: none;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
144
157
|
.pf-chatbot-container--embedded {
|
|
145
158
|
min-height: 100%;
|
|
146
159
|
}
|
|
@@ -180,4 +193,3 @@
|
|
|
180
193
|
border: unset;
|
|
181
194
|
}
|
|
182
195
|
}
|
|
183
|
-
|
|
@@ -36,4 +36,15 @@ describe('Chatbot', () => {
|
|
|
36
36
|
);
|
|
37
37
|
expect(screen.getByTestId('chatbot')).toHaveClass('pf-m-compact');
|
|
38
38
|
});
|
|
39
|
+
|
|
40
|
+
it('should render docked navigation and apply the docked navigation class', () => {
|
|
41
|
+
render(
|
|
42
|
+
<Chatbot data-testid="chatbot" dockedNav={<nav aria-label="Chatbot navigation">Navigation</nav>}>
|
|
43
|
+
Chatbot Content
|
|
44
|
+
</Chatbot>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(screen.getByTestId('chatbot')).toHaveClass('pf-chatbot--docked-nav');
|
|
48
|
+
expect(screen.getByRole('navigation', { name: 'Chatbot navigation' })).toBeInTheDocument();
|
|
49
|
+
});
|
|
39
50
|
});
|
package/src/Chatbot/Chatbot.tsx
CHANGED
|
@@ -20,6 +20,8 @@ export interface ChatbotProps {
|
|
|
20
20
|
ariaLabel?: string;
|
|
21
21
|
/** Density of information within the ChatBot */
|
|
22
22
|
isCompact?: boolean;
|
|
23
|
+
/** Optional navigation rail displayed before the chatbot content */
|
|
24
|
+
dockedNav?: React.ReactNode;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export enum ChatbotDisplayMode {
|
|
@@ -38,10 +40,11 @@ const ChatbotBase: FunctionComponent<ChatbotProps> = ({
|
|
|
38
40
|
innerRef,
|
|
39
41
|
ariaLabel,
|
|
40
42
|
isCompact,
|
|
43
|
+
dockedNav,
|
|
41
44
|
...props
|
|
42
45
|
}: ChatbotProps) => (
|
|
43
46
|
<div
|
|
44
|
-
className={`pf-chatbot pf-chatbot--${displayMode} ${!isVisible ? 'pf-chatbot--hidden' : 'pf-chatbot--visible'} ${isCompact ? 'pf-m-compact' : ''} ${className ?? ''}`}
|
|
47
|
+
className={`pf-chatbot pf-chatbot--${displayMode} ${dockedNav ? 'pf-chatbot--docked-nav' : ''} ${!isVisible ? 'pf-chatbot--hidden' : 'pf-chatbot--visible'} ${isCompact ? 'pf-m-compact' : ''} ${className ?? ''}`}
|
|
45
48
|
{...props}
|
|
46
49
|
>
|
|
47
50
|
{/* Ref is intended for use with skip to chatbot links, etc. */}
|
|
@@ -52,6 +55,7 @@ const ChatbotBase: FunctionComponent<ChatbotProps> = ({
|
|
|
52
55
|
tabIndex={-1}
|
|
53
56
|
ref={innerRef}
|
|
54
57
|
>
|
|
58
|
+
{dockedNav && <aside className="pf-chatbot__docked-nav">{dockedNav}</aside>}
|
|
55
59
|
{children}
|
|
56
60
|
</section>
|
|
57
61
|
) : undefined}
|
|
@@ -161,17 +161,23 @@ describe('ChatbotConversationHistoryNav', () => {
|
|
|
161
161
|
});
|
|
162
162
|
|
|
163
163
|
it('should close the drawer when escape key is pressed', async () => {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
164
|
+
const HistoryNavigationTest = () => {
|
|
165
|
+
const [isDrawerOpen, setIsDrawerOpen] = useState(true);
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<ChatbotConversationHistoryNav
|
|
169
|
+
onDrawerToggle={onDrawerToggle}
|
|
170
|
+
isDrawerOpen={isDrawerOpen}
|
|
171
|
+
displayMode={ChatbotDisplayMode.fullscreen}
|
|
172
|
+
setIsDrawerOpen={setIsDrawerOpen}
|
|
173
|
+
reverseButtonOrder={false}
|
|
174
|
+
handleTextInputChange={jest.fn()}
|
|
175
|
+
conversations={initialConversations}
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
render(<HistoryNavigationTest />);
|
|
175
181
|
|
|
176
182
|
fireEvent.keyDown(screen.getByPlaceholderText(/Search/i), {
|
|
177
183
|
key: 'Escape',
|
|
@@ -180,8 +186,8 @@ describe('ChatbotConversationHistoryNav', () => {
|
|
|
180
186
|
charCode: 27
|
|
181
187
|
});
|
|
182
188
|
|
|
183
|
-
waitFor(() => {
|
|
184
|
-
expect(screen.
|
|
189
|
+
await waitFor(() => {
|
|
190
|
+
expect(screen.getByRole('dialog', { hidden: true })).toHaveAttribute('hidden');
|
|
185
191
|
});
|
|
186
192
|
});
|
|
187
193
|
|
|
@@ -227,6 +227,23 @@ describe('Message bar', () => {
|
|
|
227
227
|
await userEvent.click(attachButton);
|
|
228
228
|
expect(attachToggleClickSpy).toHaveBeenCalledTimes(1);
|
|
229
229
|
});
|
|
230
|
+
it('can toggle attach menu without an optional toggle callback', async () => {
|
|
231
|
+
const setIsAttachMenuOpen = jest.fn();
|
|
232
|
+
render(
|
|
233
|
+
<MessageBar
|
|
234
|
+
onSendMessage={jest.fn}
|
|
235
|
+
attachMenuProps={{
|
|
236
|
+
isAttachMenuOpen: false,
|
|
237
|
+
setIsAttachMenuOpen,
|
|
238
|
+
attachMenuItems: ATTACH_MENU_ITEMS
|
|
239
|
+
}}
|
|
240
|
+
/>
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
await userEvent.click(screen.getByRole('button', { name: 'Attach' }));
|
|
244
|
+
|
|
245
|
+
expect(setIsAttachMenuOpen).toHaveBeenCalledWith(true);
|
|
246
|
+
});
|
|
230
247
|
it('can pass searchInputProps to search input in AttachMenu', () => {
|
|
231
248
|
render(
|
|
232
249
|
<MessageBar
|
|
@@ -28,7 +28,7 @@ export interface MessageBarWithAttachMenuProps {
|
|
|
28
28
|
/** Items in menu */
|
|
29
29
|
attachMenuItems: React.ReactNode;
|
|
30
30
|
/** A callback for when the attachment menu toggle is clicked */
|
|
31
|
-
onAttachMenuToggleClick
|
|
31
|
+
onAttachMenuToggleClick?: () => void;
|
|
32
32
|
/** A callback for when the input value in the menu changes. */
|
|
33
33
|
onAttachMenuInputChange?: (value: string) => void;
|
|
34
34
|
/** Function callback called when user selects item in menu. */
|
|
@@ -368,7 +368,7 @@ export const MessageBarBase: FunctionComponent<MessageBarProps> = ({
|
|
|
368
368
|
|
|
369
369
|
const handleAttachMenuToggle = () => {
|
|
370
370
|
attachMenuProps?.setIsAttachMenuOpen && attachMenuProps?.setIsAttachMenuOpen(!attachMenuProps?.isAttachMenuOpen);
|
|
371
|
-
attachMenuProps?.onAttachMenuToggleClick();
|
|
371
|
+
attachMenuProps?.onAttachMenuToggleClick?.();
|
|
372
372
|
};
|
|
373
373
|
|
|
374
374
|
const handleSpeechRecognition = (message) => {
|
|
@@ -525,7 +525,9 @@ export const MessageBarBase: FunctionComponent<MessageBarProps> = ({
|
|
|
525
525
|
{...(attachMenuProps && { handleTextInputChange: attachMenuProps.onAttachMenuInputChange })}
|
|
526
526
|
popperProps={{ direction: 'up', distance: 8 }}
|
|
527
527
|
searchInputPlaceholder={attachMenuProps?.attachMenuInputPlaceholder}
|
|
528
|
-
{
|
|
528
|
+
menuSearchProps={attachMenuProps?.menuSearchProps}
|
|
529
|
+
menuSearchInputProps={attachMenuProps?.menuSearchInputProps}
|
|
530
|
+
searchInputProps={attachMenuProps?.searchInputProps}
|
|
529
531
|
/>
|
|
530
532
|
);
|
|
531
533
|
}
|