communication-react-19
Version:
React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)
187 lines • 11.6 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import React, { useState, useMemo, useCallback } from 'react';
import { mergeStyles, concatStyleSets, Icon, Stack } from '@fluentui/react';
import { sendButtonStyle, sendIconStyle, sendBoxWrapperStyles, borderAndBoxShadowStyle } from './styles/SendBox.styles';
/* @conditional-compile-remove(file-sharing-acs) */
import { useV9CustomStyles } from './styles/SendBox.styles';
import { useTheme } from '../theming';
import { useLocale } from '../localization';
import { useIdentifiers } from '../identifiers';
import { InputBoxComponent } from './InputBoxComponent';
import { InputBoxButton } from './InputBoxButton';
/* @conditional-compile-remove(file-sharing-acs) */
import { SendBoxErrors } from './SendBoxErrors';
/* @conditional-compile-remove(file-sharing-acs) */
import { _AttachmentUploadCards } from './Attachment/AttachmentUploadCards';
/* @conditional-compile-remove(file-sharing-acs) */
import { attachmentUploadCardsStyles } from './styles/SendBox.styles';
/* @conditional-compile-remove(file-sharing-acs) */
import { isAttachmentUploadCompleted, hasIncompleteAttachmentUploads, toAttachmentMetadata } from './utils/SendBoxUtils';
import { MAXIMUM_LENGTH_OF_MESSAGE, isMessageTooLong, sanitizeText, isSendBoxButtonDisabled } from './utils/SendBoxUtils';
/* @conditional-compile-remove(file-sharing-acs) */
import { FluentV9ThemeProvider } from '../theming/FluentV9ThemeProvider';
/**
* Component for typing and sending messages.
*
* Supports sending typing notification when user starts entering text.
* Supports an optional message below the text input field.
*
* @public
*/
export const SendBox = (props) => {
const { disabled, systemMessage, supportNewline, onSendMessage, onTyping, onRenderIcon, onRenderSystemMessage, styles, autoFocus,
/* @conditional-compile-remove(mention) */
mentionLookupOptions,
/* @conditional-compile-remove(file-sharing-acs) */
attachments } = props;
const theme = useTheme();
const localeStrings = useLocale().strings.sendBox;
const strings = Object.assign(Object.assign({}, localeStrings), props.strings);
const ids = useIdentifiers();
const [textValue, setTextValue] = useState('');
const [textValueOverflow, setTextValueOverflow] = useState(false);
const sendTextFieldRef = React.useRef(null);
/* @conditional-compile-remove(file-sharing-acs) */
const customV9Styles = useV9CustomStyles();
/* @conditional-compile-remove(file-sharing-acs) */
const [attachmentUploadsPendingError, setAttachmentUploadsPendingError] = useState(undefined);
const sendMessageOnClick = () => {
var _a;
// don't send a message when disabled
if (disabled || textValueOverflow) {
return;
}
// Don't send message until all attachments have been uploaded successfully
/* @conditional-compile-remove(file-sharing-acs) */
setAttachmentUploadsPendingError(undefined);
/* @conditional-compile-remove(file-sharing-acs) */
if (hasIncompleteAttachmentUploads(attachments)) {
setAttachmentUploadsPendingError({ message: strings.attachmentUploadsPendingError, timestamp: Date.now() });
return;
}
const message = textValue;
// we don't want to send empty messages including spaces, newlines, tabs
// Message can be empty if there is a valid attachment upload
if (sanitizeText(message).length > 0 ||
/* @conditional-compile-remove(file-sharing-acs) */ isAttachmentUploadCompleted(attachments)) {
onSendMessage &&
onSendMessage(message,
/* @conditional-compile-remove(file-sharing-acs) */ /* @conditional-compile-remove(rich-text-editor-composite-support) */
{
/* @conditional-compile-remove(file-sharing-acs) */
attachments: toAttachmentMetadata(attachments),
/* @conditional-compile-remove(rich-text-editor-composite-support) */
type: 'text'
});
setTextValue('');
(_a = sendTextFieldRef.current) === null || _a === void 0 ? void 0 : _a.focus();
}
};
const setText = (newValue) => {
if (newValue === undefined) {
return;
}
setTextValueOverflow(isMessageTooLong(newValue.length));
setTextValue(newValue);
};
const textTooLongMessage = textValueOverflow ? strings.textTooLong : undefined;
const errorMessage = systemMessage !== null && systemMessage !== void 0 ? systemMessage : textTooLongMessage;
const isSendBoxButtonDisabledValue = useMemo(() => {
return isSendBoxButtonDisabled({
hasContent: sanitizeText(textValue).length > 0,
/* @conditional-compile-remove(file-sharing-acs) */ hasCompletedAttachmentUploads: isAttachmentUploadCompleted(attachments),
hasError: !!errorMessage,
disabled: !!disabled
});
}, [
/* @conditional-compile-remove(file-sharing-acs) */
attachments,
disabled,
errorMessage,
textValue
]);
const mergedSendButtonStyle = useMemo(() => mergeStyles(sendButtonStyle, styles === null || styles === void 0 ? void 0 : styles.sendMessageIconContainer), [styles === null || styles === void 0 ? void 0 : styles.sendMessageIconContainer]);
const mergedStyles = useMemo(() => concatStyleSets(styles), [styles]);
const mergedSendIconStyle = useMemo(() => sendIconStyle({
theme,
isSendBoxButtonDisabled: isSendBoxButtonDisabledValue,
customSendIconStyle: styles === null || styles === void 0 ? void 0 : styles.sendMessageIcon
}), [theme, isSendBoxButtonDisabledValue, styles === null || styles === void 0 ? void 0 : styles.sendMessageIcon]);
const onRenderSendIcon = useCallback((isHover) => onRenderIcon ? (onRenderIcon(isHover)) : (React.createElement(Icon, { iconName: isHover && textValue ? 'SendBoxSendHovered' : 'SendBoxSend', className: mergedSendIconStyle })), [mergedSendIconStyle, onRenderIcon, textValue]);
// Ensure that errors are cleared when there are no attachments in sendBox
/* @conditional-compile-remove(file-sharing-acs) */
React.useEffect(() => {
if (!(attachments === null || attachments === void 0 ? void 0 : attachments.filter((upload) => !upload.error).length)) {
setAttachmentUploadsPendingError(undefined);
}
}, [attachments]);
/* @conditional-compile-remove(file-sharing-acs) */
const sendBoxErrorsProps = useMemo(() => {
var _a;
return {
attachmentUploadsPendingError: attachmentUploadsPendingError,
attachmentProgressError: (_a = attachments === null || attachments === void 0 ? void 0 : attachments.filter((attachmentUpload) => attachmentUpload.error).pop()) === null || _a === void 0 ? void 0 : _a.error
};
}, [attachments, attachmentUploadsPendingError]);
/* @conditional-compile-remove(file-sharing-acs) */
const onRenderAttachmentUploads = useCallback(() => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
if (!(attachments === null || attachments === void 0 ? void 0 : attachments.filter((upload) => !upload.error).length)) {
return null;
}
return props.onRenderAttachmentUploads ? (props.onRenderAttachmentUploads()) : (React.createElement(Stack, { className: attachmentUploadCardsStyles },
React.createElement(FluentV9ThemeProvider, { v8Theme: theme, className: customV9Styles.clearBackground },
React.createElement(_AttachmentUploadCards, { attachments: attachments, onCancelAttachmentUpload: props.onCancelAttachmentUpload, strings: {
removeAttachment: (_b = (_a = props.strings) === null || _a === void 0 ? void 0 : _a.removeAttachment) !== null && _b !== void 0 ? _b : localeStrings.removeAttachment,
uploading: (_d = (_c = props.strings) === null || _c === void 0 ? void 0 : _c.uploading) !== null && _d !== void 0 ? _d : localeStrings.uploading,
uploadProgress: (_f = (_e = props.strings) === null || _e === void 0 ? void 0 : _e.uploadProgress) !== null && _f !== void 0 ? _f : localeStrings.uploadProgress,
uploadCompleted: (_h = (_g = props.strings) === null || _g === void 0 ? void 0 : _g.uploadCompleted) !== null && _h !== void 0 ? _h : localeStrings.uploadCompleted,
attachmentMoreMenu: (_k = (_j = props.strings) === null || _j === void 0 ? void 0 : _j.attachmentMoreMenu) !== null && _k !== void 0 ? _k : localeStrings.attachmentMoreMenu
}, disabled: disabled }))));
}, [
attachments,
props,
theme,
customV9Styles.clearBackground,
localeStrings.removeAttachment,
localeStrings.uploading,
localeStrings.uploadProgress,
localeStrings.uploadCompleted,
localeStrings.attachmentMoreMenu,
disabled
]);
return (React.createElement(Stack, { className: mergeStyles(sendBoxWrapperStyles, { overflow: 'visible' } // This is needed for the mention popup to be visible
) },
/* @conditional-compile-remove(file-sharing-acs) */
React.createElement(SendBoxErrors, { attachmentProgressError: sendBoxErrorsProps.attachmentProgressError
? {
message: sendBoxErrorsProps.attachmentProgressError.message,
timestamp: Date.now()
}
: undefined, attachmentUploadsPendingError: sendBoxErrorsProps.attachmentUploadsPendingError }),
React.createElement(Stack, { className: borderAndBoxShadowStyle({
theme,
hasErrorMessage: !!errorMessage,
disabled: !!disabled
}) },
React.createElement(InputBoxComponent, { autoFocus: autoFocus, "data-ui-id": ids.sendboxTextField, disabled: disabled, errorMessage: onRenderSystemMessage ? onRenderSystemMessage(errorMessage) : errorMessage, textFieldRef: sendTextFieldRef, id: "sendbox", placeholderText: strings.placeholderText, textValue: textValue, onChange: (_, newValue) => setText(newValue), onKeyDown: (ev) => {
const keyWasSendingMessage = ev.key === 'Enter' && (ev.shiftKey === false || !supportNewline);
if (!keyWasSendingMessage) {
onTyping === null || onTyping === void 0 ? void 0 : onTyping();
}
}, onEnterKeyDown: () => {
sendMessageOnClick();
}, styles: mergedStyles, supportNewline: supportNewline, maxLength: MAXIMUM_LENGTH_OF_MESSAGE,
/* @conditional-compile-remove(mention) */
mentionLookupOptions: mentionLookupOptions },
React.createElement(InputBoxButton, { onRenderIcon: onRenderSendIcon, onClick: (e) => {
if (!textValueOverflow) {
sendMessageOnClick();
}
e.stopPropagation();
}, id: 'sendIconWrapper', className: mergedSendButtonStyle, ariaLabel: localeStrings.sendButtonAriaLabel, tooltipContent: localeStrings.sendButtonAriaLabel, disabled: isSendBoxButtonDisabledValue })),
/* @conditional-compile-remove(file-sharing-acs) */
onRenderAttachmentUploads())));
};
//# sourceMappingURL=SendBox.js.map