UNPKG

communication-react-19

Version:

React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)

181 lines 8.24 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { typingIndicatorContainerStyle, typingIndicatorStringStyle } from './styles/TypingIndicator.styles'; import React from 'react'; import { mergeStyles, Stack, Text } from '@fluentui/react'; import { useLocale } from '../localization/LocalizationProvider'; import { useIdentifiers } from '../identifiers'; const MAXIMUM_LENGTH_OF_TYPING_USERS = 35; /** * Helper function to create element wrapping all typing users * @param typingUsers typing users * @param delimiter string to separate typing users * @param onRenderUser optional callback to render each typing user * @param userDisplayNameStyles optional additional IStyle to apply to each element containing users name * @returns element wrapping all typing users */ const getUsersElement = (typingUsers, delimiter, onRenderUser, userDisplayNameStyles) => { const userElements = []; typingUsers.forEach((user, index) => { let truncatedDisplayName = user.displayName; if (truncatedDisplayName && truncatedDisplayName.length > 50) { truncatedDisplayName = truncatedDisplayName.substring(0, 50) + '...'; } userElements.push(onRenderUser ? (onRenderUser(user)) : (React.createElement(Text, { className: mergeStyles(userDisplayNameStyles), key: `user-${index}` }, truncatedDisplayName))); userElements.push(React.createElement(Text, { key: `comma-${index}` }, `${delimiter}`)); }); // pop last comma userElements.pop(); return React.createElement(React.Fragment, null, userElements); }; /** * Helper function to get a string of all typing users * @param typingUsers typing users * @param delimiter string to separate typing users * @returns string of all typing users */ const getNamesString = (typingUsers, delimiter) => { const userNames = []; typingUsers.forEach((user) => { if (user.displayName) { userNames.push(user.displayName); } }); return userNames.join(delimiter); }; /** * Helper function to create span elements making up the typing indicator string * @param strings TypingIndicatorStrings containing strings to create span elements * @param usersElement JSX.Element containing all typing users * @param numTypingUsers number of total typing users * @param numUserNotMentioned number of typing users abbreviated * @returns array of span elements making up the typing indicator string */ const getSpanElements = (strings, usersElement, numTypingUsers, numTypingUsersAbbreviated) => { let variables = {}; let typingString = ''; if (numTypingUsers === 1) { typingString = strings.singleUser; variables = { user: usersElement }; } else if (numTypingUsers > 1 && numTypingUsersAbbreviated === 0) { typingString = strings.multipleUsers; variables = { users: usersElement }; } else if (numTypingUsers > 1 && numTypingUsersAbbreviated === 1) { typingString = strings.multipleUsersAbbreviateOne; variables = { users: usersElement }; } else if (numTypingUsers > 1 && numTypingUsersAbbreviated > 1) { typingString = strings.multipleUsersAbbreviateMany; variables = { users: usersElement, numOthers: React.createElement(React.Fragment, null, numTypingUsersAbbreviated) }; } return formatInlineElements(typingString, variables); }; /** * Helper function to get the string making up the typing indicator string * @param strings TypingIndicatorStrings containing strings to create span elements * @param namesString string of all typing users * @param numTypingUsers number of total typing users * @param numUserNotMentioned number of typing users abbreviated * @returns typing indicator string */ const getIndicatorString = (strings, namesString, numTypingUsers, numTypingUsersAbbreviated) => { if (numTypingUsers === 1) { return strings.singleUser.replace('{user}', namesString); } if (numTypingUsers > 1 && numTypingUsersAbbreviated === 0) { return strings.multipleUsers.replace('{users}', namesString); } if (numTypingUsers > 1 && numTypingUsersAbbreviated === 1) { return strings.multipleUsersAbbreviateOne.replace('{users}', namesString); } if (numTypingUsers > 1 && numTypingUsersAbbreviated > 1) { return strings.multipleUsersAbbreviateMany .replace('{users}', namesString) .replace('{numOthers}', `${numTypingUsersAbbreviated}`); } return undefined; }; const IndicatorComponent = (typingUsers, strings, onRenderUser, styles) => { const typingUsersMentioned = []; let totalCharacterCount = 0; const ids = useIdentifiers(); for (const typingUser of typingUsers) { if (!typingUser.displayName) { continue; } let additionalCharCount = typingUser.displayName.length; // The typing users will be separated by the delimiter. We account for that additional length when we generate the final string. if (typingUsersMentioned.length > 0) { additionalCharCount += strings.delimiter.length; } if (totalCharacterCount + additionalCharCount <= MAXIMUM_LENGTH_OF_TYPING_USERS || typingUsersMentioned.length === 0) { typingUsersMentioned.push(typingUser); totalCharacterCount += additionalCharCount; } else { break; } } const usersElement = getUsersElement(typingUsersMentioned, strings.delimiter, onRenderUser, styles === null || styles === void 0 ? void 0 : styles.typingUserDisplayName); const numUserNotMentioned = typingUsers.length - typingUsersMentioned.length; const spanElements = getSpanElements(strings, usersElement, typingUsers.length, numUserNotMentioned); const labelString = getIndicatorString(strings, getNamesString(typingUsersMentioned, strings.delimiter), typingUsers.length, numUserNotMentioned); return (React.createElement("div", { "data-ui-id": ids.typingIndicator, className: mergeStyles(typingIndicatorStringStyle, styles === null || styles === void 0 ? void 0 : styles.typingString), key: "typingStringKey", role: "status", "aria-label": labelString }, spanElements)); }; /** * Component to notify local user when one or more participants in the chat thread are typing. * * @public */ export const TypingIndicator = (props) => { const { typingUsers, onRenderUser, styles } = props; const { strings } = useLocale(); const typingUsersToRender = typingUsers.filter((typingUser) => typingUser.displayName !== undefined); const indicatorComponent = IndicatorComponent(typingUsersToRender, Object.assign(Object.assign({}, strings.typingIndicator), props.strings), onRenderUser, styles); return React.createElement(Stack, { className: mergeStyles(typingIndicatorContainerStyle, styles === null || styles === void 0 ? void 0 : styles.root) }, indicatorComponent); }; /** * Create an array of span elements by replacing the pattern "\{\}" in str with the elements * passed in as vars and creating inline elements from the rest * * @param str - The string to be formatted * @param vars - Variables to use to format the string * @returns formatted JSX elements */ const formatInlineElements = (str, vars) => { if (!str) { return []; } if (!vars) { return []; } const elements = []; // regex to search for the pattern "{}" const placeholdersRegex = /{(\w+)}/g; const regex = RegExp(placeholdersRegex); let array = regex.exec(str); let prev = 0; while (array !== null) { if (prev !== array.index) { elements.push(React.createElement(Text, { key: elements.length }, str.substring(prev, array.index))); } elements.push(React.createElement(Text, { key: elements.length }, vars[array[0].substring(1, array[0].length - 1)])); prev = regex.lastIndex; array = regex.exec(str); } elements.push(React.createElement(Text, { key: elements.length }, str.substring(prev))); return elements; }; //# sourceMappingURL=TypingIndicator.js.map