From 1ba42ebfc1bbf118837409dcae355cd47b0e0dc5 Mon Sep 17 00:00:00 2001
From: Leah Xia <107075081+Leah-Xia-Microsoft@users.noreply.github.com>
Date: Thu, 20 Feb 2025 15:50:54 -0800
Subject: [PATCH 1/6] Make onRenderAvatar API works with ChatComposite
---
common/config/babel/features.js | 4 ++-
.../FluentChatMessageComponent.tsx | 26 ++++++++++++++-----
.../react-components/src/types/OnRender.ts | 19 ++++++++++++++
.../ChatComposite/ChatComposite.tsx | 4 +++
.../composites/ChatComposite/ChatScreen.tsx | 21 ++++++++++++---
.../src/composites/common/BaseComposite.tsx | 10 ++++++-
6 files changed, 72 insertions(+), 12 deletions(-)
diff --git a/common/config/babel/features.js b/common/config/babel/features.js
index e209b34c72c..79de8f584ac 100644
--- a/common/config/babel/features.js
+++ b/common/config/babel/features.js
@@ -70,7 +70,9 @@ module.exports = {
// Feature for RTT
"rtt",
// Feature for together mode
- "together-mode"
+ "together-mode",
+ // Feature for exposing the onRenderAvatar API into the composites
+ "composite-onRenderAvatar-API"
],
stable: [
// Demo feature. Used in live-documentation of conditional compilation.
diff --git a/packages/react-components/src/components/ChatMessage/MessageComponents/FluentChatMessageComponent.tsx b/packages/react-components/src/components/ChatMessage/MessageComponents/FluentChatMessageComponent.tsx
index 5d130408ab3..c269b53b3a0 100644
--- a/packages/react-components/src/components/ChatMessage/MessageComponents/FluentChatMessageComponent.tsx
+++ b/packages/react-components/src/components/ChatMessage/MessageComponents/FluentChatMessageComponent.tsx
@@ -22,6 +22,7 @@ import { ChatMessageComponentWrapperProps } from '../ChatMessageComponentWrapper
import { BlockedMessage } from '../../../types/ChatMessage';
import { ChatMessage } from '../../../types/ChatMessage';
import { ChatMessageComponentAsMessageBubble } from './ChatMessageComponentAsMessageBubble';
+import { CustomAvatarOptions } from '../../../types';
/**
* Props for {@link FluentChatMessageComponentWrapper}
@@ -119,17 +120,28 @@ export const FluentChatMessageComponent = (props: FluentChatMessageComponentWrap
return { className: mergeClasses(chatMessageRenderStyles.rootMessage, chatMessageRenderStyles.rootCommon) };
}, [chatMessageRenderStyles.rootCommon, chatMessageRenderStyles.rootMessage]);
- const avatar = useMemo(() => {
- const chatAvatarStyle = shouldShowAvatar ? gutterWithAvatar : gutterWithHiddenAvatar;
- const personaOptions: IPersona = {
+ const personaOptions: IPersona = useMemo(
+ () => ({
hidePersonaDetails: true,
size: PersonaSize.size32,
text: message.senderDisplayName,
showOverflowTooltip: false
- };
+ }),
+ [message.senderDisplayName]
+ );
+
+ const defaultOnRenderAvatar = useCallback(
+ (props: CustomAvatarOptions) => {
+ return ;
+ },
+ [personaOptions]
+ );
+
+ const avatar = useMemo(() => {
+ const chatAvatarStyle = shouldShowAvatar ? gutterWithAvatar : gutterWithHiddenAvatar;
let renderedAvatar;
if (onRenderAvatar) {
- const avatarComponent = onRenderAvatar?.(message.senderId, personaOptions);
+ const avatarComponent = onRenderAvatar?.(message.senderId, personaOptions, defaultOnRenderAvatar);
if (!avatarComponent) {
return undefined;
} else {
@@ -138,10 +150,10 @@ export const FluentChatMessageComponent = (props: FluentChatMessageComponentWrap
}
return (
- {renderedAvatar ? renderedAvatar :
}
+ {renderedAvatar ? renderedAvatar : defaultOnRenderAvatar(personaOptions)}
);
- }, [message.senderDisplayName, message.senderId, onRenderAvatar, shouldShowAvatar]);
+ }, [defaultOnRenderAvatar, message.senderId, onRenderAvatar, personaOptions, shouldShowAvatar]);
const setMessageContainerRef = useCallback((node: HTMLDivElement | null) => {
removeFluentUIKeyboardNavigationStyles(node);
diff --git a/packages/react-components/src/types/OnRender.ts b/packages/react-components/src/types/OnRender.ts
index 402d484b05f..fec4809e51a 100644
--- a/packages/react-components/src/types/OnRender.ts
+++ b/packages/react-components/src/types/OnRender.ts
@@ -8,6 +8,8 @@ import {
PersonaPresence,
PersonaSize
} from '@fluentui/react';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { PersonaInitialsColor } from '@fluentui/react';
import { ParticipantState } from '.';
/**
@@ -21,8 +23,25 @@ export type CustomAvatarOptions = {
coinSize?: number;
/** Only show Coin and Initials */
hidePersonaDetails?: boolean;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ /**
+ * The background color when the user's initials are displayed.
+ * @defaultvalue Derived from `text`
+ */
+ initialsColor?: PersonaInitialsColor | string;
/** Text color of initials inside the coin */
initialsTextColor?: string;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ /**
+ * Image URL to use, should be a square aspect ratio and big enough to fit in the image area.
+ */
+ imageUrl?: string;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ /**
+ * The user's initials to display in the image area when there is no image.
+ * @defaultvalue Derived from `text`
+ */
+ imageInitials?: string;
/** Optional property to set the aria label of the video tile if there is no available stream. */
noVideoAvailableAriaLabel?: string;
/** User status */
diff --git a/packages/react-composites/src/composites/ChatComposite/ChatComposite.tsx b/packages/react-composites/src/composites/ChatComposite/ChatComposite.tsx
index 2ba6bfd78f7..be4f7c15f59 100644
--- a/packages/react-composites/src/composites/ChatComposite/ChatComposite.tsx
+++ b/packages/react-composites/src/composites/ChatComposite/ChatComposite.tsx
@@ -107,6 +107,8 @@ export const ChatComposite = (props: ChatCompositeProps): JSX.Element => {
adapter,
options,
onFetchAvatarPersonaData,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar,
onRenderTypingIndicator,
onRenderMessage,
onFetchParticipantMenuItems
@@ -127,6 +129,8 @@ export const ChatComposite = (props: ChatCompositeProps): JSX.Element => {
onRenderTypingIndicator={onRenderTypingIndicator}
onRenderMessage={onRenderMessage}
onFetchParticipantMenuItems={onFetchParticipantMenuItems}
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar={onRenderAvatar}
/* @conditional-compile-remove(file-sharing-acs) */
attachmentOptions={options?.attachmentOptions}
/>
diff --git a/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx b/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx
index 6d0acf4e8d5..1db5c21d326 100644
--- a/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx
+++ b/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx
@@ -17,6 +17,8 @@ import {
TypingIndicatorStylesProps,
useTheme
} from '@internal/react-components';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { CustomAvatarOptions, OnRenderAvatarCallback } from '@internal/react-components';
/* @conditional-compile-remove(rich-text-editor) */
import { RichTextEditBoxOptions } from '@internal/react-components';
/* @conditional-compile-remove(file-sharing-acs) */
@@ -95,6 +97,8 @@ import { getCreatedBy, getTextOnlyChat } from './selectors/baseSelectors';
export type ChatScreenProps = {
options?: ChatCompositeOptions;
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar?: OnRenderAvatarCallback;
onRenderMessage?: (messageProps: MessageProps, defaultOnRender?: MessageRenderer) => JSX.Element;
onRenderTypingIndicator?: (typingUsers: CommunicationParticipant[]) => JSX.Element;
onFetchParticipantMenuItems?: ParticipantMenuItemsCallback;
@@ -131,6 +135,8 @@ interface OverlayImageItem {
export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
const {
onFetchAvatarPersonaData,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar,
onRenderMessage,
onRenderTypingIndicator,
options,
@@ -229,7 +235,16 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
};
const onRenderAvatarCallback = useCallback(
- (userId?: string, defaultOptions?: AvatarPersonaProps) => {
+ (
+ userId?: string,
+ defaultOptions?: AvatarPersonaProps,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ defaultOnRender?: (props: CustomAvatarOptions) => JSX.Element
+ ) => {
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ if (onRenderAvatar) {
+ return onRenderAvatar(userId, defaultOptions, defaultOnRender);
+ }
return (
{
/>
);
},
- [onFetchAvatarPersonaData]
+ [onFetchAvatarPersonaData, /* @conditional-compile-remove(composite-onRenderAvatar-API) */ onRenderAvatar]
);
const messageThreadStyles = useMemo(() => {
@@ -380,7 +395,7 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
const titleIcon = onRenderAvatarCallback && onRenderAvatarCallback(messageSenderId, titleIconRenderOptions);
const overlayImage: OverlayImageItem = {
title: message?.senderDisplayName || '',
- titleIcon: titleIcon,
+ titleIcon: titleIcon || <>>,
attachmentId: attachment.id,
imageSrc: imageSrc,
messageId: messageId,
diff --git a/packages/react-composites/src/composites/common/BaseComposite.tsx b/packages/react-composites/src/composites/common/BaseComposite.tsx
index 695d760d9b7..4bc564452c7 100644
--- a/packages/react-composites/src/composites/common/BaseComposite.tsx
+++ b/packages/react-composites/src/composites/common/BaseComposite.tsx
@@ -12,6 +12,8 @@ import {
useTheme
} from '@fluentui/react';
import { FluentThemeProvider, ParticipantMenuItemsCallback } from '@internal/react-components';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { OnRenderAvatarCallback } from '@internal/react-components';
import React, { createContext, useContext } from 'react';
import { CompositeLocale, LocalizationProvider } from '../localization';
import { AvatarPersonaDataCallback } from './AvatarPersona';
@@ -55,9 +57,15 @@ export interface BaseCompositeProps>
* This will not affect the displayName shown in the composite.
* The displayName throughout the composite will be what is provided to the adapter when the adapter is created.
* will be what is provided to the adapter when the adapter is created.
+ *
+ * This callback will be ignored if onRenderAvatar is provided.
*/
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
-
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ /**
+ * Optional callback to override render of the avatar.
+ */
+ onRenderAvatar?: OnRenderAvatarCallback;
/**
* A callback function that can be used to provide custom menu items for a participant in
* participant list.
From 3ee07b43db3dd785d7e5bac06ae47570b1f9cd97 Mon Sep 17 00:00:00 2001
From: Leah Xia <107075081+Leah-Xia-Microsoft@users.noreply.github.com>
Date: Sun, 23 Feb 2025 21:42:54 -0800
Subject: [PATCH 2/6] Add onRenderAvatar to calling and callwithchat composites
---
.../review/beta/communication-react.api.md | 4 +
.../CallComposite/CallComposite.tsx | 21 ++++-
.../components/CallArrangement.tsx | 13 ++-
.../CallComposite/components/MediaGallery.tsx | 32 ++++++--
.../components/SidePane/usePeoplePane.tsx | 10 +++
.../CallComposite/pages/CallPage.tsx | 31 ++++++--
.../CallComposite/pages/TransferPage.tsx | 35 +++++++-
.../CallWithChatComposite.test.tsx | 2 +
.../CallWithChatComposite.tsx | 9 ++-
.../ChatComposite/ChatComposite.test.tsx | 6 ++
.../ChatComposite/ChatComposite.tsx | 4 +-
.../composites/ChatComposite/ChatScreen.tsx | 8 +-
.../ChatComposite/ChatScreenPeoplePane.tsx | 13 ++-
.../common/CallingCaptionsBanner.tsx | 28 ++++++-
.../common/ParticipantContainer.tsx | 79 +++++++++++++------
.../composites/common/PeoplePaneContent.tsx | 6 ++
.../tests/app/chat/CustomDataModel.tsx | 19 ++++-
.../tests/app/chat/FakeAdapterApp.tsx | 4 +
.../tests/app/chat/LiveTestApp.tsx | 21 +++++
19 files changed, 293 insertions(+), 52 deletions(-)
diff --git a/packages/communication-react/review/beta/communication-react.api.md b/packages/communication-react/review/beta/communication-react.api.md
index 1e39056157c..cd2b544712e 100644
--- a/packages/communication-react/review/beta/communication-react.api.md
+++ b/packages/communication-react/review/beta/communication-react.api.md
@@ -330,6 +330,7 @@ export interface BaseCompositeProps>
locale?: CompositeLocale;
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
onFetchParticipantMenuItems?: ParticipantMenuItemsCallback;
+ onRenderAvatar?: OnRenderAvatarCallback;
rtl?: boolean;
}
@@ -2863,7 +2864,10 @@ export type CreateViewResult = {
export type CustomAvatarOptions = {
coinSize?: number;
hidePersonaDetails?: boolean;
+ initialsColor?: PersonaInitialsColor | string;
initialsTextColor?: string;
+ imageUrl?: string;
+ imageInitials?: string;
noVideoAvailableAriaLabel?: string;
presence?: PersonaPresence;
size?: PersonaSize;
diff --git a/packages/react-composites/src/composites/CallComposite/CallComposite.tsx b/packages/react-composites/src/composites/CallComposite/CallComposite.tsx
index 573e7e5428f..85cc933955f 100644
--- a/packages/react-composites/src/composites/CallComposite/CallComposite.tsx
+++ b/packages/react-composites/src/composites/CallComposite/CallComposite.tsx
@@ -34,6 +34,8 @@ import {
mainScreenContainerStyleMobile
} from './styles/CallComposite.styles';
import { CallControlOptions } from './types/CallControlOptions';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { OnRenderAvatarCallback } from '@internal/react-components';
import { LayerHost, mergeStyles } from '@fluentui/react';
import { modalLayerHostStyle } from '../common/styles/ModalLocalAndRemotePIP.styles';
@@ -356,6 +358,8 @@ type MainScreenProps = {
modalLayerHostId: string;
callInvitationUrl?: string;
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar?: OnRenderAvatarCallback;
onFetchParticipantMenuItems?: ParticipantMenuItemsCallback;
options?: CallCompositeOptions;
overrideSidePane?: InjectedSidePaneProps;
@@ -408,7 +412,12 @@ const MainScreen = (props: MainScreenProps): JSX.Element => {
micHasPermission
]);
- const { callInvitationUrl, onFetchAvatarPersonaData, onFetchParticipantMenuItems } = props;
+ const {
+ callInvitationUrl,
+ onFetchAvatarPersonaData,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */ onRenderAvatar,
+ onFetchParticipantMenuItems
+ } = props;
const page = useSelector(getPage);
const endedCall = useSelector(getEndedCall);
@@ -692,6 +701,8 @@ const MainScreen = (props: MainScreenProps): JSX.Element => {
updateSidePaneRenderer={setSidePaneRenderer}
mobileChatTabHeader={props.mobileChatTabHeader}
onFetchAvatarPersonaData={onFetchAvatarPersonaData}
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar={onRenderAvatar}
latestErrors={latestInCallErrors}
latestNotifications={latestNotifications}
onDismissError={onDismissError}
@@ -705,6 +716,8 @@ const MainScreen = (props: MainScreenProps): JSX.Element => {
{
void;
mobileChatTabHeader?: MobileChatSidePaneTabHeaderProps;
latestErrors: ActiveErrorMessage[] | ActiveNotification[];
@@ -197,6 +201,8 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
setDrawerMenuItems,
inviteLink: props.callControlProps.callInvitationURL,
onFetchAvatarPersonaData: props.onFetchAvatarPersonaData,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar: props.onRenderAvatar,
onFetchParticipantMenuItems: props.callControlProps?.onFetchParticipantMenuItems,
mobileView: props.mobileView,
peopleButtonRef,
@@ -209,10 +215,9 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
props.callControlProps.callInvitationURL,
props.callControlProps?.onFetchParticipantMenuItems,
props.onFetchAvatarPersonaData,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ props.onRenderAvatar,
props.mobileView,
- peopleButtonRef,
- setParticipantActioned,
- sidePaneDismissButtonRef,
props.onCloseChatPane
]
);
@@ -582,6 +587,8 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
captionsOptions={props.captionsOptions}
isMobile={props.mobileView}
onFetchAvatarPersonaData={props.onFetchAvatarPersonaData}
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar={props.onRenderAvatar}
useTeamsCaptions={useTeamsCaptions}
/* @conditional-compile-remove(rtt) */
isRealTimeTextOn={openRealTimeText}
diff --git a/packages/react-composites/src/composites/CallComposite/components/MediaGallery.tsx b/packages/react-composites/src/composites/CallComposite/components/MediaGallery.tsx
index 24c98c9cd91..0477478330e 100644
--- a/packages/react-composites/src/composites/CallComposite/components/MediaGallery.tsx
+++ b/packages/react-composites/src/composites/CallComposite/components/MediaGallery.tsx
@@ -28,6 +28,8 @@ import { PromptProps } from './Prompt';
import { useLocalSpotlightCallbacksWithPrompt, useRemoteSpotlightCallbacksWithPrompt } from '../utils/spotlightUtils';
import { VideoTilesOptions } from '@internal/react-components';
import { getCapabilites, getIsRoomsCall, getReactionResources, getRole } from '../selectors/baseSelectors';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { OnRenderAvatarCallback } from '@internal/react-components';
const VideoGalleryStyles = {
root: {
@@ -54,6 +56,8 @@ export interface MediaGalleryProps {
isMicrophoneChecked?: boolean;
onStartLocalVideo: () => Promise;
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar?: OnRenderAvatarCallback;
isMobile?: boolean;
drawerMenuHostId?: string;
remoteVideoTileMenuOptions?: RemoteVideoTileMenuOptions;
@@ -82,7 +86,10 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
setPromptProps,
hideSpotlightButtons,
videoTilesOptions,
- captionsOptions
+ captionsOptions,
+ onFetchAvatarPersonaData,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar
} = props;
const videoGalleryProps = usePropsFor(VideoGallery);
@@ -110,19 +117,32 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
};
}, [cameraSwitcherCallback, cameraSwitcherCameras]);
- const onRenderAvatar = useCallback(
+ const defaultOnRenderAvatar = useCallback(
(userId?: string, options?: CustomAvatarOptions) => {
return (
{options?.coinSize && (
-
+
)}
);
},
- [props.onFetchAvatarPersonaData]
+ [onFetchAvatarPersonaData]
+ );
+
+ const onRenderAvatarCallback = useCallback(
+ (userId?: string, options?: CustomAvatarOptions) => {
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ if (onRenderAvatar) {
+ const defaultOnRenderAvatarWrapper = (props: CustomAvatarOptions): JSX.Element =>
+ defaultOnRenderAvatar(userId, props);
+ return onRenderAvatar(userId, options, defaultOnRenderAvatarWrapper);
+ }
+ return defaultOnRenderAvatar(userId, options);
+ },
+ [defaultOnRenderAvatar, /* @conditional-compile-remove(composite-onRenderAvatar-API) */ onRenderAvatar]
);
const remoteVideoTileMenuOptions: false | VideoTileContextualMenuProps | VideoTileDrawerMenuProps = useMemo(() => {
@@ -201,7 +221,7 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
layout={layoutBasedOnUserSelection()}
showCameraSwitcherInLocalPreview={props.isMobile}
localVideoCameraCycleButtonProps={cameraSwitcherProps}
- onRenderAvatar={onRenderAvatar}
+ onRenderAvatar={onRenderAvatarCallback}
remoteVideoTileMenu={remoteVideoTileMenuOptions}
overflowGalleryPosition={overflowGalleryPosition}
localVideoTileSize={
@@ -234,7 +254,7 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
props.localVideoTileOptions,
props.userSetGalleryLayout,
cameraSwitcherProps,
- onRenderAvatar,
+ onRenderAvatarCallback,
remoteVideoTileMenuOptions,
overflowGalleryPosition,
userRole,
diff --git a/packages/react-composites/src/composites/CallComposite/components/SidePane/usePeoplePane.tsx b/packages/react-composites/src/composites/CallComposite/components/SidePane/usePeoplePane.tsx
index a8b80da0288..a6983b61b90 100644
--- a/packages/react-composites/src/composites/CallComposite/components/SidePane/usePeoplePane.tsx
+++ b/packages/react-composites/src/composites/CallComposite/components/SidePane/usePeoplePane.tsx
@@ -7,6 +7,8 @@ import { SidePaneHeader } from '../../../common/SidePaneHeader';
import { PeoplePaneContent } from '../../../common/PeoplePaneContent';
import { useLocale } from '../../../localization';
import { ParticipantMenuItemsCallback, _DrawerMenuItemProps, MediaAccess } from '@internal/react-components';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { OnRenderAvatarCallback } from '@internal/react-components';
import { AvatarPersonaDataCallback } from '../../../common/AvatarPersona';
import { IButton, IContextualMenuProps, IContextualMenuItem } from '@fluentui/react';
import { useSelector } from '../../hooks/useSelector';
@@ -21,6 +23,8 @@ export const usePeoplePane = (props: {
setDrawerMenuItems: (items: _DrawerMenuItemProps[]) => void;
inviteLink?: string;
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar?: OnRenderAvatarCallback;
onFetchParticipantMenuItems?: ParticipantMenuItemsCallback;
mobileView?: boolean;
peopleButtonRef?: RefObject;
@@ -58,6 +62,8 @@ export const usePeoplePane = (props: {
updateSidePaneRenderer,
inviteLink,
onFetchAvatarPersonaData,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar,
onFetchParticipantMenuItems,
setDrawerMenuItems,
mobileView,
@@ -623,6 +629,8 @@ export const usePeoplePane = (props: {
void;
mobileChatTabHeader?: MobileChatSidePaneTabHeaderProps;
@@ -79,6 +82,8 @@ export const CallPage = (props: CallPageProps): JSX.Element => {
const {
callInvitationURL,
onFetchAvatarPersonaData,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar,
onFetchParticipantMenuItems,
options,
mobileView,
@@ -136,19 +141,31 @@ export const CallPage = (props: CallPageProps): JSX.Element => {
const displayName = useSelector((state) => state.displayName);
/* @conditional-compile-remove(breakout-rooms) */
- const onRenderAvatar = useCallback(
+ const defaultOnRenderAvatar = useCallback(
(userId?: string, options?: CustomAvatarOptions) => {
return (
{options?.coinSize && (
-
+
)}
);
},
- [props.onFetchAvatarPersonaData]
+ [onFetchAvatarPersonaData]
+ );
+
+ /* @conditional-compile-remove(breakout-rooms) */
+ const onRenderAvatarCallback = useCallback(
+ (userId?: string, options?: CustomAvatarOptions, defaultOnRender?: (props: CustomAvatarOptions) => JSX.Element) => {
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ if (onRenderAvatar) {
+ return onRenderAvatar(userId, options, defaultOnRender);
+ }
+ return defaultOnRenderAvatar(userId, options);
+ },
+ [defaultOnRenderAvatar, /* @conditional-compile-remove(composite-onRenderAvatar-API) */ onRenderAvatar]
);
let galleryContentWhenNotInCall = <>>;
@@ -159,7 +176,7 @@ export const CallPage = (props: CallPageProps): JSX.Element => {
userId={toFlatCommunicationIdentifier(userId)}
displayName={displayName}
initialsName={displayName}
- onRenderPlaceholder={onRenderAvatar}
+ onRenderPlaceholder={onRenderAvatarCallback}
/>
);
}
@@ -188,6 +205,8 @@ export const CallPage = (props: CallPageProps): JSX.Element => {
{...mediaGalleryProps}
{...mediaGalleryHandlers}
onFetchAvatarPersonaData={onFetchAvatarPersonaData}
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar={onRenderAvatar}
remoteVideoTileMenuOptions={options?.remoteVideoTileMenuOptions}
drawerMenuHostId={drawerMenuHostId}
localVideoTileOptions={options?.localVideoTile}
@@ -220,6 +239,8 @@ export const CallPage = (props: CallPageProps): JSX.Element => {
increaseFlyoutItemSize: mobileView
}}
onFetchAvatarPersonaData={onFetchAvatarPersonaData}
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar={onRenderAvatar}
mobileView={mobileView}
modalLayerHostId={props.modalLayerHostId}
onRenderGalleryContent={() =>
diff --git a/packages/react-composites/src/composites/CallComposite/pages/TransferPage.tsx b/packages/react-composites/src/composites/CallComposite/pages/TransferPage.tsx
index 8bcc556dbf2..34328aa49e6 100644
--- a/packages/react-composites/src/composites/CallComposite/pages/TransferPage.tsx
+++ b/packages/react-composites/src/composites/CallComposite/pages/TransferPage.tsx
@@ -26,6 +26,8 @@ import {
} from '../styles/TransferPage.styles';
import { reduceCallControlsForMobile } from '../utils';
import { LobbyPageProps } from './LobbyPage';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { OnRenderAvatarCallback } from '@internal/react-components';
// Which should be participant shown in the transfer page
type TransferPageSubject = 'transferor' | 'transferTarget';
@@ -37,6 +39,8 @@ export const TransferPage = (
props: LobbyPageProps & {
/** Callback function that can be used to provide custom data to Persona Icon rendered */
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar?: OnRenderAvatarCallback;
}
): JSX.Element => {
const errorBarProps = usePropsFor(ErrorBar);
@@ -131,7 +135,13 @@ interface TransferTileProps {
/** User id for `onFetchAvatarPersonaData` callback to provide custom data to avatars rendered */
userId?: string;
/** Callback function that can be used to provide custom data to Persona Icon rendered */
+ /** This callback will be ignored if onRenderAvatar is provided. */
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ /**
+ * Optional callback to override render of the avatar.
+ */
+ onRenderAvatar?: OnRenderAvatarCallback;
/**
* Display name of the transferor or transfer target to be shown in the label.
* @remarks `displayName` is used to generate avatar initials if `initialsName` is not provided.
@@ -148,7 +158,14 @@ interface TransferTileProps {
}
const TransferTile = (props: TransferTileProps): JSX.Element => {
- const { displayName, initialsName, userId, onFetchAvatarPersonaData, statusText } = props;
+ const {
+ displayName,
+ initialsName,
+ userId,
+ onFetchAvatarPersonaData,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */ onRenderAvatar,
+ statusText
+ } = props;
const [personaSize, setPersonaSize] = useState();
const tileRef = useRef(null);
@@ -197,11 +214,25 @@ const TransferTile = (props: TransferTileProps): JSX.Element => {
const defaultAvatar = useMemo(() => defaultOnRenderAvatar(), [defaultOnRenderAvatar]);
+ const onRenderAvatarCallback = useCallback(() => {
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ if (onRenderAvatar) {
+ return onRenderAvatar(userId, placeholderOptions, defaultOnRenderAvatar);
+ }
+ return defaultAvatar;
+ }, [
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */ onRenderAvatar,
+ userId,
+ placeholderOptions,
+ defaultOnRenderAvatar,
+ defaultAvatar
+ ]);
+
return (
- {defaultAvatar}
+ {onRenderAvatarCallback()}
{displayName}
diff --git a/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.test.tsx b/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.test.tsx
index f52f392dde2..a884b2f32b1 100644
--- a/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.test.tsx
+++ b/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.test.tsx
@@ -63,6 +63,8 @@ describe('CallWithChatComposite', () => {
locale: COMPOSITE_LOCALE_ZH_TW,
rtl: true,
onFetchAvatarPersonaData: jest.fn(),
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar: jest.fn(),
onFetchParticipantMenuItems: jest.fn()
};
diff --git a/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.tsx b/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.tsx
index 81288658b60..bca0cd07608 100644
--- a/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.tsx
+++ b/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.tsx
@@ -21,6 +21,8 @@ import { CallWithChatCompositeIcons } from '../common/icons';
import { AvatarPersonaDataCallback } from '../common/AvatarPersona';
import { CallWithChatAdapterState } from './state/CallWithChatAdapterState';
import { CallSurveyImprovementSuggestions } from '@internal/react-components';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { OnRenderAvatarCallback } from '@internal/react-components';
import {
ParticipantMenuItemsCallback,
_useContainerHeight,
@@ -290,6 +292,8 @@ type CallWithChatScreenProps = {
joinInvitationURL?: string;
callControls?: boolean | CallWithChatControlOptions;
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar?: OnRenderAvatarCallback;
onFetchParticipantMenuItems?: ParticipantMenuItemsCallback;
/* @conditional-compile-remove(file-sharing-acs) */
attachmentOptions?: AttachmentOptions;
@@ -601,6 +605,8 @@ const CallWithChatScreen = (props: CallWithChatScreenProps): JSX.Element => {
fluentTheme={theme}
options={chatCompositeOptions}
onFetchAvatarPersonaData={props.onFetchAvatarPersonaData}
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar={props.onRenderAvatar}
/>
);
}, [
@@ -609,7 +615,8 @@ const CallWithChatScreen = (props: CallWithChatScreenProps): JSX.Element => {
chatCompositeOptions,
theme,
/* @conditional-compile-remove(breakout-rooms) */ isChatInitialized,
- /* @conditional-compile-remove(breakout-rooms) */ chatSpinnerLabel
+ /* @conditional-compile-remove(breakout-rooms) */ chatSpinnerLabel,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */ props.onRenderAvatar
]);
let chatPaneTitle = callWithChatStrings.chatPaneTitle;
diff --git a/packages/react-composites/src/composites/ChatComposite/ChatComposite.test.tsx b/packages/react-composites/src/composites/ChatComposite/ChatComposite.test.tsx
index f9e750841cc..294a15ba8a6 100644
--- a/packages/react-composites/src/composites/ChatComposite/ChatComposite.test.tsx
+++ b/packages/react-composites/src/composites/ChatComposite/ChatComposite.test.tsx
@@ -76,6 +76,8 @@ describe('ChatComposite', () => {
locale: COMPOSITE_LOCALE_ZH_TW,
rtl: true,
onFetchAvatarPersonaData: jest.fn(),
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar: jest.fn(),
options: {
richTextEditor: true
}
@@ -95,6 +97,8 @@ describe('ChatComposite', () => {
locale: COMPOSITE_LOCALE_ZH_TW,
rtl: true,
onFetchAvatarPersonaData: jest.fn(),
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar: jest.fn(),
options: {
richTextEditor: false
}
@@ -146,6 +150,8 @@ describe('ChatComposite - text only mode', () => {
locale: COMPOSITE_LOCALE_ZH_TW,
rtl: true,
onFetchAvatarPersonaData: jest.fn(),
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar: jest.fn(),
options: {
richTextEditor: richTextEditor,
attachmentOptions: {
diff --git a/packages/react-composites/src/composites/ChatComposite/ChatComposite.tsx b/packages/react-composites/src/composites/ChatComposite/ChatComposite.tsx
index be4f7c15f59..188d5a8ee4c 100644
--- a/packages/react-composites/src/composites/ChatComposite/ChatComposite.tsx
+++ b/packages/react-composites/src/composites/ChatComposite/ChatComposite.tsx
@@ -126,11 +126,11 @@ export const ChatComposite = (props: ChatCompositeProps): JSX.Element => {
formFactor={formFactor}
options={options}
onFetchAvatarPersonaData={onFetchAvatarPersonaData}
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar={onRenderAvatar}
onRenderTypingIndicator={onRenderTypingIndicator}
onRenderMessage={onRenderMessage}
onFetchParticipantMenuItems={onFetchParticipantMenuItems}
- /* @conditional-compile-remove(composite-onRenderAvatar-API) */
- onRenderAvatar={onRenderAvatar}
/* @conditional-compile-remove(file-sharing-acs) */
attachmentOptions={options?.attachmentOptions}
/>
diff --git a/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx b/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx
index 1db5c21d326..3c39517c0c6 100644
--- a/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx
+++ b/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx
@@ -15,10 +15,11 @@ import {
SendBoxStylesProps,
TypingIndicator,
TypingIndicatorStylesProps,
- useTheme
+ useTheme,
+ CustomAvatarOptions
} from '@internal/react-components';
/* @conditional-compile-remove(composite-onRenderAvatar-API) */
-import { CustomAvatarOptions, OnRenderAvatarCallback } from '@internal/react-components';
+import { OnRenderAvatarCallback } from '@internal/react-components';
/* @conditional-compile-remove(rich-text-editor) */
import { RichTextEditBoxOptions } from '@internal/react-components';
/* @conditional-compile-remove(file-sharing-acs) */
@@ -238,7 +239,6 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
(
userId?: string,
defaultOptions?: AvatarPersonaProps,
- /* @conditional-compile-remove(composite-onRenderAvatar-API) */
defaultOnRender?: (props: CustomAvatarOptions) => JSX.Element
) => {
/* @conditional-compile-remove(composite-onRenderAvatar-API) */
@@ -750,6 +750,8 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
options?.participantPane === true && (
diff --git a/packages/react-composites/src/composites/ChatComposite/ChatScreenPeoplePane.tsx b/packages/react-composites/src/composites/ChatComposite/ChatScreenPeoplePane.tsx
index 0e580fb2e92..26e81a742d7 100644
--- a/packages/react-composites/src/composites/ChatComposite/ChatScreenPeoplePane.tsx
+++ b/packages/react-composites/src/composites/ChatComposite/ChatScreenPeoplePane.tsx
@@ -2,6 +2,8 @@
// Licensed under the MIT License.
import { ParticipantList, ParticipantMenuItemsCallback } from '@internal/react-components';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { OnRenderAvatarCallback } from '@internal/react-components';
import React from 'react';
import { AvatarPersonaDataCallback } from '..';
import { ParticipantContainer } from '../common/ParticipantContainer';
@@ -14,6 +16,8 @@ import { usePropsFor } from './hooks/usePropsFor';
*/
type ChatScreenPeoplePaneProps = {
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar?: OnRenderAvatarCallback;
onFetchParticipantMenuItems?: ParticipantMenuItemsCallback;
isMobile?: boolean;
};
@@ -22,7 +26,12 @@ type ChatScreenPeoplePaneProps = {
* @private
*/
export const ChatScreenPeoplePane = (props: ChatScreenPeoplePaneProps): JSX.Element => {
- const { onFetchAvatarPersonaData, onFetchParticipantMenuItems, isMobile } = props;
+ const {
+ onFetchAvatarPersonaData,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */ onRenderAvatar,
+ onFetchParticipantMenuItems,
+ isMobile
+ } = props;
const locale = useLocale();
const chatListHeader = locale.strings.chat.chatListHeader;
const participantListProps = usePropsFor(ParticipantList);
@@ -32,6 +41,8 @@ export const ChatScreenPeoplePane = (props: ChatScreenPeoplePaneProps): JSX.Elem
participantListProps={participantListProps}
title={chatListHeader}
onFetchAvatarPersonaData={onFetchAvatarPersonaData}
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar={onRenderAvatar}
onFetchParticipantMenuItems={onFetchParticipantMenuItems}
isMobile={isMobile}
/>
diff --git a/packages/react-composites/src/composites/common/CallingCaptionsBanner.tsx b/packages/react-composites/src/composites/common/CallingCaptionsBanner.tsx
index 228a2f7a483..b2df9daf1b7 100644
--- a/packages/react-composites/src/composites/common/CallingCaptionsBanner.tsx
+++ b/packages/react-composites/src/composites/common/CallingCaptionsBanner.tsx
@@ -4,6 +4,8 @@
import React from 'react';
import { useState, useEffect, useCallback } from 'react';
import { CaptionsBanner, CaptionsBannerStrings, CustomAvatarOptions } from '@internal/react-components';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { OnRenderAvatarCallback } from '@internal/react-components';
import { _DrawerMenu, _DrawerMenuItemProps, _DrawerSurface } from '@internal/react-components';
import { mergeStyles, Stack } from '@fluentui/react';
import { CallingCaptionsSettingsModal } from './CallingCaptionsSettingsModal';
@@ -19,12 +21,16 @@ export const CallingCaptionsBanner = (props: {
isMobile: boolean;
useTeamsCaptions?: boolean;
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar?: OnRenderAvatarCallback;
captionsOptions?: {
height: 'full' | 'default';
};
/* @conditional-compile-remove(rtt) */
isRealTimeTextOn?: boolean;
}): JSX.Element => {
+ const { onFetchAvatarPersonaData, /* @conditional-compile-remove(composite-onRenderAvatar-API) */ onRenderAvatar } =
+ props;
const captionsBannerProps = usePropsFor(CaptionsBanner);
const [isCaptionsSettingsOpen, setIsCaptionsSettingsOpen] = useState(false);
@@ -78,11 +84,25 @@ export const CallingCaptionsBanner = (props: {
minimizeButtonAriaLabel: strings.minimizeButtonAriaLabel
};
- const onRenderAvatar = useCallback(
+ const defaultOnRenderAvatar = useCallback(
(userId?: string, options?: CustomAvatarOptions) => {
- return ;
+ return ;
},
- [props.onFetchAvatarPersonaData]
+ [onFetchAvatarPersonaData]
+ );
+
+ const onRenderAvatarCallback = useCallback(
+ (userId?: string, options?: CustomAvatarOptions) => {
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ if (onRenderAvatar) {
+ const defaultOnRenderAvatarWrapper = (options: CustomAvatarOptions): JSX.Element => {
+ return defaultOnRenderAvatar(userId, options);
+ };
+ return onRenderAvatar(userId, options, defaultOnRenderAvatarWrapper);
+ }
+ return defaultOnRenderAvatar(userId, options);
+ },
+ [defaultOnRenderAvatar, /* @conditional-compile-remove(composite-onRenderAvatar-API) */ onRenderAvatar]
);
const { innerWidth: width } = window;
@@ -115,7 +135,7 @@ export const CallingCaptionsBanner = (props: {
void;
@@ -55,6 +66,8 @@ export const ParticipantListWithHeading = (props: {
}): JSX.Element => {
const {
onFetchAvatarPersonaData,
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar,
onFetchParticipantMenuItems,
title,
participantListProps,
@@ -82,6 +95,46 @@ export const ParticipantListWithHeading = (props: {
[theme.palette.neutralSecondary, theme.fonts.smallPlus.fontSize, props.isMobile]
);
+ const defaultOnRenderAvatar = useCallback(
+ (userId?: string, options?: CustomAvatarOptions): JSX.Element => {
+ return (
+ <>
+
+ {options?.text && (
+
+
+
+ {options?.text}
+
+
+
+ )}
+ >
+ );
+ },
+ [onFetchAvatarPersonaData, tooltipId]
+ );
+
+ const onRenderAvatarCallback = useCallback(
+ (userId?: string, options?: CustomAvatarOptions) => {
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ if (onRenderAvatar) {
+ const defaultOnRenderAvatarWrapper = (options: CustomAvatarOptions): JSX.Element =>
+ defaultOnRenderAvatar(userId, options);
+ return onRenderAvatar(userId, options, defaultOnRenderAvatarWrapper);
+ }
+ return defaultOnRenderAvatar(userId, options);
+ },
+ [defaultOnRenderAvatar, /* @conditional-compile-remove(composite-onRenderAvatar-API) */ onRenderAvatar]
+ );
+
return (
@@ -113,27 +166,7 @@ export const ParticipantListWithHeading = (props: {
{...participantListProps}
pinnedParticipants={pinnedParticipants}
styles={props.isMobile ? participantListMobileStyle : participantListStyle}
- onRenderAvatar={(userId, options) => (
- <>
-
- {options?.text && (
-
-
-
- {options?.text}
-
-
-
- )}
- >
- )}
+ onRenderAvatar={onRenderAvatarCallback}
onFetchParticipantMenuItems={onFetchParticipantMenuItems}
showParticipantOverflowTooltip={!props.isMobile}
participantAriaLabelledBy={subheadingUniqueId}
diff --git a/packages/react-composites/src/composites/common/PeoplePaneContent.tsx b/packages/react-composites/src/composites/common/PeoplePaneContent.tsx
index 5d0ec550a13..1ef1808a0dc 100644
--- a/packages/react-composites/src/composites/common/PeoplePaneContent.tsx
+++ b/packages/react-composites/src/composites/common/PeoplePaneContent.tsx
@@ -9,6 +9,8 @@ import {
ParticipantMenuItemsCallback,
_DrawerMenuItemProps
} from '@internal/react-components';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { OnRenderAvatarCallback } from '@internal/react-components';
import React, { useCallback, useMemo } from 'react';
import { CallWithChatCompositeStrings } from '../CallWithChatComposite';
import { usePropsFor } from '../CallComposite/hooks/usePropsFor';
@@ -30,6 +32,8 @@ import { useLocale } from '../localization';
export const PeoplePaneContent = (props: {
inviteLink?: string;
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar?: OnRenderAvatarCallback;
onFetchParticipantMenuItems?: ParticipantMenuItemsCallback;
setDrawerMenuItems: (drawerMenuItems: _DrawerMenuItemProps[]) => void;
setParticipantActioned?: (userId: string) => void;
@@ -128,6 +132,8 @@ export const PeoplePaneContent = (props: {
isMobile={props.mobileView}
participantListProps={participantListProps}
onFetchAvatarPersonaData={props.onFetchAvatarPersonaData}
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar={props.onRenderAvatar}
onFetchParticipantMenuItems={props.mobileView ? undefined : onFetchParticipantMenuItems}
title={strings.peoplePaneSubTitle}
headingMoreButtonAriaLabel={localeStrings.strings.call.peoplePaneMoreButtonAriaLabel}
diff --git a/packages/react-composites/tests/app/chat/CustomDataModel.tsx b/packages/react-composites/tests/app/chat/CustomDataModel.tsx
index f18d1f43f4d..ef56f662053 100644
--- a/packages/react-composites/tests/app/chat/CustomDataModel.tsx
+++ b/packages/react-composites/tests/app/chat/CustomDataModel.tsx
@@ -2,7 +2,7 @@
// Licensed under the MIT License.
import { Stack } from '@fluentui/react';
-import { CommunicationParticipant, MessageProps } from '@internal/react-components';
+import { CommunicationParticipant, CustomAvatarOptions, MessageProps } from '@internal/react-components';
import React from 'react';
import { AvatarPersonaData } from '../../../src';
@@ -66,3 +66,20 @@ export const customOnFetchAvatarPersonaData = (): Promise =>
text: 'Custom Name'
})
);
+
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+/**
+ * Custom onRenderAvatar prop of {@link ChatComposite} used for UI test
+ */
+export const customOnRenderAvatar = (
+ userId?: string,
+ options?: CustomAvatarOptions,
+ defaultOnRender?: (options: CustomAvatarOptions) => JSX.Element
+): JSX.Element | undefined => {
+ const avatarOptions = {
+ ...options,
+ text: 'Custom Name',
+ initialsColor: 'red'
+ };
+ return defaultOnRender ? defaultOnRender(avatarOptions) : undefined;
+};
diff --git a/packages/react-composites/tests/app/chat/FakeAdapterApp.tsx b/packages/react-composites/tests/app/chat/FakeAdapterApp.tsx
index 4128671fc5c..1fa1a6bdea3 100644
--- a/packages/react-composites/tests/app/chat/FakeAdapterApp.tsx
+++ b/packages/react-composites/tests/app/chat/FakeAdapterApp.tsx
@@ -21,6 +21,8 @@ import {
customOnRenderMessage,
customOnRenderTypingIndicator
} from './CustomDataModel';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { customOnRenderAvatar } from './CustomDataModel';
import { FakeChatClient, Model, Thread } from '@internal/fake-backends';
import { HiddenChatComposites } from '../lib/HiddenChatComposites';
@@ -94,6 +96,8 @@ export const FakeAdapterApp = (): JSX.Element => {
onFetchAvatarPersonaData={
fakeChatAdapterArgs.customDataModelEnabled ? customOnFetchAvatarPersonaData : undefined
}
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar={fakeChatAdapterArgs.customDataModelEnabled ? customOnRenderAvatar : undefined}
fluentTheme={fakeChatAdapterArgs.theme === 'dark' ? darkTheme : lightTheme}
/>
diff --git a/packages/react-composites/tests/app/chat/LiveTestApp.tsx b/packages/react-composites/tests/app/chat/LiveTestApp.tsx
index dbcdb9bfaa2..7d657c2763d 100644
--- a/packages/react-composites/tests/app/chat/LiveTestApp.tsx
+++ b/packages/react-composites/tests/app/chat/LiveTestApp.tsx
@@ -5,6 +5,8 @@ import { AzureCommunicationTokenCredential, CommunicationUserIdentifier } from '
import { Stack } from '@fluentui/react';
import { fromFlatCommunicationIdentifier } from '@internal/acs-ui-common';
import { _IdentifierProvider, defaultAttachmentMenuAction, MessageProps } from '@internal/react-components';
+/* @conditional-compile-remove(composite-onRenderAvatar-API) */
+import { CustomAvatarOptions } from '@internal/react-components';
import React, { useMemo } from 'react';
import { ChatComposite, COMPOSITE_LOCALE_FR_FR, useAzureCommunicationChatAdapter } from '../../../src';
// eslint-disable-next-line no-restricted-imports
@@ -111,6 +113,25 @@ export const LiveTestApp = (): JSX.Element => {
)
: undefined
}
+ /* @conditional-compile-remove(composite-onRenderAvatar-API) */
+ onRenderAvatar={
+ customDataModel
+ ? (
+ userId?: string,
+ options?: CustomAvatarOptions,
+ defaultOnRender?: (options: CustomAvatarOptions) => JSX.Element
+ ) => {
+ const avatarOptions = {
+ ...options,
+ hidePersonaDetails: true,
+ text: 'Monica Smith',
+ showOverflowTooltip: false,
+ initialsColor: 'red'
+ };
+ return defaultOnRender ? defaultOnRender(avatarOptions) : undefined;
+ }
+ : undefined
+ }
locale={useFrLocale ? COMPOSITE_LOCALE_FR_FR : undefined}
options={{
participantPane: showParticipantPane,
From bf6e03041ab55122a2fdcfbb0bc9cffc0558e96c Mon Sep 17 00:00:00 2001
From: Leah Xia <107075081+Leah-Xia-Microsoft@users.noreply.github.com>
Date: Sun, 23 Feb 2025 21:51:41 -0800
Subject: [PATCH 3/6] Change files
---
...ation-react-a241483f-046c-4c4a-a4b8-37a90c3ea0e1.json | 9 +++++++++
...ation-react-a241483f-046c-4c4a-a4b8-37a90c3ea0e1.json | 9 +++++++++
2 files changed, 18 insertions(+)
create mode 100644 change-beta/@azure-communication-react-a241483f-046c-4c4a-a4b8-37a90c3ea0e1.json
create mode 100644 change/@azure-communication-react-a241483f-046c-4c4a-a4b8-37a90c3ea0e1.json
diff --git a/change-beta/@azure-communication-react-a241483f-046c-4c4a-a4b8-37a90c3ea0e1.json b/change-beta/@azure-communication-react-a241483f-046c-4c4a-a4b8-37a90c3ea0e1.json
new file mode 100644
index 00000000000..b551206d5b4
--- /dev/null
+++ b/change-beta/@azure-communication-react-a241483f-046c-4c4a-a4b8-37a90c3ea0e1.json
@@ -0,0 +1,9 @@
+{
+ "type": "minor",
+ "area": "feature",
+ "workstream": "",
+ "comment": "Expose onRenderAvatar api to all the composites",
+ "packageName": "@azure/communication-react",
+ "email": "107075081+Leah-Xia-Microsoft@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}
diff --git a/change/@azure-communication-react-a241483f-046c-4c4a-a4b8-37a90c3ea0e1.json b/change/@azure-communication-react-a241483f-046c-4c4a-a4b8-37a90c3ea0e1.json
new file mode 100644
index 00000000000..b551206d5b4
--- /dev/null
+++ b/change/@azure-communication-react-a241483f-046c-4c4a-a4b8-37a90c3ea0e1.json
@@ -0,0 +1,9 @@
+{
+ "type": "minor",
+ "area": "feature",
+ "workstream": "",
+ "comment": "Expose onRenderAvatar api to all the composites",
+ "packageName": "@azure/communication-react",
+ "email": "107075081+Leah-Xia-Microsoft@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}
From ab2f1841762e1f7e40706f12b4e552e8aa0b3f1e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 25 Feb 2025 05:23:17 +0000
Subject: [PATCH 4/6] Update packages/react-composites ChatComposite browser
test snapshots
---
...custom-data-model-Desktop-Chrome-linux.png | Bin 33180 -> 31053 bytes
...a-model-Mobile-Android-Landscape-linux.png | Bin 60107 -> 55473 bytes
...ta-model-Mobile-Android-Portrait-linux.png | Bin 98090 -> 94659 bytes
3 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/packages/react-composites/tests/browser/snapshots/beta/tests/browser/chat/hermetic/CustomDataModel.test.ts-snapshots/custom-data-model-Desktop-Chrome-linux.png b/packages/react-composites/tests/browser/snapshots/beta/tests/browser/chat/hermetic/CustomDataModel.test.ts-snapshots/custom-data-model-Desktop-Chrome-linux.png
index b318f3da89df943bd1df5d2d9cbca7bf8621ab0c..bab0e3b830a7ba763e0e0dc48a4e88b5b3dfe852 100644
GIT binary patch
literal 31053
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
b%MIJmi}xAPs|1J)2=b@tA638Kd;C8Dtj}%X
literal 33180
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