@patternfly/chatbot 2.2.1 → 6.3.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 +4 -0
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +7 -1
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +23 -0
- package/dist/cjs/Message/Message.d.ts +17 -1
- package/dist/cjs/Message/Message.js +53 -34
- package/dist/cjs/Message/Message.test.js +52 -0
- package/dist/cjs/Message/MessageInput.d.ts +18 -0
- package/dist/cjs/Message/MessageInput.js +34 -0
- package/dist/cjs/MessageBar/MessageBar.d.ts +4 -0
- package/dist/cjs/MessageBar/MessageBar.js +2 -2
- package/dist/cjs/MessageBar/MessageBar.test.js +13 -0
- package/dist/cjs/MessageBar/MicrophoneButton.js +1 -1
- package/dist/cjs/MessageBox/MessageBox.js +5 -5
- package/dist/cjs/SourcesCard/SourcesCard.d.ts +7 -1
- package/dist/cjs/SourcesCard/SourcesCard.js +16 -10
- package/dist/cjs/SourcesCard/SourcesCard.test.js +25 -15
- package/dist/cjs/tracking/console_tracking_provider.d.ts +4 -5
- package/dist/cjs/tracking/console_tracking_provider.js +22 -15
- package/dist/cjs/tracking/posthog_tracking_provider.d.ts +2 -2
- package/dist/cjs/tracking/posthog_tracking_provider.js +21 -12
- package/dist/cjs/tracking/segment_tracking_provider.d.ts +2 -2
- package/dist/cjs/tracking/segment_tracking_provider.js +21 -12
- package/dist/cjs/tracking/trackingProviderProxy.d.ts +1 -1
- package/dist/cjs/tracking/trackingProviderProxy.js +2 -2
- package/dist/cjs/tracking/tracking_api.d.ts +1 -1
- package/dist/cjs/tracking/tracking_registry.js +46 -12
- package/dist/cjs/tracking/tracking_spi.d.ts +15 -5
- package/dist/cjs/tracking/tracking_spi.js +9 -0
- package/dist/cjs/tracking/umami_tracking_provider.d.ts +6 -2
- package/dist/cjs/tracking/umami_tracking_provider.js +66 -22
- package/dist/css/main.css +7 -7
- package/dist/css/main.css.map +1 -1
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +4 -0
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +7 -1
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +23 -0
- package/dist/esm/Message/Message.d.ts +17 -1
- package/dist/esm/Message/Message.js +53 -34
- package/dist/esm/Message/Message.test.js +52 -0
- package/dist/esm/Message/MessageInput.d.ts +18 -0
- package/dist/esm/Message/MessageInput.js +29 -0
- package/dist/esm/MessageBar/MessageBar.d.ts +4 -0
- package/dist/esm/MessageBar/MessageBar.js +2 -2
- package/dist/esm/MessageBar/MessageBar.test.js +13 -0
- package/dist/esm/MessageBar/MicrophoneButton.js +1 -1
- package/dist/esm/MessageBox/MessageBox.js +5 -5
- package/dist/esm/SourcesCard/SourcesCard.d.ts +7 -1
- package/dist/esm/SourcesCard/SourcesCard.js +17 -11
- package/dist/esm/SourcesCard/SourcesCard.test.js +25 -15
- package/dist/esm/tracking/console_tracking_provider.d.ts +4 -5
- package/dist/esm/tracking/console_tracking_provider.js +22 -15
- package/dist/esm/tracking/posthog_tracking_provider.d.ts +2 -2
- package/dist/esm/tracking/posthog_tracking_provider.js +21 -12
- package/dist/esm/tracking/segment_tracking_provider.d.ts +2 -2
- package/dist/esm/tracking/segment_tracking_provider.js +21 -12
- package/dist/esm/tracking/trackingProviderProxy.d.ts +1 -1
- package/dist/esm/tracking/trackingProviderProxy.js +2 -2
- package/dist/esm/tracking/tracking_api.d.ts +1 -1
- package/dist/esm/tracking/tracking_registry.js +46 -12
- package/dist/esm/tracking/tracking_spi.d.ts +15 -5
- package/dist/esm/tracking/tracking_spi.js +8 -1
- package/dist/esm/tracking/umami_tracking_provider.d.ts +6 -2
- package/dist/esm/tracking/umami_tracking_provider.js +66 -22
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Analytics/Analytics.md +18 -14
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx +74 -104
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/FileDetailsLabel.tsx +48 -37
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithQuickResponses.tsx +10 -0
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithSources.tsx +51 -14
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +3 -1
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +80 -104
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +35 -2
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerResizable.tsx +13 -2
- package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.tsx +6 -3
- package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotAttachment.tsx +2 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotAttachmentMenu.tsx +2 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotInDrawer.tsx +2 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/EmbeddedChatbot.tsx +2 -0
- package/patternfly-docs/content/extensions/chatbot/examples/demos/EmbeddedComparisonChatbot.tsx +62 -57
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Feedback.tsx +2 -0
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +53 -0
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +14 -0
- package/src/Message/Message.scss +4 -0
- package/src/Message/Message.test.tsx +62 -0
- package/src/Message/Message.tsx +111 -53
- package/src/Message/MessageInput.tsx +59 -0
- package/src/MessageBar/MessageBar.test.tsx +13 -0
- package/src/MessageBar/MessageBar.tsx +8 -2
- package/src/MessageBar/MicrophoneButton.tsx +1 -1
- package/src/MessageBox/MessageBox.tsx +5 -5
- package/src/SourcesCard/SourcesCard.scss +3 -7
- package/src/SourcesCard/SourcesCard.test.tsx +30 -22
- package/src/SourcesCard/SourcesCard.tsx +54 -12
- package/src/tracking/console_tracking_provider.ts +21 -17
- package/src/tracking/posthog_tracking_provider.ts +20 -13
- package/src/tracking/segment_tracking_provider.ts +20 -13
- package/src/tracking/trackingProviderProxy.ts +2 -2
- package/src/tracking/tracking_api.ts +1 -1
- package/src/tracking/tracking_registry.ts +46 -13
- package/src/tracking/tracking_spi.ts +18 -7
- package/src/tracking/umami_tracking_provider.ts +76 -20
- package/src/SourcesCard/__snapshots__/SourcesCard.test.tsx.snap +0 -34
@@ -76,6 +76,10 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
|
|
76
76
|
loadingState?: SkeletonProps;
|
77
77
|
/** Content to show in error state. Error state will appear once content is passed in. */
|
78
78
|
errorState?: HistoryEmptyStateProps;
|
79
|
+
/** Content to show in empty state. Empty state will appear once content is passed in. */
|
80
|
+
emptyState?: HistoryEmptyStateProps;
|
81
|
+
/** Content to show in no results state. No results state will appear once content is passed in. */
|
82
|
+
noResultsState?: HistoryEmptyStateProps;
|
79
83
|
}
|
80
84
|
export declare const ChatbotConversationHistoryNav: React.FunctionComponent<ChatbotConversationHistoryNavProps>;
|
81
85
|
export default ChatbotConversationHistoryNav;
|
@@ -27,7 +27,7 @@ const ChatbotConversationHistoryDropdown_1 = __importDefault(require("./ChatbotC
|
|
27
27
|
const LoadingState_1 = __importDefault(require("./LoadingState"));
|
28
28
|
const EmptyState_1 = __importDefault(require("./EmptyState"));
|
29
29
|
const ChatbotConversationHistoryNav = (_a) => {
|
30
|
-
var { onDrawerToggle, isDrawerOpen, setIsDrawerOpen, activeItemId, onSelectActiveItem, conversations, newChatButtonText = 'New chat', drawerContent, onNewChat, searchInputPlaceholder = 'Search previous conversations...', searchInputAriaLabel = 'Filter menu items', handleTextInputChange, displayMode, reverseButtonOrder = false, drawerActionsTestId = 'chatbot-nav-drawer-actions', menuProps, drawerPanelContentProps, drawerContentProps, drawerContentBodyProps, drawerHeadProps, drawerActionsProps, drawerCloseButtonProps, drawerPanelBodyProps, isLoading, loadingState, errorState } = _a, props = __rest(_a, ["onDrawerToggle", "isDrawerOpen", "setIsDrawerOpen", "activeItemId", "onSelectActiveItem", "conversations", "newChatButtonText", "drawerContent", "onNewChat", "searchInputPlaceholder", "searchInputAriaLabel", "handleTextInputChange", "displayMode", "reverseButtonOrder", "drawerActionsTestId", "menuProps", "drawerPanelContentProps", "drawerContentProps", "drawerContentBodyProps", "drawerHeadProps", "drawerActionsProps", "drawerCloseButtonProps", "drawerPanelBodyProps", "isLoading", "loadingState", "errorState"]);
|
30
|
+
var { onDrawerToggle, isDrawerOpen, setIsDrawerOpen, activeItemId, onSelectActiveItem, conversations, newChatButtonText = 'New chat', drawerContent, onNewChat, searchInputPlaceholder = 'Search previous conversations...', searchInputAriaLabel = 'Filter menu items', handleTextInputChange, displayMode, reverseButtonOrder = false, drawerActionsTestId = 'chatbot-nav-drawer-actions', menuProps, drawerPanelContentProps, drawerContentProps, drawerContentBodyProps, drawerHeadProps, drawerActionsProps, drawerCloseButtonProps, drawerPanelBodyProps, isLoading, loadingState, errorState, emptyState, noResultsState } = _a, props = __rest(_a, ["onDrawerToggle", "isDrawerOpen", "setIsDrawerOpen", "activeItemId", "onSelectActiveItem", "conversations", "newChatButtonText", "drawerContent", "onNewChat", "searchInputPlaceholder", "searchInputAriaLabel", "handleTextInputChange", "displayMode", "reverseButtonOrder", "drawerActionsTestId", "menuProps", "drawerPanelContentProps", "drawerContentProps", "drawerContentBodyProps", "drawerHeadProps", "drawerActionsProps", "drawerCloseButtonProps", "drawerPanelBodyProps", "isLoading", "loadingState", "errorState", "emptyState", "noResultsState"]);
|
31
31
|
const drawerRef = react_1.default.useRef(null);
|
32
32
|
const onExpand = () => {
|
33
33
|
drawerRef.current && drawerRef.current.focus();
|
@@ -58,6 +58,12 @@ const ChatbotConversationHistoryNav = (_a) => {
|
|
58
58
|
if (errorState) {
|
59
59
|
return react_1.default.createElement(EmptyState_1.default, Object.assign({}, errorState));
|
60
60
|
}
|
61
|
+
if (emptyState) {
|
62
|
+
return react_1.default.createElement(EmptyState_1.default, Object.assign({}, emptyState));
|
63
|
+
}
|
64
|
+
if (noResultsState) {
|
65
|
+
return react_1.default.createElement(EmptyState_1.default, Object.assign({}, noResultsState));
|
66
|
+
}
|
61
67
|
return (react_1.default.createElement(react_core_1.Menu, Object.assign({ isPlain: true, onSelect: onSelectActiveItem, activeItemId: activeItemId }, menuProps),
|
62
68
|
react_1.default.createElement(react_core_1.MenuContent, null, buildMenu())));
|
63
69
|
};
|
@@ -18,6 +18,7 @@ const react_2 = require("@testing-library/react");
|
|
18
18
|
const Chatbot_1 = require("../Chatbot/Chatbot");
|
19
19
|
const ChatbotConversationHistoryNav_1 = __importDefault(require("./ChatbotConversationHistoryNav"));
|
20
20
|
const react_core_1 = require("@patternfly/react-core");
|
21
|
+
const react_icons_1 = require("@patternfly/react-icons");
|
21
22
|
const ERROR = {
|
22
23
|
bodyText: (react_1.default.createElement(react_1.default.Fragment, null,
|
23
24
|
"To try again, check your connection and reload this page. If the issue persists,",
|
@@ -31,6 +32,16 @@ const ERROR = {
|
|
31
32
|
status: react_core_1.EmptyStateStatus.danger,
|
32
33
|
onClick: () => alert('Clicked Reload')
|
33
34
|
};
|
35
|
+
const NO_RESULTS = {
|
36
|
+
bodyText: 'Adjust your search query and try again. Check your spelling or try a more general term.',
|
37
|
+
titleText: 'No results found',
|
38
|
+
icon: react_icons_1.SearchIcon
|
39
|
+
};
|
40
|
+
const EMPTY_STATE = {
|
41
|
+
bodyText: 'Access timely assistance by starting a conversation with an AI model.',
|
42
|
+
titleText: 'Start a new chat',
|
43
|
+
icon: react_icons_1.OutlinedCommentsIcon
|
44
|
+
};
|
34
45
|
const ERROR_WITHOUT_BUTTON = {
|
35
46
|
bodyText: (react_1.default.createElement(react_1.default.Fragment, null,
|
36
47
|
"To try again, check your connection and reload this page. If the issue persists,",
|
@@ -162,4 +173,16 @@ describe('ChatbotConversationHistoryNav', () => {
|
|
162
173
|
(0, react_2.render)(react_1.default.createElement(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), reverseButtonOrder: false, handleTextInputChange: jest.fn(), conversations: initialConversations, isLoading: true, errorState: ERROR }));
|
163
174
|
expect(react_2.screen.getByRole('dialog', { name: /Loading/i })).toBeTruthy();
|
164
175
|
});
|
176
|
+
it('should accept emptyState', () => {
|
177
|
+
(0, react_2.render)(react_1.default.createElement(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), reverseButtonOrder: false, handleTextInputChange: jest.fn(), conversations: initialConversations, emptyState: EMPTY_STATE }));
|
178
|
+
expect(react_2.screen.getByRole('dialog', {
|
179
|
+
name: /Start a new chat Access timely assistance by starting a conversation with an AI model./i
|
180
|
+
})).toBeTruthy();
|
181
|
+
});
|
182
|
+
it('should accept no results state', () => {
|
183
|
+
(0, react_2.render)(react_1.default.createElement(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), reverseButtonOrder: false, handleTextInputChange: jest.fn(), conversations: initialConversations, noResultsState: NO_RESULTS }));
|
184
|
+
expect(react_2.screen.getByRole('dialog', {
|
185
|
+
name: /No results found Adjust your search query and try again. Check your spelling or try a more general term./i
|
186
|
+
})).toBeTruthy();
|
187
|
+
});
|
165
188
|
});
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import React, { ReactNode } from 'react';
|
2
|
-
import { AlertProps, AvatarProps, LabelGroupProps } from '@patternfly/react-core';
|
2
|
+
import { AlertProps, AvatarProps, ButtonProps, FormProps, LabelGroupProps } from '@patternfly/react-core';
|
3
3
|
import { ActionProps } from '../ResponseActions/ResponseActions';
|
4
4
|
import { SourcesCardProps } from '../SourcesCard';
|
5
5
|
import { QuickStart, QuickstartAction } from './QuickStarts/types';
|
@@ -104,6 +104,22 @@ export interface MessageProps extends Omit<React.HTMLProps<HTMLDivElement>, 'rol
|
|
104
104
|
openLinkInNewTab?: boolean;
|
105
105
|
/** Optional inline error message that can be displayed in the message */
|
106
106
|
error?: AlertProps;
|
107
|
+
/** Props for links */
|
108
|
+
linkProps?: ButtonProps;
|
109
|
+
/** Whether message is in edit mode */
|
110
|
+
isEditable?: boolean;
|
111
|
+
/** Placeholder for edit input */
|
112
|
+
editPlaceholder?: string;
|
113
|
+
/** Label for the English word "Update" used in edit mode. */
|
114
|
+
updateWord?: string;
|
115
|
+
/** Label for the English word "Cancel" used in edit mode. */
|
116
|
+
cancelWord?: string;
|
117
|
+
/** Callback function for when edit mode update button is clicked */
|
118
|
+
onEditUpdate?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
119
|
+
/** Callback functionf or when edit cancel update button is clicked */
|
120
|
+
onEditCancel?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
121
|
+
/** Props for edit form */
|
122
|
+
editFormProps?: FormProps;
|
107
123
|
}
|
108
124
|
export declare const MessageBase: React.FunctionComponent<MessageProps>;
|
109
125
|
declare const Message: React.ForwardRefExoticComponent<Omit<MessageProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
@@ -47,8 +47,13 @@ const rehype_external_links_1 = __importDefault(require("rehype-external-links")
|
|
47
47
|
const rehype_sanitize_1 = __importDefault(require("rehype-sanitize"));
|
48
48
|
const LinkMessage_1 = __importDefault(require("./LinkMessage/LinkMessage"));
|
49
49
|
const ErrorMessage_1 = __importDefault(require("./ErrorMessage/ErrorMessage"));
|
50
|
+
const MessageInput_1 = __importDefault(require("./MessageInput"));
|
50
51
|
const MessageBase = (_a) => {
|
51
|
-
var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], error } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "error"]);
|
52
|
+
var { role, content, extraContent, name, avatar, timestamp, isLoading, actions, sources, botWord = 'AI', loadingWord = 'Loading message', codeBlockProps, quickResponses, quickResponseContainerProps = { numLabels: 5 }, attachments, hasRoundAvatar = true, avatarProps, quickStarts, userFeedbackForm, userFeedbackComplete, isLiveRegion = true, innerRef, tableProps, openLinkInNewTab = true, additionalRehypePlugins = [], linkProps, error, isEditable, editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, editFormProps } = _a, props = __rest(_a, ["role", "content", "extraContent", "name", "avatar", "timestamp", "isLoading", "actions", "sources", "botWord", "loadingWord", "codeBlockProps", "quickResponses", "quickResponseContainerProps", "attachments", "hasRoundAvatar", "avatarProps", "quickStarts", "userFeedbackForm", "userFeedbackComplete", "isLiveRegion", "innerRef", "tableProps", "openLinkInNewTab", "additionalRehypePlugins", "linkProps", "error", "isEditable", "editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "editFormProps"]);
|
53
|
+
const [messageText, setMessageText] = react_1.default.useState(content);
|
54
|
+
react_1.default.useEffect(() => {
|
55
|
+
setMessageText(content);
|
56
|
+
}, [content]);
|
52
57
|
const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
|
53
58
|
let rehypePlugins = [rehype_unwrap_images_1.default];
|
54
59
|
if (openLinkInNewTab) {
|
@@ -66,6 +71,51 @@ const MessageBase = (_a) => {
|
|
66
71
|
// Keep timestamps consistent between Timestamp component and aria-label
|
67
72
|
const date = new Date();
|
68
73
|
const dateString = timestamp !== null && timestamp !== void 0 ? timestamp : `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
74
|
+
const renderMessage = () => {
|
75
|
+
if (isLoading) {
|
76
|
+
return react_1.default.createElement(MessageLoading_1.default, { loadingWord: loadingWord });
|
77
|
+
}
|
78
|
+
if (isEditable) {
|
79
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
80
|
+
beforeMainContent && react_1.default.createElement(react_1.default.Fragment, null, beforeMainContent),
|
81
|
+
react_1.default.createElement(MessageInput_1.default, Object.assign({ content: content, editPlaceholder: editPlaceholder, updateWord: updateWord, cancelWord: cancelWord, onEditUpdate: (event, text) => {
|
82
|
+
onEditUpdate && onEditUpdate(event);
|
83
|
+
setMessageText(text);
|
84
|
+
}, onEditCancel: onEditCancel }, editFormProps))));
|
85
|
+
}
|
86
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
87
|
+
beforeMainContent && react_1.default.createElement(react_1.default.Fragment, null, beforeMainContent),
|
88
|
+
error ? (react_1.default.createElement(ErrorMessage_1.default, Object.assign({}, error))) : (react_1.default.createElement(react_markdown_1.default, { components: {
|
89
|
+
p: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.p }, props)),
|
90
|
+
code: (_a) => {
|
91
|
+
var { children } = _a, props = __rest(_a, ["children"]);
|
92
|
+
return (react_1.default.createElement(CodeBlockMessage_1.default, Object.assign({}, props, codeBlockProps), children));
|
93
|
+
},
|
94
|
+
h1: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h1 }, props)),
|
95
|
+
h2: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h2 }, props)),
|
96
|
+
h3: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h3 }, props)),
|
97
|
+
h4: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h4 }, props)),
|
98
|
+
h5: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h5 }, props)),
|
99
|
+
h6: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h6 }, props)),
|
100
|
+
blockquote: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.blockquote }, props)),
|
101
|
+
ul: (props) => react_1.default.createElement(UnorderedListMessage_1.default, Object.assign({}, props)),
|
102
|
+
ol: (props) => react_1.default.createElement(OrderedListMessage_1.default, Object.assign({}, props)),
|
103
|
+
li: (props) => react_1.default.createElement(ListItemMessage_1.default, Object.assign({}, props)),
|
104
|
+
table: (props) => react_1.default.createElement(TableMessage_1.default, Object.assign({}, props, tableProps)),
|
105
|
+
tbody: (props) => react_1.default.createElement(TbodyMessage_1.default, Object.assign({}, props)),
|
106
|
+
thead: (props) => react_1.default.createElement(TheadMessage_1.default, Object.assign({}, props)),
|
107
|
+
tr: (props) => react_1.default.createElement(TrMessage_1.default, Object.assign({}, props)),
|
108
|
+
td: (props) => {
|
109
|
+
// Conflicts with Td type
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
111
|
+
const { width } = props, rest = __rest(props, ["width"]);
|
112
|
+
return react_1.default.createElement(TdMessage_1.default, Object.assign({}, rest));
|
113
|
+
},
|
114
|
+
th: (props) => react_1.default.createElement(ThMessage_1.default, Object.assign({}, props)),
|
115
|
+
img: (props) => react_1.default.createElement(ImageMessage_1.default, Object.assign({}, props)),
|
116
|
+
a: (props) => (react_1.default.createElement(LinkMessage_1.default, Object.assign({ href: props.href, rel: props.rel, target: props.target }, linkProps), props.children))
|
117
|
+
}, remarkPlugins: [remark_gfm_1.default], rehypePlugins: rehypePlugins }, messageText))));
|
118
|
+
};
|
69
119
|
return (react_1.default.createElement("section", Object.assign({ "aria-label": `Message from ${role} - ${dateString}`, className: `pf-chatbot__message pf-chatbot__message--${role}`, "aria-live": isLiveRegion ? 'polite' : undefined, "aria-atomic": isLiveRegion ? false : undefined, ref: innerRef }, props),
|
70
120
|
react_1.default.createElement(react_core_1.Avatar, Object.assign({ className: `pf-chatbot__message-avatar ${hasRoundAvatar ? 'pf-chatbot__message-avatar--round' : ''} ${avatarClassName ? avatarClassName : ''}`, src: avatar, alt: "" }, avatarProps)),
|
71
121
|
react_1.default.createElement("div", { className: "pf-chatbot__message-contents" },
|
@@ -76,39 +126,8 @@ const MessageBase = (_a) => {
|
|
76
126
|
react_1.default.createElement(react_core_1.Timestamp, { date: date }, timestamp)),
|
77
127
|
react_1.default.createElement("div", { className: "pf-chatbot__message-response" },
|
78
128
|
react_1.default.createElement("div", { className: "pf-chatbot__message-and-actions" },
|
79
|
-
|
80
|
-
|
81
|
-
error ? (react_1.default.createElement(ErrorMessage_1.default, Object.assign({}, error))) : (react_1.default.createElement(react_markdown_1.default, { components: {
|
82
|
-
p: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.p }, props)),
|
83
|
-
code: (_a) => {
|
84
|
-
var { children } = _a, props = __rest(_a, ["children"]);
|
85
|
-
return (react_1.default.createElement(CodeBlockMessage_1.default, Object.assign({}, props, codeBlockProps), children));
|
86
|
-
},
|
87
|
-
h1: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h1 }, props)),
|
88
|
-
h2: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h2 }, props)),
|
89
|
-
h3: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h3 }, props)),
|
90
|
-
h4: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h4 }, props)),
|
91
|
-
h5: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h5 }, props)),
|
92
|
-
h6: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.h6 }, props)),
|
93
|
-
blockquote: (props) => react_1.default.createElement(TextMessage_1.default, Object.assign({ component: react_core_1.ContentVariants.blockquote }, props)),
|
94
|
-
ul: (props) => react_1.default.createElement(UnorderedListMessage_1.default, Object.assign({}, props)),
|
95
|
-
ol: (props) => react_1.default.createElement(OrderedListMessage_1.default, Object.assign({}, props)),
|
96
|
-
li: (props) => react_1.default.createElement(ListItemMessage_1.default, Object.assign({}, props)),
|
97
|
-
table: (props) => react_1.default.createElement(TableMessage_1.default, Object.assign({}, props, tableProps)),
|
98
|
-
tbody: (props) => react_1.default.createElement(TbodyMessage_1.default, Object.assign({}, props)),
|
99
|
-
thead: (props) => react_1.default.createElement(TheadMessage_1.default, Object.assign({}, props)),
|
100
|
-
tr: (props) => react_1.default.createElement(TrMessage_1.default, Object.assign({}, props)),
|
101
|
-
td: (props) => {
|
102
|
-
// Conflicts with Td type
|
103
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
104
|
-
const { width } = props, rest = __rest(props, ["width"]);
|
105
|
-
return react_1.default.createElement(TdMessage_1.default, Object.assign({}, rest));
|
106
|
-
},
|
107
|
-
th: (props) => react_1.default.createElement(ThMessage_1.default, Object.assign({}, props)),
|
108
|
-
img: (props) => react_1.default.createElement(ImageMessage_1.default, Object.assign({}, props)),
|
109
|
-
a: (props) => (react_1.default.createElement(LinkMessage_1.default, { href: props.href, rel: props.rel, target: props.target }, props.children))
|
110
|
-
}, remarkPlugins: [remark_gfm_1.default], rehypePlugins: rehypePlugins }, content)),
|
111
|
-
afterMainContent && react_1.default.createElement(react_1.default.Fragment, null, afterMainContent))),
|
129
|
+
renderMessage(),
|
130
|
+
afterMainContent && react_1.default.createElement(react_1.default.Fragment, null, afterMainContent),
|
112
131
|
!isLoading && sources && react_1.default.createElement(SourcesCard_1.default, Object.assign({}, sources)),
|
113
132
|
quickStarts && quickStarts.quickStart && (react_1.default.createElement(QuickStartTile_1.default, { quickStart: quickStarts.quickStart, onSelectQuickStart: quickStarts.onSelectQuickStart, minuteWord: quickStarts.minuteWord, minuteWordPlural: quickStarts.minuteWordPlural, prerequisiteWord: quickStarts.prerequisiteWord, prerequisiteWordPlural: quickStarts.prerequisiteWordPlural, quickStartButtonAriaLabel: quickStarts.quickStartButtonAriaLabel })),
|
114
133
|
!isLoading && actions && react_1.default.createElement(ResponseActions_1.default, { actions: actions }),
|
@@ -602,6 +602,12 @@ describe('Message', () => {
|
|
602
602
|
// we are mocking rehype libraries, so we can't test target _blank addition on links directly with RTL
|
603
603
|
expect(rehype_external_links_1.default).not.toHaveBeenCalled();
|
604
604
|
});
|
605
|
+
it('should handle extra link props correctly', () => __awaiter(void 0, void 0, void 0, function* () {
|
606
|
+
const spy = jest.fn();
|
607
|
+
(0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", content: `[PatternFly](https://www.patternfly.org/)`, linkProps: { onClick: spy } }));
|
608
|
+
yield user_event_1.default.click(react_2.screen.getByRole('link', { name: /PatternFly/i }));
|
609
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
610
|
+
}));
|
605
611
|
it('should handle error correctly', () => {
|
606
612
|
(0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", error: ERROR }));
|
607
613
|
expect(react_2.screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
|
@@ -619,4 +625,50 @@ describe('Message', () => {
|
|
619
625
|
expect(react_2.screen.getByRole('heading', { name: /Could not load chat/i })).toBeTruthy();
|
620
626
|
expect(react_2.screen.queryByText('Test')).toBeFalsy();
|
621
627
|
});
|
628
|
+
it('should handle isEditable when there is message content', () => {
|
629
|
+
(0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", isEditable: true, content: "Test" }));
|
630
|
+
expect(react_2.screen.getByRole('textbox')).toBeTruthy();
|
631
|
+
expect(react_2.screen.getByRole('textbox')).toHaveValue('Test');
|
632
|
+
expect(react_2.screen.getByRole('button', { name: /Update/i })).toBeTruthy();
|
633
|
+
expect(react_2.screen.getByRole('button', { name: /Cancel/i })).toBeTruthy();
|
634
|
+
});
|
635
|
+
it('should handle isEditable when there is no message content', () => {
|
636
|
+
(0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", isEditable: true }));
|
637
|
+
expect(react_2.screen.getByRole('textbox')).toBeTruthy();
|
638
|
+
expect(react_2.screen.getByRole('textbox')).toHaveValue('');
|
639
|
+
expect(react_2.screen.getByRole('textbox')).toHaveAttribute('placeholder', 'Edit prompt message...');
|
640
|
+
expect(react_2.screen.getByRole('button', { name: /Update/i })).toBeTruthy();
|
641
|
+
expect(react_2.screen.getByRole('button', { name: /Cancel/i })).toBeTruthy();
|
642
|
+
});
|
643
|
+
it('should be able to change edit placeholder', () => {
|
644
|
+
(0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", isEditable: true, editPlaceholder: "I am a placeholder" }));
|
645
|
+
expect(react_2.screen.getByRole('textbox')).toBeTruthy();
|
646
|
+
expect(react_2.screen.getByRole('textbox')).toHaveValue('');
|
647
|
+
expect(react_2.screen.getByRole('textbox')).toHaveAttribute('placeholder', 'I am a placeholder');
|
648
|
+
});
|
649
|
+
it('should be able to change updateWord', () => {
|
650
|
+
(0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", isEditable: true, updateWord: "Submit" }));
|
651
|
+
expect(react_2.screen.getByRole('button', { name: /Submit/i })).toBeTruthy();
|
652
|
+
});
|
653
|
+
it('should be able to change cancelWord', () => {
|
654
|
+
(0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", isEditable: true, cancelWord: "Don't submit" }));
|
655
|
+
expect(react_2.screen.getByRole('button', { name: /Don't submit/i })).toBeTruthy();
|
656
|
+
});
|
657
|
+
it('should be able to add onEditUpdate', () => __awaiter(void 0, void 0, void 0, function* () {
|
658
|
+
const spy = jest.fn();
|
659
|
+
(0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", isEditable: true, onEditUpdate: spy }));
|
660
|
+
yield user_event_1.default.click(react_2.screen.getByRole('button', { name: /Update/i }));
|
661
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
662
|
+
}));
|
663
|
+
it('should be able to add onEditCancel', () => __awaiter(void 0, void 0, void 0, function* () {
|
664
|
+
const spy = jest.fn();
|
665
|
+
(0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", isEditable: true, onEditCancel: spy }));
|
666
|
+
yield user_event_1.default.click(react_2.screen.getByRole('button', { name: /Cancel/i }));
|
667
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
668
|
+
}));
|
669
|
+
it('should be able to add editFormProps', () => {
|
670
|
+
const { container } = (0, react_2.render)(react_1.default.createElement(Message_1.default, { avatar: "./img", role: "user", name: "User", isEditable: true, editFormProps: { className: 'test' } }));
|
671
|
+
const form = container.querySelector('form');
|
672
|
+
expect(form).toHaveClass('test');
|
673
|
+
});
|
622
674
|
});
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { FormProps } from '@patternfly/react-core';
|
3
|
+
export interface MessageInputProps extends FormProps {
|
4
|
+
/** Placeholder for edit input */
|
5
|
+
editPlaceholder?: string;
|
6
|
+
/** Label for the English word "Update" used in edit mode. */
|
7
|
+
updateWord?: string;
|
8
|
+
/** Label for the English word "Cancel" used in edit mode. */
|
9
|
+
cancelWord?: string;
|
10
|
+
/** Callback function for when edit mode update button is clicked */
|
11
|
+
onEditUpdate?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, value: string) => void;
|
12
|
+
/** Callback functionf or when edit cancel update button is clicked */
|
13
|
+
onEditCancel?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
14
|
+
/** Message text */
|
15
|
+
content?: string;
|
16
|
+
}
|
17
|
+
declare const MessageInput: React.FunctionComponent<MessageInputProps>;
|
18
|
+
export default MessageInput;
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"use strict";
|
2
|
+
// ============================================================================
|
3
|
+
// Chatbot Main - Message Input
|
4
|
+
// ============================================================================
|
5
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
6
|
+
var t = {};
|
7
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
8
|
+
t[p] = s[p];
|
9
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
10
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
11
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
12
|
+
t[p[i]] = s[p[i]];
|
13
|
+
}
|
14
|
+
return t;
|
15
|
+
};
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
18
|
+
};
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
20
|
+
const react_1 = __importDefault(require("react"));
|
21
|
+
const react_core_1 = require("@patternfly/react-core");
|
22
|
+
const MessageInput = (_a) => {
|
23
|
+
var { editPlaceholder = 'Edit prompt message...', updateWord = 'Update', cancelWord = 'Cancel', onEditUpdate, onEditCancel, content } = _a, props = __rest(_a, ["editPlaceholder", "updateWord", "cancelWord", "onEditUpdate", "onEditCancel", "content"]);
|
24
|
+
const [messageText, setMessageText] = react_1.default.useState(content !== null && content !== void 0 ? content : '');
|
25
|
+
const onChange = (event, value) => {
|
26
|
+
setMessageText(value);
|
27
|
+
};
|
28
|
+
return (react_1.default.createElement(react_core_1.Form, Object.assign({}, props),
|
29
|
+
react_1.default.createElement(react_core_1.TextArea, { placeholder: editPlaceholder, value: messageText, onChange: onChange, "aria-label": editPlaceholder, autoResize: true }),
|
30
|
+
react_1.default.createElement(react_core_1.ActionGroup, { className: "pf-chatbot__message-edit-buttons" },
|
31
|
+
react_1.default.createElement(react_core_1.Button, { variant: "primary", onClick: (event) => onEditUpdate && onEditUpdate(event, messageText) }, updateWord),
|
32
|
+
react_1.default.createElement(react_core_1.Button, { variant: "secondary", onClick: onEditCancel }, cancelWord))));
|
33
|
+
};
|
34
|
+
exports.default = MessageInput;
|
@@ -28,10 +28,14 @@ export interface MessageBarProps extends TextAreaProps {
|
|
28
28
|
className?: string;
|
29
29
|
/** Flag to always to show the send button. By default send button is shown when there is a message in the input field */
|
30
30
|
alwayShowSendButton?: boolean;
|
31
|
+
/** Placeholder for message input */
|
32
|
+
placeholder?: string;
|
31
33
|
/** Flag to disable/enable the Attach button */
|
32
34
|
hasAttachButton?: boolean;
|
33
35
|
/** Flag to enable the Microphone button */
|
34
36
|
hasMicrophoneButton?: boolean;
|
37
|
+
/** Placeholder text when listening */
|
38
|
+
listeningText?: string;
|
35
39
|
/** Flag to enable the Stop button, used for streaming content */
|
36
40
|
hasStopButton?: boolean;
|
37
41
|
/** Callback function for when stop button is clicked */
|
@@ -24,7 +24,7 @@ const AttachButton_1 = require("./AttachButton");
|
|
24
24
|
const AttachMenu_1 = __importDefault(require("../AttachMenu"));
|
25
25
|
const StopButton_1 = __importDefault(require("./StopButton"));
|
26
26
|
const MessageBar = (_a) => {
|
27
|
-
var { onSendMessage, className, alwayShowSendButton, hasAttachButton = true, hasMicrophoneButton, handleAttach, attachMenuProps, isSendButtonDisabled, handleStopButton, hasStopButton, buttonProps, onChange, displayMode, value } = _a, props = __rest(_a, ["onSendMessage", "className", "alwayShowSendButton", "hasAttachButton", "hasMicrophoneButton", "handleAttach", "attachMenuProps", "isSendButtonDisabled", "handleStopButton", "hasStopButton", "buttonProps", "onChange", "displayMode", "value"]);
|
27
|
+
var { onSendMessage, className, alwayShowSendButton, placeholder = 'Send a message...', hasAttachButton = true, hasMicrophoneButton, listeningText = 'Listening', handleAttach, attachMenuProps, isSendButtonDisabled, handleStopButton, hasStopButton, buttonProps, onChange, displayMode, value } = _a, props = __rest(_a, ["onSendMessage", "className", "alwayShowSendButton", "placeholder", "hasAttachButton", "hasMicrophoneButton", "listeningText", "handleAttach", "attachMenuProps", "isSendButtonDisabled", "handleStopButton", "hasStopButton", "buttonProps", "onChange", "displayMode", "value"]);
|
28
28
|
// Text Input
|
29
29
|
// --------------------------------------------------------------------------
|
30
30
|
const [message, setMessage] = react_1.default.useState(value !== null && value !== void 0 ? value : '');
|
@@ -186,7 +186,7 @@ const MessageBar = (_a) => {
|
|
186
186
|
};
|
187
187
|
const messageBarContents = (react_1.default.createElement(react_1.default.Fragment, null,
|
188
188
|
react_1.default.createElement("div", { className: "pf-chatbot__message-bar-input" },
|
189
|
-
react_1.default.createElement(react_core_1.TextArea, Object.assign({ className: "pf-chatbot__message-textarea", value: message, onChange: handleChange, "aria-label": isListeningMessage ?
|
189
|
+
react_1.default.createElement(react_core_1.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))),
|
190
190
|
react_1.default.createElement("div", { className: "pf-chatbot__message-bar-actions" }, renderButtons())));
|
191
191
|
if (attachMenuProps) {
|
192
192
|
return (react_1.default.createElement(AttachMenu_1.default, Object.assign({ toggle: (toggleRef) => (react_1.default.createElement("div", { ref: toggleRef, className: `pf-chatbot__message-bar ${className !== null && className !== void 0 ? className : ''}` }, messageBarContents)), filteredItems: attachMenuProps === null || attachMenuProps === void 0 ? void 0 : attachMenuProps.attachMenuItems }, (attachMenuProps && { isOpen: attachMenuProps.isAttachMenuOpen }), { onOpenChange: (isAttachMenuOpen) => {
|
@@ -76,6 +76,12 @@ describe('Message bar', () => {
|
|
76
76
|
expect(spy).toHaveBeenCalledTimes(1);
|
77
77
|
expect(spy).toHaveBeenCalledWith(expect.any(Object), 'A');
|
78
78
|
}));
|
79
|
+
it('can use specified placeholder text', () => __awaiter(void 0, void 0, void 0, function* () {
|
80
|
+
(0, react_2.render)(react_1.default.createElement(MessageBar_1.MessageBar, { onSendMessage: jest.fn, placeholder: "test placeholder" }));
|
81
|
+
const input = react_2.screen.getByRole('textbox', { name: /test placeholder/i });
|
82
|
+
yield user_event_1.default.type(input, 'Hello world');
|
83
|
+
expect(input).toHaveTextContent('Hello world');
|
84
|
+
}));
|
79
85
|
// Send button
|
80
86
|
// --------------------------------------------------------------------------
|
81
87
|
it('shows send button when text is input', () => __awaiter(void 0, void 0, void 0, function* () {
|
@@ -229,6 +235,13 @@ describe('Message bar', () => {
|
|
229
235
|
yield user_event_1.default.click(react_2.screen.getByRole('button', { name: 'Microphone button' }));
|
230
236
|
expect(react_2.screen.getByRole('tooltip', { name: 'Not currently listening' })).toBeTruthy();
|
231
237
|
}));
|
238
|
+
it('can customize the listening placeholder', () => __awaiter(void 0, void 0, void 0, function* () {
|
239
|
+
mockSpeechRecognition();
|
240
|
+
(0, react_2.render)(react_1.default.createElement(MessageBar_1.MessageBar, { onSendMessage: jest.fn, hasMicrophoneButton: true, listeningText: "I am listening" }));
|
241
|
+
yield user_event_1.default.click(react_2.screen.getByRole('button', { name: 'Microphone button' }));
|
242
|
+
const input = react_2.screen.getByRole('textbox', { name: /I am listening/i });
|
243
|
+
expect(input).toBeTruthy();
|
244
|
+
}));
|
232
245
|
it('can handle buttonProps props appropriately for microphone', () => __awaiter(void 0, void 0, void 0, function* () {
|
233
246
|
mockSpeechRecognition();
|
234
247
|
(0, react_2.render)(react_1.default.createElement(MessageBar_1.MessageBar, { onSendMessage: jest.fn, hasMicrophoneButton: true, buttonProps: { microphone: { props: { 'aria-label': 'Test' } } } }));
|
@@ -29,26 +29,26 @@ const MessageBoxBase = ({ announcement, ariaLabel = 'Scrollable message log', ch
|
|
29
29
|
setAtTop(scrollTop === 0);
|
30
30
|
setAtBottom(Math.round(scrollTop) + Math.round(clientHeight) >= Math.round(scrollHeight) - 1); // rounding means it could be within a pixel of the bottom
|
31
31
|
}
|
32
|
-
}, []);
|
32
|
+
}, [messageBoxRef]);
|
33
33
|
const checkOverflow = react_1.default.useCallback(() => {
|
34
34
|
const element = messageBoxRef.current;
|
35
35
|
if (element) {
|
36
36
|
const { scrollHeight, clientHeight } = element;
|
37
37
|
setIsOverflowing(scrollHeight >= clientHeight);
|
38
38
|
}
|
39
|
-
}, []);
|
39
|
+
}, [messageBoxRef]);
|
40
40
|
const scrollToTop = react_1.default.useCallback(() => {
|
41
41
|
const element = messageBoxRef.current;
|
42
42
|
if (element) {
|
43
43
|
element.scrollTo({ top: 0, behavior: 'smooth' });
|
44
44
|
}
|
45
|
-
}, []);
|
45
|
+
}, [messageBoxRef]);
|
46
46
|
const scrollToBottom = react_1.default.useCallback(() => {
|
47
47
|
const element = messageBoxRef.current;
|
48
48
|
if (element) {
|
49
49
|
element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' });
|
50
50
|
}
|
51
|
-
}, []);
|
51
|
+
}, [messageBoxRef]);
|
52
52
|
// Detect scroll position
|
53
53
|
react_1.default.useEffect(() => {
|
54
54
|
const element = messageBoxRef.current;
|
@@ -62,7 +62,7 @@ const MessageBoxBase = ({ announcement, ariaLabel = 'Scrollable message log', ch
|
|
62
62
|
element.removeEventListener('scroll', handleScroll);
|
63
63
|
};
|
64
64
|
}
|
65
|
-
}, [checkOverflow, handleScroll]);
|
65
|
+
}, [checkOverflow, handleScroll, messageBoxRef]);
|
66
66
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
67
67
|
react_1.default.createElement(JumpButton_1.default, { position: "top", isHidden: isOverflowing && atTop, onClick: scrollToTop }),
|
68
68
|
react_1.default.createElement("div", { role: "region", tabIndex: 0, "aria-label": ariaLabel, className: `pf-chatbot__messagebox ${position === 'bottom' && 'pf-chatbot__messagebox--bottom'} ${className !== null && className !== void 0 ? className : ''}`, ref: innerRef !== null && innerRef !== void 0 ? innerRef : messageBoxRef },
|
@@ -5,7 +5,7 @@ export interface SourcesCardProps extends CardProps {
|
|
5
5
|
className?: string;
|
6
6
|
/** Flag indicating if the pagination is disabled. */
|
7
7
|
isDisabled?: boolean;
|
8
|
-
/** Label for the English word "of"
|
8
|
+
/** @deprecated ofWord has been deprecated. Label for the English word "of." */
|
9
9
|
ofWord?: string;
|
10
10
|
/** Accessible label for the pagination component. */
|
11
11
|
paginationAriaLabel?: string;
|
@@ -14,6 +14,8 @@ export interface SourcesCardProps extends CardProps {
|
|
14
14
|
title?: string;
|
15
15
|
link: string;
|
16
16
|
body?: React.ReactNode | string;
|
17
|
+
isExternal?: boolean;
|
18
|
+
hasShowMore?: boolean;
|
17
19
|
}[];
|
18
20
|
/** Label for the English word "source" */
|
19
21
|
sourceWord?: string;
|
@@ -29,6 +31,10 @@ export interface SourcesCardProps extends CardProps {
|
|
29
31
|
onPreviousClick?: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void;
|
30
32
|
/** Function called when page is changed. */
|
31
33
|
onSetPage?: (event: React.MouseEvent | React.KeyboardEvent | MouseEvent, newPage: number) => void;
|
34
|
+
/** Label for English words "show more" */
|
35
|
+
showMoreWords?: string;
|
36
|
+
/** Label for English words "show less" */
|
37
|
+
showLessWords?: string;
|
32
38
|
}
|
33
39
|
declare const SourcesCard: React.FunctionComponent<SourcesCardProps>;
|
34
40
|
export default SourcesCard;
|
@@ -20,9 +20,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
const react_1 = __importDefault(require("react"));
|
21
21
|
// Import PatternFly components
|
22
22
|
const react_core_1 = require("@patternfly/react-core");
|
23
|
+
const react_icons_1 = require("@patternfly/react-icons");
|
23
24
|
const SourcesCard = (_a) => {
|
24
|
-
var { className, isDisabled,
|
25
|
+
var { className, isDisabled, paginationAriaLabel = 'Pagination', sources, sourceWord = 'source', sourceWordPlural = 'sources', toNextPageAriaLabel = 'Go to next page', toPreviousPageAriaLabel = 'Go to previous page', onNextClick, onPreviousClick, onSetPage, showMoreWords = 'show more', showLessWords = 'show less' } = _a, props = __rest(_a, ["className", "isDisabled", "paginationAriaLabel", "sources", "sourceWord", "sourceWordPlural", "toNextPageAriaLabel", "toPreviousPageAriaLabel", "onNextClick", "onPreviousClick", "onSetPage", "showMoreWords", "showLessWords"]);
|
25
26
|
const [page, setPage] = react_1.default.useState(1);
|
27
|
+
const [isExpanded, setIsExpanded] = react_1.default.useState(false);
|
28
|
+
const onToggle = (_event, isExpanded) => {
|
29
|
+
setIsExpanded(isExpanded);
|
30
|
+
};
|
26
31
|
const handleNewPage = (_evt, newPage) => {
|
27
32
|
setPage(newPage);
|
28
33
|
onSetPage && onSetPage(_evt, newPage);
|
@@ -37,8 +42,11 @@ const SourcesCard = (_a) => {
|
|
37
42
|
react_1.default.createElement("span", null, (0, react_core_1.pluralize)(sources.length, sourceWord, sourceWordPlural)),
|
38
43
|
react_1.default.createElement(react_core_1.Card, Object.assign({ className: "pf-chatbot__sources-card" }, props),
|
39
44
|
react_1.default.createElement(react_core_1.CardTitle, { className: "pf-chatbot__sources-card-title" },
|
40
|
-
react_1.default.createElement("a",
|
41
|
-
sources[page - 1].body && (react_1.default.createElement(react_core_1.CardBody, { className: `pf-chatbot__sources-card-body
|
45
|
+
react_1.default.createElement(react_core_1.Button, { component: "a", variant: react_core_1.ButtonVariant.link, href: sources[page - 1].link, icon: sources[page - 1].isExternal ? react_1.default.createElement(react_icons_1.ExternalLinkSquareAltIcon, null) : undefined, iconPosition: "end", isInline: true, rel: sources[page - 1].isExternal ? 'noreferrer' : undefined, target: sources[page - 1].isExternal ? '_blank' : undefined }, renderTitle(sources[page - 1].title))),
|
46
|
+
sources[page - 1].body && (react_1.default.createElement(react_core_1.CardBody, { className: `pf-chatbot__sources-card-body` }, sources[page - 1].hasShowMore ? (
|
47
|
+
// prevents extra VO announcements of button text - parent Message has aria-live
|
48
|
+
react_1.default.createElement("div", { "aria-live": "off" },
|
49
|
+
react_1.default.createElement(react_core_1.ExpandableSection, { variant: react_core_1.ExpandableSectionVariant.truncate, toggleText: isExpanded ? showLessWords : showMoreWords, onToggle: onToggle, isExpanded: isExpanded, truncateMaxLines: 2 }, sources[page - 1].body))) : (react_1.default.createElement("div", { className: "pf-chatbot__sources-card-body-text" }, sources[page - 1].body)))),
|
42
50
|
sources.length > 1 && (react_1.default.createElement(react_core_1.CardFooter, { className: "pf-chatbot__sources-card-footer-container" },
|
43
51
|
react_1.default.createElement("div", { className: "pf-chatbot__sources-card-footer" },
|
44
52
|
react_1.default.createElement("nav", { className: `pf-chatbot__sources-card-footer-buttons ${className}`, "aria-label": paginationAriaLabel },
|
@@ -50,6 +58,10 @@ const SourcesCard = (_a) => {
|
|
50
58
|
react_1.default.createElement(react_core_1.Icon, { iconSize: "lg" },
|
51
59
|
react_1.default.createElement("svg", { className: "pf-v6-svg", viewBox: "0 0 280 500", fill: "currentColor", "aria-hidden": "true", role: "img", width: "1em", height: "1em" },
|
52
60
|
react_1.default.createElement("path", { d: "M31.7 239l136-136c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9L127.9 256l96.4 96.4c9.4 9.4 9.4 24.6 0 33.9L201.7 409c-9.4 9.4-24.6 9.4-33.9 0l-136-136c-9.5-9.4-9.5-24.6-.1-34z" })))),
|
61
|
+
react_1.default.createElement("span", { "aria-hidden": "true" },
|
62
|
+
page,
|
63
|
+
"/",
|
64
|
+
sources.length),
|
53
65
|
react_1.default.createElement(react_core_1.Button, { variant: react_core_1.ButtonVariant.plain, isDisabled: isDisabled || page === sources.length, "aria-label": toNextPageAriaLabel, "data-action": "next", onClick: (event) => {
|
54
66
|
const newPage = page + 1 <= sources.length ? page + 1 : sources.length;
|
55
67
|
onNextClick && onNextClick(event, newPage);
|
@@ -57,12 +69,6 @@ const SourcesCard = (_a) => {
|
|
57
69
|
} },
|
58
70
|
react_1.default.createElement(react_core_1.Icon, { isInline: true, iconSize: "lg" },
|
59
71
|
react_1.default.createElement("svg", { className: "pf-v6-svg", viewBox: "0 0 180 500", fill: "currentColor", "aria-hidden": "true", role: "img", width: "1em", height: "1em" },
|
60
|
-
react_1.default.createElement("path", { d: "M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z" })))))
|
61
|
-
react_1.default.createElement("span", { "aria-hidden": "true" },
|
62
|
-
page,
|
63
|
-
" ",
|
64
|
-
ofWord,
|
65
|
-
" ",
|
66
|
-
sources.length)))))));
|
72
|
+
react_1.default.createElement("path", { d: "M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z" })))))))))));
|
67
73
|
};
|
68
74
|
exports.default = SourcesCard;
|