communication-react-19
Version:
React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)
99 lines • 7.71 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { ContextualMenu, DirectionalHint, Icon, mergeStyles, Persona, PersonaSize, Stack, Text } from '@fluentui/react';
import React, { useMemo, useRef, useState } from 'react';
import { useIdentifiers } from '../identifiers';
import { useLocale } from '../localization';
import { useTheme } from '../theming';
import { displayNoneStyle, iconContainerStyle, iconStyles, meContainerStyle, menuButtonContainerStyle, participantItemContainerStyle, participantStateStringStyles } from './styles/ParticipantItem.styles';
import { _preventDismissOnEvent as preventDismissOnEvent } from "../../../acs-ui-common/src";
import { useId } from '@fluentui/react-hooks';
/**
* Component to render a calling or chat participant.
*
* Displays the participant's avatar, displayName and status as well as optional icons and context menu.
*
* @public
*/
export const ParticipantItem = (props) => {
var _a, _b, _c;
const { userId, displayName, onRenderAvatar, menuItems, onRenderIcon, presence, styles, me, onClick, showParticipantOverflowTooltip } = props;
const [itemHovered, setItemHovered] = useState(false);
const [menuHidden, setMenuHidden] = useState(true);
const containerRef = useRef(null);
const theme = useTheme();
const localeStrings = useLocale().strings.participantItem;
const ids = useIdentifiers();
const participantItemId = useId();
const participantItemFlyoutId = useId();
const hasFlyout = !!(menuItems && (menuItems === null || menuItems === void 0 ? void 0 : menuItems.length) > 0);
const strings = Object.assign(Object.assign({}, localeStrings), props.strings);
const participantStateString = formatParticipantStateString(props, strings);
const showMenuIcon = !participantStateString && (itemHovered || !menuHidden) && hasFlyout;
// For 'me' show empty name so avatar will get 'Person' icon, when there is no name
const meAvatarText = (displayName === null || displayName === void 0 ? void 0 : displayName.trim()) || '';
const avatarOptions = {
text: me ? meAvatarText : (displayName === null || displayName === void 0 ? void 0 : displayName.trim()) || strings.displayNamePlaceholder,
size: PersonaSize.size32,
presence: presence,
initialsTextColor: 'white',
showOverflowTooltip: showParticipantOverflowTooltip,
showUnknownPersonaCoin: !me && (!(displayName === null || displayName === void 0 ? void 0 : displayName.trim()) || displayName === strings.displayNamePlaceholder)
};
const avatar = onRenderAvatar ? (onRenderAvatar(userId !== null && userId !== void 0 ? userId : '', avatarOptions)) : (React.createElement(Persona, Object.assign({ className: mergeStyles({
// Prevents persona text from being vertically truncated if a global line height is less than 1.15.
lineHeight: '1.15rem'
}, styles === null || styles === void 0 ? void 0 : styles.avatar) }, avatarOptions)));
const meTextStyle = useMemo(() => mergeStyles(meContainerStyle, { color: theme.palette.neutralSecondary }, styles === null || styles === void 0 ? void 0 : styles.me), [theme.palette.neutralSecondary, styles === null || styles === void 0 ? void 0 : styles.me]);
const contextualMenuStyle = useMemo(() => mergeStyles({ background: theme.palette.neutralLighterAlt }, styles === null || styles === void 0 ? void 0 : styles.menu), [theme.palette.neutralLighterAlt, styles === null || styles === void 0 ? void 0 : styles.menu]);
const infoContainerStyle = useMemo(() => mergeStyles(iconContainerStyle, { color: theme.palette.neutralSecondary, marginLeft: 'auto' }, styles === null || styles === void 0 ? void 0 : styles.iconContainer), [theme.palette.neutralSecondary, styles === null || styles === void 0 ? void 0 : styles.iconContainer]);
const onDismissMenu = () => {
setItemHovered(false);
setMenuHidden(true);
};
const menuButton = useMemo(() => (React.createElement(Stack, { horizontal: true, horizontalAlign: "end", className: mergeStyles(menuButtonContainerStyle, { color: theme.palette.neutralPrimary }), title: strings.menuTitle, "aria-controls": participantItemFlyoutId, "data-ui-id": ids.participantItemMenuButton },
React.createElement(Icon, { iconName: "ParticipantItemOptionsHovered", className: mergeStyles(iconStyles, !showMenuIcon ? displayNoneStyle : {}) }))), [
theme.palette.neutralPrimary,
strings.menuTitle,
participantItemFlyoutId,
ids.participantItemMenuButton,
showMenuIcon
]);
return (React.createElement("div", { ref: containerRef, role: 'menuitem', id: participantItemId, "aria-label": (_b = (hasFlyout ? (_a = props.strings) === null || _a === void 0 ? void 0 : _a.participantItemWithMoreOptionsAriaLabel : undefined)) !== null && _b !== void 0 ? _b : (_c = props.strings) === null || _c === void 0 ? void 0 : _c.participantItemAriaLabel, "aria-labelledby": `${props.ariaLabelledBy} ${participantItemId}`, "aria-expanded": !menuHidden, "aria-disabled": hasFlyout || props.onClick ? false : true, "aria-haspopup": hasFlyout ? true : undefined, "aria-controls": participantItemFlyoutId, "data-is-focusable": hasFlyout, "data-ui-id": "participant-item", className: mergeStyles(participantItemContainerStyle({ clickable: hasFlyout }, theme), styles === null || styles === void 0 ? void 0 : styles.root), onMouseEnter: () => setItemHovered(true), onMouseLeave: () => setItemHovered(false), onClick: () => {
if (!participantStateString) {
setItemHovered(true);
setMenuHidden(false);
onClick === null || onClick === void 0 ? void 0 : onClick(props);
}
if (!menuHidden) {
onDismissMenu();
}
}, onKeyDown: (event) => {
if (event.key === 'Enter' || event.key === ' ') {
setMenuHidden(false);
}
}, tabIndex: hasFlyout ? 0 : undefined },
React.createElement(Stack, { horizontal: true, className: mergeStyles({
flexGrow: 1,
maxWidth: '100%',
alignItems: 'center'
}) },
avatar,
me && React.createElement(Text, { className: meTextStyle }, strings.isMeText),
React.createElement(Stack, { horizontal: true, className: mergeStyles(infoContainerStyle) },
!showMenuIcon && onRenderIcon && onRenderIcon(props),
!me && participantStateString ? (React.createElement(Text, { "data-ui-id": "participant-item-state-string", className: mergeStyles(participantStateStringStyles) }, participantStateString)) : (React.createElement(React.Fragment, null, hasFlyout && (React.createElement(React.Fragment, null,
menuButton,
React.createElement(ContextualMenu, { id: participantItemFlyoutId, items: menuItems, hidden: menuHidden, target: containerRef, onItemClick: onDismissMenu, onDismiss: onDismissMenu, directionalHint: DirectionalHint.bottomRightEdge, className: contextualMenuStyle, calloutProps: {
preventDismissOnEvent
} })))))))));
};
/** @private */
export const formatParticipantStateString = (props, strings) => {
return props.participantState === 'EarlyMedia' || props.participantState === 'Ringing'
? strings === null || strings === void 0 ? void 0 : strings.participantStateRinging
: props.participantState === 'Hold'
? strings === null || strings === void 0 ? void 0 : strings.participantStateHold
: undefined;
};
//# sourceMappingURL=ParticipantItem.js.map