@patternfly/chatbot 6.8.0-prerelease.7 → 6.8.0-prerelease.8
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 +46 -5
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +125 -16
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +324 -0
- package/dist/cjs/MessageBox/MessageBox.d.ts +2 -0
- package/dist/cjs/MessageBox/MessageBox.js +3 -2
- package/dist/css/main.css +59 -7
- package/dist/css/main.css.map +1 -1
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +46 -5
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +128 -19
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +325 -1
- package/dist/esm/MessageBox/MessageBox.d.ts +2 -0
- package/dist/esm/MessageBox/MessageBox.js +3 -2
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithExpandableGroups.tsx +104 -0
- package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +12 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +52 -6
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +551 -3
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +350 -49
- package/src/ChatbotToggle/ChatbotToggle.scss +6 -0
- package/src/Message/Message.scss +5 -0
- package/src/MessageBox/MessageBox.scss +15 -1
- package/src/MessageBox/MessageBox.tsx +10 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { FunctionComponent } from 'react';
|
|
2
|
-
import { ButtonProps, DrawerProps, DrawerPanelContentProps, DrawerContentProps, DrawerContentBodyProps, DrawerHeadProps, DrawerActionsProps, DrawerCloseButtonProps, DrawerPanelBodyProps, SkeletonProps, TitleProps, SearchInputProps, MenuProps, MenuListProps, MenuItemProps, MenuGroupProps, MenuContentProps } from '@patternfly/react-core';
|
|
1
|
+
import type { FunctionComponent, ReactNode } from 'react';
|
|
2
|
+
import { ButtonProps, DrawerProps, DrawerPanelContentProps, DrawerContentProps, DrawerContentBodyProps, DrawerHeadProps, DrawerActionsProps, DrawerCloseButtonProps, DrawerPanelBodyProps, SkeletonProps, TitleProps, SearchInputProps, MenuProps, MenuListProps, MenuItemProps, MenuGroupProps, MenuContentProps, ExpandableSectionProps, ExpandableSectionToggleProps } from '@patternfly/react-core';
|
|
3
3
|
import { ChatbotDisplayMode } from '../Chatbot/Chatbot';
|
|
4
4
|
import { HistoryEmptyStateProps } from './EmptyState';
|
|
5
5
|
export interface Conversation {
|
|
@@ -24,6 +24,49 @@ export interface Conversation {
|
|
|
24
24
|
/** Custom dropdown ID to ensure uniqueness across demo instances */
|
|
25
25
|
dropdownId?: string;
|
|
26
26
|
}
|
|
27
|
+
export interface ConversationGroupShowAll {
|
|
28
|
+
/** Number of items visible when collapsed */
|
|
29
|
+
visibleCount: number;
|
|
30
|
+
/** Whether all items are shown */
|
|
31
|
+
isExpanded: boolean;
|
|
32
|
+
/** Callback when show all / show less is toggled */
|
|
33
|
+
onToggle: (isExpanded: boolean) => void;
|
|
34
|
+
/** Custom content rendered in the show all / show less toggle. Defaults to "Show all" when collapsed and "Show less" when expanded. */
|
|
35
|
+
label?: ReactNode;
|
|
36
|
+
}
|
|
37
|
+
export interface ConversationGroupExpandable {
|
|
38
|
+
/** Whether the group content is expanded */
|
|
39
|
+
isExpanded: boolean;
|
|
40
|
+
/** Callback when the group is toggled */
|
|
41
|
+
onToggle: (isExpanded: boolean) => void;
|
|
42
|
+
/** Additional props applied to ExpandableSection */
|
|
43
|
+
expandableSectionProps?: Omit<ExpandableSectionProps, 'ref'>;
|
|
44
|
+
/** Additional props applied to ExpandableSectionToggle */
|
|
45
|
+
expandableSectionToggleProps?: ExpandableSectionToggleProps;
|
|
46
|
+
}
|
|
47
|
+
export interface ConversationGroup {
|
|
48
|
+
/** Unique group id */
|
|
49
|
+
id: string;
|
|
50
|
+
/** Group label rendered as a MenuGroup heading or expandable toggle label */
|
|
51
|
+
label: ReactNode;
|
|
52
|
+
/** Conversation items or custom menu content such as a "Show all" action */
|
|
53
|
+
items: (Conversation | ReactNode)[];
|
|
54
|
+
/** Content rendered after the group's menu list */
|
|
55
|
+
footer?: ReactNode;
|
|
56
|
+
/** Custom group header that replaces the default label or expandable toggle */
|
|
57
|
+
header?: ReactNode;
|
|
58
|
+
/** When set, the group renders as an expandable section */
|
|
59
|
+
expandable?: ConversationGroupExpandable;
|
|
60
|
+
/** When set, truncates the list with an expandable show all / show less section */
|
|
61
|
+
showAll?: ConversationGroupShowAll;
|
|
62
|
+
/** Additional props applied to the conversation menu group */
|
|
63
|
+
menuGroupProps?: MenuGroupProps;
|
|
64
|
+
/** Additional props applied to the conversation list */
|
|
65
|
+
menuListProps?: Omit<MenuListProps, 'children'>;
|
|
66
|
+
}
|
|
67
|
+
export type Conversations = (Conversation | ReactNode)[] | ConversationGroup[] | {
|
|
68
|
+
[key: string]: (Conversation | ReactNode)[];
|
|
69
|
+
};
|
|
27
70
|
export interface ChatbotConversationHistoryNavProps extends DrawerProps {
|
|
28
71
|
/** Function called to toggle drawer */
|
|
29
72
|
onDrawerToggle: (event: React.KeyboardEvent | React.MouseEvent | React.TransitionEvent) => void;
|
|
@@ -35,9 +78,7 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
|
|
|
35
78
|
/** Callback function for when an item is selected */
|
|
36
79
|
onSelectActiveItem?: (event?: React.MouseEvent, itemId?: string | number) => void;
|
|
37
80
|
/** Items shown in chat history */
|
|
38
|
-
conversations:
|
|
39
|
-
[key: string]: Conversation[];
|
|
40
|
-
};
|
|
81
|
+
conversations: Conversations;
|
|
41
82
|
/** Additional button props for new chat button. */
|
|
42
83
|
newChatButtonProps?: ButtonProps;
|
|
43
84
|
/** Additional props applied to conversation menu group. If conversations is an object, you should pass an object of MenuGroupProps for each group. */
|
|
@@ -24,35 +24,144 @@ const Chatbot_1 = require("../Chatbot/Chatbot");
|
|
|
24
24
|
const ChatbotConversationHistoryDropdown_1 = __importDefault(require("./ChatbotConversationHistoryDropdown"));
|
|
25
25
|
const LoadingState_1 = __importDefault(require("./LoadingState"));
|
|
26
26
|
const EmptyState_1 = __importDefault(require("./EmptyState"));
|
|
27
|
+
const isConversation = (item) => Boolean(item && typeof item === 'object' && 'id' in item && 'text' in item && !('items' in item));
|
|
28
|
+
const isConversationGroup = (item) => Boolean(item && typeof item === 'object' && 'id' in item && 'label' in item && 'items' in item);
|
|
29
|
+
const isConversationGroupArray = (items) => items.length > 0 && isConversationGroup(items[0]);
|
|
30
|
+
const getExpandableGroupContentId = (groupId) => `chatbot-nav-group-${groupId}-content`;
|
|
31
|
+
const getGroupLabelId = (groupId) => `chatbot-nav-group-${groupId}-label`;
|
|
32
|
+
const getExpandableGroupToggleId = (groupId) => `chatbot-nav-group-${groupId}-toggle`;
|
|
33
|
+
const getShowAllToggleId = (groupId) => `chatbot-nav-group-${groupId}-show-all-toggle`;
|
|
34
|
+
const getStaticMenuLabelId = (group) => { var _a, _b; return (_b = (_a = group.menuGroupProps) === null || _a === void 0 ? void 0 : _a.titleId) !== null && _b !== void 0 ? _b : getGroupLabelId(group.id); };
|
|
35
|
+
const getMenuListLabelledBy = (group) => {
|
|
36
|
+
var _a, _b, _c, _d;
|
|
37
|
+
if (group.expandable) {
|
|
38
|
+
return (_b = (_a = group.menuGroupProps) === null || _a === void 0 ? void 0 : _a['aria-labelledby']) !== null && _b !== void 0 ? _b : getExpandableGroupToggleId(group.id);
|
|
39
|
+
}
|
|
40
|
+
return (_d = (_c = group.menuGroupProps) === null || _c === void 0 ? void 0 : _c['aria-labelledby']) !== null && _d !== void 0 ? _d : getStaticMenuLabelId(group);
|
|
41
|
+
};
|
|
42
|
+
const getMenuListProps = (group) => {
|
|
43
|
+
var _a, _b;
|
|
44
|
+
return (Object.assign(Object.assign({}, group.menuListProps), { 'aria-labelledby': (_b = (_a = group.menuListProps) === null || _a === void 0 ? void 0 : _a['aria-labelledby']) !== null && _b !== void 0 ? _b : getMenuListLabelledBy(group) }));
|
|
45
|
+
};
|
|
46
|
+
const focusElementById = (id) => {
|
|
47
|
+
var _a;
|
|
48
|
+
(_a = document.getElementById(id)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
49
|
+
};
|
|
50
|
+
// Matches PatternFly's Menu "view more" example: expanding moves focus to the first
|
|
51
|
+
// newly revealed item; collapsing keeps focus on the toggle as it updates to "Show all".
|
|
52
|
+
const useShowAllFocusManagement = (isExpanded, toggleId, firstOverflowItemRef, hasOverflowItems) => {
|
|
53
|
+
const wasExpandedRef = (0, react_1.useRef)(isExpanded);
|
|
54
|
+
(0, react_1.useEffect)(() => {
|
|
55
|
+
var _a;
|
|
56
|
+
if (hasOverflowItems && isExpanded !== wasExpandedRef.current) {
|
|
57
|
+
if (isExpanded) {
|
|
58
|
+
(_a = firstOverflowItemRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
focusElementById(toggleId);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
wasExpandedRef.current = isExpanded;
|
|
65
|
+
}, [isExpanded, toggleId, firstOverflowItemRef, hasOverflowItems]);
|
|
66
|
+
};
|
|
67
|
+
// The toggle is rendered as a real MenuItem (rather than a standalone button) so it
|
|
68
|
+
// participates in the Menu's built-in roving tabindex and arrow-key navigation just
|
|
69
|
+
// like any other conversation item, instead of being skipped over or interrupting
|
|
70
|
+
// arrow-key handling. Since it keeps a stable key and DOM position regardless of how
|
|
71
|
+
// many overflow items are shown, it also keeps focus across clicks without being
|
|
72
|
+
// unmounted or recreated.
|
|
73
|
+
const ShowAllGroupBody = ({ group, getNavItem }) => {
|
|
74
|
+
const { visibleCount, isExpanded, onToggle, label } = group.showAll;
|
|
75
|
+
const conversationItems = group.items.filter(isConversation);
|
|
76
|
+
const alwaysVisibleItems = conversationItems.slice(0, visibleCount);
|
|
77
|
+
const overflowItems = conversationItems.slice(visibleCount);
|
|
78
|
+
const hasOverflowItems = overflowItems.length > 0;
|
|
79
|
+
const toggleId = getShowAllToggleId(group.id);
|
|
80
|
+
const firstOverflowItemRef = (0, react_1.useRef)(null);
|
|
81
|
+
const toggleLabel = label !== null && label !== void 0 ? label : (isExpanded ? 'Show less' : 'Show all');
|
|
82
|
+
useShowAllFocusManagement(isExpanded, toggleId, firstOverflowItemRef, hasOverflowItems);
|
|
83
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(react_core_1.MenuList, Object.assign({}, getMenuListProps(group), { children: [alwaysVisibleItems.map((chat) => ((0, jsx_runtime_1.jsx)(react_1.Fragment, { children: getNavItem(chat) }, chat.id))), isExpanded &&
|
|
84
|
+
overflowItems.map((chat, index) => ((0, jsx_runtime_1.jsx)(react_1.Fragment, { children: getNavItem(chat, undefined, index === 0 ? firstOverflowItemRef : undefined) }, chat.id))), hasOverflowItems && ((0, jsx_runtime_1.jsx)(react_core_1.MenuItem, { id: toggleId, itemId: toggleId, className: "pf-chatbot__menu-show-all-toggle", onClick: () => onToggle(!isExpanded), children: toggleLabel }, "show-all-toggle"))] })), group.footer] }));
|
|
85
|
+
};
|
|
86
|
+
const ExpandableConversationGroup = ({ group, children }) => {
|
|
87
|
+
var _a, _b;
|
|
88
|
+
const toggleId = getExpandableGroupToggleId(group.id);
|
|
89
|
+
const contentId = getExpandableGroupContentId(group.id);
|
|
90
|
+
const { isExpanded, onToggle, expandableSectionProps, expandableSectionToggleProps } = group.expandable;
|
|
91
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `pf-chatbot__menu-item-header pf-chatbot__menu-item-header--expandable ${(_b = (_a = group.menuGroupProps) === null || _a === void 0 ? void 0 : _a.className) !== null && _b !== void 0 ? _b : ''}`, children: [(0, jsx_runtime_1.jsx)(react_core_1.ExpandableSectionToggle, Object.assign({ toggleId: toggleId, contentId: contentId, isExpanded: isExpanded, onToggle: onToggle, toggleWrapper: "h3", className: "pf-chatbot__menu-group-toggle" }, expandableSectionToggleProps, { children: group.label })), (0, jsx_runtime_1.jsx)(react_core_1.ExpandableSection, Object.assign({ isDetached: true, isExpanded: isExpanded, toggleId: toggleId, contentId: contentId }, expandableSectionProps, { children: isExpanded && children }))] }));
|
|
92
|
+
};
|
|
93
|
+
const buildConversationMenuSegments = (groups) => groups.reduce((segments, group) => {
|
|
94
|
+
if (group.expandable) {
|
|
95
|
+
return [...segments, { type: 'expandable', group }];
|
|
96
|
+
}
|
|
97
|
+
const last = segments[segments.length - 1];
|
|
98
|
+
if ((last === null || last === void 0 ? void 0 : last.type) === 'static') {
|
|
99
|
+
last.groups.push(group);
|
|
100
|
+
return segments;
|
|
101
|
+
}
|
|
102
|
+
return [...segments, { type: 'static', groups: [group] }];
|
|
103
|
+
}, []);
|
|
27
104
|
const ChatbotConversationHistoryNav = (_a) => {
|
|
28
105
|
var { onDrawerToggle, isDrawerOpen, setIsDrawerOpen, activeItemId, onSelectActiveItem, conversations, menuListProps, newChatButtonText = 'New chat', drawerContent, onNewChat, newChatButtonProps, searchInputPlaceholder = 'Search previous conversations...', searchInputAriaLabel = 'Search previous conversations', searchInputProps, handleTextInputChange, displayMode, reverseButtonOrder = false, drawerActionsTestId = 'chatbot-nav-drawer-actions', drawerPanelContentProps, drawerContentProps, drawerContentBodyProps, drawerHeadProps, drawerActionsProps, drawerCloseButtonProps, drawerPanelBodyProps, hasDrawerHeadDivider, isLoading, loadingState, errorState, emptyState, noResultsState, isCompact, title = 'Chat history', navTitleProps, navTitleIcon = (0, jsx_runtime_1.jsx)(react_icons_1.RhUiClockIcon, {}), searchInputScreenReaderText, searchActionStart, searchActionEnd, searchToolbar, menuProps, menuGroupProps, menuContentProps } = _a, props = __rest(_a, ["onDrawerToggle", "isDrawerOpen", "setIsDrawerOpen", "activeItemId", "onSelectActiveItem", "conversations", "menuListProps", "newChatButtonText", "drawerContent", "onNewChat", "newChatButtonProps", "searchInputPlaceholder", "searchInputAriaLabel", "searchInputProps", "handleTextInputChange", "displayMode", "reverseButtonOrder", "drawerActionsTestId", "drawerPanelContentProps", "drawerContentProps", "drawerContentBodyProps", "drawerHeadProps", "drawerActionsProps", "drawerCloseButtonProps", "drawerPanelBodyProps", "hasDrawerHeadDivider", "isLoading", "loadingState", "errorState", "emptyState", "noResultsState", "isCompact", "title", "navTitleProps", "navTitleIcon", "searchInputScreenReaderText", "searchActionStart", "searchActionEnd", "searchToolbar", "menuProps", "menuGroupProps", "menuContentProps"]);
|
|
29
106
|
const drawerRef = (0, react_1.useRef)(null);
|
|
30
107
|
const onExpand = () => {
|
|
31
108
|
drawerRef.current && drawerRef.current.focus();
|
|
32
109
|
};
|
|
33
|
-
const
|
|
34
|
-
const getNavItem = (conversation) => {
|
|
110
|
+
const getNavItem = (conversation, itemOverrides, itemRef) => {
|
|
35
111
|
var _a;
|
|
36
|
-
return ((0, jsx_runtime_1.jsx)(react_core_1.MenuItem, Object.assign({ className: `pf-chatbot__menu-item ${activeItemId && activeItemId === conversation.id ? 'pf-chatbot__menu-item--active' : ''}`, itemId: conversation.id }, (conversation.noIcon ? {} : { icon: (_a = conversation.icon) !== null && _a !== void 0 ? _a : (0, jsx_runtime_1.jsx)(react_icons_1.RhUiCommentIcon, {}) }), (conversation.menuItems
|
|
112
|
+
return ((0, jsx_runtime_1.jsx)(react_core_1.MenuItem, Object.assign({ className: `pf-chatbot__menu-item ${activeItemId && activeItemId === conversation.id ? 'pf-chatbot__menu-item--active' : ''}`, itemId: conversation.id, ref: itemRef }, (conversation.noIcon ? {} : { icon: (_a = conversation.icon) !== null && _a !== void 0 ? _a : (0, jsx_runtime_1.jsx)(react_icons_1.RhUiCommentIcon, {}) }), (conversation.menuItems
|
|
37
113
|
? {
|
|
38
114
|
actions: ((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryDropdown_1.default, { menuClassName: conversation.menuClassName, onSelect: conversation.onSelect, menuItems: conversation.menuItems, label: conversation.label }))
|
|
39
115
|
}
|
|
40
|
-
: {}), conversation.additionalProps, { children: conversation.text })));
|
|
116
|
+
: {}), conversation.additionalProps, itemOverrides, { children: conversation.text })));
|
|
41
117
|
};
|
|
118
|
+
const renderConversationItems = (items, keyPrefix = '') => items.map((item, index) => {
|
|
119
|
+
if (isConversation(item)) {
|
|
120
|
+
return (0, jsx_runtime_1.jsx)(react_1.Fragment, { children: getNavItem(item) }, item.id);
|
|
121
|
+
}
|
|
122
|
+
const key = (0, react_1.isValidElement)(item) && item.key != null ? String(item.key) : `${keyPrefix}-${index}`;
|
|
123
|
+
return (0, jsx_runtime_1.jsx)(react_1.Fragment, { children: item }, key);
|
|
124
|
+
});
|
|
125
|
+
const renderGroupBody = (group) => {
|
|
126
|
+
if (group.showAll) {
|
|
127
|
+
return (0, jsx_runtime_1.jsx)(ShowAllGroupBody, { group: group, getNavItem: getNavItem });
|
|
128
|
+
}
|
|
129
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_core_1.MenuList, Object.assign({}, getMenuListProps(group), { children: renderConversationItems(group.items, group.id) })), group.footer] }));
|
|
130
|
+
};
|
|
131
|
+
const renderConversationGroup = (group) => {
|
|
132
|
+
var _a, _b, _c, _d;
|
|
133
|
+
if (group.header) {
|
|
134
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `pf-chatbot__menu-item-header ${(_b = (_a = group.menuGroupProps) === null || _a === void 0 ? void 0 : _a.className) !== null && _b !== void 0 ? _b : ''}`, children: [group.header, renderGroupBody(group)] }, group.id));
|
|
135
|
+
}
|
|
136
|
+
const labelId = getStaticMenuLabelId(group);
|
|
137
|
+
return ((0, jsx_runtime_1.jsx)(react_core_1.MenuGroup, Object.assign({ className: "pf-chatbot__menu-item-header", label: group.label, labelHeadingLevel: "h3" }, group.menuGroupProps, { titleId: labelId, "aria-labelledby": (_d = (_c = group.menuGroupProps) === null || _c === void 0 ? void 0 : _c['aria-labelledby']) !== null && _d !== void 0 ? _d : labelId, children: renderGroupBody(group) }), group.id));
|
|
138
|
+
};
|
|
139
|
+
const renderConversationMenu = (content, key) => ((0, jsx_runtime_1.jsx)(react_core_1.Menu, Object.assign({ className: "pf-chatbot__history-menu", isPlain: true, onSelect: onSelectActiveItem, activeItemId: activeItemId }, menuProps, { children: (0, jsx_runtime_1.jsx)(react_core_1.MenuContent, Object.assign({}, menuContentProps, { children: content })) }), key));
|
|
140
|
+
const renderConversationMenuSegment = (segment) => {
|
|
141
|
+
if (segment.type === 'static') {
|
|
142
|
+
return renderConversationMenu((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: segment.groups.map(renderConversationGroup) }), segment.groups.map((group) => group.id).join('-'));
|
|
143
|
+
}
|
|
144
|
+
return ((0, jsx_runtime_1.jsx)(ExpandableConversationGroup, { group: segment.group, children: renderConversationMenu(renderGroupBody(segment.group)) }, segment.group.id));
|
|
145
|
+
};
|
|
146
|
+
const normalizeObjectGroups = (groupedConversations) => Object.keys(groupedConversations).map((groupKey) => ({
|
|
147
|
+
id: groupKey,
|
|
148
|
+
label: groupKey,
|
|
149
|
+
items: groupedConversations[groupKey],
|
|
150
|
+
menuGroupProps: menuGroupProps && typeof menuGroupProps === 'object' && groupKey in menuGroupProps
|
|
151
|
+
? menuGroupProps[groupKey]
|
|
152
|
+
: undefined,
|
|
153
|
+
menuListProps: menuListProps && typeof menuListProps === 'object' && groupKey in menuListProps
|
|
154
|
+
? menuListProps[groupKey]
|
|
155
|
+
: undefined
|
|
156
|
+
}));
|
|
42
157
|
const buildConversations = () => {
|
|
43
158
|
if (Array.isArray(conversations)) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
else {
|
|
49
|
-
return conversation;
|
|
50
|
-
}
|
|
51
|
-
}) })));
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: Object.keys(conversations).map((navGroup) => ((0, jsx_runtime_1.jsx)(react_core_1.MenuGroup, Object.assign({ className: "pf-chatbot__menu-item-header", label: navGroup, labelHeadingLevel: "h3" }, menuGroupProps === null || menuGroupProps === void 0 ? void 0 : menuGroupProps[navGroup], { children: (0, jsx_runtime_1.jsx)(react_core_1.MenuList, Object.assign({}, menuListProps === null || menuListProps === void 0 ? void 0 : menuListProps[navGroup], { children: conversations[navGroup].map((conversation) => ((0, jsx_runtime_1.jsx)(react_1.Fragment, { children: getNavItem(conversation) }, conversation.id))) })) }), navGroup))) }));
|
|
159
|
+
if (isConversationGroupArray(conversations)) {
|
|
160
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: buildConversationMenuSegments(conversations).map(renderConversationMenuSegment) });
|
|
161
|
+
}
|
|
162
|
+
return renderConversationMenu((0, jsx_runtime_1.jsx)(react_core_1.MenuList, Object.assign({}, menuListProps, { children: renderConversationItems(conversations) })));
|
|
55
163
|
}
|
|
164
|
+
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: buildConversationMenuSegments(normalizeObjectGroups(conversations)).map(renderConversationMenuSegment) }));
|
|
56
165
|
};
|
|
57
166
|
// Menu Content
|
|
58
167
|
// - Consumers should pass an array to <Chatbot> of the list of conversations
|
|
@@ -67,7 +176,7 @@ const ChatbotConversationHistoryNav = (_a) => {
|
|
|
67
176
|
if (noResultsState) {
|
|
68
177
|
return (0, jsx_runtime_1.jsx)(EmptyState_1.default, Object.assign({}, noResultsState));
|
|
69
178
|
}
|
|
70
|
-
return
|
|
179
|
+
return buildConversations();
|
|
71
180
|
};
|
|
72
181
|
const renderDrawerContent = () => ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(react_core_1.DrawerPanelBody, Object.assign({}, drawerPanelBodyProps, { children: renderMenuContent() })) }));
|
|
73
182
|
const searchInputContainer = handleTextInputChange && ((0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__input", children: [(0, jsx_runtime_1.jsx)(react_core_1.SearchInput, Object.assign({ "aria-label": searchInputAriaLabel, onChange: (_event, value) => handleTextInputChange(value), placeholder: searchInputPlaceholder }, searchInputProps)), searchInputScreenReaderText && ((0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__filter-announcement pf-chatbot-m-hidden", children: searchInputScreenReaderText }))] }));
|
|
@@ -19,6 +19,7 @@ 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
21
|
const react_icons_1 = require("@patternfly/react-icons");
|
|
22
|
+
const react_2 = require("react");
|
|
22
23
|
const ERROR = {
|
|
23
24
|
bodyText: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["To try again, check your connection and reload this page. If the issue persists,", ' ', (0, jsx_runtime_1.jsx)("a", { href: "", children: "contact the support team" }), "."] })),
|
|
24
25
|
buttonText: 'Reload',
|
|
@@ -274,4 +275,327 @@ describe('ChatbotConversationHistoryNav', () => {
|
|
|
274
275
|
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: initialConversations, navTitleIcon: (0, jsx_runtime_1.jsx)(react_icons_1.BellIcon, { "data-testid": "bell" }) }));
|
|
275
276
|
expect(react_1.screen.getByTestId('bell')).toBeInTheDocument();
|
|
276
277
|
});
|
|
278
|
+
it('renders static and expandable groups from ConversationGroup[]', () => {
|
|
279
|
+
const groups = [
|
|
280
|
+
{
|
|
281
|
+
id: 'pinned',
|
|
282
|
+
label: 'Pinned chats',
|
|
283
|
+
items: initialConversations
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: 'chats',
|
|
287
|
+
label: 'Chats',
|
|
288
|
+
expandable: {
|
|
289
|
+
isExpanded: true,
|
|
290
|
+
onToggle: jest.fn()
|
|
291
|
+
},
|
|
292
|
+
items: [{ id: '2', text: 'Chatbot extension' }]
|
|
293
|
+
}
|
|
294
|
+
];
|
|
295
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: groups }));
|
|
296
|
+
expect(react_1.screen.getByRole('heading', { name: 'Pinned chats', level: 3 })).toBeInTheDocument();
|
|
297
|
+
expect(react_1.screen.queryByRole('button', { name: 'Pinned chats' })).not.toBeInTheDocument();
|
|
298
|
+
expect(react_1.screen.getByRole('button', { name: 'Chats' })).toBeInTheDocument();
|
|
299
|
+
});
|
|
300
|
+
it('labels each MenuList with aria-labelledby referencing its visible label', () => {
|
|
301
|
+
const groups = [
|
|
302
|
+
{
|
|
303
|
+
id: 'pinned',
|
|
304
|
+
label: 'Pinned chats',
|
|
305
|
+
items: initialConversations
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
id: 'chats',
|
|
309
|
+
label: 'Chats',
|
|
310
|
+
expandable: {
|
|
311
|
+
isExpanded: true,
|
|
312
|
+
onToggle: jest.fn()
|
|
313
|
+
},
|
|
314
|
+
items: [{ id: '2', text: 'Chatbot extension' }]
|
|
315
|
+
}
|
|
316
|
+
];
|
|
317
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: groups }));
|
|
318
|
+
const pinnedHeading = react_1.screen.getByRole('heading', { name: 'Pinned chats', level: 3 });
|
|
319
|
+
expect(pinnedHeading).toHaveAttribute('id', 'chatbot-nav-group-pinned-label');
|
|
320
|
+
expect(pinnedHeading.closest('section')).toHaveAttribute('aria-labelledby', 'chatbot-nav-group-pinned-label');
|
|
321
|
+
expect(react_1.screen.getByRole('menu', { name: 'Pinned chats' })).toHaveAttribute('aria-labelledby', 'chatbot-nav-group-pinned-label');
|
|
322
|
+
expect(document.querySelector('ul.pf-v6-c-menu__list[aria-labelledby="chatbot-nav-group-pinned-label"]')).toBeTruthy();
|
|
323
|
+
const chatsToggle = react_1.screen.getByRole('button', { name: 'Chats' });
|
|
324
|
+
expect(chatsToggle).toHaveAttribute('id', 'chatbot-nav-group-chats-toggle');
|
|
325
|
+
expect(react_1.screen.getByRole('menu', { name: 'Chats' })).toHaveAttribute('aria-labelledby', 'chatbot-nav-group-chats-toggle');
|
|
326
|
+
expect(document.querySelector('ul.pf-v6-c-menu__list[aria-labelledby="chatbot-nav-group-chats-toggle"]')).toBeTruthy();
|
|
327
|
+
});
|
|
328
|
+
it('labels grouped object conversations on the menu list element', () => {
|
|
329
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: { Today: initialConversations } }));
|
|
330
|
+
expect(document.querySelector('ul.pf-v6-c-menu__list')).toHaveAttribute('aria-labelledby', 'chatbot-nav-group-Today-label');
|
|
331
|
+
});
|
|
332
|
+
it('labels each menu list in a shared static menu with its group title id', () => {
|
|
333
|
+
const groups = [
|
|
334
|
+
{
|
|
335
|
+
id: 'pinned',
|
|
336
|
+
label: 'Pinned chats',
|
|
337
|
+
items: initialConversations
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
id: 'recent',
|
|
341
|
+
label: 'Recent chats',
|
|
342
|
+
items: [{ id: '2', text: 'Chatbot extension' }]
|
|
343
|
+
}
|
|
344
|
+
];
|
|
345
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: groups }));
|
|
346
|
+
expect(react_1.screen.getByRole('menu', { name: 'Pinned chats' })).toHaveAttribute('aria-labelledby', 'chatbot-nav-group-pinned-label');
|
|
347
|
+
expect(react_1.screen.getByRole('menu', { name: 'Recent chats' })).toHaveAttribute('aria-labelledby', 'chatbot-nav-group-recent-label');
|
|
348
|
+
});
|
|
349
|
+
it('does not autofocus the first menu item when an expandable group is expanded', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
350
|
+
const ExpandableGroupDemo = () => {
|
|
351
|
+
const [isExpanded, setIsExpanded] = (0, react_2.useState)(false);
|
|
352
|
+
return ((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.embedded, setIsDrawerOpen: jest.fn(), conversations: [
|
|
353
|
+
{
|
|
354
|
+
id: 'saved-prompts',
|
|
355
|
+
label: 'Saved prompts',
|
|
356
|
+
expandable: {
|
|
357
|
+
isExpanded,
|
|
358
|
+
onToggle: setIsExpanded
|
|
359
|
+
},
|
|
360
|
+
items: [
|
|
361
|
+
{ id: '7', text: 'Summarize this document' },
|
|
362
|
+
{ id: '8', text: 'Draft a release announcement' }
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
] }));
|
|
366
|
+
};
|
|
367
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ExpandableGroupDemo, {}));
|
|
368
|
+
react_1.fireEvent.click(react_1.screen.getByRole('button', { name: 'Saved prompts' }));
|
|
369
|
+
yield (0, react_1.waitFor)(() => {
|
|
370
|
+
expect(react_1.screen.getByRole('menuitem', { name: 'Summarize this document' })).toBeInTheDocument();
|
|
371
|
+
});
|
|
372
|
+
expect(react_1.screen.getByRole('menuitem', { name: 'Summarize this document' })).not.toHaveFocus();
|
|
373
|
+
}));
|
|
374
|
+
it('does not render a collapsed expandable group menu', () => {
|
|
375
|
+
const groups = [
|
|
376
|
+
{
|
|
377
|
+
id: 'chats',
|
|
378
|
+
label: 'Chats',
|
|
379
|
+
items: [
|
|
380
|
+
{ id: '2', text: 'Chat two' },
|
|
381
|
+
{ id: '3', text: 'Chat three' }
|
|
382
|
+
]
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
id: 'saved-prompts',
|
|
386
|
+
label: 'Saved prompts',
|
|
387
|
+
expandable: {
|
|
388
|
+
isExpanded: false,
|
|
389
|
+
onToggle: jest.fn()
|
|
390
|
+
},
|
|
391
|
+
items: [
|
|
392
|
+
{ id: '7', text: 'Summarize this document' },
|
|
393
|
+
{ id: '8', text: 'Draft a release announcement' }
|
|
394
|
+
]
|
|
395
|
+
}
|
|
396
|
+
];
|
|
397
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.embedded, setIsDrawerOpen: jest.fn(), conversations: groups }));
|
|
398
|
+
// Collapsed expandable groups render their toggle outside the menu and omit the menu
|
|
399
|
+
// entirely until expanded, so their items are not part of arrow-key navigation.
|
|
400
|
+
expect(react_1.screen.getAllByRole('menu')).toHaveLength(1);
|
|
401
|
+
expect(react_1.screen.getByRole('menu', { name: 'Chats' })).toHaveAttribute('aria-labelledby', 'chatbot-nav-group-chats-label');
|
|
402
|
+
expect(react_1.screen.queryByRole('menuitem', { name: 'Summarize this document' })).not.toBeInTheDocument();
|
|
403
|
+
expect(react_1.screen.queryByRole('menuitem', { name: 'Draft a release announcement' })).not.toBeInTheDocument();
|
|
404
|
+
const firstItem = react_1.screen.getByRole('menuitem', { name: 'Chat two' });
|
|
405
|
+
const lastItem = react_1.screen.getByRole('menuitem', { name: 'Chat three' });
|
|
406
|
+
lastItem.focus();
|
|
407
|
+
react_1.fireEvent.keyDown(lastItem, { key: 'ArrowDown' });
|
|
408
|
+
expect(firstItem).toHaveFocus();
|
|
409
|
+
});
|
|
410
|
+
it('collapses and expands a group when expandable.onToggle is called', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
411
|
+
const onToggle = jest.fn();
|
|
412
|
+
const groups = [
|
|
413
|
+
{
|
|
414
|
+
id: 'chats',
|
|
415
|
+
label: 'Chats',
|
|
416
|
+
expandable: {
|
|
417
|
+
isExpanded: true,
|
|
418
|
+
onToggle
|
|
419
|
+
},
|
|
420
|
+
items: [{ id: '2', text: 'Chatbot extension' }]
|
|
421
|
+
}
|
|
422
|
+
];
|
|
423
|
+
const { rerender } = (0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: groups }));
|
|
424
|
+
expect(react_1.screen.getByRole('menuitem', { name: /Chatbot extension/i })).toBeInTheDocument();
|
|
425
|
+
react_1.fireEvent.click(react_1.screen.getByRole('button', { name: 'Chats' }));
|
|
426
|
+
expect(onToggle).toHaveBeenCalledWith(false);
|
|
427
|
+
rerender((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: [
|
|
428
|
+
Object.assign(Object.assign({}, groups[0]), { expandable: {
|
|
429
|
+
isExpanded: false,
|
|
430
|
+
onToggle
|
|
431
|
+
} })
|
|
432
|
+
] }));
|
|
433
|
+
yield (0, react_1.waitFor)(() => {
|
|
434
|
+
expect(react_1.screen.queryByRole('menuitem', { name: /Chatbot extension/i })).not.toBeInTheDocument();
|
|
435
|
+
});
|
|
436
|
+
}));
|
|
437
|
+
it('renders custom menu items and footers supplied in ConversationGroup', () => {
|
|
438
|
+
const groups = [
|
|
439
|
+
{
|
|
440
|
+
id: 'chats',
|
|
441
|
+
label: 'Chats',
|
|
442
|
+
items: [
|
|
443
|
+
initialConversations[0],
|
|
444
|
+
(0, jsx_runtime_1.jsx)(react_core_1.MenuItem, { itemId: "show-all", children: "Show all" }, "show-all")
|
|
445
|
+
],
|
|
446
|
+
footer: (0, jsx_runtime_1.jsx)("div", { "data-testid": "group-footer", children: "Footer content" })
|
|
447
|
+
}
|
|
448
|
+
];
|
|
449
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: groups }));
|
|
450
|
+
expect(react_1.screen.getByRole('menuitem', { name: 'Show all' })).toBeInTheDocument();
|
|
451
|
+
expect(react_1.screen.getByTestId('group-footer')).toBeInTheDocument();
|
|
452
|
+
});
|
|
453
|
+
it('moves focus to the first overflow item when show all is expanded', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
454
|
+
const recentChats = [
|
|
455
|
+
{ id: '2', text: 'Chat two' },
|
|
456
|
+
{ id: '3', text: 'Chat three' },
|
|
457
|
+
{ id: '4', text: 'Chat four' },
|
|
458
|
+
{ id: '5', text: 'Chat five' },
|
|
459
|
+
{ id: '6', text: 'Chat six' }
|
|
460
|
+
];
|
|
461
|
+
const VISIBLE_CHAT_COUNT = 3;
|
|
462
|
+
const ShowAllDemo = () => {
|
|
463
|
+
const [isShowingAllChats, setIsShowingAllChats] = (0, react_2.useState)(false);
|
|
464
|
+
return ((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.embedded, setIsDrawerOpen: jest.fn(), conversations: [
|
|
465
|
+
{
|
|
466
|
+
id: 'chats',
|
|
467
|
+
label: 'Chats',
|
|
468
|
+
items: recentChats,
|
|
469
|
+
showAll: {
|
|
470
|
+
visibleCount: VISIBLE_CHAT_COUNT,
|
|
471
|
+
isExpanded: isShowingAllChats,
|
|
472
|
+
onToggle: setIsShowingAllChats
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
] }));
|
|
476
|
+
};
|
|
477
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ShowAllDemo, {}));
|
|
478
|
+
react_1.fireEvent.click(react_1.screen.getByRole('menuitem', { name: /Show all/i }));
|
|
479
|
+
yield (0, react_1.waitFor)(() => {
|
|
480
|
+
expect(react_1.screen.getByRole('menuitem', { name: 'Chat five' })).toHaveFocus();
|
|
481
|
+
});
|
|
482
|
+
}));
|
|
483
|
+
it('keeps focus on the toggle when show all is collapsed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
484
|
+
const recentChats = [
|
|
485
|
+
{ id: '2', text: 'Chat two' },
|
|
486
|
+
{ id: '3', text: 'Chat three' },
|
|
487
|
+
{ id: '4', text: 'Chat four' },
|
|
488
|
+
{ id: '5', text: 'Chat five' }
|
|
489
|
+
];
|
|
490
|
+
const ShowAllDemo = () => {
|
|
491
|
+
const [isShowingAllChats, setIsShowingAllChats] = (0, react_2.useState)(true);
|
|
492
|
+
return ((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.embedded, setIsDrawerOpen: jest.fn(), conversations: [
|
|
493
|
+
{
|
|
494
|
+
id: 'chats',
|
|
495
|
+
label: 'Chats',
|
|
496
|
+
items: recentChats,
|
|
497
|
+
showAll: {
|
|
498
|
+
visibleCount: 2,
|
|
499
|
+
isExpanded: isShowingAllChats,
|
|
500
|
+
onToggle: setIsShowingAllChats
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
] }));
|
|
504
|
+
};
|
|
505
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ShowAllDemo, {}));
|
|
506
|
+
react_1.fireEvent.click(react_1.screen.getByRole('menuitem', { name: 'Show less' }));
|
|
507
|
+
yield (0, react_1.waitFor)(() => {
|
|
508
|
+
expect(react_1.screen.getByRole('menuitem', { name: /Show all/i })).toHaveFocus();
|
|
509
|
+
});
|
|
510
|
+
}));
|
|
511
|
+
it('renders overflow items above the show all toggle so it stays in a fixed position', () => {
|
|
512
|
+
const recentChats = [
|
|
513
|
+
{ id: '2', text: 'Chat two' },
|
|
514
|
+
{ id: '3', text: 'Chat three' },
|
|
515
|
+
{ id: '4', text: 'Chat four' }
|
|
516
|
+
];
|
|
517
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.embedded, setIsDrawerOpen: jest.fn(), conversations: [
|
|
518
|
+
{
|
|
519
|
+
id: 'chats',
|
|
520
|
+
label: 'Chats',
|
|
521
|
+
items: recentChats,
|
|
522
|
+
showAll: {
|
|
523
|
+
visibleCount: 1,
|
|
524
|
+
isExpanded: true,
|
|
525
|
+
onToggle: jest.fn()
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
] }));
|
|
529
|
+
const overflowItem = react_1.screen.getByRole('menuitem', { name: 'Chat four' });
|
|
530
|
+
const toggle = react_1.screen.getByRole('menuitem', { name: 'Show less' });
|
|
531
|
+
const isToggleAfterOverflowItem = Boolean(
|
|
532
|
+
// eslint-disable-next-line no-bitwise
|
|
533
|
+
overflowItem.compareDocumentPosition(toggle) & Node.DOCUMENT_POSITION_FOLLOWING);
|
|
534
|
+
expect(isToggleAfterOverflowItem).toBe(true);
|
|
535
|
+
});
|
|
536
|
+
it('allows arrow key navigation to flow through the show all toggle like any other menu item', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
537
|
+
const recentChats = [
|
|
538
|
+
{ id: '2', text: 'Chat two' },
|
|
539
|
+
{ id: '3', text: 'Chat three' },
|
|
540
|
+
{ id: '4', text: 'Chat four' }
|
|
541
|
+
];
|
|
542
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.embedded, setIsDrawerOpen: jest.fn(), conversations: [
|
|
543
|
+
{
|
|
544
|
+
id: 'chats',
|
|
545
|
+
label: 'Chats',
|
|
546
|
+
items: recentChats,
|
|
547
|
+
showAll: {
|
|
548
|
+
visibleCount: 2,
|
|
549
|
+
isExpanded: false,
|
|
550
|
+
onToggle: jest.fn()
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
] }));
|
|
554
|
+
const lastVisibleItem = react_1.screen.getByRole('menuitem', { name: 'Chat three' });
|
|
555
|
+
const toggle = react_1.screen.getByRole('menuitem', { name: /Show all/i });
|
|
556
|
+
lastVisibleItem.focus();
|
|
557
|
+
react_1.fireEvent.keyDown(lastVisibleItem, { key: 'ArrowDown' });
|
|
558
|
+
yield (0, react_1.waitFor)(() => {
|
|
559
|
+
expect(toggle).toHaveFocus();
|
|
560
|
+
});
|
|
561
|
+
react_1.fireEvent.keyDown(toggle, { key: 'ArrowUp' });
|
|
562
|
+
yield (0, react_1.waitFor)(() => {
|
|
563
|
+
expect(lastVisibleItem).toHaveFocus();
|
|
564
|
+
});
|
|
565
|
+
}));
|
|
566
|
+
it('preserves custom menu item identity when preceding items change', () => {
|
|
567
|
+
const renderShowAll = () => ((0, jsx_runtime_1.jsx)(react_core_1.MenuItem, { itemId: "show-all-chats", children: "Show all" }, "show-all-chats"));
|
|
568
|
+
const { rerender } = (0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: [
|
|
569
|
+
{
|
|
570
|
+
id: 'chats',
|
|
571
|
+
label: 'Chats',
|
|
572
|
+
items: [initialConversations[0], renderShowAll()]
|
|
573
|
+
}
|
|
574
|
+
] }));
|
|
575
|
+
const showAllBefore = react_1.screen.getByRole('menuitem', { name: 'Show all' });
|
|
576
|
+
rerender((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: [
|
|
577
|
+
{
|
|
578
|
+
id: 'chats',
|
|
579
|
+
label: 'Chats',
|
|
580
|
+
items: [initialConversations[0], initialConversations[1], initialConversations[2], renderShowAll()]
|
|
581
|
+
}
|
|
582
|
+
] }));
|
|
583
|
+
expect(react_1.screen.getByRole('menuitem', { name: 'Show all' })).toBe(showAllBefore);
|
|
584
|
+
});
|
|
585
|
+
it('passes expandableSectionProps from expandable ConversationGroup', () => {
|
|
586
|
+
const groups = [
|
|
587
|
+
{
|
|
588
|
+
id: 'saved',
|
|
589
|
+
label: 'Saved prompts',
|
|
590
|
+
expandable: {
|
|
591
|
+
isExpanded: true,
|
|
592
|
+
onToggle: jest.fn(),
|
|
593
|
+
expandableSectionProps: { className: 'test-expandable-section' }
|
|
594
|
+
},
|
|
595
|
+
items: initialConversations
|
|
596
|
+
}
|
|
597
|
+
];
|
|
598
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryNav_1.default, { onDrawerToggle: onDrawerToggle, isDrawerOpen: true, displayMode: Chatbot_1.ChatbotDisplayMode.fullscreen, setIsDrawerOpen: jest.fn(), conversations: groups }));
|
|
599
|
+
expect(document.querySelector('.test-expandable-section')).toBeInTheDocument();
|
|
600
|
+
});
|
|
277
601
|
});
|
|
@@ -27,6 +27,8 @@ export interface MessageBoxProps extends HTMLProps<HTMLDivElement> {
|
|
|
27
27
|
jumpButtonTopTooltipProps?: TooltipProps;
|
|
28
28
|
/** Props passed to top jump button tooltip */
|
|
29
29
|
jumpButtonBottomTooltipProps?: TooltipProps;
|
|
30
|
+
/** Whether scrollbar is on the page or message box in embedded mode */
|
|
31
|
+
hasOuterScrollbar?: boolean;
|
|
30
32
|
}
|
|
31
33
|
export interface MessageBoxHandle extends HTMLDivElement {
|
|
32
34
|
/** Scrolls to the top of the message box */
|
|
@@ -22,6 +22,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
22
22
|
const react_1 = require("react");
|
|
23
23
|
const JumpButton_1 = __importDefault(require("./JumpButton"));
|
|
24
24
|
const react_core_1 = require("@patternfly/react-core");
|
|
25
|
+
const react_styles_1 = require("@patternfly/react-styles");
|
|
25
26
|
const SCROLL_EDGE_DELTA = 100;
|
|
26
27
|
const getScrollMetrics = (element) => {
|
|
27
28
|
const { scrollTop, scrollHeight, clientHeight } = element;
|
|
@@ -46,7 +47,7 @@ const shouldFollowContentGrowth = (metrics, enableSmartScroll, autoScroll, pause
|
|
|
46
47
|
metrics.nearBottom ||
|
|
47
48
|
(enableSmartScroll && autoScroll && !pauseAutoScroll && metrics.roundedScrollTop > 0);
|
|
48
49
|
exports.MessageBox = (0, react_1.forwardRef)((_a, ref) => {
|
|
49
|
-
var { announcement, ariaLabel = 'Scrollable message log', children, className, position = 'top', onScrollToTopClick, onScrollToBottomClick, enableSmartScroll = false, jumpButtonTopProps, jumpButtonBottomProps, jumpButtonBottomTooltipProps, jumpButtonTopTooltipProps } = _a, props = __rest(_a, ["announcement", "ariaLabel", "children", "className", "position", "onScrollToTopClick", "onScrollToBottomClick", "enableSmartScroll", "jumpButtonTopProps", "jumpButtonBottomProps", "jumpButtonBottomTooltipProps", "jumpButtonTopTooltipProps"]);
|
|
50
|
+
var { announcement, ariaLabel = 'Scrollable message log', children, className, position = 'top', onScrollToTopClick, onScrollToBottomClick, enableSmartScroll = false, jumpButtonTopProps, jumpButtonBottomProps, jumpButtonBottomTooltipProps, jumpButtonTopTooltipProps, hasOuterScrollbar = false } = _a, props = __rest(_a, ["announcement", "ariaLabel", "children", "className", "position", "onScrollToTopClick", "onScrollToBottomClick", "enableSmartScroll", "jumpButtonTopProps", "jumpButtonBottomProps", "jumpButtonBottomTooltipProps", "jumpButtonTopTooltipProps", "hasOuterScrollbar"]);
|
|
50
51
|
const [atTop, setAtTop] = (0, react_1.useState)(true);
|
|
51
52
|
const [atBottom, setAtBottom] = (0, react_1.useState)(false);
|
|
52
53
|
const [isOverflowing, setIsOverflowing] = (0, react_1.useState)(false);
|
|
@@ -257,6 +258,6 @@ exports.MessageBox = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
257
258
|
onTouchMove,
|
|
258
259
|
onTouchEnd
|
|
259
260
|
};
|
|
260
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "top", isHidden: !hasMeasuredScroll || (isOverflowing && atTop), onClick: scrollToTop, jumpButtonProps: jumpButtonTopProps, jumpButtonTooltipProps: jumpButtonTopTooltipProps }), (0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "bottom", isHidden: !hasMeasuredScroll || (isOverflowing && atBottom), onClick: () => scrollToBottom({ resumeSmartScroll: true }), jumpButtonProps: jumpButtonBottomProps, jumpButtonTooltipProps: jumpButtonBottomTooltipProps }), (0, jsx_runtime_1.jsxs)("div", Object.assign({ role: "region", tabIndex: 0, "aria-label": ariaLabel, className:
|
|
261
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "top", isHidden: !hasMeasuredScroll || (isOverflowing && atTop), onClick: scrollToTop, jumpButtonProps: jumpButtonTopProps, jumpButtonTooltipProps: jumpButtonTopTooltipProps }), (0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "bottom", isHidden: !hasMeasuredScroll || (isOverflowing && atBottom), onClick: () => scrollToBottom({ resumeSmartScroll: true }), jumpButtonProps: jumpButtonBottomProps, jumpButtonTooltipProps: jumpButtonBottomTooltipProps }), (0, jsx_runtime_1.jsxs)("div", Object.assign({ role: "region", tabIndex: 0, "aria-label": ariaLabel, className: (0, react_styles_1.css)('pf-chatbot__messagebox', position === 'bottom' && 'pf-chatbot__messagebox--bottom', hasOuterScrollbar && 'pf-chatbot__messagebox--outer-scroll', className), ref: messageBoxRef }, props, (enableSmartScroll ? Object.assign({}, smartScrollHandlers) : {}), { children: [children, (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__messagebox-announcement pf-chatbot-m-hidden", "aria-live": "polite", children: announcement })] }))] }));
|
|
261
262
|
});
|
|
262
263
|
exports.default = exports.MessageBox;
|