@pega/cosmos-react-social 5.0.0-dev.4.9 → 5.0.0-dev.6.0
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/lib/components/Autopilot/Autopilot.d.ts +15 -0
- package/lib/components/Autopilot/Autopilot.d.ts.map +1 -0
- package/lib/components/Autopilot/Autopilot.js +50 -0
- package/lib/components/Autopilot/Autopilot.js.map +1 -0
- package/lib/components/Autopilot/AutopilotAvatar.d.ts +8 -0
- package/lib/components/Autopilot/AutopilotAvatar.d.ts.map +1 -0
- package/lib/components/Autopilot/AutopilotAvatar.js +14 -0
- package/lib/components/Autopilot/AutopilotAvatar.js.map +1 -0
- package/lib/components/Autopilot/AutopilotGlimpse.d.ts +9 -0
- package/lib/components/Autopilot/AutopilotGlimpse.d.ts.map +1 -0
- package/lib/components/Autopilot/AutopilotGlimpse.js +26 -0
- package/lib/components/Autopilot/AutopilotGlimpse.js.map +1 -0
- package/lib/components/Autopilot/index.d.ts +4 -0
- package/lib/components/Autopilot/index.d.ts.map +1 -0
- package/lib/components/Autopilot/index.js +3 -0
- package/lib/components/Autopilot/index.js.map +1 -0
- package/lib/components/Chat/Chat.d.ts +29 -0
- package/lib/components/Chat/Chat.d.ts.map +1 -0
- package/lib/components/Chat/Chat.js +64 -0
- package/lib/components/Chat/Chat.js.map +1 -0
- package/lib/components/Chat/Chat.types.d.ts +136 -0
- package/lib/components/Chat/Chat.types.d.ts.map +1 -0
- package/lib/components/Chat/Chat.types.js +10 -0
- package/lib/components/Chat/Chat.types.js.map +1 -0
- package/lib/components/Chat/ChatBody.d.ts +7 -0
- package/lib/components/Chat/ChatBody.d.ts.map +1 -0
- package/lib/components/Chat/ChatBody.js +292 -0
- package/lib/components/Chat/ChatBody.js.map +1 -0
- package/lib/components/Chat/ChatComposer.d.ts +50 -0
- package/lib/components/Chat/ChatComposer.d.ts.map +1 -0
- package/lib/components/Chat/ChatComposer.js +221 -0
- package/lib/components/Chat/ChatComposer.js.map +1 -0
- package/lib/components/Chat/ChatHeader.d.ts +29 -0
- package/lib/components/Chat/ChatHeader.d.ts.map +1 -0
- package/lib/components/Chat/ChatHeader.js +44 -0
- package/lib/components/Chat/ChatHeader.js.map +1 -0
- package/lib/components/Chat/ChatSettingsPanel.d.ts +7 -0
- package/lib/components/Chat/ChatSettingsPanel.d.ts.map +1 -0
- package/lib/components/Chat/ChatSettingsPanel.js +15 -0
- package/lib/components/Chat/ChatSettingsPanel.js.map +1 -0
- package/lib/components/Chat/ChatSettingsPanel.styles.d.ts +16 -0
- package/lib/components/Chat/ChatSettingsPanel.styles.d.ts.map +1 -0
- package/lib/components/Chat/ChatSettingsPanel.styles.js +47 -0
- package/lib/components/Chat/ChatSettingsPanel.styles.js.map +1 -0
- package/lib/components/Chat/Message.d.ts +6 -0
- package/lib/components/Chat/Message.d.ts.map +1 -0
- package/lib/components/Chat/Message.js +98 -0
- package/lib/components/Chat/Message.js.map +1 -0
- package/lib/components/Chat/Message.styles.d.ts +43 -0
- package/lib/components/Chat/Message.styles.d.ts.map +1 -0
- package/lib/components/Chat/Message.styles.js +270 -0
- package/lib/components/Chat/Message.styles.js.map +1 -0
- package/lib/components/Chat/SuggestedReplyPicker.d.ts +42 -0
- package/lib/components/Chat/SuggestedReplyPicker.d.ts.map +1 -0
- package/lib/components/Chat/SuggestedReplyPicker.js +143 -0
- package/lib/components/Chat/SuggestedReplyPicker.js.map +1 -0
- package/lib/components/Chat/SystemMessage.d.ts +8 -0
- package/lib/components/Chat/SystemMessage.d.ts.map +1 -0
- package/lib/components/Chat/SystemMessage.js +50 -0
- package/lib/components/Chat/SystemMessage.js.map +1 -0
- package/lib/components/Chat/TypeIndicator.d.ts +6 -0
- package/lib/components/Chat/TypeIndicator.d.ts.map +1 -0
- package/lib/components/Chat/TypeIndicator.js +12 -0
- package/lib/components/Chat/TypeIndicator.js.map +1 -0
- package/lib/components/Chat/index.d.ts +15 -0
- package/lib/components/Chat/index.d.ts.map +1 -0
- package/lib/components/Chat/index.js +10 -0
- package/lib/components/Chat/index.js.map +1 -0
- package/lib/components/Email/ContextMenuPopover.d.ts +5 -0
- package/lib/components/Email/ContextMenuPopover.d.ts.map +1 -0
- package/lib/components/Email/ContextMenuPopover.js +53 -0
- package/lib/components/Email/ContextMenuPopover.js.map +1 -0
- package/lib/components/Email/Email.d.ts +7 -0
- package/lib/components/Email/Email.d.ts.map +1 -0
- package/lib/components/Email/Email.js +248 -0
- package/lib/components/Email/Email.js.map +1 -0
- package/lib/components/Email/Email.styles.d.ts +67 -0
- package/lib/components/Email/Email.styles.d.ts.map +1 -0
- package/lib/components/Email/Email.styles.js +392 -0
- package/lib/components/Email/Email.styles.js.map +1 -0
- package/lib/components/Email/Email.types.d.ts +394 -0
- package/lib/components/Email/Email.types.d.ts.map +1 -0
- package/lib/components/Email/Email.types.js +2 -0
- package/lib/components/Email/Email.types.js.map +1 -0
- package/lib/components/Email/EmailCaseView.d.ts +6 -0
- package/lib/components/Email/EmailCaseView.d.ts.map +1 -0
- package/lib/components/Email/EmailCaseView.js +36 -0
- package/lib/components/Email/EmailCaseView.js.map +1 -0
- package/lib/components/Email/EmailComposer.d.ts +10 -0
- package/lib/components/Email/EmailComposer.d.ts.map +1 -0
- package/lib/components/Email/EmailComposer.js +255 -0
- package/lib/components/Email/EmailComposer.js.map +1 -0
- package/lib/components/Email/EmailConversation.d.ts +17 -0
- package/lib/components/Email/EmailConversation.d.ts.map +1 -0
- package/lib/components/Email/EmailConversation.js +174 -0
- package/lib/components/Email/EmailConversation.js.map +1 -0
- package/lib/components/Email/EmailEntity.d.ts +11 -0
- package/lib/components/Email/EmailEntity.d.ts.map +1 -0
- package/lib/components/Email/EmailEntity.js +51 -0
- package/lib/components/Email/EmailEntity.js.map +1 -0
- package/lib/components/Email/EmailManager.d.ts +6 -0
- package/lib/components/Email/EmailManager.d.ts.map +1 -0
- package/lib/components/Email/EmailManager.js +21 -0
- package/lib/components/Email/EmailManager.js.map +1 -0
- package/lib/components/Email/EmailNotificationPanel.d.ts +7 -0
- package/lib/components/Email/EmailNotificationPanel.d.ts.map +1 -0
- package/lib/components/Email/EmailNotificationPanel.js +15 -0
- package/lib/components/Email/EmailNotificationPanel.js.map +1 -0
- package/lib/components/Email/EmailSelector.d.ts +22 -0
- package/lib/components/Email/EmailSelector.d.ts.map +1 -0
- package/lib/components/Email/EmailSelector.js +121 -0
- package/lib/components/Email/EmailSelector.js.map +1 -0
- package/lib/components/Email/EmailShell.d.ts +6 -0
- package/lib/components/Email/EmailShell.d.ts.map +1 -0
- package/lib/components/Email/EmailShell.js +46 -0
- package/lib/components/Email/EmailShell.js.map +1 -0
- package/lib/components/Email/EmailSummaryItem.d.ts +11 -0
- package/lib/components/Email/EmailSummaryItem.d.ts.map +1 -0
- package/lib/components/Email/EmailSummaryItem.js +118 -0
- package/lib/components/Email/EmailSummaryItem.js.map +1 -0
- package/lib/components/Email/EmailSummaryList.d.ts +6 -0
- package/lib/components/Email/EmailSummaryList.d.ts.map +1 -0
- package/lib/components/Email/EmailSummaryList.js +95 -0
- package/lib/components/Email/EmailSummaryList.js.map +1 -0
- package/lib/components/Email/EntityList.d.ts +6 -0
- package/lib/components/Email/EntityList.d.ts.map +1 -0
- package/lib/components/Email/EntityList.js +74 -0
- package/lib/components/Email/EntityList.js.map +1 -0
- package/lib/components/Email/index.d.ts +13 -0
- package/lib/components/Email/index.d.ts.map +1 -0
- package/lib/components/Email/index.js +12 -0
- package/lib/components/Email/index.js.map +1 -0
- package/lib/components/Email/utils/EntityHighlighter.d.ts +10 -0
- package/lib/components/Email/utils/EntityHighlighter.d.ts.map +1 -0
- package/lib/components/Email/utils/EntityHighlighter.js +260 -0
- package/lib/components/Email/utils/EntityHighlighter.js.map +1 -0
- package/lib/components/Feed/Feed.context.d.ts +56 -0
- package/lib/components/Feed/Feed.context.d.ts.map +1 -0
- package/lib/components/Feed/Feed.context.js +14 -0
- package/lib/components/Feed/Feed.context.js.map +1 -0
- package/lib/components/Feed/Feed.d.ts +6 -0
- package/lib/components/Feed/Feed.d.ts.map +1 -0
- package/lib/components/Feed/Feed.js +152 -0
- package/lib/components/Feed/Feed.js.map +1 -0
- package/lib/components/Feed/Feed.types.d.ts +334 -0
- package/lib/components/Feed/Feed.types.d.ts.map +1 -0
- package/lib/components/Feed/Feed.types.js +2 -0
- package/lib/components/Feed/Feed.types.js.map +1 -0
- package/lib/components/Feed/FeedAnnouncer.d.ts +6 -0
- package/lib/components/Feed/FeedAnnouncer.d.ts.map +1 -0
- package/lib/components/Feed/FeedAnnouncer.js +11 -0
- package/lib/components/Feed/FeedAnnouncer.js.map +1 -0
- package/lib/components/Feed/FeedAttachments.d.ts +8 -0
- package/lib/components/Feed/FeedAttachments.d.ts.map +1 -0
- package/lib/components/Feed/FeedAttachments.js +97 -0
- package/lib/components/Feed/FeedAttachments.js.map +1 -0
- package/lib/components/Feed/FeedButton.d.ts +5 -0
- package/lib/components/Feed/FeedButton.d.ts.map +1 -0
- package/lib/components/Feed/FeedButton.js +15 -0
- package/lib/components/Feed/FeedButton.js.map +1 -0
- package/lib/components/Feed/FeedContent.d.ts +6 -0
- package/lib/components/Feed/FeedContent.d.ts.map +1 -0
- package/lib/components/Feed/FeedContent.js +46 -0
- package/lib/components/Feed/FeedContent.js.map +1 -0
- package/lib/components/Feed/FeedContentFooter.d.ts +5 -0
- package/lib/components/Feed/FeedContentFooter.d.ts.map +1 -0
- package/lib/components/Feed/FeedContentFooter.js +34 -0
- package/lib/components/Feed/FeedContentFooter.js.map +1 -0
- package/lib/components/Feed/FeedContentHeader.d.ts +5 -0
- package/lib/components/Feed/FeedContentHeader.d.ts.map +1 -0
- package/lib/components/Feed/FeedContentHeader.js +107 -0
- package/lib/components/Feed/FeedContentHeader.js.map +1 -0
- package/lib/components/Feed/FeedEditRegion.d.ts +6 -0
- package/lib/components/Feed/FeedEditRegion.d.ts.map +1 -0
- package/lib/components/Feed/FeedEditRegion.js +57 -0
- package/lib/components/Feed/FeedEditRegion.js.map +1 -0
- package/lib/components/Feed/FeedInputRegion.d.ts +8 -0
- package/lib/components/Feed/FeedInputRegion.d.ts.map +1 -0
- package/lib/components/Feed/FeedInputRegion.js +86 -0
- package/lib/components/Feed/FeedInputRegion.js.map +1 -0
- package/lib/components/Feed/FeedLikeButton.d.ts +7 -0
- package/lib/components/Feed/FeedLikeButton.d.ts.map +1 -0
- package/lib/components/Feed/FeedLikeButton.js +102 -0
- package/lib/components/Feed/FeedLikeButton.js.map +1 -0
- package/lib/components/Feed/FeedModalList.d.ts +5 -0
- package/lib/components/Feed/FeedModalList.d.ts.map +1 -0
- package/lib/components/Feed/FeedModalList.js +39 -0
- package/lib/components/Feed/FeedModalList.js.map +1 -0
- package/lib/components/Feed/FeedNewPost.d.ts +7 -0
- package/lib/components/Feed/FeedNewPost.d.ts.map +1 -0
- package/lib/components/Feed/FeedNewPost.js +30 -0
- package/lib/components/Feed/FeedNewPost.js.map +1 -0
- package/lib/components/Feed/FeedNewPostTypeMenu.d.ts +6 -0
- package/lib/components/Feed/FeedNewPostTypeMenu.d.ts.map +1 -0
- package/lib/components/Feed/FeedNewPostTypeMenu.js +375 -0
- package/lib/components/Feed/FeedNewPostTypeMenu.js.map +1 -0
- package/lib/components/Feed/FeedPost.d.ts +8 -0
- package/lib/components/Feed/FeedPost.d.ts.map +1 -0
- package/lib/components/Feed/FeedPost.js +85 -0
- package/lib/components/Feed/FeedPost.js.map +1 -0
- package/lib/components/Feed/FeedReply.d.ts +7 -0
- package/lib/components/Feed/FeedReply.d.ts.map +1 -0
- package/lib/components/Feed/FeedReply.js +36 -0
- package/lib/components/Feed/FeedReply.js.map +1 -0
- package/lib/components/Feed/FeedReplyInput.d.ts +7 -0
- package/lib/components/Feed/FeedReplyInput.d.ts.map +1 -0
- package/lib/components/Feed/FeedReplyInput.js +21 -0
- package/lib/components/Feed/FeedReplyInput.js.map +1 -0
- package/lib/components/Feed/FeedRichText.d.ts +6 -0
- package/lib/components/Feed/FeedRichText.d.ts.map +1 -0
- package/lib/components/Feed/FeedRichText.js +155 -0
- package/lib/components/Feed/FeedRichText.js.map +1 -0
- package/lib/components/Feed/index.d.ts +7 -0
- package/lib/components/Feed/index.d.ts.map +1 -0
- package/lib/components/Feed/index.js +6 -0
- package/lib/components/Feed/index.js.map +1 -0
- package/lib/components/HashtagButton/HashtagButton.d.ts +23 -0
- package/lib/components/HashtagButton/HashtagButton.d.ts.map +1 -0
- package/lib/components/HashtagButton/HashtagButton.js +44 -0
- package/lib/components/HashtagButton/HashtagButton.js.map +1 -0
- package/lib/components/HashtagButton/index.d.ts +3 -0
- package/lib/components/HashtagButton/index.d.ts.map +1 -0
- package/lib/components/HashtagButton/index.js +3 -0
- package/lib/components/HashtagButton/index.js.map +1 -0
- package/lib/components/MentionButton/MentionButton.d.ts +35 -0
- package/lib/components/MentionButton/MentionButton.d.ts.map +1 -0
- package/lib/components/MentionButton/MentionButton.js +94 -0
- package/lib/components/MentionButton/MentionButton.js.map +1 -0
- package/lib/components/MentionButton/index.d.ts +3 -0
- package/lib/components/MentionButton/index.d.ts.map +1 -0
- package/lib/components/MentionButton/index.js +3 -0
- package/lib/components/MentionButton/index.js.map +1 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +13 -0
- package/lib/index.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createElement as _createElement } from "react";
|
|
3
|
+
import { forwardRef, useCallback, useImperativeHandle, useLayoutEffect, useRef, useState, useEffect } from 'react';
|
|
4
|
+
import styled, { css } from 'styled-components';
|
|
5
|
+
import { Button, debounce, defaultThemeProp, Flex, getFocusables, Icon, Progress, registerIcon, StyledButton, useAfterInitialEffect, useArrows, useI18n, useItemIntersection, useLiveLog, useOuterEvent } from '@pega/cosmos-react-core';
|
|
6
|
+
import * as caretDownIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/caret-down.icon';
|
|
7
|
+
import { isMessageListItem, isSystemMessageListItem, isTypeIndicatorListItem } from './Chat.types';
|
|
8
|
+
import Message from './Message';
|
|
9
|
+
import SystemMessage, { StyledSystemMessage } from './SystemMessage';
|
|
10
|
+
import TypeIndicator from './TypeIndicator';
|
|
11
|
+
import { StyledMessageContainer } from './Message.styles';
|
|
12
|
+
registerIcon(caretDownIcon);
|
|
13
|
+
export const NewMessageSeparatorId = 'new-message-separator';
|
|
14
|
+
const StyledMessageList = styled.ul ``;
|
|
15
|
+
const StyledSession = styled.div ``;
|
|
16
|
+
const StyledChatBody = styled.div(props => {
|
|
17
|
+
const { theme: { base: { shadow: { high: shadowHigh, focus }, colors: { white }, spacing, palette } } } = props;
|
|
18
|
+
return css `
|
|
19
|
+
position: relative;
|
|
20
|
+
overflow-y: hidden;
|
|
21
|
+
|
|
22
|
+
> ${StyledButton} {
|
|
23
|
+
position: absolute;
|
|
24
|
+
bottom: 0;
|
|
25
|
+
left: 50%;
|
|
26
|
+
transform: translate(-50%, -50%);
|
|
27
|
+
box-shadow: ${shadowHigh};
|
|
28
|
+
z-index: 1;
|
|
29
|
+
background-color: ${white};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
> ${StyledMessageList} {
|
|
33
|
+
overflow-y: auto;
|
|
34
|
+
list-style-type: none;
|
|
35
|
+
height: 100%;
|
|
36
|
+
|
|
37
|
+
> ${StyledSession} {
|
|
38
|
+
padding-inline: 1rem;
|
|
39
|
+
:not(:last-child) {
|
|
40
|
+
border-block-end: 0.0625rem solid ${palette['border-line']};
|
|
41
|
+
margin-block-end: ${spacing};
|
|
42
|
+
padding-block-end: ${spacing};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
${StyledMessageContainer}, ${StyledSystemMessage} {
|
|
46
|
+
&:focus-visible {
|
|
47
|
+
box-shadow: ${focus};
|
|
48
|
+
outline: none;
|
|
49
|
+
}
|
|
50
|
+
margin-block-end: ${spacing};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
});
|
|
55
|
+
StyledChatBody.defaultProps = defaultThemeProp;
|
|
56
|
+
const ScrollToLatest = props => {
|
|
57
|
+
const { scrollContainerRef } = props;
|
|
58
|
+
useLayoutEffect(() => {
|
|
59
|
+
if (scrollContainerRef.current) {
|
|
60
|
+
scrollContainerRef.current.scrollTop =
|
|
61
|
+
scrollContainerRef.current.scrollHeight - scrollContainerRef.current.offsetHeight;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return null;
|
|
65
|
+
};
|
|
66
|
+
const ChatMessage = ({ agentSerial, message }) => {
|
|
67
|
+
const getAgentVariant = (senderId, senderType, direction = 'in') => {
|
|
68
|
+
if (agentSerial.current === null)
|
|
69
|
+
return undefined;
|
|
70
|
+
if (senderType === 'agent' && direction === 'in') {
|
|
71
|
+
if (agentSerial.current[senderId] !== undefined) {
|
|
72
|
+
return agentSerial.current[senderId];
|
|
73
|
+
}
|
|
74
|
+
const nextIndex = Object.entries(agentSerial.current).length;
|
|
75
|
+
agentSerial.current[senderId] = nextIndex;
|
|
76
|
+
return nextIndex;
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
};
|
|
80
|
+
if (isMessageListItem(message)) {
|
|
81
|
+
const { id, senderType, direction, senderId } = message;
|
|
82
|
+
const agentVariant = getAgentVariant(senderId, senderType, direction);
|
|
83
|
+
return _createElement(Message, { ...message, key: id, agentVariant: agentVariant });
|
|
84
|
+
}
|
|
85
|
+
if (isSystemMessageListItem(message)) {
|
|
86
|
+
const extraProps = {};
|
|
87
|
+
const { id } = message;
|
|
88
|
+
if (id === NewMessageSeparatorId) {
|
|
89
|
+
extraProps['data-new-message-separator'] = NewMessageSeparatorId;
|
|
90
|
+
}
|
|
91
|
+
return _createElement(SystemMessage, { ...message, key: id, ...extraProps });
|
|
92
|
+
}
|
|
93
|
+
if (isTypeIndicatorListItem(message)) {
|
|
94
|
+
const { id, senderId, senderType } = message;
|
|
95
|
+
const agentVariant = getAgentVariant(senderId, senderType);
|
|
96
|
+
return _createElement(TypeIndicator, { ...message, key: id, agentVariant: agentVariant });
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
};
|
|
100
|
+
const ChatBody = forwardRef(function ChatBody(props, ref) {
|
|
101
|
+
const { transcripts, liveChat, unreadMessageCount = 0, onScrollToButtonClick, handle, loading, offset = -1, loadMore, ...restProps } = props;
|
|
102
|
+
const t = useI18n();
|
|
103
|
+
const conversationRef = useRef(null);
|
|
104
|
+
const scrollRef = useRef(false);
|
|
105
|
+
const buttonRef = useRef(null);
|
|
106
|
+
const focusInMessageList = useRef(false);
|
|
107
|
+
const initialMessageListFocused = useRef(false);
|
|
108
|
+
const elementRef = useRef(null);
|
|
109
|
+
const activeElementIndex = useRef(-1);
|
|
110
|
+
const agentSerial = useRef({});
|
|
111
|
+
const [messageList, setMessageList] = useState([]);
|
|
112
|
+
const [verticalNav, setVerticalNav] = useState(true);
|
|
113
|
+
const { announcePolite } = useLiveLog();
|
|
114
|
+
const isScrolledToLatest = useCallback(() => {
|
|
115
|
+
return conversationRef.current
|
|
116
|
+
? conversationRef.current.scrollHeight -
|
|
117
|
+
conversationRef.current.scrollTop -
|
|
118
|
+
conversationRef.current.offsetHeight <=
|
|
119
|
+
30
|
|
120
|
+
: true;
|
|
121
|
+
}, [conversationRef.current]);
|
|
122
|
+
const scrollToLatestMessage = useCallback(() => {
|
|
123
|
+
if (conversationRef.current) {
|
|
124
|
+
conversationRef.current.scrollTop = conversationRef.current.scrollHeight;
|
|
125
|
+
focusInMessageList.current = true;
|
|
126
|
+
messageList[messageList.length - 1]?.focus();
|
|
127
|
+
activeElementIndex.current = messageList.length - 1;
|
|
128
|
+
}
|
|
129
|
+
}, [conversationRef.current, messageList]);
|
|
130
|
+
const [displayScrollLatest, setDisplayScrollToLatest] = useState(false);
|
|
131
|
+
const onMessageListScroll = useCallback(() => {
|
|
132
|
+
const isScrolled = isScrolledToLatest();
|
|
133
|
+
if (scrollRef.current && isScrolled) {
|
|
134
|
+
scrollRef.current = false;
|
|
135
|
+
if (unreadMessageCount) {
|
|
136
|
+
onScrollToButtonClick?.();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!scrollRef.current && !isScrolled) {
|
|
140
|
+
scrollRef.current = true;
|
|
141
|
+
}
|
|
142
|
+
setDisplayScrollToLatest(!isScrolled);
|
|
143
|
+
}, [isScrolledToLatest(), unreadMessageCount]);
|
|
144
|
+
const scrollToNewMessage = useCallback(() => {
|
|
145
|
+
if (conversationRef.current) {
|
|
146
|
+
setDisplayScrollToLatest(false);
|
|
147
|
+
const newMessageIndicatorEle = conversationRef.current.querySelector('[data-new-message-separator]');
|
|
148
|
+
if (newMessageIndicatorEle &&
|
|
149
|
+
newMessageIndicatorEle.nextElementSibling instanceof HTMLElement) {
|
|
150
|
+
conversationRef.current.scrollTop = newMessageIndicatorEle.offsetTop;
|
|
151
|
+
focusInMessageList.current = true;
|
|
152
|
+
newMessageIndicatorEle.nextElementSibling.focus();
|
|
153
|
+
activeElementIndex.current =
|
|
154
|
+
messageList.findIndex(item => item?.hasAttribute('data-new-message-separator')) + 1;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
scrollToLatestMessage();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}, [conversationRef.current, messageList]);
|
|
161
|
+
useImperativeHandle(handle, () => ({
|
|
162
|
+
isScrolledToLatest,
|
|
163
|
+
scrollToLatestMessage,
|
|
164
|
+
scrollToNewMessage
|
|
165
|
+
}), [isScrolledToLatest, scrollToLatestMessage, scrollToNewMessage]);
|
|
166
|
+
useItemIntersection(conversationRef, offset, () => {
|
|
167
|
+
loadMore?.();
|
|
168
|
+
}, ':scope > div > li');
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
const focusableElements = conversationRef.current?.querySelectorAll('li[type="system"],li[type="message"],li[type="typing"]');
|
|
171
|
+
if (focusableElements)
|
|
172
|
+
setMessageList(Array.from(focusableElements));
|
|
173
|
+
if (focusableElements && initialMessageListFocused.current === false) {
|
|
174
|
+
activeElementIndex.current = focusableElements.length - 1;
|
|
175
|
+
}
|
|
176
|
+
}, [transcripts, liveChat]);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
elementRef.current = messageList[activeElementIndex.current];
|
|
179
|
+
if (verticalNav) {
|
|
180
|
+
elementRef.current?.focus();
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
getFocusables(elementRef)[0]?.focus();
|
|
184
|
+
}
|
|
185
|
+
}, [verticalNav]);
|
|
186
|
+
useArrows(conversationRef, {
|
|
187
|
+
cycle: true,
|
|
188
|
+
selector: ':scope > div > li',
|
|
189
|
+
dir: 'up-down',
|
|
190
|
+
allowTabFocus: true
|
|
191
|
+
}, [transcripts, liveChat]);
|
|
192
|
+
useArrows(elementRef, {
|
|
193
|
+
cycle: true,
|
|
194
|
+
selector: 'li :is(a, button, input, textarea, select, details)',
|
|
195
|
+
dir: 'left-right',
|
|
196
|
+
allowTabFocus: true
|
|
197
|
+
}, [transcripts, liveChat, verticalNav]);
|
|
198
|
+
const focusSiblingElement = (currentElement, isShiftKey = false) => {
|
|
199
|
+
elementRef.current = currentElement;
|
|
200
|
+
const focusableElements = getFocusables(elementRef);
|
|
201
|
+
if (focusableElements?.length > 0) {
|
|
202
|
+
focusableElements?.[isShiftKey ? focusableElements.length - 1 : 0].focus();
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const element = isShiftKey
|
|
206
|
+
? elementRef.current?.previousElementSibling
|
|
207
|
+
: elementRef.current?.nextElementSibling;
|
|
208
|
+
if (element)
|
|
209
|
+
focusSiblingElement(element, isShiftKey);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
useOuterEvent('mousedown', [conversationRef], () => {
|
|
213
|
+
focusInMessageList.current = false;
|
|
214
|
+
});
|
|
215
|
+
useAfterInitialEffect(() => {
|
|
216
|
+
if (liveChat?.length > 0) {
|
|
217
|
+
const unreadMessage = liveChat[liveChat.length - 1];
|
|
218
|
+
if (isSystemMessageListItem(unreadMessage) ||
|
|
219
|
+
(isMessageListItem(unreadMessage) && unreadMessage.direction === 'in')) {
|
|
220
|
+
announcePolite({
|
|
221
|
+
message: t('new_message'),
|
|
222
|
+
type: 'alert'
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}, [liveChat]);
|
|
227
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Flex, { ...restProps, as: StyledChatBody, item: {
|
|
228
|
+
grow: 1
|
|
229
|
+
}, container: {
|
|
230
|
+
direction: 'column'
|
|
231
|
+
}, ref: ref, children: [_jsxs(StyledMessageList, { ref: conversationRef, onScroll: debounce(onMessageListScroll, 100), onFocus: () => {
|
|
232
|
+
if (!focusInMessageList.current) {
|
|
233
|
+
initialMessageListFocused.current = true;
|
|
234
|
+
elementRef.current = messageList[activeElementIndex.current];
|
|
235
|
+
elementRef.current?.focus();
|
|
236
|
+
focusInMessageList.current = true;
|
|
237
|
+
}
|
|
238
|
+
}, onKeyDown: e => {
|
|
239
|
+
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
|
240
|
+
elementRef.current = messageList[activeElementIndex.current];
|
|
241
|
+
if (getFocusables(elementRef).length > 0) {
|
|
242
|
+
setVerticalNav(false);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
246
|
+
if (e.key === 'ArrowUp') {
|
|
247
|
+
activeElementIndex.current =
|
|
248
|
+
activeElementIndex.current === 0
|
|
249
|
+
? messageList.length - 1
|
|
250
|
+
: activeElementIndex.current - 1;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
activeElementIndex.current =
|
|
254
|
+
activeElementIndex.current === messageList.length - 1
|
|
255
|
+
? 0
|
|
256
|
+
: activeElementIndex.current + 1;
|
|
257
|
+
}
|
|
258
|
+
setVerticalNav(true);
|
|
259
|
+
}
|
|
260
|
+
else if (e.key === 'Tab') {
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
focusInMessageList.current = false;
|
|
263
|
+
if (e.shiftKey) {
|
|
264
|
+
const prevElement = conversationRef?.current?.parentElement?.previousElementSibling;
|
|
265
|
+
if (prevElement)
|
|
266
|
+
focusSiblingElement(prevElement, true);
|
|
267
|
+
}
|
|
268
|
+
else if (unreadMessageCount > 0 || displayScrollLatest) {
|
|
269
|
+
buttonRef?.current?.focus();
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
const nextElement = conversationRef.current?.parentElement?.nextElementSibling;
|
|
273
|
+
if (nextElement)
|
|
274
|
+
focusSiblingElement(nextElement);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}, children: [loading && _jsx(Progress, { as: 'li', placement: 'block' }), transcripts.map(session => {
|
|
278
|
+
return (_jsx(StyledSession, { children: session.messages.map(message => (_jsx(ChatMessage, { message: message, agentSerial: agentSerial }, message.id))) }, session.id));
|
|
279
|
+
}), _jsx(StyledSession, { children: liveChat.map(message => (_jsx(ChatMessage, { message: message, agentSerial: agentSerial }, message.id))) })] }), (unreadMessageCount > 0 || displayScrollLatest) && (_jsxs(Button, { "aria-label": unreadMessageCount > 0
|
|
280
|
+
? t('scroll_to_unread_messages')
|
|
281
|
+
: t('scroll_to_latest_message'), onClick: () => {
|
|
282
|
+
if (unreadMessageCount > 0) {
|
|
283
|
+
scrollToNewMessage();
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
scrollToLatestMessage();
|
|
287
|
+
}
|
|
288
|
+
onScrollToButtonClick?.();
|
|
289
|
+
}, icon: unreadMessageCount === 0, ref: buttonRef, children: [_jsx(Icon, { name: 'caret-down' }), unreadMessageCount > 0 && _jsxs(_Fragment, { children: ["\u00A0 ", t('new_messages')] })] }))] }), isScrolledToLatest() && _jsx(ScrollToLatest, { scrollContainerRef: conversationRef })] }));
|
|
290
|
+
});
|
|
291
|
+
export default ChatBody;
|
|
292
|
+
//# sourceMappingURL=ChatBody.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatBody.js","sourceRoot":"","sources":["../../../src/components/Chat/ChatBody.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n forwardRef,\n useCallback,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n useEffect\n} from 'react';\nimport type { FunctionComponent, PropsWithoutRef, RefObject } from 'react';\nimport styled, { css } from 'styled-components';\n\nimport {\n Button,\n debounce,\n defaultThemeProp,\n Flex,\n getFocusables,\n Icon,\n Progress,\n registerIcon,\n StyledButton,\n useAfterInitialEffect,\n useArrows,\n useI18n,\n useItemIntersection,\n useLiveLog,\n useOuterEvent\n} from '@pega/cosmos-react-core';\nimport type { ForwardProps } from '@pega/cosmos-react-core';\nimport * as caretDownIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/caret-down.icon';\n\nimport { isMessageListItem, isSystemMessageListItem, isTypeIndicatorListItem } from './Chat.types';\nimport type {\n ChatBodyHandleValue,\n ChatBodyListItemProps,\n ChatBodyProps,\n MessageProps\n} from './Chat.types';\nimport Message from './Message';\nimport SystemMessage, { StyledSystemMessage } from './SystemMessage';\nimport TypeIndicator from './TypeIndicator';\nimport { StyledMessageContainer } from './Message.styles';\n\nregisterIcon(caretDownIcon);\n\nexport const NewMessageSeparatorId = 'new-message-separator';\n\nconst StyledMessageList = styled.ul``;\nconst StyledSession = styled.div``;\n\nconst StyledChatBody = styled.div(props => {\n const {\n theme: {\n base: {\n shadow: { high: shadowHigh, focus },\n colors: { white },\n spacing,\n palette\n }\n }\n } = props;\n return css`\n position: relative;\n overflow-y: hidden;\n\n > ${StyledButton} {\n position: absolute;\n bottom: 0;\n left: 50%;\n transform: translate(-50%, -50%);\n box-shadow: ${shadowHigh};\n z-index: 1;\n background-color: ${white};\n }\n\n > ${StyledMessageList} {\n overflow-y: auto;\n list-style-type: none;\n height: 100%;\n\n > ${StyledSession} {\n padding-inline: 1rem;\n :not(:last-child) {\n border-block-end: 0.0625rem solid ${palette['border-line']};\n margin-block-end: ${spacing};\n padding-block-end: ${spacing};\n }\n }\n ${StyledMessageContainer}, ${StyledSystemMessage} {\n &:focus-visible {\n box-shadow: ${focus};\n outline: none;\n }\n margin-block-end: ${spacing};\n }\n }\n `;\n});\n\nStyledChatBody.defaultProps = defaultThemeProp;\n\nconst ScrollToLatest: FunctionComponent<{\n scrollContainerRef: RefObject<HTMLUListElement>;\n}> = props => {\n const { scrollContainerRef } = props;\n useLayoutEffect(() => {\n if (scrollContainerRef.current) {\n scrollContainerRef.current.scrollTop =\n scrollContainerRef.current.scrollHeight - scrollContainerRef.current.offsetHeight;\n }\n });\n return null;\n};\n\ninterface ChatMessageProps {\n message: ChatBodyListItemProps;\n agentSerial: RefObject<{ [agentId: string]: number }>;\n}\n\nconst ChatMessage: FunctionComponent<ChatMessageProps> = ({ agentSerial, message }) => {\n const getAgentVariant = (\n senderId: MessageProps['senderId'],\n senderType: MessageProps['senderType'],\n direction: MessageProps['direction'] = 'in'\n ) => {\n if (agentSerial.current === null) return undefined;\n if (senderType === 'agent' && direction === 'in') {\n if (agentSerial.current[senderId] !== undefined) {\n return agentSerial.current[senderId];\n }\n\n const nextIndex = Object.entries(agentSerial.current).length;\n agentSerial.current[senderId] = nextIndex;\n return nextIndex;\n }\n return undefined;\n };\n\n if (isMessageListItem(message)) {\n const { id, senderType, direction, senderId } = message;\n const agentVariant = getAgentVariant(senderId, senderType, direction);\n return <Message {...message} key={id} agentVariant={agentVariant} />;\n }\n\n if (isSystemMessageListItem(message)) {\n const extraProps: Record<string, string> = {};\n const { id } = message;\n if (id === NewMessageSeparatorId) {\n extraProps['data-new-message-separator'] = NewMessageSeparatorId;\n }\n\n return <SystemMessage {...message} key={id} {...extraProps} />;\n }\n\n if (isTypeIndicatorListItem(message)) {\n const { id, senderId, senderType } = message;\n const agentVariant = getAgentVariant(senderId, senderType);\n\n return <TypeIndicator {...message} key={id} agentVariant={agentVariant} />;\n }\n\n return null;\n};\n\nconst ChatBody: FunctionComponent<ChatBodyProps & ForwardProps> = forwardRef(function ChatBody(\n props: PropsWithoutRef<ChatBodyProps>,\n ref: ChatBodyProps['ref']\n) {\n const {\n transcripts,\n liveChat,\n unreadMessageCount = 0,\n onScrollToButtonClick,\n handle,\n loading,\n offset = -1,\n loadMore,\n ...restProps\n } = props;\n\n const t = useI18n();\n const conversationRef = useRef<HTMLUListElement>(null);\n const scrollRef = useRef(false);\n const buttonRef = useRef<HTMLButtonElement>(null);\n const focusInMessageList = useRef(false);\n const initialMessageListFocused = useRef(false);\n const elementRef = useRef<HTMLElement | null>(null);\n const activeElementIndex = useRef(-1);\n const agentSerial = useRef<{ [agentId: string]: number }>({});\n\n const [messageList, setMessageList] = useState<(HTMLLIElement | null)[]>([]);\n const [verticalNav, setVerticalNav] = useState(true);\n\n const { announcePolite } = useLiveLog();\n\n const isScrolledToLatest: ChatBodyHandleValue['isScrolledToLatest'] = useCallback(() => {\n return conversationRef.current\n ? conversationRef.current.scrollHeight -\n conversationRef.current.scrollTop -\n conversationRef.current.offsetHeight <=\n 30\n : true;\n }, [conversationRef.current]);\n\n const scrollToLatestMessage: ChatBodyHandleValue['scrollToLatestMessage'] = useCallback(() => {\n if (conversationRef.current) {\n conversationRef.current.scrollTop = conversationRef.current.scrollHeight;\n focusInMessageList.current = true;\n messageList[messageList.length - 1]?.focus();\n activeElementIndex.current = messageList.length - 1;\n }\n }, [conversationRef.current, messageList]);\n\n const [displayScrollLatest, setDisplayScrollToLatest] = useState(false);\n\n const onMessageListScroll = useCallback(() => {\n const isScrolled = isScrolledToLatest();\n if (scrollRef.current && isScrolled) {\n scrollRef.current = false;\n if (unreadMessageCount) {\n onScrollToButtonClick?.();\n }\n }\n\n if (!scrollRef.current && !isScrolled) {\n scrollRef.current = true;\n }\n\n setDisplayScrollToLatest(!isScrolled);\n }, [isScrolledToLatest(), unreadMessageCount]);\n\n const scrollToNewMessage: ChatBodyHandleValue['scrollToNewMessage'] = useCallback(() => {\n if (conversationRef.current) {\n setDisplayScrollToLatest(false);\n const newMessageIndicatorEle = conversationRef.current.querySelector<HTMLElement>(\n '[data-new-message-separator]'\n );\n if (\n newMessageIndicatorEle &&\n newMessageIndicatorEle.nextElementSibling instanceof HTMLElement\n ) {\n conversationRef.current.scrollTop = newMessageIndicatorEle.offsetTop;\n focusInMessageList.current = true;\n newMessageIndicatorEle.nextElementSibling.focus();\n activeElementIndex.current =\n messageList.findIndex(item => item?.hasAttribute('data-new-message-separator')) + 1;\n } else {\n scrollToLatestMessage();\n }\n }\n }, [conversationRef.current, messageList]);\n\n useImperativeHandle(\n handle,\n () => ({\n isScrolledToLatest,\n scrollToLatestMessage,\n scrollToNewMessage\n }),\n [isScrolledToLatest, scrollToLatestMessage, scrollToNewMessage]\n );\n\n useItemIntersection(\n conversationRef,\n offset,\n () => {\n loadMore?.();\n },\n ':scope > div > li'\n );\n\n useEffect(() => {\n const focusableElements = conversationRef.current?.querySelectorAll<HTMLLIElement>(\n 'li[type=\"system\"],li[type=\"message\"],li[type=\"typing\"]'\n );\n\n if (focusableElements) setMessageList(Array.from(focusableElements));\n\n if (focusableElements && initialMessageListFocused.current === false) {\n activeElementIndex.current = focusableElements.length - 1;\n }\n }, [transcripts, liveChat]);\n\n useEffect(() => {\n elementRef.current = messageList[activeElementIndex.current];\n if (verticalNav) {\n elementRef.current?.focus();\n } else {\n getFocusables(elementRef)[0]?.focus();\n }\n }, [verticalNav]);\n\n useArrows(\n conversationRef,\n {\n cycle: true,\n selector: ':scope > div > li',\n dir: 'up-down',\n allowTabFocus: true\n },\n [transcripts, liveChat]\n );\n\n useArrows(\n elementRef,\n {\n cycle: true,\n selector: 'li :is(a, button, input, textarea, select, details)',\n dir: 'left-right',\n allowTabFocus: true\n },\n [transcripts, liveChat, verticalNav]\n );\n\n const focusSiblingElement = (currentElement: Element, isShiftKey = false) => {\n elementRef.current = currentElement as HTMLElement;\n const focusableElements = getFocusables(elementRef);\n if (focusableElements?.length > 0) {\n focusableElements?.[isShiftKey ? focusableElements.length - 1 : 0].focus();\n } else {\n const element = isShiftKey\n ? elementRef.current?.previousElementSibling\n : elementRef.current?.nextElementSibling;\n\n if (element) focusSiblingElement(element, isShiftKey);\n }\n };\n\n useOuterEvent('mousedown', [conversationRef], () => {\n focusInMessageList.current = false;\n });\n\n useAfterInitialEffect(() => {\n if (liveChat?.length > 0) {\n const unreadMessage = liveChat[liveChat.length - 1];\n if (\n isSystemMessageListItem(unreadMessage) ||\n (isMessageListItem(unreadMessage) && unreadMessage.direction === 'in')\n ) {\n announcePolite({\n message: t('new_message'),\n type: 'alert'\n });\n }\n }\n }, [liveChat]);\n\n return (\n <>\n <Flex\n {...restProps}\n as={StyledChatBody}\n item={{\n grow: 1\n }}\n container={{\n direction: 'column'\n }}\n ref={ref}\n >\n <StyledMessageList\n ref={conversationRef}\n onScroll={debounce(onMessageListScroll, 100)}\n onFocus={() => {\n if (!focusInMessageList.current) {\n initialMessageListFocused.current = true;\n elementRef.current = messageList[activeElementIndex.current];\n elementRef.current?.focus();\n focusInMessageList.current = true;\n }\n }}\n onKeyDown={e => {\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n elementRef.current = messageList[activeElementIndex.current];\n if (getFocusables(elementRef).length > 0) {\n setVerticalNav(false);\n }\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n if (e.key === 'ArrowUp') {\n activeElementIndex.current =\n activeElementIndex.current === 0\n ? messageList.length - 1\n : activeElementIndex.current - 1;\n } else {\n activeElementIndex.current =\n activeElementIndex.current === messageList.length - 1\n ? 0\n : activeElementIndex.current + 1;\n }\n setVerticalNav(true);\n } else if (e.key === 'Tab') {\n e.preventDefault();\n focusInMessageList.current = false;\n if (e.shiftKey) {\n const prevElement = conversationRef?.current?.parentElement?.previousElementSibling;\n if (prevElement) focusSiblingElement(prevElement, true);\n } else if (unreadMessageCount > 0 || displayScrollLatest) {\n buttonRef?.current?.focus();\n } else {\n const nextElement = conversationRef.current?.parentElement?.nextElementSibling;\n if (nextElement) focusSiblingElement(nextElement);\n }\n }\n }}\n >\n {loading && <Progress as='li' placement='block' />}\n {transcripts.map(session => {\n return (\n <StyledSession key={session.id}>\n {session.messages.map(message => (\n <ChatMessage message={message} agentSerial={agentSerial} key={message.id} />\n ))}\n </StyledSession>\n );\n })}\n <StyledSession>\n {liveChat.map(message => (\n <ChatMessage message={message} agentSerial={agentSerial} key={message.id} />\n ))}\n </StyledSession>\n </StyledMessageList>\n {(unreadMessageCount > 0 || displayScrollLatest) && (\n <Button\n aria-label={\n unreadMessageCount > 0\n ? t('scroll_to_unread_messages')\n : t('scroll_to_latest_message')\n }\n onClick={() => {\n if (unreadMessageCount > 0) {\n scrollToNewMessage();\n } else {\n scrollToLatestMessage();\n }\n onScrollToButtonClick?.();\n }}\n icon={unreadMessageCount === 0}\n ref={buttonRef}\n >\n <Icon name='caret-down' />\n {unreadMessageCount > 0 && <> {t('new_messages')}</>}\n </Button>\n )}\n </Flex>\n {isScrolledToLatest() && <ScrollToLatest scrollContainerRef={conversationRef} />}\n </>\n );\n});\n\nexport default ChatBody;\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { FunctionComponent, Ref, ReactNode, RefObject, KeyboardEvent } from 'react';
|
|
2
|
+
import type { ForwardProps, FileUploadItemProps } from '@pega/cosmos-react-core';
|
|
3
|
+
export type SpecialKeysConfig = {
|
|
4
|
+
/** Collection of specialKeys */
|
|
5
|
+
keys: string[];
|
|
6
|
+
/** onSpecialKey is fired when a special key is typed in Textarea */
|
|
7
|
+
onSpecialKey: (e: KeyboardEvent<HTMLTextAreaElement>, key: string) => void;
|
|
8
|
+
};
|
|
9
|
+
export interface ChatComposerProps {
|
|
10
|
+
/** Place holder */
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
/** Default message to populate on load */
|
|
13
|
+
defaultMessage?: string;
|
|
14
|
+
/** Max character limit */
|
|
15
|
+
maxLength?: number;
|
|
16
|
+
/** Callback when send button is clicked */
|
|
17
|
+
onSend: (message: string) => void;
|
|
18
|
+
/** Enter / Return key sends message */
|
|
19
|
+
enterKeySendsMessage?: boolean;
|
|
20
|
+
/** Max number of attachments to be allowed in chat composer.
|
|
21
|
+
* If zero is passed, add attachments icon will be hidden. same rule applies if prop is not passed.
|
|
22
|
+
* If attachments added are >= maxAttachments, add attachments icon will be disabled.
|
|
23
|
+
*/
|
|
24
|
+
maxAttachments?: number;
|
|
25
|
+
/** show/hide emoji */
|
|
26
|
+
showEmoji?: boolean;
|
|
27
|
+
/** Attachments list */
|
|
28
|
+
attachments?: FileUploadItemProps[];
|
|
29
|
+
/** Callback when attachment icon is clicked */
|
|
30
|
+
onAddAttachment?: () => void;
|
|
31
|
+
/** Special Keys configuration */
|
|
32
|
+
specialKeysConfig?: SpecialKeysConfig;
|
|
33
|
+
/** Disables the composer area */
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
/** Children */
|
|
36
|
+
children?: ReactNode;
|
|
37
|
+
/** Ref to the Imperative handle */
|
|
38
|
+
ref?: Ref<ChatComposerImperativeHandleProps>;
|
|
39
|
+
}
|
|
40
|
+
export declare const StyledAttachmentsListContainer: import("styled-components").StyledComponent<"div", import("styled-components").DefaultTheme, {}, never>;
|
|
41
|
+
export interface ChatComposerImperativeHandleProps {
|
|
42
|
+
/** Function to append a message */
|
|
43
|
+
appendToMessage: (appendMessage: string, position?: 'cursor' | 'start' | 'end' | 'specialKey') => void;
|
|
44
|
+
/** Actual ref to Chat composer component */
|
|
45
|
+
chatComposerRef: RefObject<HTMLDivElement | undefined>;
|
|
46
|
+
textAreaRef: RefObject<HTMLTextAreaElement | undefined>;
|
|
47
|
+
}
|
|
48
|
+
declare const ChatComposer: FunctionComponent<ChatComposerProps & ForwardProps>;
|
|
49
|
+
export default ChatComposer;
|
|
50
|
+
//# sourceMappingURL=ChatComposer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatComposer.d.ts","sourceRoot":"","sources":["../../../src/components/Chat/ChatComposer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EACjB,GAAG,EAEH,SAAS,EAET,SAAS,EACT,aAAa,EACd,MAAM,OAAO,CAAC;AAqBf,OAAO,KAAK,EAGV,YAAY,EACZ,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AAQjC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gCAAgC;IAChC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,oEAAoE;IACpE,YAAY,EAAE,CAAC,CAAC,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5E,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,mBAAmB;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,uCAAuC;IACvC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uBAAuB;IACvB,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACpC,+CAA+C;IAC/C,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iCAAiC;IACjC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe;IACf,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,mCAAmC;IACnC,GAAG,CAAC,EAAE,GAAG,CAAC,iCAAiC,CAAC,CAAC;CAC9C;AA4ED,eAAO,MAAM,8BAA8B,yGAUzC,CAAC;AAEH,MAAM,WAAW,iCAAiC;IAChD,mCAAmC;IACnC,eAAe,EAAE,CACf,aAAa,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,KAAK,GAAG,YAAY,KACjD,IAAI,CAAC;IACV,4CAA4C;IAC5C,eAAe,EAAE,SAAS,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACvD,WAAW,EAAE,SAAS,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC;CACzD;AAED,QAAA,MAAM,YAAY,EAAE,iBAAiB,CAAC,iBAAiB,GAAG,YAAY,CAiTrE,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createElement as _createElement } from "react";
|
|
3
|
+
import { forwardRef, useState, useEffect, useCallback, useImperativeHandle, useRef } from 'react';
|
|
4
|
+
import styled, { css } from 'styled-components';
|
|
5
|
+
import { Flex, TextArea, Button, Icon, registerIcon, Popover, EmojiPicker, hasProp, useI18n, useElement, defaultThemeProp, Tooltip, useOuterEvent, VisuallyHiddenText, FileUploadItem, Grid } from '@pega/cosmos-react-core';
|
|
6
|
+
import BareButton from '@pega/cosmos-react-core/lib/components/Button/BareButton';
|
|
7
|
+
import * as timesIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/times.icon';
|
|
8
|
+
import * as faceHappyIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/face-happy.icon';
|
|
9
|
+
import * as paperClipIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/paper-clip.icon';
|
|
10
|
+
registerIcon(timesIcon, faceHappyIcon, paperClipIcon);
|
|
11
|
+
const StyledAttachmentItem = styled(FileUploadItem) ``;
|
|
12
|
+
const StyledAttachmentElementContainer = styled.div(({ disabled, theme }) => {
|
|
13
|
+
return (disabled &&
|
|
14
|
+
css `
|
|
15
|
+
&:focus {
|
|
16
|
+
box-shadow: ${theme.base.shadow.focus};
|
|
17
|
+
border-radius: 50%;
|
|
18
|
+
outline: none;
|
|
19
|
+
}
|
|
20
|
+
`);
|
|
21
|
+
});
|
|
22
|
+
const StyledPreviewButton = styled(BareButton) ``;
|
|
23
|
+
const StyledDeleteButton = styled(BareButton) ``;
|
|
24
|
+
const StyledChatComposer = styled.footer(({ theme, disabled, theme: { base: { 'disabled-opacity': transparentBg, spacing } } }) => {
|
|
25
|
+
return css `
|
|
26
|
+
padding: calc(2 * ${spacing});
|
|
27
|
+
background-color: ${theme.base.palette['secondary-background']};
|
|
28
|
+
border-end-start-radius: inherit;
|
|
29
|
+
border-end-end-radius: inherit;
|
|
30
|
+
textarea {
|
|
31
|
+
border-radius: ${theme.base['border-radius']};
|
|
32
|
+
height: 5rem;
|
|
33
|
+
border: 0.0625rem solid ${theme.base.colors.gray['extra-light']};
|
|
34
|
+
max-width: 100%;
|
|
35
|
+
}
|
|
36
|
+
${StyledAttachmentItem} {
|
|
37
|
+
${disabled &&
|
|
38
|
+
css `
|
|
39
|
+
pointer-events: none;
|
|
40
|
+
opacity: ${transparentBg};
|
|
41
|
+
`}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
${StyledAttachmentElementContainer} {
|
|
45
|
+
${disabled &&
|
|
46
|
+
css `
|
|
47
|
+
pointer-events: none;
|
|
48
|
+
`}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
${StyledPreviewButton} {
|
|
52
|
+
color: ${theme.base.palette.interactive};
|
|
53
|
+
font-weight: bold;
|
|
54
|
+
&:hover {
|
|
55
|
+
text-decoration: underline;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
${StyledDeleteButton} {
|
|
60
|
+
color: ${theme.base.palette.interactive};
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
});
|
|
64
|
+
StyledChatComposer.defaultProps = defaultThemeProp;
|
|
65
|
+
StyledAttachmentElementContainer.defaultProps = defaultThemeProp;
|
|
66
|
+
export const StyledAttachmentsListContainer = styled.div(({ theme }) => {
|
|
67
|
+
return css `
|
|
68
|
+
max-height: 5rem;
|
|
69
|
+
overflow-y: auto;
|
|
70
|
+
|
|
71
|
+
&:focus-within {
|
|
72
|
+
box-shadow: ${theme.base.shadow['focus-group']};
|
|
73
|
+
border-radius: calc(0.5 * ${theme.base['border-radius']});
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
});
|
|
77
|
+
const ChatComposer = forwardRef(function ChatComposer(props, ref) {
|
|
78
|
+
const { children, attachments = [], maxAttachments = 0, onAddAttachment, maxLength, placeholder: defaultPlaceHolder, defaultMessage = '', onSend, specialKeysConfig, disabled = false, enterKeySendsMessage = true, showEmoji, ...restProps } = props;
|
|
79
|
+
const [message, setMessage] = useState(defaultMessage);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
setMessage(defaultMessage);
|
|
82
|
+
}, [defaultMessage]);
|
|
83
|
+
const t = useI18n();
|
|
84
|
+
const textAreaRef = useRef(null);
|
|
85
|
+
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
|
86
|
+
const [emojiBtnEl, setEmojiBtnEl] = useElement();
|
|
87
|
+
const [attachmentsBtnEl, setAttachmentsBtnEl] = useElement();
|
|
88
|
+
const [announcement, setAnnouncement] = useState('');
|
|
89
|
+
const lastEnteredSpecialKey = useRef(null);
|
|
90
|
+
const appendToMessage = useCallback((appendMessage, position, selectText = true) => {
|
|
91
|
+
let selectionStart;
|
|
92
|
+
let selectionEnd;
|
|
93
|
+
if (textAreaRef.current) {
|
|
94
|
+
if (position === 'start') {
|
|
95
|
+
textAreaRef.current.value = appendMessage + textAreaRef.current.value;
|
|
96
|
+
selectionStart = 0;
|
|
97
|
+
selectionEnd = appendMessage.length;
|
|
98
|
+
}
|
|
99
|
+
else if (position === 'end') {
|
|
100
|
+
const initialEnd = textAreaRef.current.value.length;
|
|
101
|
+
textAreaRef.current.value += appendMessage;
|
|
102
|
+
selectionStart = initialEnd;
|
|
103
|
+
selectionEnd = initialEnd + appendMessage.length;
|
|
104
|
+
}
|
|
105
|
+
else if (position === 'specialKey' && lastEnteredSpecialKey.current) {
|
|
106
|
+
const replaceChars = lastEnteredSpecialKey.current.length;
|
|
107
|
+
const currentSelectionStart = textAreaRef.current.selectionStart;
|
|
108
|
+
textAreaRef.current.value =
|
|
109
|
+
textAreaRef.current.value.substring(0, textAreaRef.current.selectionStart - replaceChars) +
|
|
110
|
+
appendMessage +
|
|
111
|
+
textAreaRef.current.value.substring(textAreaRef.current.selectionStart, textAreaRef.current.value.length);
|
|
112
|
+
selectionStart = currentSelectionStart - replaceChars;
|
|
113
|
+
selectionEnd = currentSelectionStart + appendMessage.length - replaceChars;
|
|
114
|
+
lastEnteredSpecialKey.current = null;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const currentSelectionStart = textAreaRef.current.selectionStart;
|
|
118
|
+
textAreaRef.current.value =
|
|
119
|
+
textAreaRef.current.value.substring(0, textAreaRef.current.selectionStart) +
|
|
120
|
+
appendMessage +
|
|
121
|
+
textAreaRef.current.value.substring(textAreaRef.current.selectionStart, textAreaRef.current.value.length);
|
|
122
|
+
selectionStart = currentSelectionStart;
|
|
123
|
+
selectionEnd = currentSelectionStart + appendMessage.length;
|
|
124
|
+
}
|
|
125
|
+
textAreaRef.current.selectionStart = selectText ? selectionStart : selectionEnd;
|
|
126
|
+
textAreaRef.current.selectionEnd = selectionEnd;
|
|
127
|
+
textAreaRef.current.focus();
|
|
128
|
+
setMessage(textAreaRef.current.value);
|
|
129
|
+
}
|
|
130
|
+
}, [setMessage, textAreaRef.current]);
|
|
131
|
+
const chatComposerRef = useRef(null);
|
|
132
|
+
useImperativeHandle(ref, () => ({ appendToMessage, chatComposerRef, textAreaRef }), [
|
|
133
|
+
appendToMessage,
|
|
134
|
+
chatComposerRef.current,
|
|
135
|
+
textAreaRef.current
|
|
136
|
+
]);
|
|
137
|
+
const sendMessage = useCallback((newMessage) => {
|
|
138
|
+
if (onSend) {
|
|
139
|
+
onSend(newMessage);
|
|
140
|
+
setMessage('');
|
|
141
|
+
}
|
|
142
|
+
}, [onSend]);
|
|
143
|
+
const handleEnterKeyDown = useCallback((e) => {
|
|
144
|
+
if (enterKeySendsMessage && e.keyCode === 13 && !e.shiftKey) {
|
|
145
|
+
if (!maxLength || message.length <= maxLength) {
|
|
146
|
+
sendMessage(message);
|
|
147
|
+
setAnnouncement(t('message_sent'));
|
|
148
|
+
}
|
|
149
|
+
e.preventDefault();
|
|
150
|
+
e.stopPropagation();
|
|
151
|
+
}
|
|
152
|
+
}, [onSend, message, maxLength]);
|
|
153
|
+
const handleTextAreaChange = useCallback((e) => {
|
|
154
|
+
setMessage(e.currentTarget.value);
|
|
155
|
+
setAnnouncement('');
|
|
156
|
+
}, [setMessage]);
|
|
157
|
+
const handleSpecialKey = useCallback((e) => {
|
|
158
|
+
if (!specialKeysConfig)
|
|
159
|
+
return;
|
|
160
|
+
const { keys, onSpecialKey } = specialKeysConfig;
|
|
161
|
+
if (specialKeysConfig && keys.length > 0) {
|
|
162
|
+
const composerValue = e.currentTarget.value.substring(0, e.currentTarget.selectionStart);
|
|
163
|
+
const specialKey = keys.find(key => composerValue.endsWith(key));
|
|
164
|
+
if (specialKey) {
|
|
165
|
+
lastEnteredSpecialKey.current = specialKey;
|
|
166
|
+
onSpecialKey(e, specialKey);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}, [specialKeysConfig]);
|
|
170
|
+
const toggleEmoji = () => {
|
|
171
|
+
setShowEmojiPicker(!showEmojiPicker);
|
|
172
|
+
};
|
|
173
|
+
const onEmojiSelect = emojiElem => {
|
|
174
|
+
// Check that it is not a custom emoji
|
|
175
|
+
if (hasProp(emojiElem, 'native')) {
|
|
176
|
+
appendToMessage(emojiElem.native, 'cursor', false);
|
|
177
|
+
}
|
|
178
|
+
setShowEmojiPicker(false);
|
|
179
|
+
};
|
|
180
|
+
const EmojiPickerRef = useRef(null);
|
|
181
|
+
useOuterEvent('mousedown', [emojiBtnEl, EmojiPickerRef], () => {
|
|
182
|
+
setShowEmojiPicker(false);
|
|
183
|
+
});
|
|
184
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Flex, { as: StyledChatComposer, disabled: disabled, container: {
|
|
185
|
+
direction: 'column',
|
|
186
|
+
gap: 2
|
|
187
|
+
}, item: {
|
|
188
|
+
shrink: 0
|
|
189
|
+
}, ...restProps, ref: chatComposerRef, children: [_jsxs(Flex, { container: {
|
|
190
|
+
direction: 'column',
|
|
191
|
+
gap: 0.5
|
|
192
|
+
}, children: [_jsx(Grid, { container: {
|
|
193
|
+
cols: 'repeat(auto-fill, minmax(12rem, 1fr))',
|
|
194
|
+
gap: 1
|
|
195
|
+
}, role: 'group', "aria-label": `${t('attachments')}`, as: StyledAttachmentsListContainer, children: attachments.map((attachment, idx) => {
|
|
196
|
+
if (disabled) {
|
|
197
|
+
const attachmentProps = {
|
|
198
|
+
...attachment,
|
|
199
|
+
onDelete: undefined,
|
|
200
|
+
onDownload: undefined,
|
|
201
|
+
onPreview: undefined,
|
|
202
|
+
onEdit: undefined,
|
|
203
|
+
onCancel: undefined
|
|
204
|
+
};
|
|
205
|
+
return _createElement(StyledAttachmentItem, { ...attachmentProps, key: attachmentProps.name });
|
|
206
|
+
}
|
|
207
|
+
return (_jsx("div", { "aria-label": `${t('attachments_x_of_y', [idx + 1, attachments.length])}`, children: _jsx(StyledAttachmentItem, { ...attachment }) }, attachment.name));
|
|
208
|
+
}) }), _jsx(TextArea, { ref: textAreaRef, maxLength: maxLength, displayCharCount: maxLength !== undefined, hardStop: false, placeholder: defaultPlaceHolder, value: message, disabled: disabled, onKeyUp: handleSpecialKey, onKeyDown: handleEnterKeyDown, onChange: handleTextAreaChange, onFocus: handleTextAreaChange, "aria-label": t('enter_message'), autoResize: false })] }), _jsxs(Flex, { container: { gap: 0.5 }, children: [showEmoji && (_jsx(Button, { ref: setEmojiBtnEl, onClick: toggleEmoji, variant: 'simple', disabled: disabled, icon: true, "aria-label": t('add_emoji'), children: _jsx(Icon, { name: 'face-happy' }) })), maxAttachments > 0 && (_jsxs(StyledAttachmentElementContainer, { disabled: attachments.length >= maxAttachments, ref: setAttachmentsBtnEl, tabIndex: attachments.length >= maxAttachments ? 0 : undefined, role: 'button', children: [_jsx(Button, { onClick: onAddAttachment, variant: 'simple', disabled: !!(disabled || attachments.length >= maxAttachments), icon: true, "aria-label": attachments.length >= maxAttachments
|
|
209
|
+
? t('email_disable_attachment', [maxAttachments])
|
|
210
|
+
: t('email_add_attachment', [maxAttachments]), children: _jsx(Icon, { name: 'paper-clip' }) }), attachments.length >= maxAttachments && (_jsx(Tooltip, { target: attachmentsBtnEl, showDelay: 'none', hideDelay: 'none', ...restProps, children: t('email_disable_attachment', [maxAttachments]) }))] })), _jsx(Flex, { item: { grow: 1 }, children: children }), _jsx(Button, { variant: 'primary', disabled: disabled || (!!maxLength && message.length > maxLength), onClick: () => {
|
|
211
|
+
sendMessage(message);
|
|
212
|
+
setAnnouncement(t('message_sent'));
|
|
213
|
+
}, children: t('send') }), announcement && (_jsx(VisuallyHiddenText, { "aria-live": 'assertive', children: announcement }))] })] }), _jsx(Popover, { show: showEmojiPicker, target: emojiBtnEl, placement: 'top-start', modifiers: [
|
|
214
|
+
{
|
|
215
|
+
name: 'flip',
|
|
216
|
+
enabled: true
|
|
217
|
+
}
|
|
218
|
+
], children: _jsx(EmojiPicker, { ref: EmojiPickerRef, onSelect: onEmojiSelect }) })] }));
|
|
219
|
+
});
|
|
220
|
+
export default ChatComposer;
|
|
221
|
+
//# sourceMappingURL=ChatComposer.js.map
|