Skip to content

Commit ded9427

Browse files
ModalPiPiP in mobile CallWithChat set to higher position and fixed bounds. (#1748)
* working * moved fix to CallWithChatPane * cleaner * Fix CallWithChatComposite rtl * fix bounds of CallWithChatPane when rtl = true * assign minDragPosition to ModalLocalAndRemotePIP to stabilize flaky bounds * Change files * Update composite automation snapshots * Update composite automation snapshots * Update composite automation snapshots * Update composite automation snapshots * update stable api Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 19b769c commit ded9427

14 files changed

Lines changed: 118 additions & 37 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "none",
3+
"packageName": "@internal/react-components",
4+
"email": "miguelgamis@microsoft.com",
5+
"dependentChangeType": "none"
6+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Moved starting position of PiPiP higher in chat/people pane of CallWithChat composite. Also fixed PiPiP bounds such that it that does not go off screen.",
4+
"packageName": "@internal/react-composites",
5+
"email": "miguelgamis@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { IContextualMenuItemStyles } from '@fluentui/react';
1414
import { IContextualMenuStyles } from '@fluentui/react';
1515
import { IIconProps } from '@fluentui/react';
1616
import { IMessageBarProps } from '@fluentui/react';
17+
import { IModalProps } from '@fluentui/react';
1718
import { IPersonaStyleProps } from '@fluentui/react';
1819
import { IPersonaStyles } from '@fluentui/react';
1920
import { IRenderFunction } from '@fluentui/react';
@@ -24,6 +25,7 @@ import { PartialTheme } from '@fluentui/react';
2425
import { PersonaPresence } from '@fluentui/react';
2526
import { PersonaSize } from '@fluentui/react';
2627
import { default as React_2 } from 'react';
28+
import * as React_3 from 'react';
2729
import { Theme } from '@fluentui/react';
2830

2931
// @public
@@ -464,6 +466,14 @@ export interface ErrorBarStrings {
464466
// @public
465467
export type ErrorType = keyof ErrorBarStrings;
466468

469+
// @internal (undocumented)
470+
export interface _ExtendedIModalProps extends IModalProps {
471+
// (undocumented)
472+
maxDragPosition?: _ICoordinates;
473+
// (undocumented)
474+
minDragPosition?: _ICoordinates;
475+
}
476+
467477
// @internal
468478
export const _FileCard: (props: _FileCardProps) => JSX.Element;
469479

@@ -517,6 +527,12 @@ export interface HorizontalGalleryStyles extends BaseCustomStyles {
517527
previousButton?: IStyle;
518528
}
519529

530+
// @internal (undocumented)
531+
export type _ICoordinates = {
532+
x: number;
533+
y: number;
534+
};
535+
520536
// @internal
521537
export const _IdentifierProvider: (props: _IdentifierProviderProps) => JSX.Element;
522538

@@ -746,6 +762,9 @@ export interface MicrophoneButtonStyles extends ControlBarButtonStyles {
746762
menuStyles?: Partial<MicrophoneButtonContextualMenuStyles>;
747763
}
748764

765+
// @internal (undocumented)
766+
export const _ModalClone: React_3.FunctionComponent<_ExtendedIModalProps>;
767+
749768
// @public
750769
export type OnRenderAvatarCallback = (
751770
userId?: string, options?: CustomAvatarOptions,

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

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,23 +43,25 @@ import { useWindow } from '@fluentui/react-window-provider';
4343

4444
// @TODO - need to change this to a panel whenever the breakpoint is under medium (verify the spec)
4545

46-
interface ExtendedIModalProps extends IModalProps {
47-
minDragPosition?: ICoordinates;
48-
maxDragPosition?: ICoordinates;
46+
/** @internal */
47+
export interface _ExtendedIModalProps extends IModalProps {
48+
minDragPosition?: _ICoordinates;
49+
maxDragPosition?: _ICoordinates;
4950
}
5051

5152
const animationDuration = AnimationVariables.durationValue2;
52-
type ICoordinates = { x: number; y: number };
53+
/** @internal */
54+
export type _ICoordinates = { x: number; y: number };
5355

5456
interface IModalInternalState {
5557
onModalCloseTimer: number;
5658
allowTouchBodyScroll?: boolean;
5759
scrollableContent: HTMLDivElement | null;
58-
lastSetCoordinates: ICoordinates;
60+
lastSetCoordinates: _ICoordinates;
5961
/** Minimum clamped position, if dragging and clamping (`dragOptions.keepInBounds`) are enabled */
60-
minPosition?: ICoordinates;
62+
minPosition?: _ICoordinates;
6163
/** Maximum clamped position, if dragging and clamping (`dragOptions.keepInBounds`) are enabled */
62-
maxPosition?: ICoordinates;
64+
maxPosition?: _ICoordinates;
6365
events: EventGroup;
6466
/** Ensures we dispose the same keydown callback as was registered */
6567
disposeOnKeyDown?: () => void;
@@ -69,9 +71,9 @@ interface IModalInternalState {
6971
hasBeenOpened?: boolean;
7072
}
7173

72-
const ZERO: ICoordinates = { x: 0, y: 0 };
74+
const ZERO: _ICoordinates = { x: 0, y: 0 };
7375

74-
const DEFAULT_PROPS: Partial<ExtendedIModalProps> = {
76+
const DEFAULT_PROPS: Partial<_ExtendedIModalProps> = {
7577
isOpen: false,
7678
isDarkOverlay: true,
7779
className: '',
@@ -108,7 +110,7 @@ const useComponentRef = (props: IModalProps, focusTrapZone: React.RefObject<IFoc
108110
);
109111
};
110112

111-
const ModalBase: React.FunctionComponent<ExtendedIModalProps> = React.forwardRef<HTMLDivElement, ExtendedIModalProps>(
113+
const ModalBase: React.FunctionComponent<_ExtendedIModalProps> = React.forwardRef<HTMLDivElement, _ExtendedIModalProps>(
112114
(propsWithoutDefaults, ref) => {
113115
const props = getPropsWithDefaults(DEFAULT_PROPS, propsWithoutDefaults);
114116
const {
@@ -158,7 +160,7 @@ const ModalBase: React.FunctionComponent<ExtendedIModalProps> = React.forwardRef
158160

159161
const [isModalOpen, setIsModalOpen] = React.useState(isOpen);
160162
const [isVisible, setIsVisible] = React.useState(isOpen);
161-
const [coordinates, setCoordinates] = React.useState<ICoordinates>(ZERO);
163+
const [coordinates, setCoordinates] = React.useState<_ICoordinates>(ZERO);
162164
const [modalRectangleTop, setModalRectangleTop] = React.useState<number | undefined>();
163165

164166
const [isModalMenuOpen, { toggle: toggleModalMenuOpen, setFalse: setModalMenuClose }] = useBoolean(false);
@@ -241,7 +243,7 @@ const ModalBase: React.FunctionComponent<ExtendedIModalProps> = React.forwardRef
241243
* @param position The position on the axis.
242244
*/
243245
const getClampedAxis = React.useCallback(
244-
(axis: keyof ICoordinates, position: number) => {
246+
(axis: keyof _ICoordinates, position: number) => {
245247
const { minPosition, maxPosition } = internalState;
246248
if (keepInBounds && minPosition && maxPosition) {
247249
position = Math.max(minPosition[axis], position);
@@ -538,9 +540,9 @@ const getDraggableZoneClassNames = memoizeFunction((className: string, isDraggin
538540
});
539541

540542
interface IDragData {
541-
position: ICoordinates;
542-
lastPosition?: ICoordinates;
543-
delta: ICoordinates;
543+
position: _ICoordinates;
544+
lastPosition?: _ICoordinates;
545+
delta: _ICoordinates;
544546
}
545547

546548
interface IDraggableZoneProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -560,7 +562,7 @@ interface IDraggableZoneProps extends React.HTMLAttributes<HTMLDivElement> {
560562
/**
561563
* the X and Y coordinates to use as an offest to position the draggable content
562564
*/
563-
position?: ICoordinates;
565+
position?: _ICoordinates;
564566

565567
/**
566568
* Callback for when dragging starts
@@ -580,8 +582,8 @@ interface IDraggableZoneProps extends React.HTMLAttributes<HTMLDivElement> {
580582

581583
interface IDraggableZoneState {
582584
isDragging: boolean;
583-
position: ICoordinates;
584-
lastPosition?: ICoordinates;
585+
position: _ICoordinates;
586+
lastPosition?: _ICoordinates;
585587
}
586588

587589
const eventMapping = {
@@ -793,7 +795,7 @@ class DraggableZone extends React.Component<IDraggableZoneProps, IDraggableZoneS
793795
* Get the control position based off the event that fired
794796
* @param event - The event to get offsets from
795797
*/
796-
private _getControlPosition(event: MouseTouchEvent<HTMLElement>): ICoordinates | undefined {
798+
private _getControlPosition(event: MouseTouchEvent<HTMLElement>): _ICoordinates | undefined {
797799
const touchObj = this._getActiveTouch(event);
798800

799801
// did we get the right touch?
@@ -875,7 +877,7 @@ class DraggableZone extends React.Component<IDraggableZoneProps, IDraggableZoneS
875877
* Create DragData based off of the last known position and the new position passed in
876878
* @param position The new position as part of the drag
877879
*/
878-
private _createDragDataFromPosition(position: ICoordinates): IDragData {
880+
private _createDragDataFromPosition(position: _ICoordinates): IDragData {
879881
const { lastPosition } = this.state;
880882

881883
// If we have no lastPosition, use the given position
@@ -1035,13 +1037,13 @@ const getStyles = (props: IModalStyleProps): IModalStyles => {
10351037
};
10361038
};
10371039

1038-
/** @private */
1039-
export const ModalClone: React.FunctionComponent<ExtendedIModalProps> = styled<
1040-
ExtendedIModalProps,
1040+
/** @internal */
1041+
export const _ModalClone: React.FunctionComponent<_ExtendedIModalProps> = styled<
1042+
_ExtendedIModalProps,
10411043
IModalStyleProps,
10421044
IModalStyles
10431045
>(ModalBase, getStyles, undefined, {
10441046
scope: 'Modal',
10451047
fields: ['theme', 'styles', 'enableAriaHiddenSiblings']
10461048
});
1047-
ModalClone.displayName = 'Modal';
1049+
_ModalClone.displayName = 'Modal';

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import { useId } from '@fluentui/react-hooks';
4343
import { LocalVideoCameraCycleButton, LocalVideoCameraCycleButtonProps } from './LocalVideoCameraButton';
4444
/* @conditional-compile-remove(call-with-chat-composite) @conditional-compile-remove(local-camera-switcher) */
4545
import { localVideoTileWithControlsContainerStyle, LOCAL_VIDEO_TILE_ZINDEX } from './styles/VideoGallery.styles';
46-
import { ModalClone } from './ModalClone/ModalClone';
46+
import { _ModalClone } from './ModalClone/ModalClone';
4747

4848
// Currently the Calling JS SDK supports up to 4 remote video streams
4949
const DEFAULT_MAX_REMOTE_VIDEO_STREAMS = 4;
@@ -387,7 +387,7 @@ export const VideoGallery = (props: VideoGalleryProps): JSX.Element => {
387387
(horizontalGalleryPresent ? (
388388
<Stack className={mergeStyles(localVideoTileContainerStyle(theme, isNarrow))}>{localVideoTile}</Stack>
389389
) : (
390-
<ModalClone
390+
<_ModalClone
391391
isOpen={true}
392392
isModeless={true}
393393
dragOptions={DRAG_OPTIONS}
@@ -396,7 +396,7 @@ export const VideoGallery = (props: VideoGalleryProps): JSX.Element => {
396396
maxDragPosition={maxDragPosition}
397397
>
398398
{localVideoTile}
399-
</ModalClone>
399+
</_ModalClone>
400400
))}
401401
{
402402
/* @conditional-compile-remove(call-with-chat-composite) @conditional-compile-remove(local-camera-switcher) */

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,4 @@ export * from './Drawer';
106106
export type { SendBoxErrorBarError } from './SendBoxErrorBar';
107107
export * from './FileCard';
108108
export * from './FileCardGroup';
109+
export * from './ModalClone/ModalClone';

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { IContextualMenuItemStyles } from '@fluentui/react';
1414
import { IContextualMenuStyles } from '@fluentui/react';
1515
import { IIconProps } from '@fluentui/react';
1616
import { IMessageBarProps } from '@fluentui/react';
17+
import { IModalProps } from '@fluentui/react';
1718
import { IPersonaStyleProps } from '@fluentui/react';
1819
import { IPersonaStyles } from '@fluentui/react';
1920
import { IRenderFunction } from '@fluentui/react';
@@ -24,6 +25,7 @@ import { PartialTheme } from '@fluentui/react';
2425
import { PersonaPresence } from '@fluentui/react';
2526
import { PersonaSize } from '@fluentui/react';
2627
import { default as React_2 } from 'react';
28+
import * as React_3 from 'react';
2729
import { Theme } from '@fluentui/react';
2830

2931
// @public
@@ -455,6 +457,14 @@ export interface ErrorBarStrings {
455457
// @public
456458
export type ErrorType = keyof ErrorBarStrings;
457459

460+
// @internal (undocumented)
461+
export interface _ExtendedIModalProps extends IModalProps {
462+
// (undocumented)
463+
maxDragPosition?: _ICoordinates;
464+
// (undocumented)
465+
minDragPosition?: _ICoordinates;
466+
}
467+
458468
// @internal
459469
export const _FileCard: (props: _FileCardProps) => JSX.Element;
460470

@@ -508,6 +518,12 @@ export interface HorizontalGalleryStyles extends BaseCustomStyles {
508518
previousButton?: IStyle;
509519
}
510520

521+
// @internal (undocumented)
522+
export type _ICoordinates = {
523+
x: number;
524+
y: number;
525+
};
526+
511527
// @internal
512528
export const _IdentifierProvider: (props: _IdentifierProviderProps) => JSX.Element;
513529

@@ -736,6 +752,9 @@ export interface MicrophoneButtonStyles extends ControlBarButtonStyles {
736752
menuStyles?: Partial<MicrophoneButtonContextualMenuStyles>;
737753
}
738754

755+
// @internal (undocumented)
756+
export const _ModalClone: React_3.FunctionComponent<_ExtendedIModalProps>;
757+
739758
// @public
740759
export type OnRenderAvatarCallback = (
741760
userId?: string, options?: CustomAvatarOptions,

packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ type CallWithChatScreenProps = {
138138
onFetchParticipantMenuItems?: ParticipantMenuItemsCallback;
139139
/* @conditional-compile-remove(file-sharing) */
140140
fileSharing?: FileSharingOptions;
141+
rtl?: boolean;
141142
};
142143

143144
const CallWithChatScreen = (props: CallWithChatScreenProps): JSX.Element => {
@@ -267,6 +268,7 @@ const CallWithChatScreen = (props: CallWithChatScreenProps): JSX.Element => {
267268
activePane={activePane}
268269
/* @conditional-compile-remove(file-sharing) */
269270
fileSharing={props.fileSharing}
271+
rtl={props.rtl}
270272
/>
271273
)}
272274
</Stack>
@@ -317,9 +319,9 @@ const CallWithChatScreen = (props: CallWithChatScreenProps): JSX.Element => {
317319
* @public
318320
*/
319321
export const CallWithChatComposite = (props: CallWithChatCompositeProps): JSX.Element => {
320-
const { adapter, fluentTheme, formFactor, joinInvitationURL, options } = props;
322+
const { adapter, fluentTheme, rtl, formFactor, joinInvitationURL, options } = props;
321323
return (
322-
<BaseProvider fluentTheme={fluentTheme} locale={props.locale} icons={props.icons}>
324+
<BaseProvider fluentTheme={fluentTheme} rtl={rtl} locale={props.locale} icons={props.icons}>
323325
<CallWithChatScreen
324326
{...props}
325327
callWithChatAdapter={adapter}

packages/react-composites/src/composites/CallWithChatComposite/CallWithChatPane.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { drawerContainerStyles } from './styles/CallWithChatCompositeStyles';
2020
import { TabHeader } from './TabHeader';
2121
/* @conditional-compile-remove(file-sharing) */
2222
import { FileSharingOptions } from '../ChatComposite';
23+
import { _ICoordinates } from '@internal/react-components';
24+
import { _pxToRem } from '@internal/acs-ui-common';
2325

2426
/**
2527
* Pane that is used to store chat and people for CallWithChat composite
@@ -40,6 +42,7 @@ export const CallWithChatPane = (props: {
4042
inviteLink?: string;
4143
/* @conditional-compile-remove(file-sharing) */
4244
fileSharing?: FileSharingOptions;
45+
rtl?: boolean;
4346
}): JSX.Element => {
4447
const [drawerMenuItems, setDrawerMenuItems] = useState<_DrawerMenuItemProps[]>([]);
4548

@@ -87,6 +90,7 @@ export const CallWithChatPane = (props: {
8790
</CallAdapterProvider>
8891
);
8992

93+
const modalLayerHost = document.getElementById(props.modalLayerHostId);
9094
const pipStyles = useMemo(() => getPipStyles(theme), [theme]);
9195

9296
const dataUiId =
@@ -107,12 +111,24 @@ export const CallWithChatPane = (props: {
107111
</Stack.Item>
108112
</Stack>
109113
</Stack.Item>
110-
{props.mobileView && (
114+
{props.mobileView && modalLayerHost && (
111115
<ModalLocalAndRemotePIP
112116
callAdapter={props.callAdapter}
113117
modalLayerHostId={props.modalLayerHostId}
114118
hidden={hidden}
115119
styles={pipStyles}
120+
minDragPosition={{
121+
x: props.rtl
122+
? -1 * modalPipRightPositionPx
123+
: modalPipRightPositionPx - modalLayerHost.getBoundingClientRect().width + modalPipWidthPx,
124+
y: -1 * modalPipTopPositionPx
125+
}}
126+
maxDragPosition={{
127+
x: props.rtl
128+
? modalLayerHost.getBoundingClientRect().width - modalPipRightPositionPx - modalPipWidthPx
129+
: modalPipRightPositionPx,
130+
y: modalLayerHost.getBoundingClientRect().height - modalPipTopPositionPx - modalPipHeightPx
131+
}}
116132
/>
117133
)}
118134
{drawerMenuItems.length > 0 && (
@@ -150,17 +166,20 @@ const sidePaneTokens: IStackTokens = {
150166
childrenGap: '0.5rem'
151167
};
152168

153-
/**
154-
* @private
155-
*/
169+
const modalPipRightPositionPx = 16;
170+
const modalPipTopPositionPx = 52;
171+
const modalPipWidthPx = 88;
172+
const modalPipHeightPx = 128;
173+
156174
const getPipStyles = (theme: ITheme): ModalLocalAndRemotePIPStyles => ({
157175
modal: {
158176
main: {
159177
borderRadius: theme.effects.roundedCorner4,
160178
boxShadow: theme.effects.elevation8,
161179
// Above the message thread / people pane.
162180
zIndex: 2,
163-
...(theme.rtl ? { left: '1rem' } : { right: '1rem' })
181+
...(theme.rtl ? { left: _pxToRem(modalPipRightPositionPx) } : { right: _pxToRem(modalPipRightPositionPx) }),
182+
top: _pxToRem(modalPipTopPositionPx)
164183
}
165184
}
166185
});

0 commit comments

Comments
 (0)