@patternfly/chatbot 6.4.0-prerelease.4 → 6.4.0-prerelease.6

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 (24) hide show
  1. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +1 -1
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +13 -4
  4. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +6 -12
  5. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +21 -0
  6. package/dist/cjs/MessageBar/MessageBar.js +19 -4
  7. package/dist/css/main.css +31 -27
  8. package/dist/css/main.css.map +1 -1
  9. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +1 -1
  10. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
  11. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +13 -4
  12. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +7 -13
  13. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +21 -0
  14. package/dist/esm/MessageBar/MessageBar.js +19 -4
  15. package/package.json +1 -1
  16. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotConversationEditing.tsx +202 -0
  17. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +14 -1
  18. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx +6 -6
  19. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +0 -1
  20. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +40 -32
  21. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +70 -0
  22. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +55 -49
  23. package/src/ChatbotModal/ChatbotModal.scss +1 -1
  24. package/src/MessageBar/MessageBar.tsx +23 -3
@@ -8,6 +8,7 @@ import { useRef, Fragment } from 'react';
8
8
  // Import PatternFly components
9
9
  import {
10
10
  Button,
11
+ ButtonProps,
11
12
  Drawer,
12
13
  DrawerPanelContent,
13
14
  DrawerContent,
@@ -18,13 +19,10 @@ import {
18
19
  DrawerCloseButton,
19
20
  DrawerContentBody,
20
21
  SearchInput,
21
- Menu,
22
- MenuList,
23
- MenuGroup,
24
- MenuItem,
25
- MenuContent,
26
- MenuItemProps,
27
- MenuProps,
22
+ List,
23
+ ListItem,
24
+ ListItemProps,
25
+ Title,
28
26
  DrawerPanelContentProps,
29
27
  DrawerContentProps,
30
28
  DrawerContentBodyProps,
@@ -33,9 +31,10 @@ import {
33
31
  DrawerCloseButtonProps,
34
32
  DrawerPanelBodyProps,
35
33
  SkeletonProps,
36
- Title,
37
34
  Icon,
38
- ButtonProps
35
+ MenuProps, // Remove in next breaking change
36
+ TitleProps,
37
+ ListProps
39
38
  } from '@patternfly/react-core';
40
39
 
41
40
  import { OutlinedClockIcon, OutlinedCommentAltIcon, PenToSquareIcon } from '@patternfly/react-icons';
@@ -61,8 +60,10 @@ export interface Conversation {
61
60
  label?: string;
62
61
  /** Callback for when user selects item. */
63
62
  onSelect?: (event?: React.MouseEvent, value?: string | number) => void;
64
- /** Additional props passed to conversation menu item */
65
- additionalProps?: MenuItemProps;
63
+ /** Additional props passed to conversation button item */
64
+ additionalProps?: ButtonProps;
65
+ /** Additional props passed to conversation list item */
66
+ listItemProps?: Omit<ListItemProps, 'children'>;
66
67
  }
67
68
  export interface ChatbotConversationHistoryNavProps extends DrawerProps {
68
69
  /** Function called to toggle drawer */
@@ -79,6 +80,10 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
79
80
  conversations: Conversation[] | { [key: string]: Conversation[] };
80
81
  /** Additional button props for new chat button. */
81
82
  newChatButtonProps?: ButtonProps;
83
+ /** Additional props applied to all conversation list headers */
84
+ titleProps?: Partial<TitleProps>;
85
+ /** Additional props applied to conversation list. If conversations is an object, you should pass an object of ListProps for each group. */
86
+ listProps?: ListProps | { [key: string]: ListProps };
82
87
  /** Text shown in blue button */
83
88
  newChatButtonText?: string;
84
89
  /** Callback function for when blue button is clicked. Omit to hide blue "new chat button" */
@@ -97,7 +102,7 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
97
102
  reverseButtonOrder?: boolean;
98
103
  /** Custom test id for the drawer actions */
99
104
  drawerActionsTestId?: string;
100
- /** Additional props applied to menu */
105
+ /** @deprecated Additional props applied to list container */
101
106
  menuProps?: MenuProps;
102
107
  /** Additional props applied to panel */
103
108
  drawerPanelContentProps?: DrawerPanelContentProps;
@@ -136,6 +141,8 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
136
141
  activeItemId,
137
142
  onSelectActiveItem,
138
143
  conversations,
144
+ titleProps,
145
+ listProps,
139
146
  newChatButtonText = 'New chat',
140
147
  drawerContent,
141
148
  onNewChat,
@@ -146,7 +153,6 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
146
153
  displayMode,
147
154
  reverseButtonOrder = false,
148
155
  drawerActionsTestId = 'chatbot-nav-drawer-actions',
149
- menuProps,
150
156
  drawerPanelContentProps,
151
157
  drawerContentProps,
152
158
  drawerContentBodyProps,
@@ -170,55 +176,59 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
170
176
  };
171
177
 
172
178
  const getNavItem = (conversation: Conversation) => (
173
- <MenuItem
174
- className={`pf-chatbot__menu-item ${activeItemId && activeItemId === conversation.id ? 'pf-chatbot__menu-item--active' : ''}`}
175
- itemId={conversation.id}
179
+ <ListItem
180
+ className={`pf-chatbot__conversation-list-item ${activeItemId && activeItemId === conversation.id ? 'pf-chatbot__conversation-list-item--active' : ''}`}
176
181
  key={conversation.id}
177
- {...(conversation.noIcon ? {} : { icon: conversation.icon ?? <OutlinedCommentAltIcon /> })}
178
- /* eslint-disable indent */
179
- {...(conversation.menuItems
180
- ? {
181
- actions: (
182
- <ConversationHistoryDropdown
183
- menuClassName={conversation.menuClassName}
184
- onSelect={conversation.onSelect}
185
- menuItems={conversation.menuItems}
186
- label={conversation.label}
187
- />
188
- )
189
- }
190
- : {})}
191
- {...conversation.additionalProps}
182
+ {...conversation.listItemProps}
192
183
  /* eslint-enable indent */
193
184
  >
194
- {conversation.text}
195
- </MenuItem>
185
+ <>
186
+ <Button
187
+ className="pf-chatbot__conversation-history-item"
188
+ variant="link"
189
+ {...conversation.additionalProps}
190
+ {...(conversation.noIcon ? {} : { icon: conversation.icon ?? <OutlinedCommentAltIcon /> })}
191
+ onClick={(event) => onSelectActiveItem?.(event, conversation.id)}
192
+ >
193
+ {conversation.text}
194
+ </Button>
195
+ {conversation.menuItems && (
196
+ <ConversationHistoryDropdown
197
+ menuClassName={conversation.menuClassName}
198
+ onSelect={conversation.onSelect}
199
+ menuItems={conversation.menuItems}
200
+ label={conversation.label}
201
+ />
202
+ )}
203
+ </>
204
+ </ListItem>
196
205
  );
197
206
 
198
- const buildMenu = () => {
207
+ const buildConversations = () => {
199
208
  if (Array.isArray(conversations)) {
200
- // Render for array of MenuItemObject
201
209
  return (
202
- <MenuList>
210
+ <List className="pf-chatbot__conversation-list" isPlain {...listProps}>
203
211
  {conversations.map((conversation) => (
204
212
  <Fragment key={conversation.id}>{getNavItem(conversation)}</Fragment>
205
213
  ))}
206
- </MenuList>
214
+ </List>
207
215
  );
208
216
  } else {
209
- // Render for object with NavItemObject arrays as values
210
217
  return (
211
- <>
218
+ <div>
212
219
  {Object.keys(conversations).map((navGroup) => (
213
- <MenuGroup className="pf-chatbot__menu-item-header" label={navGroup} key={navGroup}>
214
- <MenuList>
220
+ <section key={navGroup}>
221
+ <Title headingLevel="h4" className="pf-chatbot__conversation-list-header" {...titleProps}>
222
+ {navGroup}
223
+ </Title>
224
+ <List className="pf-chatbot__conversation-list" isPlain {...listProps?.[navGroup]}>
215
225
  {conversations[navGroup].map((conversation) => (
216
226
  <Fragment key={conversation.id}>{getNavItem(conversation)}</Fragment>
217
227
  ))}
218
- </MenuList>
219
- </MenuGroup>
228
+ </List>
229
+ </section>
220
230
  ))}
221
- </>
231
+ </div>
222
232
  );
223
233
  }
224
234
  };
@@ -238,11 +248,7 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
238
248
  if (noResultsState) {
239
249
  return <HistoryEmptyState {...noResultsState} />;
240
250
  }
241
- return (
242
- <Menu isPlain onSelect={onSelectActiveItem} activeItemId={activeItemId} {...menuProps}>
243
- <MenuContent>{buildMenu()}</MenuContent>
244
- </Menu>
245
- );
251
+ return <>{buildConversations()}</>;
246
252
  };
247
253
 
248
254
  const renderDrawerContent = () => (
@@ -20,7 +20,7 @@
20
20
  padding-block-end: var(--pf-t--global--spacer--xl);
21
21
  }
22
22
  .pf-v6-c-modal-box__header {
23
- padding-block-end: var(--pf-t--global--spacer--lg);
23
+ padding-block-end: var(--pf-t--global--spacer--sm);
24
24
  }
25
25
  }
26
26
 
@@ -141,6 +141,7 @@ export const MessageBarBase: FunctionComponent<MessageBarProps> = ({
141
141
  const [message, setMessage] = useState<string | number>(value ?? '');
142
142
  const [isListeningMessage, setIsListeningMessage] = useState<boolean>(false);
143
143
  const [hasSentMessage, setHasSentMessage] = useState(false);
144
+ const [isComposing, setIsComposing] = useState(false);
144
145
  const inputRef = useRef<HTMLTextAreaElement>(null);
145
146
  const textareaRef = (innerRef as React.RefObject<HTMLTextAreaElement>) ?? inputRef;
146
147
  const attachButtonRef = useRef<HTMLButtonElement>(null);
@@ -285,21 +286,38 @@ export const MessageBarBase: FunctionComponent<MessageBarProps> = ({
285
286
 
286
287
  const handleKeyDown = useCallback(
287
288
  (event: ReactKeyboardEvent) => {
288
- if (event.key === 'Enter' && !event.shiftKey) {
289
+ // Japanese and other languages may use IME for character input.
290
+ // In these cases, enter is used to select the final input, so we need to check for composition end instead.
291
+ // See more info at https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/
292
+ // Chrome, Edge, and Firefox seem to work well with just the compose event.
293
+ // Safari is a little bit special. We need to handle 229 as well in this case.
294
+ const nativeEvent = event.nativeEvent as KeyboardEvent;
295
+ const isCompositionKey = nativeEvent.which === 229;
296
+ const isCurrentlyComposing = isComposing || isCompositionKey;
297
+
298
+ if (event.key === 'Enter' && !isCurrentlyComposing && !event.shiftKey) {
289
299
  event.preventDefault();
290
300
  if (!isSendButtonDisabled && !hasStopButton) {
291
301
  handleSend(message);
292
302
  }
293
303
  }
294
- if (event.key === 'Enter' && event.shiftKey) {
304
+ if (event.key === 'Enter' && !isCurrentlyComposing && event.shiftKey) {
295
305
  if (textareaRef.current) {
296
306
  handleNewLine(textareaRef.current);
297
307
  }
298
308
  }
299
309
  },
300
- [isSendButtonDisabled, hasStopButton, handleSend, message]
310
+ [isSendButtonDisabled, hasStopButton, handleSend, message, isComposing]
301
311
  );
302
312
 
313
+ const handleCompositionStart = useCallback(() => {
314
+ setIsComposing(true);
315
+ }, []);
316
+
317
+ const handleCompositionEnd = useCallback(() => {
318
+ setIsComposing(false);
319
+ }, []);
320
+
303
321
  const handleAttachMenuToggle = () => {
304
322
  attachMenuProps?.setIsAttachMenuOpen && attachMenuProps?.setIsAttachMenuOpen(!attachMenuProps?.isAttachMenuOpen);
305
323
  attachMenuProps?.onAttachMenuToggleClick();
@@ -402,6 +420,8 @@ export const MessageBarBase: FunctionComponent<MessageBarProps> = ({
402
420
  placeholder={isListeningMessage ? listeningText : placeholder}
403
421
  ref={textareaRef}
404
422
  onKeyDown={handleKeyDown}
423
+ onCompositionStart={handleCompositionStart}
424
+ onCompositionEnd={handleCompositionEnd}
405
425
  {...props}
406
426
  />
407
427
  </div>