Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,9 @@ export type CallCompositeIcons = {

// @public
export type CallCompositeOptions = {
captionsBanner?: {
height: 'full' | 'default';
};
errorBar?: boolean;
callControls?: boolean | CallControlOptions;
deviceChecks?: DeviceCheckOptions;
Expand Down
44 changes: 38 additions & 6 deletions packages/react-components/src/components/CaptionsBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { Stack, FocusZone, Spinner } from '@fluentui/react';
import { Stack, FocusZone, Spinner, useTheme } from '@fluentui/react';
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { _Caption } from './Caption';
import {
captionContainerClassName,
captionsBannerClassName,
captionsBannerFullHeightClassName,
captionsContainerClassName,
loadingBannerFullHeightStyles,
loadingBannerStyles
} from './styles/Captions.style';
import { OnRenderAvatarCallback } from '../types';
Expand Down Expand Up @@ -50,16 +52,30 @@ export interface _CaptionsBannerProps {
* @defaultValue 'default'
*/
formFactor?: 'default' | 'compact';
captionsOptions?: {
height: 'full' | 'default';
};
}

const scrollOffsetAllowance = 20;
Comment thread
PorterNan marked this conversation as resolved.
Outdated

/**
* @internal
* A component for displaying a CaptionsBanner with user icon, displayName and captions text.
*/
export const _CaptionsBanner = (props: _CaptionsBannerProps): JSX.Element => {
const { captions, isCaptionsOn, startCaptionsInProgress, onRenderAvatar, strings, formFactor = 'default' } = props;
const {
captions,
isCaptionsOn,
startCaptionsInProgress,
onRenderAvatar,
strings,
formFactor = 'default',
captionsOptions
} = props;
const captionsScrollDivRef = useRef<HTMLDivElement>(null);
const [isAtBottomOfScroll, setIsAtBottomOfScroll] = useState<boolean>(true);
const theme = useTheme();

const scrollToBottom = (): void => {
if (captionsScrollDivRef.current) {
Expand All @@ -73,7 +89,7 @@ export const _CaptionsBanner = (props: _CaptionsBannerProps): JSX.Element => {
}
const atBottom =
Math.ceil(captionsScrollDivRef.current.scrollTop) >=
captionsScrollDivRef.current.scrollHeight - captionsScrollDivRef.current.clientHeight;
captionsScrollDivRef.current.scrollHeight - captionsScrollDivRef.current.clientHeight - scrollOffsetAllowance;

setIsAtBottomOfScroll(atBottom);
}, []);
Expand All @@ -97,9 +113,17 @@ export const _CaptionsBanner = (props: _CaptionsBannerProps): JSX.Element => {
return (
<>
{startCaptionsInProgress && (
<FocusZone as="ul" className={captionsContainerClassName}>
<FocusZone as="ul" className={captionsContainerClassName} data-ui-id="captions-banner">
{isCaptionsOn && (
<div ref={captionsScrollDivRef} className={captionsBannerClassName(formFactor)}>
<div
ref={captionsScrollDivRef}
className={
captionsOptions?.height === 'full'
? captionsBannerFullHeightClassName(theme)
: captionsBannerClassName(formFactor)
}
data-ui-id="captions-banner-inner"
>
{captions.map((caption) => {
return (
<div key={caption.id} className={captionContainerClassName} data-is-focusable={true}>
Expand All @@ -110,7 +134,15 @@ export const _CaptionsBanner = (props: _CaptionsBannerProps): JSX.Element => {
</div>
)}
{!isCaptionsOn && (
<Stack verticalAlign="center" styles={loadingBannerStyles(formFactor)} data-is-focusable={true}>
<Stack
verticalAlign="center"
styles={
captionsOptions?.height === 'full'
? loadingBannerFullHeightStyles(theme)
: loadingBannerStyles(formFactor)
}
data-is-focusable={true}
>
<Spinner label={strings?.captionsBannerSpinnerText} ariaLive="assertive" labelPosition="right" />
</Stack>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { IStackStyles, mergeStyles } from '@fluentui/react';
import { IStackStyles, ITheme, mergeStyles } from '@fluentui/react';
import { _pxToRem } from '@internal/acs-ui-common';
import { scrollbarStyles } from './Common.style';

Expand Down Expand Up @@ -62,6 +62,22 @@ export const captionsBannerClassName = (formFactor: 'default' | 'compact'): stri
});
};

/**
* @private
*/
export const captionsBannerFullHeightClassName = (theme: ITheme): string => {
return mergeStyles({
overflowX: 'hidden',
overflowY: 'auto',
height: '100%',
width: '100%',
position: 'absolute',
backgroundColor: theme.palette.white,
left: 0,
...scrollbarStyles
});
};

/**
* @private
*/
Expand All @@ -73,6 +89,21 @@ export const loadingBannerStyles = (formFactor: 'default' | 'compact'): IStackSt
};
};

/**
* @private
*/
export const loadingBannerFullHeightStyles = (theme: ITheme): IStackStyles => {
return {
root: {
height: '100%',
width: '100%',
position: 'absolute',
left: 0,
backgroundColor: theme.palette.white
}
};
};

/**
* @private
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ export interface LocalVideoTileOptions {
* @public
*/
export type CallCompositeOptions = {
captionsBanner?: {
height: 'full' | 'default';
};
/**
* Surface Azure Communication Services backend errors in the UI with {@link @azure/communication-react#ErrorBar}.
* Hide or show the error bar.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ export interface CallArrangementProps {
pinnedParticipants?: string[];
setPinnedParticipants?: (pinnedParticipants: string[]) => void;
doNotShowCameraAccessNotifications?: boolean;
captionsOptions?: {
height: 'full' | 'default';
};
}

/**
Expand Down Expand Up @@ -471,6 +474,13 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
const minMaxDragPosition = useMinMaxDragPosition(props.modalLayerHostId);
const pipStyles = useMemo(() => getPipStyles(theme), [theme]);

const galleryContainerStyles = useMemo(() => {
return {
...mediaGalleryContainerStyles,
...(props?.captionsOptions?.height === 'full' ? { root: { postion: 'absolute' } } : {})
};
}, [props?.captionsOptions?.height]);

if (isTeamsMeeting) {
filteredLatestErrorNotifications
.filter((notification) => notification.type === 'teamsMeetingCallNetworkQualityLow')
Expand Down Expand Up @@ -595,7 +605,7 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
<Stack horizontal grow>
<Stack.Item style={callCompositeContainerCSS}>
<Stack.Item styles={callGalleryStyles} grow>
<Stack verticalFill styles={mediaGalleryContainerStyles}>
<Stack verticalFill styles={galleryContainerStyles}>
<Stack.Item styles={notificationsContainerStyles}>
{
/* @conditional-compile-remove(breakout-rooms) */
Expand Down Expand Up @@ -633,6 +643,7 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
{true &&
/* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */ !isInLocalHold && (
<CaptionsBanner
captionsOptions={props.captionsOptions}
isMobile={props.mobileView}
onFetchAvatarPersonaData={props.onFetchAvatarPersonaData}
useTeamsCaptions={useTeamsCaptions}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export interface MediaGalleryProps {
setPromptProps: (props: PromptProps) => void;
hideSpotlightButtons?: boolean;
videoTilesOptions?: VideoTilesOptions;
captionsOptions?: {
height: 'full' | 'default';
};
}

/**
Expand All @@ -78,7 +81,8 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
setIsPromptOpen,
setPromptProps,
hideSpotlightButtons,
videoTilesOptions
videoTilesOptions,
captionsOptions
} = props;

const videoGalleryProps = usePropsFor(VideoGallery);
Expand Down Expand Up @@ -160,6 +164,10 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
setPromptProps
);

const galleryStyles = useMemo(() => {
return { ...VideoGalleryStyles, ...(captionsOptions?.height === 'full' ? { root: { postion: 'absolute' } } : {}) };
}, [captionsOptions?.height]);

const onPinParticipant = useMemo(() => {
return setPinnedParticipants
? (userId: string) => {
Expand Down Expand Up @@ -190,7 +198,7 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
videoTilesOptions={videoTilesOptions}
localVideoViewOptions={localVideoViewOptions}
remoteVideoViewOptions={remoteVideoViewOptions}
styles={VideoGalleryStyles}
styles={galleryStyles}
layout={layoutBasedOnUserSelection()}
showCameraSwitcherInLocalPreview={props.isMobile}
localVideoCameraCycleButtonProps={cameraSwitcherProps}
Expand Down Expand Up @@ -222,27 +230,28 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
);
}, [
videoGalleryProps,
videoTilesOptions,
galleryStyles,
props.isMobile,
props.localVideoTileOptions,
props.userSetGalleryLayout,
cameraSwitcherProps,
onRenderAvatar,
remoteVideoTileMenuOptions,
overflowGalleryPosition,
userRole,
isRoomsCall,
containerAspectRatio,
props.userSetGalleryLayout,
pinnedParticipants,
onPinParticipant,
onUnpinParticipant,
layoutBasedOnTilePosition,
reactionResources,
hideSpotlightButtons,
onStartLocalSpotlightWithPrompt,
onStopLocalSpotlightWithPrompt,
onStartRemoteSpotlightWithPrompt,
onStopRemoteSpotlightWithPrompt,
hideSpotlightButtons,
videoTilesOptions
layoutBasedOnTilePosition
]);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const CallPage = (props: CallPageProps): JSX.Element => {
setPromptProps={setPromptProps}
hideSpotlightButtons={options?.spotlight?.hideSpotlightButtons}
videoTilesOptions={options?.videoTilesOptions}
captionsOptions={options?.captionsBanner}
/>
);
}
Expand Down Expand Up @@ -214,6 +215,7 @@ export const CallPage = (props: CallPageProps): JSX.Element => {
setPinnedParticipants={setPinnedParticipants}
/* @conditional-compile-remove(call-readiness) */
doNotShowCameraAccessNotifications={props.options?.deviceChecks?.camera === 'doNotPrompt'}
captionsOptions={options?.captionsBanner}
/>
{<Prompt isOpen={isPromptOpen} onDismiss={() => setIsPromptOpen(false)} {...promptProps} />}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export const galleryParentContainerStyles = (backgroundColor: string): IStackSty
*/
export const mediaGalleryContainerStyles: IStackItemStyles = {
root: {
height: '100%'
height: '100%',
width: '100%'
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export const CaptionsBanner = (props: {
isMobile: boolean;
useTeamsCaptions?: boolean;
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
captionsOptions?: {
height: 'full' | 'default';
};
}): JSX.Element => {
const captionsBannerProps = useAdaptedSelector(_captionsBannerSelector);

Expand All @@ -36,9 +39,15 @@ export const CaptionsBanner = (props: {
setIsCaptionsSettingsOpen(false);
};

const containerClassName = mergeStyles({
position: 'relative'
});
const containerClassName = mergeStyles(
props.captionsOptions?.height === 'full'
? mergeStyles({
position: 'absolute',
height: '100%',
width: '100%'
})
: { position: 'relative' }
);

const floatingChildClassName = mergeStyles({
position: 'absolute',
Expand Down Expand Up @@ -90,6 +99,7 @@ export const CaptionsBanner = (props: {
<_CaptionsBanner
{...captionsBannerProps}
{...handlers}
captionsOptions={props.captionsOptions}
onRenderAvatar={onRenderAvatar}
formFactor={props.isMobile ? 'compact' : 'default'}
strings={captionsBannerStrings}
Expand Down