diff --git a/css/_popover.scss b/css/_popover.scss new file mode 100644 index 0000000000..2af5e8ed98 --- /dev/null +++ b/css/_popover.scss @@ -0,0 +1,33 @@ +/** + * Mousemove padding styles are used to add invisible elements to the popover + * to allow mouse movement from the popover trigger to the popover itself + * without triggering a mouseleave event. + */ +.popover-mousemove-padding-bottom { + bottom: -15px; + height: 20px; + position: absolute; + right: 0; + width: 100%; +} +.popover-mousemove-padding-right { + height: 100%; + position: absolute; + right: -20; + top: 0; + width: 40px; +} + +/** + * An invisible element is added to the top of the popover to ensure the mouse + * stays over the popover when the popover's height is shrunk, which would then + * normally leave the mouse outside of the popover itself and cause a mouseleave + * event. + */ +.popover-mouse-padding-top { + height: 30px; + position: absolute; + right: 0; + top: -25px; + width: 100%; +} diff --git a/css/_videolayout_default.scss b/css/_videolayout_default.scss index a6d5800328..21a1fedbcc 100644 --- a/css/_videolayout_default.scss +++ b/css/_videolayout_default.scss @@ -378,28 +378,6 @@ .remote-video-menu-trigger { margin-top: 7px; } -.popover-mousemove-padding-bottom { - bottom: -15px; - height: 20px; - position: absolute; - right: 0; - width: 100%; -} -.popover-mousemove-padding-right { - height: 100%; - position: absolute; - right: -20; - top: 0; - width: 40px; -} - -.popover-mouse-top-padding { - height: 30px; - position: absolute; - right: 0; - top: -25px; - width: 100%; -} /** * Audio indicator on video thumbnails. diff --git a/css/main.scss b/css/main.scss index 53db76ebe4..a0681c33e6 100644 --- a/css/main.scss +++ b/css/main.scss @@ -69,6 +69,7 @@ @import 'aui-components/dropdown'; @import '404'; @import 'policy'; +@import 'popover'; @import 'filmstrip'; @import 'unsupported-browser/main'; @import 'modals/invite/add-people'; diff --git a/react/features/base/popover/components/Popover.native.js b/react/features/base/popover/components/Popover.native.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/react/features/base/popover/components/Popover.web.js b/react/features/base/popover/components/Popover.web.js new file mode 100644 index 0000000000..64bcc60510 --- /dev/null +++ b/react/features/base/popover/components/Popover.web.js @@ -0,0 +1,179 @@ +import InlineDialog from '@atlaskit/inline-dialog'; +import React, { Component } from 'react'; + +/** + * 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', + '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]]; +} + +/** + * Implements a React {@code Component} for showing an {@code InlineDialog} on + * mouseenter of the trigger and contents, and hiding the dialog on mouseleave. + * + * @extends Component + */ +class Popover extends Component { + /** + * Default values for {@code Popover} component's properties. + * + * @static + */ + static defaultProps = { + className: '', + id: '' + }; + + /** + * {@code Popover} component's property types. + * + * @static + */ + static propTypes = { + /** + * A child React Element to use as the trigger for showing the dialog. + */ + children: React.PropTypes.object, + + /** + * Additional CSS classnames to apply to the root of the {@code Popover} + * component. + */ + className: React.PropTypes.string, + + /** + * The ReactElement to display within the dialog. + */ + content: React.PropTypes.object, + + /** + * An id attribute to apply to the root of the {@code Popover} + * component. + */ + id: React.PropTypes.string, + + /** + * Callback to invoke when the popover has opened. + */ + onPopoverOpen: React.PropTypes.func, + + /** + * From which side of the dialog trigger the dialog should display. The + * value will be passed to {@code InlineDialog}. + */ + position: React.PropTypes.string + }; + + /** + * Initializes a new {@code Popover} instance. + * + * @param {Object} props - The read-only properties with which the new + * instance is to be initialized. + */ + constructor(props) { + super(props); + + this.state = { + /** + * Whether or not the {@code InlineDialog} should be displayed. + * + * @type {boolean} + */ + showDialog: false + }; + + // Bind event handlers so they are only bound once for every instance. + this._onHideDialog = this._onHideDialog.bind(this); + this._onShowDialog = this._onShowDialog.bind(this); + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + return ( +