@patternfly/chatbot 6.3.0 → 6.4.0-prerelease.2
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/ChatbotConversationHistoryNav.d.ts +5 -1
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +3 -3
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +17 -0
- package/dist/cjs/FileDropZone/FileDropZone.d.ts +1 -2
- package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.js +1 -10
- package/dist/cjs/Message/Message.d.ts +4 -2
- package/dist/cjs/Message/Message.js +4 -4
- package/dist/cjs/Message/Message.test.js +26 -0
- package/dist/cjs/Message/MessageInput.d.ts +3 -1
- package/dist/cjs/Message/MessageInput.js +2 -2
- package/dist/cjs/MessageBar/AttachButton.d.ts +2 -2
- package/dist/cjs/MessageBar/MessageBar.d.ts +2 -2
- package/dist/cjs/MessageBox/MessageBox.js +1 -1
- package/dist/cjs/MessageDivider/MessageDivider.d.ts +9 -0
- package/dist/cjs/MessageDivider/MessageDivider.js +23 -0
- package/dist/cjs/MessageDivider/MessageDivider.test.d.ts +1 -0
- package/dist/cjs/MessageDivider/MessageDivider.test.js +29 -0
- package/dist/cjs/MessageDivider/index.d.ts +2 -0
- package/dist/cjs/MessageDivider/index.js +23 -0
- package/dist/cjs/ResponseActions/ResponseActions.d.ts +1 -0
- package/dist/cjs/ResponseActions/ResponseActions.js +4 -4
- package/dist/cjs/ResponseActions/ResponseActions.test.js +6 -1
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +4 -1
- package/dist/css/main.css +56 -55
- package/dist/css/main.css.map +1 -1
- package/dist/dynamic/MessageDivider/package.json +1 -0
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +5 -1
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +5 -5
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +17 -0
- package/dist/esm/FileDropZone/FileDropZone.d.ts +1 -2
- package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.js +1 -7
- package/dist/esm/Message/Message.d.ts +4 -2
- package/dist/esm/Message/Message.js +4 -4
- package/dist/esm/Message/Message.test.js +26 -0
- package/dist/esm/Message/MessageInput.d.ts +3 -1
- package/dist/esm/Message/MessageInput.js +2 -2
- package/dist/esm/MessageBar/AttachButton.d.ts +2 -2
- package/dist/esm/MessageBar/MessageBar.d.ts +2 -2
- package/dist/esm/MessageBox/MessageBox.js +1 -1
- package/dist/esm/MessageDivider/MessageDivider.d.ts +9 -0
- package/dist/esm/MessageDivider/MessageDivider.js +21 -0
- package/dist/esm/MessageDivider/MessageDivider.test.d.ts +1 -0
- package/dist/esm/MessageDivider/MessageDivider.test.js +24 -0
- package/dist/esm/MessageDivider/index.d.ts +2 -0
- package/dist/esm/MessageDivider/index.js +2 -0
- package/dist/esm/ResponseActions/ResponseActions.d.ts +1 -0
- package/dist/esm/ResponseActions/ResponseActions.js +5 -5
- package/dist/esm/ResponseActions/ResponseActions.test.js +6 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -3
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDividers.tsx +24 -0
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +15 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +39 -7
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +9 -0
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +196 -0
- package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +9 -1
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +33 -1
- package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotDisplayMode.tsx +486 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +565 -0
- package/src/Chatbot/Chatbot.scss +1 -1
- package/src/ChatbotContent/ChatbotContent.scss +1 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +14 -2
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +58 -0
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +29 -12
- package/src/ChatbotFooter/ChatbotFooter.scss +1 -1
- package/src/ChatbotHeader/ChatbotHeader.scss +3 -3
- package/src/ChatbotToggle/ChatbotToggle.scss +2 -2
- package/src/FileDropZone/FileDropZone.tsx +2 -2
- package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +3 -27
- package/src/Message/Message.scss +9 -7
- package/src/Message/Message.test.tsx +35 -0
- package/src/Message/Message.tsx +8 -4
- package/src/Message/MessageInput.tsx +5 -1
- package/src/MessageBar/AttachButton.tsx +2 -2
- package/src/MessageBar/MessageBar.tsx +2 -2
- package/src/MessageBar/SendButton.scss +3 -3
- package/src/MessageBox/JumpButton.scss +1 -1
- package/src/MessageBox/MessageBox.tsx +1 -1
- package/src/MessageDivider/MessageDivider.scss +45 -0
- package/src/MessageDivider/MessageDivider.test.tsx +24 -0
- package/src/MessageDivider/MessageDivider.tsx +35 -0
- package/src/MessageDivider/index.ts +3 -0
- package/src/ResponseActions/ResponseActions.test.tsx +6 -1
- package/src/ResponseActions/ResponseActions.tsx +24 -3
- package/src/index.ts +3 -0
- package/src/main.scss +1 -52
- package/dist/cjs/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.d.ts +0 -62
- package/dist/cjs/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.js +0 -139
- package/dist/esm/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.d.ts +0 -62
- package/dist/esm/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.js +0 -133
- package/src/Message/CodeBlockMessage/ExpandableSectionForSyntaxHighlighter.tsx +0 -223
|
@@ -32,10 +32,13 @@ import {
|
|
|
32
32
|
DrawerActionsProps,
|
|
33
33
|
DrawerCloseButtonProps,
|
|
34
34
|
DrawerPanelBodyProps,
|
|
35
|
-
SkeletonProps
|
|
35
|
+
SkeletonProps,
|
|
36
|
+
Title,
|
|
37
|
+
Icon,
|
|
38
|
+
ButtonProps
|
|
36
39
|
} from '@patternfly/react-core';
|
|
37
40
|
|
|
38
|
-
import { OutlinedCommentAltIcon } from '@patternfly/react-icons';
|
|
41
|
+
import { OutlinedClockIcon, OutlinedCommentAltIcon } from '@patternfly/react-icons';
|
|
39
42
|
import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
|
|
40
43
|
import ConversationHistoryDropdown from './ChatbotConversationHistoryDropdown';
|
|
41
44
|
import LoadingState from './LoadingState';
|
|
@@ -74,6 +77,8 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
|
|
|
74
77
|
onSelectActiveItem?: (event?: React.MouseEvent, itemId?: string | number) => void;
|
|
75
78
|
/** Items shown in conversation history */
|
|
76
79
|
conversations: Conversation[] | { [key: string]: Conversation[] };
|
|
80
|
+
/** Additional button props for new chat button. */
|
|
81
|
+
newChatButtonProps?: ButtonProps;
|
|
77
82
|
/** Text shown in blue button */
|
|
78
83
|
newChatButtonText?: string;
|
|
79
84
|
/** Callback function for when blue button is clicked. Omit to hide blue "new chat button" */
|
|
@@ -120,6 +125,8 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
|
|
|
120
125
|
noResultsState?: HistoryEmptyStateProps;
|
|
121
126
|
/** Sets drawer to compact styling. */
|
|
122
127
|
isCompact?: boolean;
|
|
128
|
+
/** Display title */
|
|
129
|
+
title?: string;
|
|
123
130
|
}
|
|
124
131
|
|
|
125
132
|
export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversationHistoryNavProps> = ({
|
|
@@ -132,6 +139,7 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
|
|
|
132
139
|
newChatButtonText = 'New chat',
|
|
133
140
|
drawerContent,
|
|
134
141
|
onNewChat,
|
|
142
|
+
newChatButtonProps,
|
|
135
143
|
searchInputPlaceholder = 'Search previous conversations...',
|
|
136
144
|
searchInputAriaLabel = 'Filter menu items',
|
|
137
145
|
handleTextInputChange,
|
|
@@ -152,6 +160,7 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
|
|
|
152
160
|
emptyState,
|
|
153
161
|
noResultsState,
|
|
154
162
|
isCompact,
|
|
163
|
+
title = 'Chat history',
|
|
155
164
|
...props
|
|
156
165
|
}: ChatbotConversationHistoryNavProps) => {
|
|
157
166
|
const drawerRef = useRef<HTMLDivElement>(null);
|
|
@@ -238,15 +247,6 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
|
|
|
238
247
|
|
|
239
248
|
const renderDrawerContent = () => (
|
|
240
249
|
<>
|
|
241
|
-
{handleTextInputChange && (
|
|
242
|
-
<div className="pf-chatbot__input">
|
|
243
|
-
<SearchInput
|
|
244
|
-
aria-label={searchInputAriaLabel}
|
|
245
|
-
onChange={(_event, value) => handleTextInputChange(value)}
|
|
246
|
-
placeholder={searchInputPlaceholder}
|
|
247
|
-
/>
|
|
248
|
-
</div>
|
|
249
|
-
)}
|
|
250
250
|
<DrawerPanelBody {...drawerPanelBodyProps}>{renderMenuContent()}</DrawerPanelBody>
|
|
251
251
|
</>
|
|
252
252
|
);
|
|
@@ -262,12 +262,29 @@ export const ChatbotConversationHistoryNav: FunctionComponent<ChatbotConversatio
|
|
|
262
262
|
>
|
|
263
263
|
<DrawerCloseButton onClick={onDrawerToggle} {...drawerCloseButtonProps} />
|
|
264
264
|
{onNewChat && (
|
|
265
|
-
<Button size={isCompact ? 'sm' : undefined} onClick={onNewChat}>
|
|
265
|
+
<Button size={isCompact ? 'sm' : undefined} onClick={onNewChat} {...newChatButtonProps}>
|
|
266
266
|
{newChatButtonText}
|
|
267
267
|
</Button>
|
|
268
268
|
)}
|
|
269
269
|
</DrawerActions>
|
|
270
270
|
</DrawerHead>
|
|
271
|
+
<div className="pf-chatbot__title-container">
|
|
272
|
+
<Title headingLevel="h3">
|
|
273
|
+
<Icon size="lg" className="pf-chatbot__title-icon">
|
|
274
|
+
<OutlinedClockIcon />
|
|
275
|
+
</Icon>
|
|
276
|
+
{title}
|
|
277
|
+
</Title>
|
|
278
|
+
{!isLoading && handleTextInputChange && (
|
|
279
|
+
<div className="pf-chatbot__input">
|
|
280
|
+
<SearchInput
|
|
281
|
+
aria-label={searchInputAriaLabel}
|
|
282
|
+
onChange={(_event, value) => handleTextInputChange(value)}
|
|
283
|
+
placeholder={searchInputPlaceholder}
|
|
284
|
+
/>
|
|
285
|
+
</div>
|
|
286
|
+
)}
|
|
287
|
+
</div>
|
|
271
288
|
{isLoading ? <LoadingState {...loadingState} /> : renderDrawerContent()}
|
|
272
289
|
</>
|
|
273
290
|
);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// ============================================================================
|
|
7
7
|
.pf-chatbot__footer {
|
|
8
8
|
--pf-chatbot__footer--RowGap: var(--pf-t--global--spacer--md);
|
|
9
|
-
background-color: var(--pf-t--
|
|
9
|
+
background-color: var(--pf-t--global--background--color--secondary--default);
|
|
10
10
|
display: flex;
|
|
11
11
|
flex-direction: column;
|
|
12
12
|
row-gap: var(--pf-chatbot__footer--RowGap);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
grid-template-columns: 1fr auto;
|
|
10
10
|
gap: var(--pf-t--global--spacer--sm);
|
|
11
11
|
position: relative; // this is so focus ring on parent chatbot doesn't include header
|
|
12
|
-
background-color: var(--pf-t--
|
|
12
|
+
background-color: var(--pf-t--global--background--color--secondary--default);
|
|
13
13
|
justify-content: space-between;
|
|
14
14
|
padding: var(--pf-t--global--spacer--lg);
|
|
15
15
|
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
.pf-chatbot--drawer,
|
|
77
77
|
.pf-chatbot--docked {
|
|
78
78
|
.pf-chatbot__header {
|
|
79
|
-
background-color: var(--pf-t--
|
|
79
|
+
background-color: var(--pf-t--global--background--color--secondary--default);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
.pf-chatbot.pf-m-compact {
|
|
145
145
|
.pf-chatbot__header {
|
|
146
146
|
gap: var(--pf-t--global--spacer--sm);
|
|
147
|
-
padding: var(--pf-t--global--spacer--
|
|
147
|
+
padding: var(--pf-t--global--spacer--md);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
.pf-chatbot__header .pf-chatbot__title img {
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
inset-block-end: var(--pf-t--global--spacer--md);
|
|
7
7
|
inset-inline-end: var(--pf-t--global--spacer--md);
|
|
8
8
|
background-color: var(--pf-t--global--background--color--inverse--default);
|
|
9
|
-
--pf-v6-c-button__icon--Color: var(--pf-t--
|
|
9
|
+
--pf-v6-c-button__icon--Color: var(--pf-t--global--icon--color--inverse);
|
|
10
10
|
padding: var(--pf-t--global--spacer--md);
|
|
11
11
|
|
|
12
12
|
&:hover,
|
|
13
13
|
&:focus {
|
|
14
|
-
background-color: var(--pf-t--
|
|
14
|
+
background-color: var(--pf-t--color--gray--70);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
.pf-v6-c-button__icon {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MultipleFileUpload, MultipleFileUploadMain } from '@patternfly/react-core';
|
|
2
2
|
import type { FunctionComponent } from 'react';
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { ChatbotDisplayMode } from '../Chatbot';
|
|
5
5
|
import { UploadIcon } from '@patternfly/react-icons';
|
|
6
|
-
import { Accept, FileError, FileRejection } from 'react-dropzone
|
|
6
|
+
import { Accept, DropEvent, FileError, FileRejection } from 'react-dropzone';
|
|
7
7
|
|
|
8
8
|
export interface FileDropZoneProps {
|
|
9
9
|
/** Content displayed when the drop zone is not currently in use */
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
// Chatbot Main - Message - Content - Code Block
|
|
3
3
|
// ============================================================================
|
|
4
4
|
import { useState, useRef, useId, useCallback, useEffect } from 'react';
|
|
5
|
-
import SyntaxHighlighter from 'react-syntax-highlighter';
|
|
6
|
-
import { obsidian } from 'react-syntax-highlighter/dist/esm/styles/hljs';
|
|
7
5
|
// Import PatternFly components
|
|
8
6
|
import {
|
|
9
7
|
CodeBlock,
|
|
@@ -20,7 +18,6 @@ import {
|
|
|
20
18
|
|
|
21
19
|
import { CheckIcon } from '@patternfly/react-icons/dist/esm/icons/check-icon';
|
|
22
20
|
import { CopyIcon } from '@patternfly/react-icons/dist/esm/icons/copy-icon';
|
|
23
|
-
import { ExpandableSectionForSyntaxHighlighter } from './ExpandableSectionForSyntaxHighlighter';
|
|
24
21
|
|
|
25
22
|
export interface CodeBlockMessageProps {
|
|
26
23
|
/** Content rendered in code block */
|
|
@@ -137,30 +134,7 @@ const CodeBlockMessage = ({
|
|
|
137
134
|
<CodeBlock actions={actions}>
|
|
138
135
|
<CodeBlockCode>
|
|
139
136
|
<>
|
|
140
|
-
{
|
|
141
|
-
// SyntaxHighlighter doesn't work with ExpandableSection because it targets the direct child
|
|
142
|
-
// Forked for now and adjusted to match what we need
|
|
143
|
-
<ExpandableSectionForSyntaxHighlighter
|
|
144
|
-
variant={ExpandableSectionVariant.truncate}
|
|
145
|
-
isExpanded={isExpanded}
|
|
146
|
-
isDetached
|
|
147
|
-
toggleId={toggleId}
|
|
148
|
-
contentId={contentId}
|
|
149
|
-
language={language}
|
|
150
|
-
{...expandableSectionProps}
|
|
151
|
-
>
|
|
152
|
-
<SyntaxHighlighter
|
|
153
|
-
{...props}
|
|
154
|
-
language={language}
|
|
155
|
-
style={obsidian}
|
|
156
|
-
PreTag="div"
|
|
157
|
-
CodeTag="div"
|
|
158
|
-
wrapLongLines
|
|
159
|
-
>
|
|
160
|
-
{String(children).replace(/\n$/, '')}
|
|
161
|
-
</SyntaxHighlighter>
|
|
162
|
-
</ExpandableSectionForSyntaxHighlighter>
|
|
163
|
-
) : (
|
|
137
|
+
{isExpandable ? (
|
|
164
138
|
<ExpandableSection
|
|
165
139
|
variant={ExpandableSectionVariant.truncate}
|
|
166
140
|
isExpanded={isExpanded}
|
|
@@ -171,6 +145,8 @@ const CodeBlockMessage = ({
|
|
|
171
145
|
>
|
|
172
146
|
{children}
|
|
173
147
|
</ExpandableSection>
|
|
148
|
+
) : (
|
|
149
|
+
children
|
|
174
150
|
)}
|
|
175
151
|
</>
|
|
176
152
|
</CodeBlockCode>
|
package/src/Message/Message.scss
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
display: flex;
|
|
6
6
|
align-items: flex-start;
|
|
7
7
|
gap: var(--pf-t--global--spacer--lg);
|
|
8
|
-
padding-bottom: var(--pf-t--global--spacer--
|
|
8
|
+
padding-bottom: var(--pf-t--global--spacer--xl);
|
|
9
9
|
|
|
10
10
|
// Avatar
|
|
11
11
|
// --------------------------------------------------------------------------
|
|
@@ -48,20 +48,22 @@
|
|
|
48
48
|
|
|
49
49
|
// Author name
|
|
50
50
|
.pf-chatbot__message-name {
|
|
51
|
-
font-family: var(
|
|
51
|
+
font-family: var(
|
|
52
|
+
--pf-v6-c-content--heading--FontFamily,
|
|
53
|
+
redhatdisplayvf,
|
|
54
|
+
redhatdisplay,
|
|
55
|
+
helvetica,
|
|
56
|
+
arial,
|
|
57
|
+
sans-serif
|
|
58
|
+
);
|
|
52
59
|
font-weight: 600;
|
|
53
60
|
font-size: var(--pf-t--global--font--size--sm);
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
// Badge
|
|
57
64
|
.pf-v6-c-label {
|
|
58
|
-
--pf-v6-c-label--m-outline--BorderColor: var(--pf-t--global--border--color--on-secondary);
|
|
59
65
|
--pf-v6-c-label--FontSize: var(--pf-t--global--font--size--xs);
|
|
60
66
|
font-weight: var(--pf-t--global--font--weight--body--bold);
|
|
61
|
-
|
|
62
|
-
.pf-v6-c-label__content {
|
|
63
|
-
--pf-v6-c-label--Color: var(--pf-t--global--border--color--on-secondary);
|
|
64
|
-
}
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
// Timestamp
|
|
@@ -12,6 +12,7 @@ const ALL_ACTIONS = [
|
|
|
12
12
|
{ label: /Good response/i },
|
|
13
13
|
{ label: /Bad response/i },
|
|
14
14
|
{ label: /Copy/i },
|
|
15
|
+
{ label: /Edit/i },
|
|
15
16
|
{ label: /Share/i },
|
|
16
17
|
{ label: /Listen/i }
|
|
17
18
|
];
|
|
@@ -426,6 +427,8 @@ describe('Message', () => {
|
|
|
426
427
|
// eslint-disable-next-line no-console
|
|
427
428
|
copy: { onClick: () => console.log('Copy') },
|
|
428
429
|
// eslint-disable-next-line no-console
|
|
430
|
+
edit: { onClick: () => console.log('Edit') },
|
|
431
|
+
// eslint-disable-next-line no-console
|
|
429
432
|
share: { onClick: () => console.log('Share') },
|
|
430
433
|
// eslint-disable-next-line no-console
|
|
431
434
|
download: { onClick: () => console.log('Download') },
|
|
@@ -454,6 +457,8 @@ describe('Message', () => {
|
|
|
454
457
|
// eslint-disable-next-line no-console
|
|
455
458
|
copy: { onClick: () => console.log('Copy') },
|
|
456
459
|
// eslint-disable-next-line no-console
|
|
460
|
+
edit: { onClick: () => console.log('Edit') },
|
|
461
|
+
// eslint-disable-next-line no-console
|
|
457
462
|
share: { onClick: () => console.log('Share') },
|
|
458
463
|
// eslint-disable-next-line no-console
|
|
459
464
|
download: { onClick: () => console.log('Download') },
|
|
@@ -467,6 +472,36 @@ describe('Message', () => {
|
|
|
467
472
|
expect(screen.queryByRole('button', { name: label })).toBeFalsy();
|
|
468
473
|
});
|
|
469
474
|
});
|
|
475
|
+
it('should not show actions if isEditable is true', async () => {
|
|
476
|
+
render(
|
|
477
|
+
<Message
|
|
478
|
+
avatar="./img"
|
|
479
|
+
role="bot"
|
|
480
|
+
name="Bot"
|
|
481
|
+
content="Hi"
|
|
482
|
+
isEditable
|
|
483
|
+
actions={{
|
|
484
|
+
// eslint-disable-next-line no-console
|
|
485
|
+
positive: { onClick: () => console.log('Good response') },
|
|
486
|
+
// eslint-disable-next-line no-console
|
|
487
|
+
negative: { onClick: () => console.log('Bad response') },
|
|
488
|
+
// eslint-disable-next-line no-console
|
|
489
|
+
copy: { onClick: () => console.log('Copy') },
|
|
490
|
+
// eslint-disable-next-line no-console
|
|
491
|
+
edit: { onClick: () => console.log('Edit') },
|
|
492
|
+
// eslint-disable-next-line no-console
|
|
493
|
+
share: { onClick: () => console.log('Share') },
|
|
494
|
+
// eslint-disable-next-line no-console
|
|
495
|
+
download: { onClick: () => console.log('Download') },
|
|
496
|
+
// eslint-disable-next-line no-console
|
|
497
|
+
listen: { onClick: () => console.log('Listen') }
|
|
498
|
+
}}
|
|
499
|
+
/>
|
|
500
|
+
);
|
|
501
|
+
ALL_ACTIONS.forEach(({ label }) => {
|
|
502
|
+
expect(screen.queryByRole('button', { name: label })).toBeFalsy();
|
|
503
|
+
});
|
|
504
|
+
});
|
|
470
505
|
it('should render unordered lists correctly', () => {
|
|
471
506
|
render(<Message avatar="./img" role="user" name="User" content={UNORDERED_LIST} />);
|
|
472
507
|
expect(screen.getByText('Here is an unordered list:')).toBeTruthy();
|
package/src/Message/Message.tsx
CHANGED
|
@@ -44,7 +44,7 @@ import ImageMessage from './ImageMessage/ImageMessage';
|
|
|
44
44
|
import rehypeUnwrapImages from 'rehype-unwrap-images';
|
|
45
45
|
import rehypeExternalLinks from 'rehype-external-links';
|
|
46
46
|
import rehypeSanitize from 'rehype-sanitize';
|
|
47
|
-
import { PluggableList } from '
|
|
47
|
+
import { PluggableList } from 'unified';
|
|
48
48
|
import LinkMessage from './LinkMessage/LinkMessage';
|
|
49
49
|
import ErrorMessage from './ErrorMessage/ErrorMessage';
|
|
50
50
|
import MessageInput from './MessageInput';
|
|
@@ -99,7 +99,7 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
|
|
|
99
99
|
isLoading?: boolean;
|
|
100
100
|
/** Array of attachments attached to a message */
|
|
101
101
|
attachments?: MessageAttachment[];
|
|
102
|
-
/** Props for message actions, such as feedback (positive or negative), copy button, share, and listen */
|
|
102
|
+
/** Props for message actions, such as feedback (positive or negative), copy button, edit message, share, and listen */
|
|
103
103
|
actions?: {
|
|
104
104
|
[key: string]: ActionProps;
|
|
105
105
|
};
|
|
@@ -179,6 +179,8 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
|
|
|
179
179
|
onEditUpdate?: (event: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
|
180
180
|
/** Callback functionf or when edit cancel update button is clicked */
|
|
181
181
|
onEditCancel?: (event: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
|
182
|
+
/** Ref applied to editable message input */
|
|
183
|
+
inputRef?: Ref<HTMLTextAreaElement>;
|
|
182
184
|
/** Props for edit form */
|
|
183
185
|
editFormProps?: FormProps;
|
|
184
186
|
/** Sets message to compact styling. */
|
|
@@ -219,6 +221,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
|
|
|
219
221
|
cancelWord = 'Cancel',
|
|
220
222
|
onEditUpdate,
|
|
221
223
|
onEditCancel,
|
|
224
|
+
inputRef,
|
|
222
225
|
editFormProps,
|
|
223
226
|
isCompact,
|
|
224
227
|
...props
|
|
@@ -256,7 +259,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
|
|
|
256
259
|
<>
|
|
257
260
|
{beforeMainContent && <>{beforeMainContent}</>}
|
|
258
261
|
<MessageInput
|
|
259
|
-
content={
|
|
262
|
+
content={messageText}
|
|
260
263
|
editPlaceholder={editPlaceholder}
|
|
261
264
|
updateWord={updateWord}
|
|
262
265
|
cancelWord={cancelWord}
|
|
@@ -265,6 +268,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
|
|
|
265
268
|
setMessageText(value);
|
|
266
269
|
}}
|
|
267
270
|
onEditCancel={onEditCancel}
|
|
271
|
+
inputRef={inputRef}
|
|
268
272
|
{...editFormProps}
|
|
269
273
|
/>
|
|
270
274
|
</>
|
|
@@ -369,7 +373,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
|
|
|
369
373
|
isCompact={isCompact}
|
|
370
374
|
/>
|
|
371
375
|
)}
|
|
372
|
-
{!isLoading && actions && <ResponseActions actions={actions} />}
|
|
376
|
+
{!isLoading && !isEditable && actions && <ResponseActions actions={actions} />}
|
|
373
377
|
{userFeedbackForm && <UserFeedback {...userFeedbackForm} timestamp={dateString} isCompact={isCompact} />}
|
|
374
378
|
{userFeedbackComplete && (
|
|
375
379
|
<UserFeedbackComplete {...userFeedbackComplete} timestamp={dateString} isCompact={isCompact} />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// Chatbot Main - Message Input
|
|
3
3
|
// ============================================================================
|
|
4
|
-
import type { FormEvent, FunctionComponent } from 'react';
|
|
4
|
+
import type { FormEvent, FunctionComponent, Ref } from 'react';
|
|
5
5
|
import { useState } from 'react';
|
|
6
6
|
import { ActionGroup, Button, Form, FormProps, TextArea } from '@patternfly/react-core';
|
|
7
7
|
|
|
@@ -16,6 +16,8 @@ export interface MessageInputProps extends FormProps {
|
|
|
16
16
|
onEditUpdate?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, value: string) => void;
|
|
17
17
|
/** Callback functionf or when edit cancel update button is clicked */
|
|
18
18
|
onEditCancel?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
|
19
|
+
/** Ref applied to editable message input */
|
|
20
|
+
inputRef?: Ref<HTMLTextAreaElement>;
|
|
19
21
|
/** Message text */
|
|
20
22
|
content?: string;
|
|
21
23
|
}
|
|
@@ -26,6 +28,7 @@ const MessageInput: FunctionComponent<MessageInputProps> = ({
|
|
|
26
28
|
cancelWord = 'Cancel',
|
|
27
29
|
onEditUpdate,
|
|
28
30
|
onEditCancel,
|
|
31
|
+
inputRef,
|
|
29
32
|
content,
|
|
30
33
|
...props
|
|
31
34
|
}: MessageInputProps) => {
|
|
@@ -43,6 +46,7 @@ const MessageInput: FunctionComponent<MessageInputProps> = ({
|
|
|
43
46
|
onChange={onChange}
|
|
44
47
|
aria-label={editPlaceholder}
|
|
45
48
|
autoResize
|
|
49
|
+
ref={inputRef}
|
|
46
50
|
/>
|
|
47
51
|
<ActionGroup className="pf-chatbot__message-edit-buttons">
|
|
48
52
|
<Button variant="primary" onClick={(event) => onEditUpdate && onEditUpdate(event, messageText)}>
|
|
@@ -6,8 +6,8 @@ import type { Ref, FunctionComponent } from 'react';
|
|
|
6
6
|
import { forwardRef } from 'react';
|
|
7
7
|
|
|
8
8
|
// Import PatternFly components
|
|
9
|
-
import { Button, ButtonProps,
|
|
10
|
-
import { Accept, DropzoneOptions, FileError, FileRejection, useDropzone } from 'react-dropzone';
|
|
9
|
+
import { Button, ButtonProps, Icon, Tooltip, TooltipProps } from '@patternfly/react-core';
|
|
10
|
+
import { Accept, DropEvent, DropzoneOptions, FileError, FileRejection, useDropzone } from 'react-dropzone';
|
|
11
11
|
import { PaperclipIcon } from '@patternfly/react-icons/dist/esm/icons/paperclip-icon';
|
|
12
12
|
|
|
13
13
|
export interface AttachButtonProps extends ButtonProps {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ChangeEvent, FunctionComponent, KeyboardEvent as ReactKeyboardEvent, Ref } from 'react';
|
|
2
2
|
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { Accept, DropzoneOptions, FileError, FileRejection } from 'react-dropzone
|
|
4
|
-
import { ButtonProps,
|
|
3
|
+
import { Accept, DropEvent, DropzoneOptions, FileError, FileRejection } from 'react-dropzone';
|
|
4
|
+
import { ButtonProps, TextArea, TextAreaProps, TooltipProps } from '@patternfly/react-core';
|
|
5
5
|
|
|
6
6
|
// Import Chatbot components
|
|
7
7
|
import SendButton from './SendButton';
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
|
|
14
14
|
&:hover,
|
|
15
15
|
&:focus {
|
|
16
|
-
background-color:
|
|
16
|
+
background-color: rgba(146, 197, 249, 0.25); // --pf-t--global--color--nonstatus--blue--default @ 25%;
|
|
17
17
|
color: var(--pf-t--global--color--brand--hover);
|
|
18
18
|
|
|
19
19
|
.pf-v6-c-button__icon {
|
|
20
|
-
color: var(--pf-t--
|
|
20
|
+
color: var(--pf-t--global--color--brand--hover);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
.pf-v6-c-button.pf-chatbot__button--send:hover,
|
|
39
39
|
.pf-v6-c-button.pf-chatbot__button--send:focus {
|
|
40
|
-
background-color:
|
|
40
|
+
background-color: rgba(146, 197, 249, 0.25); // --pf-t--global--color--nonstatus--blue--default @ 25%;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
border-radius: var(--pf-t--global--border--radius--pill) !important;
|
|
15
15
|
--pf-v6-c-button--MinWidth: 2rem !important;
|
|
16
16
|
background-color: var(--pf-t--global--background--color--primary--default) !important;
|
|
17
|
-
border: 1px solid var(--pf-t--
|
|
17
|
+
border: 1px solid var(--pf-t--global--border--color--default) !important;
|
|
18
18
|
box-shadow: var(--pf-t--global--box-shadow--sm);
|
|
19
19
|
color: var(--pf-t--global--icon--color--subtle) !important;
|
|
20
20
|
transform: translate3d(-50%, 0, 0) !important;
|
|
@@ -310,7 +310,7 @@ export const MessageBox = forwardRef(
|
|
|
310
310
|
role="region"
|
|
311
311
|
tabIndex={0}
|
|
312
312
|
aria-label={ariaLabel}
|
|
313
|
-
className={`pf-chatbot__messagebox ${position === 'bottom'
|
|
313
|
+
className={`pf-chatbot__messagebox ${position === 'bottom' ? 'pf-chatbot__messagebox--bottom' : ''} ${className ?? ''}`}
|
|
314
314
|
ref={messageBoxRef}
|
|
315
315
|
{...props}
|
|
316
316
|
{...(enableSmartScroll ? { ...smartScrollHandlers } : {})}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Chatbot Main - Message Divider
|
|
3
|
+
// ============================================================================
|
|
4
|
+
.pf-chatbot__message-divider {
|
|
5
|
+
display: grid;
|
|
6
|
+
padding-block-end: var(--pf-t--global--spacer--xl);
|
|
7
|
+
|
|
8
|
+
.pf-v6-c-divider,
|
|
9
|
+
.pf-v6-c-label {
|
|
10
|
+
grid-row: 1 / 1;
|
|
11
|
+
grid-column: 1 / 1;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.pf-v6-c-label {
|
|
15
|
+
--pf-v6-c-label--BackgroundColor: var(--pf-t--global--background--color--tertiary--default);
|
|
16
|
+
--pf-v6-c-label--BorderColor: var(--pf-t--global--border--color--default);
|
|
17
|
+
--pf-v6-c-label--PaddingInlineStart: var(--pf-t--global--spacer--action--horizontal--compact);
|
|
18
|
+
--pf-v6-c-label--PaddingInlineEnd: var(--pf-t--global--spacer--action--horizontal--compact);
|
|
19
|
+
|
|
20
|
+
.pf-v6-c-label__text {
|
|
21
|
+
font-weight: var(--pf-t--global--font--weight--body--bold);
|
|
22
|
+
text-align: center;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&.pf-m-divider {
|
|
27
|
+
.pf-v6-c-label {
|
|
28
|
+
--pf-v6-c-label--BackgroundColor: var(--pf-t--global--background--color--secondary--default);
|
|
29
|
+
--pf-v6-c-label--MaxWidth: 75%;
|
|
30
|
+
|
|
31
|
+
justify-self: center;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.pf-v6-c-divider {
|
|
35
|
+
align-self: center;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&.pf-m-wrap {
|
|
40
|
+
.pf-v6-c-label,
|
|
41
|
+
.pf-v6-c-label__text {
|
|
42
|
+
white-space: normal;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
import MessageDivider from './MessageDivider';
|
|
4
|
+
|
|
5
|
+
describe('MessageDivider', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
jest.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
it('should render default correctly with variant = date and content = new Date().toLocaleDateString()', () => {
|
|
10
|
+
render(<MessageDivider data-testid="message-divider" />);
|
|
11
|
+
expect(screen.getByText(new Date().toLocaleDateString())).toBeInTheDocument();
|
|
12
|
+
expect(screen.getByTestId('message-divider')).toHaveClass('pf-m-divider');
|
|
13
|
+
});
|
|
14
|
+
it('should render inset variant correctly', () => {
|
|
15
|
+
render(<MessageDivider variant="inset" content="test" data-testid="message-divider" />);
|
|
16
|
+
expect(screen.getByText('test')).toBeInTheDocument();
|
|
17
|
+
expect(screen.getByTestId('message-divider')).toHaveClass('pf-m-divider');
|
|
18
|
+
});
|
|
19
|
+
it('should render fullWidth variant correctly', () => {
|
|
20
|
+
render(<MessageDivider variant="fullWidth" content="test" data-testid="message-divider" />);
|
|
21
|
+
expect(screen.getByText('test')).toBeInTheDocument();
|
|
22
|
+
expect(screen.getByTestId('message-divider')).not.toHaveClass('pf-m-divider');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Chatbot Main - Message Divider
|
|
3
|
+
// ============================================================================
|
|
4
|
+
import type { FunctionComponent } from 'react';
|
|
5
|
+
import { Divider, Label } from '@patternfly/react-core';
|
|
6
|
+
|
|
7
|
+
export interface MessageDividerProps {
|
|
8
|
+
/** Variant of the divider */
|
|
9
|
+
variant?: 'inset' | 'fullWidth';
|
|
10
|
+
/** Content of the message divider */
|
|
11
|
+
content?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const MessageDivider: FunctionComponent<MessageDividerProps> = ({
|
|
15
|
+
variant = 'inset',
|
|
16
|
+
content = new Date().toLocaleDateString(),
|
|
17
|
+
...props
|
|
18
|
+
}: MessageDividerProps) => {
|
|
19
|
+
if (variant === 'inset') {
|
|
20
|
+
return (
|
|
21
|
+
<div className="pf-chatbot__message-divider pf-m-divider pf-m-wrap" {...props}>
|
|
22
|
+
<Divider />
|
|
23
|
+
<Label variant="outline">{content}</Label>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="pf-chatbot__message-divider pf-m-wrap" {...props}>
|
|
30
|
+
<Label>{content}</Label>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default MessageDivider;
|
|
@@ -9,6 +9,7 @@ const ALL_ACTIONS = [
|
|
|
9
9
|
{ type: 'positive', label: 'Good response', clickedLabel: 'Response recorded' },
|
|
10
10
|
{ type: 'negative', label: 'Bad response', clickedLabel: 'Response recorded' },
|
|
11
11
|
{ type: 'copy', label: 'Copy', clickedLabel: 'Copied' },
|
|
12
|
+
{ type: 'edit', label: 'Edit', clickedLabel: 'Editing' },
|
|
12
13
|
{ type: 'share', label: 'Share', clickedLabel: 'Shared' },
|
|
13
14
|
{ type: 'listen', label: 'Listen', clickedLabel: 'Listening' }
|
|
14
15
|
];
|
|
@@ -44,6 +45,7 @@ const ALL_ACTIONS_DATA_TEST = [
|
|
|
44
45
|
{ type: 'positive', label: 'Good response', dataTestId: 'positive' },
|
|
45
46
|
{ type: 'negative', label: 'Bad response', dataTestId: 'negative' },
|
|
46
47
|
{ type: 'copy', label: 'Copy', dataTestId: 'copy' },
|
|
48
|
+
{ type: 'edit', label: 'Edit', dataTestId: 'edit' },
|
|
47
49
|
{ type: 'share', label: 'Share', dataTestId: 'share' },
|
|
48
50
|
{ type: 'download', label: 'Download', dataTestId: 'download' },
|
|
49
51
|
{ type: 'listen', label: 'Listen', dataTestId: 'listen' }
|
|
@@ -60,6 +62,7 @@ describe('ResponseActions', () => {
|
|
|
60
62
|
positive: { onClick: jest.fn() },
|
|
61
63
|
negative: { onClick: jest.fn() },
|
|
62
64
|
copy: { onClick: jest.fn() },
|
|
65
|
+
edit: { onClick: jest.fn() },
|
|
63
66
|
share: { onClick: jest.fn() },
|
|
64
67
|
download: { onClick: jest.fn() },
|
|
65
68
|
listen: { onClick: jest.fn() }
|
|
@@ -69,10 +72,11 @@ describe('ResponseActions', () => {
|
|
|
69
72
|
const goodBtn = screen.getByRole('button', { name: 'Good response' });
|
|
70
73
|
const badBtn = screen.getByRole('button', { name: 'Bad response' });
|
|
71
74
|
const copyBtn = screen.getByRole('button', { name: 'Copy' });
|
|
75
|
+
const editBtn = screen.getByRole('button', { name: 'Edit' });
|
|
72
76
|
const shareBtn = screen.getByRole('button', { name: 'Share' });
|
|
73
77
|
const downloadBtn = screen.getByRole('button', { name: 'Download' });
|
|
74
78
|
const listenBtn = screen.getByRole('button', { name: 'Listen' });
|
|
75
|
-
const buttons = [goodBtn, badBtn, copyBtn, shareBtn, downloadBtn, listenBtn];
|
|
79
|
+
const buttons = [goodBtn, badBtn, copyBtn, editBtn, shareBtn, downloadBtn, listenBtn];
|
|
76
80
|
buttons.forEach((button) => {
|
|
77
81
|
expect(button).toBeTruthy();
|
|
78
82
|
});
|
|
@@ -265,6 +269,7 @@ describe('ResponseActions', () => {
|
|
|
265
269
|
{ type: 'positive', ariaLabel: 'Thumbs up' },
|
|
266
270
|
{ type: 'negative', ariaLabel: 'Thumbs down' },
|
|
267
271
|
{ type: 'copy', ariaLabel: 'Copy the message' },
|
|
272
|
+
{ type: 'edit', ariaLabel: 'Edit this message' },
|
|
268
273
|
{ type: 'share', ariaLabel: 'Share it with friends' },
|
|
269
274
|
{ type: 'download', ariaLabel: 'Download your cool message' },
|
|
270
275
|
{ type: 'listen', ariaLabel: 'Listen up' }
|