@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.
@@ -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 { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon';
59
- import { RhUiBuildFillIcon, RhUiCopyFillIcon } from '@patternfly/react-icons';
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
+ };
@@ -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 {
@@ -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
  });
@@ -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
- render(
165
- <ChatbotConversationHistoryNav
166
- onDrawerToggle={onDrawerToggle}
167
- isDrawerOpen={true}
168
- displayMode={ChatbotDisplayMode.fullscreen}
169
- setIsDrawerOpen={jest.fn()}
170
- reverseButtonOrder={false}
171
- handleTextInputChange={jest.fn()}
172
- conversations={initialConversations}
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.queryByText('ChatBot documentation')).not.toBeInTheDocument();
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: () => void;
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
- {...attachMenuProps}
528
+ menuSearchProps={attachMenuProps?.menuSearchProps}
529
+ menuSearchInputProps={attachMenuProps?.menuSearchInputProps}
530
+ searchInputProps={attachMenuProps?.searchInputProps}
529
531
  />
530
532
  );
531
533
  }