Skip to content

Commit 3cb1e0c

Browse files
authored
MeetingComposite: Use a SplitButton to show device selection flyout in CameraButton (#1436)
1 parent c6d8a57 commit 3cb1e0c

22 files changed

Lines changed: 256 additions & 10 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Include cameras in CameraButton selector",
4+
"packageName": "@internal/calling-component-bindings",
5+
"email": "82062616+prprabhu-ms@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}
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 camera selection menu to CameraButton",
4+
"packageName": "@internal/react-components",
5+
"email": "82062616+prprabhu-ms@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Use split CameraButton in MeetingComposite control bar",
4+
"packageName": "@internal/react-composites",
5+
"email": "82062616+prprabhu-ms@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Update CameraButton story to show device flyout",
4+
"packageName": "@internal/storybook",
5+
"email": "82062616+prprabhu-ms@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

packages/calling-component-bindings/review/calling-component-bindings.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ export interface CallProviderProps {
102102
export type CameraButtonSelector = (state: CallClientState, props: CallingBaseSelectorProps) => {
103103
disabled: boolean;
104104
checked: boolean;
105+
cameras: VideoDeviceInfo[];
106+
selectedCamera?: VideoDeviceInfo;
105107
};
106108

107109
// @public

packages/calling-component-bindings/src/callControlSelectors.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ export type CameraButtonSelector = (
7070
) => {
7171
disabled: boolean;
7272
checked: boolean;
73+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
74+
cameras: VideoDeviceInfo[];
75+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
76+
selectedCamera?: VideoDeviceInfo;
7377
};
7478

7579
/**
@@ -86,7 +90,11 @@ export const cameraButtonSelector: CameraButtonSelector = reselect.createSelecto
8690

8791
return {
8892
disabled: !deviceManager.selectedCamera || !permission,
89-
checked: localVideoStreams !== undefined && localVideoStreams.length > 0 ? !!localVideoFromCall : previewOn
93+
checked: localVideoStreams !== undefined && localVideoStreams.length > 0 ? !!localVideoFromCall : previewOn,
94+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
95+
cameras: deviceManager.cameras,
96+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
97+
selectedCamera: deviceManager.selectedCamera
9098
};
9199
}
92100
);

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,21 +488,35 @@ export interface CallState {
488488
// @public
489489
export const CameraButton: (props: CameraButtonProps) => JSX.Element;
490490

491+
// @public
492+
export interface CameraButtonContextualMenuStyles extends IContextualMenuStyles {
493+
menuItemStyles?: IContextualMenuItemStyles;
494+
}
495+
491496
// @public
492497
export interface CameraButtonProps extends ControlBarButtonProps {
498+
cameras?: OptionsDevice[];
499+
enableDeviceSelectionMenu?: boolean;
493500
localVideoViewOptions?: VideoStreamOptions;
501+
onSelectCamera?: (device: OptionsDevice) => Promise<void>;
494502
onToggleCamera?: (options?: VideoStreamOptions) => Promise<void>;
503+
selectedCamera?: OptionsDevice;
495504
strings?: Partial<CameraButtonStrings>;
505+
styles?: Partial<CameraButtonStyles>;
496506
}
497507

498508
// @public
499509
export type CameraButtonSelector = (state: CallClientState, props: CallingBaseSelectorProps) => {
500510
disabled: boolean;
501511
checked: boolean;
512+
cameras: VideoDeviceInfo[];
513+
selectedCamera?: VideoDeviceInfo;
502514
};
503515

504516
// @public
505517
export interface CameraButtonStrings {
518+
cameraMenuTitle: string;
519+
cameraMenuTooltip: string;
506520
offLabel: string;
507521
onLabel: string;
508522
tooltipDisabledContent?: string;
@@ -511,6 +525,11 @@ export interface CameraButtonStrings {
511525
tooltipVideoLoadingContent?: string;
512526
}
513527

528+
// @public
529+
export interface CameraButtonStyles extends ControlBarButtonStyles {
530+
menuStyles?: Partial<CameraButtonContextualMenuStyles>;
531+
}
532+
514533
// Warning: (ae-incompatible-release-tags) The symbol "ChatAdapter" is marked as @public, but its signature references "FileUploadAdapter" which is marked as @beta
515534
//
516535
// @public

packages/communication-react/src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,12 @@ export type { LocalVideoCameraCycleButtonProps } from '../../react-components/sr
201201
export * from '../../react-components/src/localization/locales';
202202
export * from '../../react-components/src/theming';
203203
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
204-
export type { MicrophoneButtonContextualMenuStyles, MicrophoneButtonStyles } from '../../react-components/src';
204+
export type {
205+
CameraButtonContextualMenuStyles,
206+
CameraButtonStyles,
207+
MicrophoneButtonContextualMenuStyles,
208+
MicrophoneButtonStyles
209+
} from '../../react-components/src';
205210

206211
export * from '../../calling-stateful-client/src';
207212
export * from '../../chat-stateful-client/src';

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,27 @@ export type CallParticipantListParticipant = ParticipantListParticipant & {
5858
// @public
5959
export const CameraButton: (props: CameraButtonProps) => JSX.Element;
6060

61+
// @public
62+
export interface CameraButtonContextualMenuStyles extends IContextualMenuStyles {
63+
menuItemStyles?: IContextualMenuItemStyles;
64+
}
65+
6166
// @public
6267
export interface CameraButtonProps extends ControlBarButtonProps {
68+
cameras?: OptionsDevice[];
69+
enableDeviceSelectionMenu?: boolean;
6370
localVideoViewOptions?: VideoStreamOptions;
71+
onSelectCamera?: (device: OptionsDevice) => Promise<void>;
6472
onToggleCamera?: (options?: VideoStreamOptions) => Promise<void>;
73+
selectedCamera?: OptionsDevice;
6574
strings?: Partial<CameraButtonStrings>;
75+
styles?: Partial<CameraButtonStyles>;
6676
}
6777

6878
// @public
6979
export interface CameraButtonStrings {
80+
cameraMenuTitle: string;
81+
cameraMenuTooltip: string;
7082
offLabel: string;
7183
onLabel: string;
7284
tooltipDisabledContent?: string;
@@ -75,6 +87,11 @@ export interface CameraButtonStrings {
7587
tooltipVideoLoadingContent?: string;
7688
}
7789

90+
// @public
91+
export interface CameraButtonStyles extends ControlBarButtonStyles {
92+
menuStyles?: Partial<CameraButtonContextualMenuStyles>;
93+
}
94+
7895
// @public
7996
export interface ChatMessage extends MessageCommon {
8097
// (undocumented)

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

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

4+
import { IContextualMenuProps } from '@fluentui/react';
45
import React, { useCallback, useState } from 'react';
56
import { useLocale } from '../localization';
67
import { VideoStreamOptions } from '../types';
78
import { ControlBarButton, ControlBarButtonProps } from './ControlBarButton';
89
import { HighContrastAwareIcon } from './HighContrastAwareIcon';
910

11+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
12+
import { IContextualMenuItemStyles, IContextualMenuStyles } from '@fluentui/react';
13+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
14+
import { ControlBarButtonStyles } from './ControlBarButton';
15+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
16+
import { OptionsDevice, generateDefaultDeviceMenuProps } from './DevicesButton';
17+
1018
const defaultLocalVideoViewOptions = {
1119
scalingMode: 'Crop',
1220
isMirrored: true
@@ -30,8 +38,43 @@ export interface CameraButtonStrings {
3038
tooltipOffContent?: string;
3139
/** Tooltip content when the button is disabled due to video loading. */
3240
tooltipVideoLoadingContent?: string;
41+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
42+
/**
43+
* Title of camera menu
44+
*/
45+
cameraMenuTitle: string;
46+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
47+
/**
48+
* Tooltip of camera menu
49+
*/
50+
cameraMenuTooltip: string;
3351
}
3452

53+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
54+
/**
55+
* Styles for {@link CameraButton}
56+
*
57+
* @public
58+
*/
59+
export interface CameraButtonStyles extends ControlBarButtonStyles {
60+
/**
61+
* Styles for the {@link CameraButton} menu.
62+
*/
63+
menuStyles?: Partial<CameraButtonContextualMenuStyles>;
64+
}
65+
66+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
67+
/**
68+
* Styles for the {@link CameraButton} menu.
69+
*
70+
* @public
71+
*/
72+
export interface CameraButtonContextualMenuStyles extends IContextualMenuStyles {
73+
/**
74+
* Styles for the items inside the {@link CameraButton} button menu.
75+
*/
76+
menuItemStyles?: IContextualMenuItemStyles;
77+
}
3578
/**
3679
* Props for {@link CameraButton} component.
3780
*
@@ -48,11 +91,37 @@ export interface CameraButtonProps extends ControlBarButtonProps {
4891
* Options for rendering local video view.
4992
*/
5093
localVideoViewOptions?: VideoStreamOptions;
51-
94+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
95+
/**
96+
* Available cameras for selection
97+
*/
98+
cameras?: OptionsDevice[];
99+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
100+
/**
101+
* Camera that is shown as currently selected
102+
*/
103+
selectedCamera?: OptionsDevice;
104+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
105+
/**
106+
* Callback when a camera is selected
107+
*/
108+
onSelectCamera?: (device: OptionsDevice) => Promise<void>;
109+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
110+
/**
111+
* Whether to use a {@link SplitButton} with a {@link IContextualMenu} for device selection.
112+
*
113+
* default: false
114+
*/
115+
enableDeviceSelectionMenu?: boolean;
52116
/**
53117
* Optional strings to override in component
54118
*/
55119
strings?: Partial<CameraButtonStrings>;
120+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
121+
/**
122+
* Styles for {@link CameraButton} and the device selection flyout.
123+
*/
124+
styles?: Partial<CameraButtonStyles>;
56125
}
57126

