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