@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.
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +1 -1
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +13 -4
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +6 -12
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +21 -0
- package/dist/cjs/MessageBar/MessageBar.js +19 -4
- package/dist/css/main.css +31 -27
- package/dist/css/main.css.map +1 -1
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +1 -1
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.js +6 -6
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +13 -4
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +7 -13
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +21 -0
- package/dist/esm/MessageBar/MessageBar.js +19 -4
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotConversationEditing.tsx +202 -0
- package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +14 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.test.tsx +6 -6
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +0 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +40 -32
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +70 -0
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +55 -49
- package/src/ChatbotModal/ChatbotModal.scss +1 -1
- package/src/MessageBar/MessageBar.tsx +23 -3
|
@@ -26,6 +26,7 @@ export const MessageBarBase = (_a) => {
|
|
|
26
26
|
const [message, setMessage] = useState(value !== null && value !== void 0 ? value : '');
|
|
27
27
|
const [isListeningMessage, setIsListeningMessage] = useState(false);
|
|
28
28
|
const [hasSentMessage, setHasSentMessage] = useState(false);
|
|
29
|
+
const [isComposing, setIsComposing] = useState(false);
|
|
29
30
|
const inputRef = useRef(null);
|
|
30
31
|
const textareaRef = (_b = innerRef) !== null && _b !== void 0 ? _b : inputRef;
|
|
31
32
|
const attachButtonRef = useRef(null);
|
|
@@ -151,18 +152,32 @@ export const MessageBarBase = (_a) => {
|
|
|
151
152
|
setMessage('');
|
|
152
153
|
}, [onSendMessage]);
|
|
153
154
|
const handleKeyDown = useCallback((event) => {
|
|
154
|
-
|
|
155
|
+
// Japanese and other languages may use IME for character input.
|
|
156
|
+
// In these cases, enter is used to select the final input, so we need to check for composition end instead.
|
|
157
|
+
// See more info at https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/
|
|
158
|
+
// Chrome, Edge, and Firefox seem to work well with just the compose event.
|
|
159
|
+
// Safari is a little bit special. We need to handle 229 as well in this case.
|
|
160
|
+
const nativeEvent = event.nativeEvent;
|
|
161
|
+
const isCompositionKey = nativeEvent.which === 229;
|
|
162
|
+
const isCurrentlyComposing = isComposing || isCompositionKey;
|
|
163
|
+
if (event.key === 'Enter' && !isCurrentlyComposing && !event.shiftKey) {
|
|
155
164
|
event.preventDefault();
|
|
156
165
|
if (!isSendButtonDisabled && !hasStopButton) {
|
|
157
166
|
handleSend(message);
|
|
158
167
|
}
|
|
159
168
|
}
|
|
160
|
-
if (event.key === 'Enter' && event.shiftKey) {
|
|
169
|
+
if (event.key === 'Enter' && !isCurrentlyComposing && event.shiftKey) {
|
|
161
170
|
if (textareaRef.current) {
|
|
162
171
|
handleNewLine(textareaRef.current);
|
|
163
172
|
}
|
|
164
173
|
}
|
|
165
|
-
}, [isSendButtonDisabled, hasStopButton, handleSend, message]);
|
|
174
|
+
}, [isSendButtonDisabled, hasStopButton, handleSend, message, isComposing]);
|
|
175
|
+
const handleCompositionStart = useCallback(() => {
|
|
176
|
+
setIsComposing(true);
|
|
177
|
+
}, []);
|
|
178
|
+
const handleCompositionEnd = useCallback(() => {
|
|
179
|
+
setIsComposing(false);
|
|
180
|
+
}, []);
|
|
166
181
|
const handleAttachMenuToggle = () => {
|
|
167
182
|
(attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.setIsAttachMenuOpen) && (attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.setIsAttachMenuOpen(!(attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.isAttachMenuOpen)));
|
|
168
183
|
attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.onAttachMenuToggleClick();
|
|
@@ -178,7 +193,7 @@ export const MessageBarBase = (_a) => {
|
|
|
178
193
|
}
|
|
179
194
|
return (_jsxs(_Fragment, { children: [attachMenuProps && (_jsx(AttachButton, Object.assign({ ref: attachButtonRef, onClick: handleAttachMenuToggle, isDisabled: isListeningMessage, tooltipContent: (_d = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _d === void 0 ? void 0 : _d.tooltipContent, isCompact: isCompact, tooltipProps: (_e = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _e === void 0 ? void 0 : _e.tooltipProps, allowedFileTypes: allowedFileTypes, minSize: minSize, maxSize: maxSize, maxFiles: maxFiles, isAttachmentDisabled: isAttachmentDisabled, onAttach: onAttach, onAttachRejected: onAttachRejected, validator: validator, dropzoneProps: dropzoneProps }, (_f = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _f === void 0 ? void 0 : _f.props))), !attachMenuProps && hasAttachButton && (_jsx(AttachButton, Object.assign({ onAttachAccepted: handleAttach, isDisabled: isListeningMessage, tooltipContent: (_g = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _g === void 0 ? void 0 : _g.tooltipContent, inputTestId: (_h = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _h === void 0 ? void 0 : _h.inputTestId, isCompact: isCompact, tooltipProps: (_j = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _j === void 0 ? void 0 : _j.tooltipProps, allowedFileTypes: allowedFileTypes, minSize: minSize, maxSize: maxSize, maxFiles: maxFiles, isAttachmentDisabled: isAttachmentDisabled, onAttach: onAttach, onAttachRejected: onAttachRejected, validator: validator, dropzoneProps: dropzoneProps }, (_k = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.attach) === null || _k === void 0 ? void 0 : _k.props))), hasMicrophoneButton && (_jsx(MicrophoneButton, Object.assign({ isListening: isListeningMessage, onIsListeningChange: setIsListeningMessage, onSpeechRecognition: handleSpeechRecognition, tooltipContent: (_l = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _l === void 0 ? void 0 : _l.tooltipContent, language: (_m = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _m === void 0 ? void 0 : _m.language, isCompact: isCompact, tooltipProps: (_o = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _o === void 0 ? void 0 : _o.tooltipProps }, (_p = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.microphone) === null || _p === void 0 ? void 0 : _p.props))), (alwayShowSendButton || message) && (_jsx(SendButton, Object.assign({ value: message, onClick: () => handleSend(message), isDisabled: isSendButtonDisabled, tooltipContent: (_q = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.send) === null || _q === void 0 ? void 0 : _q.tooltipContent, isCompact: isCompact, tooltipProps: (_r = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.send) === null || _r === void 0 ? void 0 : _r.tooltipProps }, (_s = buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.send) === null || _s === void 0 ? void 0 : _s.props)))] }));
|
|
180
195
|
};
|
|
181
|
-
const messageBarContents = (_jsxs(_Fragment, { children: [_jsx("div", { className: `pf-chatbot__message-bar-input ${isCompact ? 'pf-m-compact' : ''}`, children: _jsx(TextArea, Object.assign({ className: "pf-chatbot__message-textarea", value: message, onChange: handleChange, "aria-label": isListeningMessage ? listeningText : placeholder, placeholder: isListeningMessage ? listeningText : placeholder, ref: textareaRef, onKeyDown: handleKeyDown }, props)) }), _jsx("div", { className: "pf-chatbot__message-bar-actions", children: renderButtons() })] }));
|
|
196
|
+
const messageBarContents = (_jsxs(_Fragment, { children: [_jsx("div", { className: `pf-chatbot__message-bar-input ${isCompact ? 'pf-m-compact' : ''}`, children: _jsx(TextArea, Object.assign({ className: "pf-chatbot__message-textarea", value: message, onChange: handleChange, "aria-label": isListeningMessage ? listeningText : placeholder, placeholder: isListeningMessage ? listeningText : placeholder, ref: textareaRef, onKeyDown: handleKeyDown, onCompositionStart: handleCompositionStart, onCompositionEnd: handleCompositionEnd }, props)) }), _jsx("div", { className: "pf-chatbot__message-bar-actions", children: renderButtons() })] }));
|
|
182
197
|
if (attachMenuProps) {
|
|
183
198
|
return (_jsx(AttachMenu, Object.assign({ toggle: (toggleRef) => (_jsx("div", { ref: toggleRef, className: `pf-chatbot__message-bar ${className !== null && className !== void 0 ? className : ''}`, children: messageBarContents })), filteredItems: attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.attachMenuItems }, (attachMenuProps && { isOpen: attachMenuProps.isAttachMenuOpen }), { onOpenChange: (isAttachMenuOpen) => {
|
|
184
199
|
var _a;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patternfly/chatbot",
|
|
3
|
-
"version": "6.4.0-prerelease.
|
|
3
|
+
"version": "6.4.0-prerelease.6",
|
|
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",
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// From Cursor, with aid
|
|
2
|
+
import React, { FunctionComponent, useState, useRef, useEffect } from 'react';
|
|
3
|
+
import { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
|
|
4
|
+
import ChatbotConversationHistoryNav, {
|
|
5
|
+
Conversation
|
|
6
|
+
} from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
|
|
7
|
+
import { ChatbotModal } from '@patternfly/chatbot/dist/dynamic/ChatbotModal';
|
|
8
|
+
import {
|
|
9
|
+
Checkbox,
|
|
10
|
+
DropdownItem,
|
|
11
|
+
DropdownList,
|
|
12
|
+
Button,
|
|
13
|
+
TextInput,
|
|
14
|
+
Form,
|
|
15
|
+
FormGroup,
|
|
16
|
+
ModalHeader,
|
|
17
|
+
ModalBody,
|
|
18
|
+
ModalFooter
|
|
19
|
+
} from '@patternfly/react-core';
|
|
20
|
+
|
|
21
|
+
export const ChatbotHeaderTitleDemo: FunctionComponent = () => {
|
|
22
|
+
const [isDrawerOpen, setIsDrawerOpen] = useState(true);
|
|
23
|
+
const displayMode = ChatbotDisplayMode.embedded;
|
|
24
|
+
|
|
25
|
+
// Modal state
|
|
26
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
27
|
+
const [editingConversationId, setEditingConversationId] = useState<string | number | null>(null);
|
|
28
|
+
const [editingText, setEditingText] = useState('');
|
|
29
|
+
|
|
30
|
+
// Ref for the text input
|
|
31
|
+
const textInputRef = useRef<HTMLInputElement>(null);
|
|
32
|
+
|
|
33
|
+
// Focus the text input when modal opens
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (isModalOpen && textInputRef.current) {
|
|
36
|
+
textInputRef.current.focus();
|
|
37
|
+
// Move cursor to the end of the text
|
|
38
|
+
const length = textInputRef.current.value.length;
|
|
39
|
+
textInputRef.current.setSelectionRange(length, length);
|
|
40
|
+
}
|
|
41
|
+
}, [isModalOpen]);
|
|
42
|
+
|
|
43
|
+
const findConversationAndGroup = (conversations: { [key: string]: Conversation[] }, itemId: string | number) => {
|
|
44
|
+
for (const [groupKey, conversationList] of Object.entries(conversations)) {
|
|
45
|
+
const conversationIndex = conversationList.findIndex((conv) => conv.id === itemId);
|
|
46
|
+
if (conversationIndex !== -1) {
|
|
47
|
+
return { groupKey, conversationIndex, conversation: conversationList[conversationIndex] };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const onRenameClick = (itemId: string | number) => {
|
|
54
|
+
const result = findConversationAndGroup(conversations, itemId);
|
|
55
|
+
if (result) {
|
|
56
|
+
setEditingConversationId(itemId);
|
|
57
|
+
setEditingText(result.conversation.text);
|
|
58
|
+
setIsModalOpen(true);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const handleModalSave = () => {
|
|
63
|
+
if (editingConversationId) {
|
|
64
|
+
setConversations((prevConversations) => {
|
|
65
|
+
const result = findConversationAndGroup(prevConversations, editingConversationId);
|
|
66
|
+
if (!result) {
|
|
67
|
+
return prevConversations;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { groupKey, conversationIndex } = result;
|
|
71
|
+
const newConversations = { ...prevConversations };
|
|
72
|
+
const newGroup = [...newConversations[groupKey]];
|
|
73
|
+
|
|
74
|
+
newGroup[conversationIndex] = { ...newGroup[conversationIndex], text: editingText };
|
|
75
|
+
newConversations[groupKey] = newGroup;
|
|
76
|
+
|
|
77
|
+
return newConversations;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
handleModalClose();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleModalCancel = () => {
|
|
84
|
+
handleModalClose();
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const handleModalClose = () => {
|
|
88
|
+
setIsModalOpen(false);
|
|
89
|
+
setEditingConversationId(null);
|
|
90
|
+
setEditingText('');
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handleTextInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
94
|
+
if (event.key === 'Enter') {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
handleModalSave();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const renderMenuItems = (itemId: string | number) => [
|
|
101
|
+
<DropdownList key={`list-${itemId}`}>
|
|
102
|
+
<DropdownItem value="Download" id="Download">
|
|
103
|
+
Download
|
|
104
|
+
</DropdownItem>
|
|
105
|
+
<DropdownItem value="Rename" id="Rename" onClick={() => onRenameClick(itemId)}>
|
|
106
|
+
Rename
|
|
107
|
+
</DropdownItem>
|
|
108
|
+
<DropdownItem value="Archive" id="Archive">
|
|
109
|
+
Archive
|
|
110
|
+
</DropdownItem>
|
|
111
|
+
<DropdownItem value="Delete" id="Delete">
|
|
112
|
+
Delete
|
|
113
|
+
</DropdownItem>
|
|
114
|
+
</DropdownList>
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
const initialConversations: { [key: string]: Conversation[] } = {
|
|
118
|
+
Today: [{ id: '1', text: 'Red Hat products and services' }],
|
|
119
|
+
'This month': [
|
|
120
|
+
{
|
|
121
|
+
id: '2',
|
|
122
|
+
text: 'Enterprise Linux installation and setup'
|
|
123
|
+
},
|
|
124
|
+
{ id: '3', text: 'Troubleshoot system crash' }
|
|
125
|
+
],
|
|
126
|
+
March: [
|
|
127
|
+
{ id: '4', text: 'Ansible security and updates' },
|
|
128
|
+
{ id: '5', text: 'Red Hat certification' },
|
|
129
|
+
{ id: '6', text: 'Lightspeed user documentation' }
|
|
130
|
+
],
|
|
131
|
+
February: [
|
|
132
|
+
{ id: '7', text: 'Crashing pod assistance' },
|
|
133
|
+
{ id: '8', text: 'OpenShift AI pipelines' },
|
|
134
|
+
{ id: '9', text: 'Updating subscription plan' },
|
|
135
|
+
{ id: '10', text: 'Red Hat licensing options' }
|
|
136
|
+
],
|
|
137
|
+
January: [
|
|
138
|
+
{ id: '11', text: 'RHEL system performance' },
|
|
139
|
+
{ id: '12', text: 'Manage user accounts' }
|
|
140
|
+
]
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const [conversations, setConversations] = useState(initialConversations);
|
|
144
|
+
|
|
145
|
+
// Create conversations with menu items dynamically
|
|
146
|
+
const conversationsWithMenuItems = () => {
|
|
147
|
+
const newConversations = { ...conversations };
|
|
148
|
+
Object.keys(newConversations).forEach((groupKey) => {
|
|
149
|
+
newConversations[groupKey] = newConversations[groupKey].map((conv) => ({
|
|
150
|
+
...conv,
|
|
151
|
+
menuItems: renderMenuItems(conv.id)
|
|
152
|
+
}));
|
|
153
|
+
});
|
|
154
|
+
return newConversations;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<>
|
|
159
|
+
<Checkbox
|
|
160
|
+
label="Display drawer"
|
|
161
|
+
isChecked={isDrawerOpen}
|
|
162
|
+
onChange={() => setIsDrawerOpen(!isDrawerOpen)}
|
|
163
|
+
id="drawer-actions-visible"
|
|
164
|
+
name="drawer-actions-visible"
|
|
165
|
+
></Checkbox>
|
|
166
|
+
<ChatbotConversationHistoryNav
|
|
167
|
+
displayMode={displayMode}
|
|
168
|
+
onDrawerToggle={() => setIsDrawerOpen(!isDrawerOpen)}
|
|
169
|
+
isDrawerOpen={isDrawerOpen}
|
|
170
|
+
setIsDrawerOpen={setIsDrawerOpen}
|
|
171
|
+
conversations={conversationsWithMenuItems()}
|
|
172
|
+
drawerContent={<div>Drawer content</div>}
|
|
173
|
+
/>
|
|
174
|
+
|
|
175
|
+
<ChatbotModal displayMode={displayMode} isOpen={isModalOpen} onClose={handleModalClose}>
|
|
176
|
+
<ModalHeader title="Rename Conversation" />
|
|
177
|
+
<ModalBody>
|
|
178
|
+
<Form>
|
|
179
|
+
<FormGroup label="Conversation Name" fieldId="conversation-name" isRequired>
|
|
180
|
+
<TextInput
|
|
181
|
+
isRequired
|
|
182
|
+
ref={textInputRef}
|
|
183
|
+
value={editingText}
|
|
184
|
+
onChange={(_, value) => setEditingText(value)}
|
|
185
|
+
onKeyDown={handleTextInputKeyDown}
|
|
186
|
+
id="conversation-name"
|
|
187
|
+
/>
|
|
188
|
+
</FormGroup>
|
|
189
|
+
</Form>
|
|
190
|
+
</ModalBody>
|
|
191
|
+
<ModalFooter>
|
|
192
|
+
<Button key="save" variant="primary" onClick={handleModalSave}>
|
|
193
|
+
Save
|
|
194
|
+
</Button>
|
|
195
|
+
<Button key="cancel" variant="link" onClick={handleModalCancel}>
|
|
196
|
+
Cancel
|
|
197
|
+
</Button>
|
|
198
|
+
</ModalFooter>
|
|
199
|
+
</ChatbotModal>
|
|
200
|
+
</>
|
|
201
|
+
);
|
|
202
|
+
};
|
|
@@ -86,7 +86,7 @@ import userAvatar from '../Messages/user_avatar.svg';
|
|
|
86
86
|
import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
|
|
87
87
|
import termsAndConditionsHeader from './PF-TermsAndConditionsHeader.svg';
|
|
88
88
|
import { CloseIcon, SearchIcon, OutlinedCommentsIcon } from '@patternfly/react-icons';
|
|
89
|
-
import { FunctionComponent, FormEvent, useState, useRef, MouseEvent, isValidElement, cloneElement, Children, ReactNode, Ref, MouseEvent as ReactMouseEvent, CSSProperties} from 'react';
|
|
89
|
+
import { FunctionComponent, FormEvent, useState, useRef, MouseEvent, isValidElement, cloneElement, Children, ReactNode, Ref, MouseEvent as ReactMouseEvent, CSSProperties, useEffect} from 'react';
|
|
90
90
|
|
|
91
91
|
## Structure
|
|
92
92
|
|
|
@@ -374,6 +374,19 @@ To help users track important conversations, add a "pin" option to the conversat
|
|
|
374
374
|
|
|
375
375
|
```
|
|
376
376
|
|
|
377
|
+
### Renaming conversations in history drawer
|
|
378
|
+
|
|
379
|
+
You can allow users to rename a conversation in the history drawer by implementing a modal that opens upon clicking a "Rename" (or similar) action. When doing so, you must ensure the following:
|
|
380
|
+
|
|
381
|
+
- When the modal opens, focus is placed at the end of the text input.
|
|
382
|
+
- When the modal closes, focus goes back to the action toggle that was previously opened.
|
|
383
|
+
- Changes can be canceled via the **<kbd>Escape</kbd>** key or clicking a "Cancel" button.
|
|
384
|
+
- Changes can be saved via the **<kbd>Enter</kbd>** key or by clicking a "Save" button.
|
|
385
|
+
|
|
386
|
+
```js file="./ChatbotConversationEditing.tsx"
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
|
|
377
390
|
### Drawer with active conversation
|
|
378
391
|
|
|
379
392
|
If you're showing a conversation that is already active, you can set the `activeItemId` prop on your `<ChatbotConversationHistoryNav>` to apply an active visual state.
|
|
@@ -14,13 +14,13 @@ describe('ChatbotConversationHistoryDropdown', () => {
|
|
|
14
14
|
|
|
15
15
|
it('should render the dropdown', () => {
|
|
16
16
|
render(<ChatbotConversationHistoryDropdown menuItems={menuItems} menuClassName="custom-class" />);
|
|
17
|
-
expect(screen.queryByRole('
|
|
17
|
+
expect(screen.queryByRole('button', { name: /Conversation options/i })).toBeInTheDocument();
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
it('should display the dropdown menuItems', () => {
|
|
21
21
|
render(<ChatbotConversationHistoryDropdown menuItems={menuItems} />);
|
|
22
22
|
|
|
23
|
-
const toggle = screen.queryByRole('
|
|
23
|
+
const toggle = screen.queryByRole('button', { name: /Conversation options/i })!;
|
|
24
24
|
|
|
25
25
|
expect(toggle).toBeInTheDocument();
|
|
26
26
|
fireEvent.click(toggle);
|
|
@@ -33,7 +33,7 @@ describe('ChatbotConversationHistoryDropdown', () => {
|
|
|
33
33
|
|
|
34
34
|
it('should invoke onSelect callback when menuitem is clicked', () => {
|
|
35
35
|
render(<ChatbotConversationHistoryDropdown menuItems={menuItems} onSelect={onSelect} />);
|
|
36
|
-
const toggle = screen.queryByRole('
|
|
36
|
+
const toggle = screen.queryByRole('button', { name: /Conversation options/i })!;
|
|
37
37
|
fireEvent.click(toggle);
|
|
38
38
|
fireEvent.click(screen.getByText('Rename'));
|
|
39
39
|
|
|
@@ -42,7 +42,7 @@ describe('ChatbotConversationHistoryDropdown', () => {
|
|
|
42
42
|
|
|
43
43
|
it('should toggle the dropdown when menuitem is clicked', () => {
|
|
44
44
|
render(<ChatbotConversationHistoryDropdown menuItems={menuItems} onSelect={onSelect} />);
|
|
45
|
-
const toggle = screen.queryByRole('
|
|
45
|
+
const toggle = screen.queryByRole('button', { name: /Conversation options/i })!;
|
|
46
46
|
fireEvent.click(toggle);
|
|
47
47
|
fireEvent.click(screen.getByText('Delete'));
|
|
48
48
|
|
|
@@ -53,7 +53,7 @@ describe('ChatbotConversationHistoryDropdown', () => {
|
|
|
53
53
|
|
|
54
54
|
it('should close the dropdown when user clicks outside', () => {
|
|
55
55
|
render(<ChatbotConversationHistoryDropdown menuItems={menuItems} onSelect={onSelect} />);
|
|
56
|
-
const toggle = screen.queryByRole('
|
|
56
|
+
const toggle = screen.queryByRole('button', { name: /Conversation options/i })!;
|
|
57
57
|
fireEvent.click(toggle);
|
|
58
58
|
|
|
59
59
|
expect(screen.queryByText('Delete')).toBeInTheDocument();
|
|
@@ -64,7 +64,7 @@ describe('ChatbotConversationHistoryDropdown', () => {
|
|
|
64
64
|
|
|
65
65
|
it('should show the tooltip when the user hovers over the toggle button', async () => {
|
|
66
66
|
render(<ChatbotConversationHistoryDropdown menuItems={menuItems} label="Actions dropdown" />);
|
|
67
|
-
const toggle = screen.queryByRole('
|
|
67
|
+
const toggle = screen.queryByRole('button', { name: /Actions dropdown/i })!;
|
|
68
68
|
|
|
69
69
|
fireEvent(
|
|
70
70
|
toggle,
|
|
@@ -6,10 +6,7 @@
|
|
|
6
6
|
position: absolute;
|
|
7
7
|
border-radius: var(--pf-t--global--border--radius--medium);
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
// ----------------------------------------------------------------------------
|
|
11
|
-
.pf-chatbot__input {
|
|
12
|
-
}
|
|
9
|
+
|
|
13
10
|
// Drawer title
|
|
14
11
|
// ----------------------------------------------------------------------------
|
|
15
12
|
.pf-chatbot__title-container {
|
|
@@ -27,50 +24,61 @@
|
|
|
27
24
|
}
|
|
28
25
|
// Drawer menu
|
|
29
26
|
// ----------------------------------------------------------------------------
|
|
30
|
-
.pf-
|
|
31
|
-
--pf-v6-c-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
.pf-v6-c-menu__item-main {
|
|
37
|
-
--pf-v6-c-menu__item-main--ColumnGap: var(--pf-t--global--spacer--md);
|
|
27
|
+
.pf-chatbot__conversation-list {
|
|
28
|
+
--pf-v6-c-list--Gap: var(--pf-t--global--spacer--xs);
|
|
29
|
+
|
|
30
|
+
margin-block-start: var(--pf-t--global--spacer--md);
|
|
31
|
+
margin-block-end: var(--pf-t--global--spacer--md);
|
|
38
32
|
}
|
|
39
|
-
|
|
33
|
+
|
|
34
|
+
.pf-chatbot__conversation-list-header {
|
|
40
35
|
color: var(--pf-t--global--text--color--subtle);
|
|
41
36
|
font-weight: var(--pf-t--global--font--weight--body--bold);
|
|
42
37
|
font-size: var(--pf-t--global--icon--size--font--sm);
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
padding-inline-start: var(--pf-t--global--spacer--sm);
|
|
39
|
+
padding-inline-end: var(--pf-t--global--spacer--sm);
|
|
45
40
|
position: -webkit-sticky;
|
|
46
41
|
position: sticky;
|
|
47
42
|
top: 0;
|
|
48
43
|
background-color: var(--pf-t--global--background--color--floating--default);
|
|
49
44
|
z-index: var(--pf-t--global--z-index--md);
|
|
50
45
|
}
|
|
51
|
-
.pf-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
46
|
+
.pf-chatbot__conversation-list-item {
|
|
47
|
+
& > span {
|
|
48
|
+
display: flex;
|
|
49
|
+
column-gap: var(--pf-t--global--spacer--sm);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
& .pf-chatbot__conversation-history-item {
|
|
53
|
+
--pf-v6-c-button--JustifyContent: flex-start;
|
|
54
|
+
--pf-v6-c-button--FontSize: var(--pf-t--global--font--size--body--lg);
|
|
55
|
+
--pf-v6-c-button--m-link--Color: var(--pf-t--global--text--color--regular);
|
|
56
|
+
--pf-v6-c-button--m-link__icon--Color: var(--pf-t--global--icon--color--regular);
|
|
57
|
+
--pf-v6-c-button--m-link--hover--Color: var(--pf-t--global--text--color--regular--hover);
|
|
58
|
+
--pf-v6-c-button--m-link--hover__icon--Color: var(--pf-t--global--icon--color--regular);
|
|
59
|
+
--pf-v6-c-button--m-link--m-clicked--Color: var(--pf-t--global--text--color--regular--clicked);
|
|
60
|
+
--pf-v6-c-button--m-link--m-clicked__icon--Color: var(--pf-t--global--icon--color--regular);
|
|
61
|
+
|
|
62
|
+
column-gap: var(--pf-t--global--spacer--md);
|
|
63
|
+
flex-basis: 100%;
|
|
64
|
+
|
|
65
|
+
& .pf-v6-c-button__text {
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
text-overflow: ellipsis;
|
|
68
|
+
white-space: nowrap;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
64
71
|
}
|
|
72
|
+
|
|
65
73
|
.pf-chatbot__history-actions {
|
|
66
74
|
transform: rotate(90deg);
|
|
67
75
|
}
|
|
68
76
|
|
|
69
|
-
.pf-
|
|
77
|
+
.pf-chatbot__conversation-list-item--active {
|
|
70
78
|
background-color: var(--pf-t--global--background--color--action--plain--clicked);
|
|
71
79
|
}
|
|
72
80
|
|
|
73
|
-
button.pf-
|
|
81
|
+
button.pf-chatbot__conversation-list-item--active {
|
|
74
82
|
background-color: initial;
|
|
75
83
|
}
|
|
76
84
|
}
|
|
@@ -233,8 +241,8 @@
|
|
|
233
241
|
}
|
|
234
242
|
}
|
|
235
243
|
|
|
236
|
-
.pf-
|
|
237
|
-
|
|
244
|
+
.pf-chatbot__conversation-history-item {
|
|
245
|
+
--pf-v6-c-button--FontSize: var(--pf-t--global--font--size--body--md);
|
|
238
246
|
}
|
|
239
247
|
|
|
240
248
|
.pf-v6-c-drawer__head {
|
|
@@ -491,4 +491,74 @@ describe('ChatbotConversationHistoryNav', () => {
|
|
|
491
491
|
const iconElement = container.querySelector('.pf-chatbot__title-icon');
|
|
492
492
|
expect(iconElement).toBeInTheDocument();
|
|
493
493
|
});
|
|
494
|
+
|
|
495
|
+
it('Passes titleProps to Title', () => {
|
|
496
|
+
render(
|
|
497
|
+
<ChatbotConversationHistoryNav
|
|
498
|
+
onDrawerToggle={onDrawerToggle}
|
|
499
|
+
isDrawerOpen={true}
|
|
500
|
+
displayMode={ChatbotDisplayMode.fullscreen}
|
|
501
|
+
setIsDrawerOpen={jest.fn()}
|
|
502
|
+
conversations={{ Today: initialConversations }}
|
|
503
|
+
titleProps={{ className: 'test' }}
|
|
504
|
+
/>
|
|
505
|
+
);
|
|
506
|
+
expect(screen.getByRole('heading', { name: /Today/i })).toHaveClass('test');
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('Overrides Title heading level when titleProps.headingLevel is passed', () => {
|
|
510
|
+
render(
|
|
511
|
+
<ChatbotConversationHistoryNav
|
|
512
|
+
onDrawerToggle={onDrawerToggle}
|
|
513
|
+
isDrawerOpen={true}
|
|
514
|
+
displayMode={ChatbotDisplayMode.fullscreen}
|
|
515
|
+
setIsDrawerOpen={jest.fn()}
|
|
516
|
+
conversations={{ Today: initialConversations }}
|
|
517
|
+
titleProps={{ headingLevel: 'h2' }}
|
|
518
|
+
/>
|
|
519
|
+
);
|
|
520
|
+
expect(screen.queryByRole('heading', { name: /Today/i, level: 4 })).not.toBeInTheDocument();
|
|
521
|
+
expect(screen.getByRole('heading', { name: /Today/i, level: 2 })).toBeInTheDocument();
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('Passes listProps to List when conversations is an array', () => {
|
|
525
|
+
render(
|
|
526
|
+
<ChatbotConversationHistoryNav
|
|
527
|
+
onDrawerToggle={onDrawerToggle}
|
|
528
|
+
isDrawerOpen={true}
|
|
529
|
+
displayMode={ChatbotDisplayMode.fullscreen}
|
|
530
|
+
setIsDrawerOpen={jest.fn()}
|
|
531
|
+
conversations={initialConversations}
|
|
532
|
+
listProps={{ className: 'test' }}
|
|
533
|
+
/>
|
|
534
|
+
);
|
|
535
|
+
expect(screen.getByRole('list')).toHaveClass('test');
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('Passes listProps to List when conversations is an object', () => {
|
|
539
|
+
render(
|
|
540
|
+
<ChatbotConversationHistoryNav
|
|
541
|
+
onDrawerToggle={onDrawerToggle}
|
|
542
|
+
isDrawerOpen={true}
|
|
543
|
+
displayMode={ChatbotDisplayMode.fullscreen}
|
|
544
|
+
setIsDrawerOpen={jest.fn()}
|
|
545
|
+
conversations={{ Today: initialConversations }}
|
|
546
|
+
listProps={{ Today: { className: 'test' } }}
|
|
547
|
+
/>
|
|
548
|
+
);
|
|
549
|
+
expect(screen.getByRole('list')).toHaveClass('test');
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('Passes listItemProps to ListItem', () => {
|
|
553
|
+
render(
|
|
554
|
+
<ChatbotConversationHistoryNav
|
|
555
|
+
onDrawerToggle={onDrawerToggle}
|
|
556
|
+
isDrawerOpen={true}
|
|
557
|
+
displayMode={ChatbotDisplayMode.fullscreen}
|
|
558
|
+
setIsDrawerOpen={jest.fn()}
|
|
559
|
+
conversations={[{ id: '1', text: 'ChatBot documentation', listItemProps: { className: 'test' } }]}
|
|
560
|
+
/>
|
|
561
|
+
);
|
|
562
|
+
expect(screen.getByRole('listitem')).toHaveClass('test');
|
|
563
|
+
});
|
|
494
564
|
});
|