diff --git a/res/css/_components.scss b/res/css/_components.scss index 582dc59517..fa388c4e6a 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -86,6 +86,7 @@ @import "./views/elements/_Field.scss"; @import "./views/elements/_ImageView.scss"; @import "./views/elements/_InlineSpinner.scss"; +@import "./views/elements/_InteractiveTooltip.scss"; @import "./views/elements/_ManageIntegsButton.scss"; @import "./views/elements/_MemberEventListSummary.scss"; @import "./views/elements/_MessageEditor.scss"; diff --git a/res/css/views/elements/_InteractiveTooltip.scss b/res/css/views/elements/_InteractiveTooltip.scss new file mode 100644 index 0000000000..11f548fa18 --- /dev/null +++ b/res/css/views/elements/_InteractiveTooltip.scss @@ -0,0 +1,164 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_InteractiveTooltip_wrapper { + position: fixed; + z-index: 5000; +} + +.mx_InteractiveTooltip_background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 1.0; + z-index: 5000; +} + +.mx_InteractiveTooltip { + border-radius: 4px; + box-shadow: 4px 4px 12px 0 $menu-box-shadow-color; + background-color: $menu-bg-color; + color: $primary-fg-color; + position: absolute; + font-size: 14px; + z-index: 5001; +} + +.mx_InteractiveTooltip_right { + right: 0; +} + +.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_right { + right: 8px; +} + +.mx_InteractiveTooltip_chevron_right { + position: absolute; + right: -8px; + top: 0px; + width: 0; + height: 0; + border-top: 8px solid transparent; + border-left: 8px solid $menu-bg-color; + border-bottom: 8px solid transparent; +} + +.mx_InteractiveTooltip_chevron_right::after { + content: ''; + width: 0; + height: 0; + border-top: 7px solid transparent; + border-left: 7px solid $menu-bg-color; + border-bottom: 7px solid transparent; + position: absolute; + top: -7px; + right: 1px; +} + +.mx_InteractiveTooltip_left { + left: 0; +} + +.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_left { + left: 8px; +} + +.mx_InteractiveTooltip_chevron_left { + position: absolute; + left: -8px; + top: 0px; + width: 0; + height: 0; + border-top: 8px solid transparent; + border-right: 8px solid $menu-bg-color; + border-bottom: 8px solid transparent; +} + +.mx_InteractiveTooltip_chevron_left::after { + content: ''; + width: 0; + height: 0; + border-top: 7px solid transparent; + border-right: 7px solid $menu-bg-color; + border-bottom: 7px solid transparent; + position: absolute; + top: -7px; + left: 1px; +} + +.mx_InteractiveTooltip_top { + top: 0; +} + +.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_top { + top: 8px; +} + +.mx_InteractiveTooltip_chevron_top { + position: absolute; + left: 0px; + top: -8px; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-bottom: 8px solid $menu-bg-color; + border-right: 8px solid transparent; +} + +.mx_InteractiveTooltip_chevron_top::after { + content: ''; + width: 0; + height: 0; + border-left: 7px solid transparent; + border-bottom: 7px solid $menu-bg-color; + border-right: 7px solid transparent; + position: absolute; + left: -7px; + top: 1px; +} + +.mx_InteractiveTooltip_bottom { + bottom: 0; +} + +.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_bottom { + bottom: 8px; +} + +.mx_InteractiveTooltip_chevron_bottom { + position: absolute; + left: 0px; + bottom: -8px; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-top: 8px solid $menu-bg-color; + border-right: 8px solid transparent; +} + +.mx_InteractiveTooltip_chevron_bottom::after { + content: ''; + width: 0; + height: 0; + border-left: 7px solid transparent; + border-top: 7px solid $menu-bg-color; + border-right: 7px solid transparent; + position: absolute; + left: -7px; + bottom: 1px; +} diff --git a/src/components/views/elements/InteractiveTooltip.js b/src/components/views/elements/InteractiveTooltip.js new file mode 100644 index 0000000000..7c582a2b71 --- /dev/null +++ b/src/components/views/elements/InteractiveTooltip.js @@ -0,0 +1,141 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +const InteractiveTooltipContainerId = "mx_InteractiveTooltip_Container"; + +function getOrCreateContainer() { + let container = document.getElementById(InteractiveTooltipContainerId); + + if (!container) { + container = document.createElement("div"); + container.id = InteractiveTooltipContainerId; + document.body.appendChild(container); + } + + return container; +} + +export default class InteractiveTooltip extends React.Component { + propTypes: { + top: PropTypes.number, + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + chevronOffset: PropTypes.number, + chevronFace: PropTypes.string, // top, bottom, left, right or none + // Function to be called on menu close + onFinished: PropTypes.func, + + // If true, insert an invisible screen-sized element behind the + // menu that when clicked will close it. + hasBackground: PropTypes.bool, + + // The component to render as the context menu + elementClass: PropTypes.element.isRequired, + // on resize callback + windowResize: PropTypes.func, + // method to close menu + closeTooltip: PropTypes.func, + }; + + render() { + const position = {}; + let chevronFace = null; + const props = this.props; + + if (props.top) { + position.top = props.top; + } else { + position.bottom = props.bottom; + } + + if (props.left) { + position.left = props.left; + chevronFace = 'left'; + } else { + position.right = props.right; + chevronFace = 'right'; + } + + const chevronOffset = {}; + if (props.chevronFace) { + chevronFace = props.chevronFace; + } + const hasChevron = chevronFace && chevronFace !== "none"; + + if (chevronFace === 'top' || chevronFace === 'bottom') { + chevronOffset.left = props.chevronOffset; + } else { + chevronOffset.top = props.chevronOffset; + } + + const chevron = hasChevron ? +
: + undefined; + const className = 'mx_InteractiveTooltip_wrapper'; + + const menuClasses = classNames({ + 'mx_InteractiveTooltip': true, + 'mx_InteractiveTooltip_left': !hasChevron && position.left, + 'mx_InteractiveTooltip_right': !hasChevron && position.right, + 'mx_InteractiveTooltip_top': !hasChevron && position.top, + 'mx_InteractiveTooltip_bottom': !hasChevron && position.bottom, + 'mx_InteractiveTooltip_withChevron_left': chevronFace === 'left', + 'mx_InteractiveTooltip_withChevron_right': chevronFace === 'right', + 'mx_InteractiveTooltip_withChevron_top': chevronFace === 'top', + 'mx_InteractiveTooltip_withChevron_bottom': chevronFace === 'bottom', + }); + + const ElementClass = props.elementClass; + + return
+
+ { chevron } + +
+ { props.hasBackground &&
} +
; + } +} + +export function createTooltip(ElementClass, props, hasBackground=true) { + const closeTooltip = function(...args) { + ReactDOM.unmountComponentAtNode(getOrCreateContainer()); + + if (props && props.onFinished) { + props.onFinished.apply(null, args); + } + }; + + // We only reference closeTooltip once per call to createTooltip + const menu = ; + + ReactDOM.render(menu, getOrCreateContainer()); + + return {close: closeTooltip}; +}