Skip to content

Commit 40d5101

Browse files
Add a new prop 'menuItems' that allows developers to inject contextual menu in Video Tile (#2507)
* Add a new prop that allows developers to inject contextual menu and drawer menu items in Video Tile * Change files * minor changes * conditional compile for pinned participants * minor change * Addressed Comments * minor changes * minor change * API.md files * minor change * updated API.md * minor change Co-authored-by: Anjul Garg <anjulgarg@live.com>
1 parent 0387d92 commit 40d5101

9 files changed

Lines changed: 149 additions & 5 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Add a new prop 'menuItems' that allows developers to inject contextual menu items in Video Tile",
4+
"packageName": "@internal/react-components",
5+
"email": "97124699+prabhjot-msft@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

packages/communication-react/review/beta/communication-react.api.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { IContextualMenuItemStyles } from '@fluentui/react';
4141
import { IContextualMenuStyles } from '@fluentui/react';
4242
import { IDropdownOption } from '@fluentui/react';
4343
import { IDropdownStyles } from '@fluentui/react';
44+
import { IIconProps } from '@fluentui/react';
4445
import { ILinkStyles } from '@fluentui/react';
4546
import { IMessageBarProps } from '@fluentui/react';
4647
import { IncomingCall } from '@azure/communication-calling';
@@ -3174,6 +3175,15 @@ export interface VideoStreamRendererViewState {
31743175
// @public
31753176
export const VideoTile: (props: VideoTileProps) => JSX.Element;
31763177

3178+
// @beta
3179+
export type VideoTileMenuItems = Array<{
3180+
key: string;
3181+
ariaLabel?: string;
3182+
text: string;
3183+
onClick: () => void;
3184+
iconProps: IIconProps;
3185+
}>;
3186+
31773187
// @public
31783188
export interface VideoTileProps {
31793189
children?: React_2.ReactNode;
@@ -3183,6 +3193,7 @@ export interface VideoTileProps {
31833193
isMuted?: boolean;
31843194
isPinned?: boolean;
31853195
isSpeaking?: boolean;
3196+
menuItems?: VideoTileMenuItems;
31863197
noVideoAvailableAriaLabel?: string;
31873198
onRenderPlaceholder?: OnRenderAvatarCallback;
31883199
participantState?: ParticipantState;

packages/communication-react/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ export type {
147147
MicrophoneDomainPermissionsProps
148148
} from '../../react-components/src';
149149

150+
/* @conditional-compile-remove(pinned-participants) */
151+
export type { VideoTileMenuItems } from '../../react-components/src';
152+
150153
export type {
151154
_IdentifierProviderProps,
152155
_Identifiers,

packages/react-components/review/beta/react-components.api.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,15 @@ export interface VideoStreamOptions {
16731673
// @public
16741674
export const VideoTile: (props: VideoTileProps) => JSX.Element;
16751675

1676+
// @beta
1677+
export type VideoTileMenuItems = Array<{
1678+
key: string;
1679+
ariaLabel?: string;
1680+
text: string;
1681+
onClick: () => void;
1682+
iconProps: IIconProps;
1683+
}>;
1684+
16761685
// @public
16771686
export interface VideoTileProps {
16781687
children?: React_2.ReactNode;
@@ -1682,6 +1691,7 @@ export interface VideoTileProps {
16821691
isMuted?: boolean;
16831692
isPinned?: boolean;
16841693
isSpeaking?: boolean;
1694+
menuItems?: VideoTileMenuItems;
16851695
noVideoAvailableAriaLabel?: string;
16861696
onRenderPlaceholder?: OnRenderAvatarCallback;
16871697
participantState?: ParticipantState;

packages/react-components/review/stable/react-components.api.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,15 @@ export interface VideoStreamOptions {
14181418
// @public
14191419
export const VideoTile: (props: VideoTileProps) => JSX.Element;
14201420

1421+
// @beta
1422+
export type VideoTileMenuItems = Array<{
1423+
key: string;
1424+
ariaLabel?: string;
1425+
text: string;
1426+
onClick: () => void;
1427+
iconProps: IIconProps;
1428+
}>;
1429+
14211430
// @public
14221431
export interface VideoTileProps {
14231432
children?: React_2.ReactNode;

packages/react-components/src/components/VideoTile.tsx

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33

4-
import { Icon, IStyle, mergeStyles, Persona, Stack, Text } from '@fluentui/react';
4+
import { Icon, IStyle, mergeStyles, Persona, Stack, Text, IIconProps } from '@fluentui/react';
5+
/* @conditional-compile-remove(pinned-participants) */
6+
import { MoreHorizontal20Filled } from '@fluentui/react-icons';
57
import { Ref } from '@fluentui/react-northstar';
68
import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
79
import { useIdentifiers } from '../identifiers';
@@ -23,8 +25,12 @@ import {
2325
participantStateStringStyles
2426
} from './styles/VideoTile.styles';
2527
/* @conditional-compile-remove(pinned-participants) */
26-
import { pinIconStyle } from './styles/VideoTile.styles';
28+
import { pinIconStyle, menuButtonStyles } from './styles/VideoTile.styles';
2729
import { getVideoTileOverrideColor } from './utils/videoTileStylesUtils';
30+
/* @conditional-compile-remove(pinned-participants) */
31+
import { DefaultButton, concatStyleSets, DirectionalHint } from '@fluentui/react';
32+
/* @conditional-compile-remove(pinned-participants) */
33+
import { mapMenuItemsToContextualMenuItems } from './utils';
2834

2935
/**
3036
* Strings of {@link VideoTile} that can be overridden.
@@ -89,6 +95,11 @@ export interface VideoTileProps {
8995
*/
9096
isMuted?: boolean;
9197
/* @conditional-compile-remove(pinned-participants) */
98+
/**
99+
* Display custom menu items in the VideoTile's contextual menu.
100+
*/
101+
menuItems?: VideoTileMenuItems;
102+
/* @conditional-compile-remove(pinned-participants) */
92103
/**
93104
* If true, the video tile will show the pin icon.
94105
*/
@@ -158,8 +169,39 @@ const DefaultPlaceholder = (props: CustomAvatarOptions): JSX.Element => {
158169
);
159170
};
160171

172+
/**
173+
* @beta
174+
* MenuItems to be diplayed in video tile in the contextual/drawer menu
175+
*/
176+
export type VideoTileMenuItems = Array<{
177+
key: string;
178+
ariaLabel?: string;
179+
text: string;
180+
onClick: () => void;
181+
iconProps: IIconProps;
182+
}>;
183+
184+
/* @conditional-compile-remove(pinned-participants) */
185+
const menuIcon = (): JSX.Element => <MoreHorizontal20Filled primaryFill="currentColor" />;
186+
161187
const defaultPersonaStyles = { root: { margin: 'auto', maxHeight: '100%' } };
162188

189+
/* @conditional-compile-remove(pinned-participants) */
190+
const VideoTileMoreOptionsButton = (props: { menuItems?: VideoTileMenuItems; menuStyles?: IStyle }): JSX.Element => {
191+
const { menuItems, menuStyles } = props;
192+
if (!menuItems || menuItems.length === 0) {
193+
return <></>;
194+
}
195+
return (
196+
<DefaultButton
197+
styles={concatStyleSets(menuButtonStyles, menuStyles ?? {})}
198+
onRenderIcon={menuIcon}
199+
menuIconProps={{ hidden: true }}
200+
menuProps={{ items: mapMenuItemsToContextualMenuItems(menuItems), directionalHint: DirectionalHint.topRightEdge }}
201+
/>
202+
);
203+
};
204+
163205
/**
164206
* A component to render the video stream for a single call participant.
165207
*
@@ -184,6 +226,8 @@ export const VideoTile = (props: VideoTileProps): JSX.Element => {
184226
userId,
185227
noVideoAvailableAriaLabel,
186228
isSpeaking,
229+
/* @conditional-compile-remove(pinned-participants) */
230+
menuItems,
187231
personaMinSize = DEFAULT_PERSONA_MIN_SIZE_PX,
188232
personaMaxSize = DEFAULT_PERSONA_MAX_SIZE_PX
189233
} = props;
@@ -237,7 +281,6 @@ export const VideoTile = (props: VideoTileProps): JSX.Element => {
237281

238282
const canShowLabel = showLabel && (displayName || (showMuteIndicator && isMuted));
239283
const participantStateString = participantStateStringTrampoline(props, locale);
240-
241284
return (
242285
<Ref innerRef={videoTileRef}>
243286
<Stack
@@ -304,6 +347,10 @@ export const VideoTile = (props: VideoTileProps): JSX.Element => {
304347
<Icon iconName="VideoTileMicOff" />
305348
</Stack>
306349
)}
350+
{
351+
/* @conditional-compile-remove(pinned-participants) */
352+
<VideoTileMoreOptionsButton menuItems={menuItems} menuStyles={props.styles} />
353+
}
307354
{
308355
/* @conditional-compile-remove(pinned-participants) */
309356
isPinned && (

packages/react-components/src/components/styles/VideoTile.styles.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33

4-
import { IStyle, mergeStyles, Theme } from '@fluentui/react';
4+
import { IButtonStyles, IStyle, mergeStyles, Theme } from '@fluentui/react';
55

66
/**
77
* @private
@@ -119,3 +119,39 @@ export const participantStateStringStyles = (theme: Theme): IStyle => {
119119
padding: '0.1rem'
120120
};
121121
};
122+
123+
/**
124+
* @private
125+
*/
126+
export const menuButtonStyles: IButtonStyles = {
127+
root: {
128+
background: 'none',
129+
border: 'none',
130+
borderRadius: 0,
131+
minHeight: '0.125rem',
132+
minWidth: '2rem',
133+
width: '100%',
134+
maxWidth: '3rem',
135+
svg: {
136+
verticalAlign: 'text-top'
137+
}
138+
},
139+
splitButtonMenuButton: {
140+
border: 'none'
141+
},
142+
flexContainer: {
143+
flexFlow: 'column',
144+
display: 'contents'
145+
},
146+
label: {
147+
fontSize: '0.625rem',
148+
fontWeight: '400',
149+
lineHeight: '1rem',
150+
cursor: 'pointer',
151+
display: 'block',
152+
margin: '0rem 0.25rem',
153+
overflow: 'hidden',
154+
textOverflow: 'ellipsis',
155+
whiteSpace: 'nowrap'
156+
}
157+
};

packages/react-components/src/components/utils.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33

4-
import { IIconProps, MessageBarType } from '@fluentui/react';
4+
import { IContextualMenuItem, IIconProps, MessageBarType } from '@fluentui/react';
55
import { ActiveErrorMessage, ErrorType } from './ErrorBar';
6+
import { VideoTileMenuItems } from './VideoTile';
67

78
/**
89
* @private
@@ -149,6 +150,24 @@ export const messageBarType = (errorType: ErrorType): MessageBarType => {
149150
}
150151
};
151152

153+
/**
154+
* @private
155+
*/
156+
export const mapMenuItemsToContextualMenuItems = (menuItems: VideoTileMenuItems): IContextualMenuItem[] => {
157+
const contextualMenuItems: IContextualMenuItem[] = [];
158+
menuItems.map((item) => {
159+
contextualMenuItems.push({
160+
key: item.key,
161+
text: item.text,
162+
ariaLabel: item.ariaLabel,
163+
onClick: item.onClick,
164+
iconProps: item.iconProps
165+
});
166+
});
167+
168+
return contextualMenuItems;
169+
};
170+
152171
/**
153172
* @private
154173
* @param errorType

packages/react-components/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export {
2020
/* @conditional-compile-remove(rooms) */
2121
export type { _Permissions, _PermissionsProviderProps, Role } from './permissions';
2222

23+
export type { VideoTileMenuItems } from './components/VideoTile';
24+
2325
export type {
2426
BaseCustomStyles,
2527
CallParticipantListParticipant,

0 commit comments

Comments
 (0)