58127
/**
@@ -98,6 +167,28 @@ export const CameraButton = (props: CameraButtonProps): JSX.Element => {
98167
onRenderOffIcon={props.onRenderOffIcon ?? onRenderCameraOffIcon}
99168
strings={strings}
100169
labelKey={props.labelKey ?? 'cameraButtonLabel'}
170+
menuProps={props.menuProps ?? generateDefaultDeviceMenuPropsTrampoline(props, strings)}
171+
menuIconProps={props.menuIconProps ?? !enableDeviceSelectionMenuTrampoline(props) ? { hidden: true } : undefined}
172+
split={props.split ?? enableDeviceSelectionMenuTrampoline(props)}
101173
/>
102174
);
103175
};
176+
177+
const generateDefaultDeviceMenuPropsTrampoline = (
178+
props: CameraButtonProps,
179+
strings: CameraButtonStrings
180+
): IContextualMenuProps | undefined => {
181+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
182+
if (props.enableDeviceSelectionMenu) {
183+
return generateDefaultDeviceMenuProps({ ...props, styles: props.styles?.menuStyles }, strings);
184+
}
185+
return undefined;
186+
};
187+
188+
const enableDeviceSelectionMenuTrampoline = (props: CameraButtonProps): boolean => {
189+
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
190+
if (props.enableDeviceSelectionMenu) {
191+
return true;
192+
}
193+
return false;
194+
};

0 commit comments

Comments
 (0)