UNPKG

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
// 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