feat(context-menu) Show participants context menu overlaid in a portal
This commit is contained in:
parent
9ef71e3b15
commit
9f7a4f86d8
|
@ -3,48 +3,45 @@
|
||||||
* to allow mouse movement from the popover trigger to the popover itself
|
* to allow mouse movement from the popover trigger to the popover itself
|
||||||
* without triggering a mouseleave event.
|
* without triggering a mouseleave event.
|
||||||
*/
|
*/
|
||||||
.popover-mousemove-padding-bottom {
|
|
||||||
bottom: -15px;
|
|
||||||
height: 20px;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
%vertical-popover-padding {
|
%vertical-popover-padding {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 40px;
|
width: 20px;
|
||||||
|
padding: 20px 0;
|
||||||
|
top: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
%horizontal-popover-padding {
|
||||||
|
height: 25px;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 35px;
|
||||||
|
left: -35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover-mousemove-padding-left {
|
.popover-mousemove-padding-left {
|
||||||
@extend %vertical-popover-padding;
|
@extend %vertical-popover-padding;
|
||||||
left: -20px;
|
left: -35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover-mousemove-padding-right {
|
.popover-mousemove-padding-right {
|
||||||
@extend %vertical-popover-padding;
|
@extend %vertical-popover-padding;
|
||||||
right: -20px;
|
right: -35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
.popover-mousemove-padding-bottom {
|
||||||
* An invisible element is added to the top of the popover to ensure the mouse
|
@extend %horizontal-popover-padding;
|
||||||
* stays over the popover when the popover's height is shrunk, which would then
|
bottom: -40px;
|
||||||
* normally leave the mouse outside of the popover itself and cause a mouseleave
|
}
|
||||||
* event.
|
|
||||||
*/
|
.popover-mousemove-padding-top {
|
||||||
.popover-mouse-padding-top {
|
@extend %horizontal-popover-padding;
|
||||||
height: 30px;
|
top: -40px;
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: -25px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
background-color: $popoverBg;
|
|
||||||
border-radius: 3px;
|
|
||||||
margin: -16px -24px;
|
margin: -16px -24px;
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
z-index: $popoverZ;
|
z-index: $popoverZ;
|
||||||
|
|
|
@ -1,35 +1,10 @@
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import InlineDialog from '@atlaskit/inline-dialog';
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { Drawer, DrawerPortal } from '../../../toolbox/components/web';
|
import { Drawer, DrawerPortal, DialogPortal } from '../../../toolbox/components/web';
|
||||||
|
import { isMobileBrowser } from '../../environment/utils';
|
||||||
/**
|
import { getContextMenuStyle } from '../functions.web';
|
||||||
* A map of dialog positions, relative to trigger, to css classes used to
|
|
||||||
* manipulate elements for handling mouse events.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @type {object}
|
|
||||||
*/
|
|
||||||
const DIALOG_TO_PADDING_POSITION = {
|
|
||||||
'left': 'popover-mousemove-padding-right',
|
|
||||||
'right': 'popover-mousemove-padding-left',
|
|
||||||
'top': 'popover-mousemove-padding-bottom'
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes the position expected by {@code InlineDialog} and maps it to a CSS
|
|
||||||
* class that can be used styling the elements used for preventing mouseleave
|
|
||||||
* events when moving from the trigger to the dialog.
|
|
||||||
*
|
|
||||||
* @param {string} position - From which position the dialog will display.
|
|
||||||
* @private
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function _mapPositionToPaddingClass(position = 'left') {
|
|
||||||
return DIALOG_TO_PADDING_POSITION[position.split('-')[0]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of {@link Popover}.
|
* The type of the React {@code Component} props of {@link Popover}.
|
||||||
|
@ -90,6 +65,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
type State = {
|
type State = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The style to apply to the context menu in order to position it correctly.
|
||||||
|
*/
|
||||||
|
contextMenuStyle: Object,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the {@code InlineDialog} should be displayed.
|
* Whether or not the {@code InlineDialog} should be displayed.
|
||||||
*/
|
*/
|
||||||
|
@ -118,6 +98,7 @@ class Popover extends Component<Props, State> {
|
||||||
*/
|
*/
|
||||||
_containerRef: Object;
|
_containerRef: Object;
|
||||||
|
|
||||||
|
_contextMenuRef: HTMLElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new {@code Popover} instance.
|
* Initializes a new {@code Popover} instance.
|
||||||
|
@ -129,7 +110,8 @@ class Popover extends Component<Props, State> {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
showDialog: false
|
showDialog: false,
|
||||||
|
contextMenuStyle: null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bind event handlers so they are only bound once for every instance.
|
// Bind event handlers so they are only bound once for every instance.
|
||||||
|
@ -140,6 +122,9 @@ class Popover extends Component<Props, State> {
|
||||||
this._onEscKey = this._onEscKey.bind(this);
|
this._onEscKey = this._onEscKey.bind(this);
|
||||||
this._onThumbClick = this._onThumbClick.bind(this);
|
this._onThumbClick = this._onThumbClick.bind(this);
|
||||||
this._onTouchStart = this._onTouchStart.bind(this);
|
this._onTouchStart = this._onTouchStart.bind(this);
|
||||||
|
this._setContextMenuRef = this._setContextMenuRef.bind(this);
|
||||||
|
this._setContextMenuStyle = this._setContextMenuStyle.bind(this);
|
||||||
|
this._getCustomDialogStyle = this._getCustomDialogStyle.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -179,7 +164,7 @@ class Popover extends Component<Props, State> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { children, className, content, id, overflowDrawer, position } = this.props;
|
const { children, className, content, id, overflowDrawer } = this.props;
|
||||||
|
|
||||||
if (overflowDrawer) {
|
if (overflowDrawer) {
|
||||||
return (
|
return (
|
||||||
|
@ -208,16 +193,47 @@ class Popover extends Component<Props, State> {
|
||||||
onMouseEnter = { this._onShowDialog }
|
onMouseEnter = { this._onShowDialog }
|
||||||
onMouseLeave = { this._onHideDialog }
|
onMouseLeave = { this._onHideDialog }
|
||||||
ref = { this._containerRef }>
|
ref = { this._containerRef }>
|
||||||
<InlineDialog
|
{ this.state.showDialog && (
|
||||||
content = { this._renderContent() }
|
<DialogPortal
|
||||||
isOpen = { this.state.showDialog }
|
getRef = { this._setContextMenuRef }
|
||||||
placement = { position }>
|
setSize = { this._setContextMenuStyle }
|
||||||
{ children }
|
style = { this.state.contextMenuStyle }>
|
||||||
</InlineDialog>
|
{this._renderContent()}
|
||||||
|
</DialogPortal>
|
||||||
|
)}
|
||||||
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setContextMenuStyle: (size: Object) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the context menu dialog style for positioning it on screen.
|
||||||
|
*
|
||||||
|
* @param {DOMRectReadOnly} size -The size info of the current context menu.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_setContextMenuStyle(size) {
|
||||||
|
const style = this._getCustomDialogStyle(size);
|
||||||
|
|
||||||
|
this.setState({ contextMenuStyle: style });
|
||||||
|
}
|
||||||
|
|
||||||
|
_setContextMenuRef: (elem: HTMLElement) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the context menu's ref.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} elem -The html element of the context menu.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_setContextMenuRef(elem) {
|
||||||
|
this._contextMenuRef = elem;
|
||||||
|
}
|
||||||
|
|
||||||
_onTouchStart: (event: TouchEvent) => void;
|
_onTouchStart: (event: TouchEvent) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -230,9 +246,9 @@ class Popover extends Component<Props, State> {
|
||||||
_onTouchStart(event) {
|
_onTouchStart(event) {
|
||||||
if (this.state.showDialog
|
if (this.state.showDialog
|
||||||
&& !this.props.overflowDrawer
|
&& !this.props.overflowDrawer
|
||||||
&& this._containerRef
|
&& this._contextMenuRef
|
||||||
&& this._containerRef.current
|
&& this._contextMenuRef.contains
|
||||||
&& !this._containerRef.current.contains(event.target)) {
|
&& !this._contextMenuRef.contains(event.target)) {
|
||||||
this._onHideDialog();
|
this._onHideDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,7 +262,10 @@ class Popover extends Component<Props, State> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onHideDialog() {
|
_onHideDialog() {
|
||||||
this.setState({ showDialog: false });
|
this.setState({
|
||||||
|
showDialog: false,
|
||||||
|
contextMenuStyle: null
|
||||||
|
});
|
||||||
|
|
||||||
if (this.props.onPopoverClose) {
|
if (this.props.onPopoverClose) {
|
||||||
this.props.onPopoverClose();
|
this.props.onPopoverClose();
|
||||||
|
@ -327,6 +346,24 @@ class Popover extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getCustomDialogStyle: (DOMRectReadOnly) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets style for positioning the context menu on screen in regards to the trigger's
|
||||||
|
* position.
|
||||||
|
*
|
||||||
|
* @param {DOMRectReadOnly} size -The current context menu's size info.
|
||||||
|
*
|
||||||
|
* @returns {Object} - The new style of the context menu.
|
||||||
|
*/
|
||||||
|
_getCustomDialogStyle(size) {
|
||||||
|
if (this._containerRef && this._containerRef.current) {
|
||||||
|
const bounds = this._containerRef.current.getBoundingClientRect();
|
||||||
|
|
||||||
|
return getContextMenuStyle(bounds, size, this.props.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the React Element to be displayed in the {@code InlineDialog}.
|
* Renders the React Element to be displayed in the {@code InlineDialog}.
|
||||||
* Also adds padding to support moving the mouse from the trigger to the
|
* Also adds padding to support moving the mouse from the trigger to the
|
||||||
|
@ -336,15 +373,20 @@ class Popover extends Component<Props, State> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
_renderContent() {
|
_renderContent() {
|
||||||
const { content, position } = this.props;
|
const { content } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className = 'popover'
|
className = 'popover popupmenu'
|
||||||
onKeyDown = { this._onEscKey }>
|
onKeyDown = { this._onEscKey }>
|
||||||
{ content }
|
{ content }
|
||||||
<div className = 'popover-mouse-padding-top' />
|
{!isMobileBrowser() && (
|
||||||
<div className = { _mapPositionToPaddingClass(position) } />
|
<>
|
||||||
|
<div className = 'popover-mousemove-padding-top' />
|
||||||
|
<div className = 'popover-mousemove-padding-right' />
|
||||||
|
<div className = 'popover-mousemove-padding-left' />
|
||||||
|
<div className = 'popover-mousemove-padding-bottom' />
|
||||||
|
</>)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
const LEFT_RIGHT_OFFSET = 25;
|
||||||
|
const TOP_BOTTOM_OFFSET = 20;
|
||||||
|
|
||||||
|
const getLeftAlignedStyle = bounds => {
|
||||||
|
return {
|
||||||
|
position: 'fixed',
|
||||||
|
right: `${window.innerWidth - bounds.x + LEFT_RIGHT_OFFSET}px`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRightAlignedStyle = bounds => {
|
||||||
|
return {
|
||||||
|
position: 'fixed',
|
||||||
|
left: `${bounds.x + bounds.width + LEFT_RIGHT_OFFSET}px`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTopAlignedStyle = bounds => {
|
||||||
|
return {
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: `${window.innerHeight - bounds.y + TOP_BOTTOM_OFFSET}px`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBottomAlignedStyle = bounds => {
|
||||||
|
return {
|
||||||
|
position: 'fixed',
|
||||||
|
top: `${bounds.y + bounds.height + TOP_BOTTOM_OFFSET}px`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLeftRightStartAlign = (bounds, size) => {
|
||||||
|
return {
|
||||||
|
top: `${Math.min(bounds.y + 15, window.innerHeight - size.height - 20)}px`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLeftRightMidAlign = (bounds, size) => {
|
||||||
|
return {
|
||||||
|
bottom: `${window.innerHeight - bounds.y - bounds.height - (size.height / 2)}px`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLeftRightEndAlign = (bounds, size) => {
|
||||||
|
return {
|
||||||
|
bottom: `${Math.min(window.innerHeight - bounds.y - bounds.height, window.innerHeight - size.height)}px`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTopBotStartAlign = bounds => {
|
||||||
|
return {
|
||||||
|
right: `${window.innerWidth - bounds.x + 10}px`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTopBotMidAlign = (bounds, size) => {
|
||||||
|
return {
|
||||||
|
right: `${window.innerWidth - bounds.x - (size.width / 2)}px`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTopBotEndAlign = bounds => {
|
||||||
|
return {
|
||||||
|
left: `${bounds.x + bounds.width + 10}px`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the trigger element's and the context menu's bounds/size info and
|
||||||
|
* computes the style to apply to the context menu to positioning it correctly
|
||||||
|
* in regards to the given position info.
|
||||||
|
*
|
||||||
|
* @param {DOMRect} triggerBounds -The bounds info of the trigger html element.
|
||||||
|
* @param {DOMRectReadOnly} dialogSize - The size info of the context menu.
|
||||||
|
* @param {string} position - The position of the context menu in regards to the trigger element.
|
||||||
|
*
|
||||||
|
* @returns {Object} = The style to apply to context menu for positioning it correctly.
|
||||||
|
*/
|
||||||
|
export const getContextMenuStyle = (triggerBounds: DOMRect,
|
||||||
|
dialogSize: DOMRectReadOnly,
|
||||||
|
position: string) => {
|
||||||
|
const parsed = position.split('-');
|
||||||
|
|
||||||
|
switch (parsed[0]) {
|
||||||
|
case 'top': {
|
||||||
|
let alignmentStyle = {};
|
||||||
|
|
||||||
|
if (parsed[1]) {
|
||||||
|
alignmentStyle = parsed[1] === 'start'
|
||||||
|
? getTopBotStartAlign(triggerBounds)
|
||||||
|
: getTopBotEndAlign(triggerBounds);
|
||||||
|
} else {
|
||||||
|
alignmentStyle = getTopBotMidAlign(triggerBounds, dialogSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...getTopAlignedStyle(triggerBounds),
|
||||||
|
...alignmentStyle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'bottom': {
|
||||||
|
let alignmentStyle = {};
|
||||||
|
|
||||||
|
if (parsed[1]) {
|
||||||
|
alignmentStyle = parsed[1] === 'start'
|
||||||
|
? getTopBotStartAlign(triggerBounds)
|
||||||
|
: getTopBotEndAlign(triggerBounds);
|
||||||
|
} else {
|
||||||
|
alignmentStyle = getTopBotMidAlign(triggerBounds, dialogSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...getBottomAlignedStyle(triggerBounds),
|
||||||
|
...alignmentStyle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'left': {
|
||||||
|
let alignmentStyle = {};
|
||||||
|
|
||||||
|
if (parsed[1]) {
|
||||||
|
alignmentStyle = parsed[1] === 'start'
|
||||||
|
? getLeftRightStartAlign(triggerBounds, dialogSize)
|
||||||
|
: getLeftRightEndAlign(triggerBounds, dialogSize);
|
||||||
|
} else {
|
||||||
|
alignmentStyle = getLeftRightMidAlign(triggerBounds, dialogSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...getLeftAlignedStyle(triggerBounds),
|
||||||
|
...alignmentStyle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'right': {
|
||||||
|
let alignmentStyle = {};
|
||||||
|
|
||||||
|
if (parsed[1]) {
|
||||||
|
alignmentStyle = parsed[1] === 'start'
|
||||||
|
? getLeftRightStartAlign(triggerBounds, dialogSize)
|
||||||
|
: getLeftRightEndAlign(triggerBounds, dialogSize);
|
||||||
|
} else {
|
||||||
|
alignmentStyle = getLeftRightMidAlign(triggerBounds, dialogSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...getRightAlignedStyle(triggerBounds),
|
||||||
|
...alignmentStyle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return {
|
||||||
|
...getLeftAlignedStyle(triggerBounds),
|
||||||
|
...getLeftRightEndAlign(triggerBounds, dialogSize)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -3,7 +3,6 @@
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
import { overwriteConfig } from '../base/config';
|
import { overwriteConfig } from '../base/config';
|
||||||
import { isLayoutTileView } from '../video-layout';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CLEAR_TOOLBOX_TIMEOUT,
|
CLEAR_TOOLBOX_TIMEOUT,
|
||||||
|
@ -134,13 +133,10 @@ export function showToolbox(timeout: number = 0): Object {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
enabled,
|
enabled,
|
||||||
visible,
|
visible
|
||||||
overflowDrawer
|
|
||||||
} = state['features/toolbox'];
|
} = state['features/toolbox'];
|
||||||
const { contextMenuOpened } = state['features/base/responsive-ui'];
|
|
||||||
const contextMenuOpenedInTileview = isLayoutTileView(state) && contextMenuOpened && !overflowDrawer;
|
|
||||||
|
|
||||||
if (enabled && !visible && !contextMenuOpenedInTileview) {
|
if (enabled && !visible) {
|
||||||
dispatch(setToolboxVisible(true));
|
dispatch(setToolboxVisible(true));
|
||||||
|
|
||||||
// If the Toolbox is always visible, there's no need for a timeout
|
// If the Toolbox is always visible, there's no need for a timeout
|
||||||
|
@ -178,23 +174,6 @@ export function setOverflowDrawer(displayAsDrawer: boolean) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disables and hides the toolbox on demand when in tile view.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
export function hideToolboxOnTileView() {
|
|
||||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
|
||||||
const state = getState();
|
|
||||||
const { overflowDrawer } = state['features/toolbox'];
|
|
||||||
|
|
||||||
if (!overflowDrawer && isLayoutTileView(state)) {
|
|
||||||
dispatch(hideToolbox(true));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals that toolbox timeout should be cleared.
|
* Signals that toolbox timeout should be cleared.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component(s) to be displayed within the drawer portal.
|
||||||
|
*/
|
||||||
|
children: React$Node,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom class name to apply on the container div.
|
||||||
|
*/
|
||||||
|
className?: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function used to get the refferrence to the container div.
|
||||||
|
*/
|
||||||
|
getRef?: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function used to get the updated size info of the container on it's resize.
|
||||||
|
*/
|
||||||
|
setSize?: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom style to apply to the container div.
|
||||||
|
*/
|
||||||
|
style?: Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component meant to render a drawer at the bottom of the screen,
|
||||||
|
* by creating a portal containing the component's children.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
function DialogPortal({ children, className, style, getRef, setSize }: Props) {
|
||||||
|
const [ portalTarget ] = useState(() => {
|
||||||
|
const portalDiv = document.createElement('div');
|
||||||
|
|
||||||
|
return portalDiv;
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (style) {
|
||||||
|
for (const styleProp of Object.keys(style)) {
|
||||||
|
// https://github.com/facebook/flow/issues/3733
|
||||||
|
const objStyle: Object = portalTarget.style;
|
||||||
|
|
||||||
|
objStyle[styleProp] = style[styleProp];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (className) {
|
||||||
|
portalTarget.className = className;
|
||||||
|
}
|
||||||
|
}, [ style, className ]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (portalTarget && getRef) {
|
||||||
|
getRef(portalTarget);
|
||||||
|
}
|
||||||
|
}, [ portalTarget ]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const size = {
|
||||||
|
width: 1,
|
||||||
|
height: 1
|
||||||
|
};
|
||||||
|
const observer = new ResizeObserver(entries => {
|
||||||
|
const { contentRect } = entries[0];
|
||||||
|
|
||||||
|
if (contentRect.width !== size.width || contentRect.height !== size.height) {
|
||||||
|
setSize && setSize(contentRect);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (document.body) {
|
||||||
|
document.body.appendChild(portalTarget);
|
||||||
|
observer.observe(portalTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.unobserve(portalTarget);
|
||||||
|
if (document.body) {
|
||||||
|
document.body.removeChild(portalTarget);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return ReactDOM.createPortal(
|
||||||
|
children,
|
||||||
|
portalTarget
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DialogPortal;
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import DialogPortal from './DialogPortal';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
|
@ -18,29 +18,10 @@ type Props = {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
function DrawerPortal({ children }: Props) {
|
function DrawerPortal({ children }: Props) {
|
||||||
const [ portalTarget ] = useState(() => {
|
return (
|
||||||
const portalDiv = document.createElement('div');
|
<DialogPortal className = 'drawer-portal'>
|
||||||
|
{ children }
|
||||||
portalDiv.className = 'drawer-portal';
|
</DialogPortal>
|
||||||
|
|
||||||
return portalDiv;
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (document.body) {
|
|
||||||
document.body.appendChild(portalTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (document.body) {
|
|
||||||
document.body.removeChild(portalTarget);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return ReactDOM.createPortal(
|
|
||||||
children,
|
|
||||||
portalTarget
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,3 +4,4 @@ export { default as ToolbarButton } from './ToolbarButton';
|
||||||
export { default as Toolbox } from './Toolbox';
|
export { default as Toolbox } from './Toolbox';
|
||||||
export { default as Drawer } from './Drawer';
|
export { default as Drawer } from './Drawer';
|
||||||
export { default as DrawerPortal } from './DrawerPortal';
|
export { default as DrawerPortal } from './DrawerPortal';
|
||||||
|
export { default as DialogPortal } from './DialogPortal';
|
||||||
|
|
|
@ -14,7 +14,6 @@ import { connect } from '../../../base/redux';
|
||||||
import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actions';
|
import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actions';
|
||||||
import { getLocalVideoTrack } from '../../../base/tracks';
|
import { getLocalVideoTrack } from '../../../base/tracks';
|
||||||
import ConnectionIndicatorContent from '../../../connection-indicator/components/web/ConnectionIndicatorContent';
|
import ConnectionIndicatorContent from '../../../connection-indicator/components/web/ConnectionIndicatorContent';
|
||||||
import { hideToolboxOnTileView } from '../../../toolbox/actions';
|
|
||||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||||
import { renderConnectionStatus } from '../../actions.web';
|
import { renderConnectionStatus } from '../../actions.web';
|
||||||
|
|
||||||
|
@ -196,7 +195,6 @@ class LocalVideoMenuTriggerButton extends Component<Props> {
|
||||||
*/
|
*/
|
||||||
_onPopoverOpen() {
|
_onPopoverOpen() {
|
||||||
this.props.dispatch(setParticipantContextMenuOpen(true));
|
this.props.dispatch(setParticipantContextMenuOpen(true));
|
||||||
this.props.dispatch(hideToolboxOnTileView());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPopoverClose: () => void;
|
_onPopoverClose: () => void;
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { Popover } from '../../../base/popover';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actions';
|
import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actions';
|
||||||
import { requestRemoteControl, stopController } from '../../../remote-control';
|
import { requestRemoteControl, stopController } from '../../../remote-control';
|
||||||
import { hideToolboxOnTileView } from '../../../toolbox/actions';
|
|
||||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||||
import { renderConnectionStatus } from '../../actions.web';
|
import { renderConnectionStatus } from '../../actions.web';
|
||||||
|
|
||||||
|
@ -234,7 +233,6 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
*/
|
*/
|
||||||
_onPopoverOpen() {
|
_onPopoverOpen() {
|
||||||
this.props.dispatch(setParticipantContextMenuOpen(true));
|
this.props.dispatch(setParticipantContextMenuOpen(true));
|
||||||
this.props.dispatch(hideToolboxOnTileView());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPopoverClose: () => void;
|
_onPopoverClose: () => void;
|
||||||
|
|
Loading…
Reference in New Issue