Replace non-functional Interactive Tooltip
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
fc66a15504
commit
38dc3c8bd1
4 changed files with 14 additions and 443 deletions
|
@ -108,7 +108,6 @@
|
||||||
@import "./views/elements/_IconButton.scss";
|
@import "./views/elements/_IconButton.scss";
|
||||||
@import "./views/elements/_ImageView.scss";
|
@import "./views/elements/_ImageView.scss";
|
||||||
@import "./views/elements/_InlineSpinner.scss";
|
@import "./views/elements/_InlineSpinner.scss";
|
||||||
@import "./views/elements/_InteractiveTooltip.scss";
|
|
||||||
@import "./views/elements/_ManageIntegsButton.scss";
|
@import "./views/elements/_ManageIntegsButton.scss";
|
||||||
@import "./views/elements/_PowerSelector.scss";
|
@import "./views/elements/_PowerSelector.scss";
|
||||||
@import "./views/elements/_ProgressBar.scss";
|
@import "./views/elements/_ProgressBar.scss";
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
/*
|
|
||||||
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 {
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: $interactive-tooltip-bg-color;
|
|
||||||
color: $interactive-tooltip-fg-color;
|
|
||||||
position: absolute;
|
|
||||||
font-size: $font-10px;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 6px;
|
|
||||||
z-index: 5001;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_top {
|
|
||||||
top: 10px; // 8px chevron + 2px spacing
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip_chevron_top {
|
|
||||||
position: absolute;
|
|
||||||
left: calc(50% - 8px);
|
|
||||||
top: -8px;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: 8px solid transparent;
|
|
||||||
border-bottom: 8px solid $interactive-tooltip-bg-color;
|
|
||||||
border-right: 8px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adapted from https://codyhouse.co/blog/post/css-rounded-triangles-with-clip-path
|
|
||||||
// by Sebastiano Guerriero (@guerriero_se)
|
|
||||||
@supports (clip-path: polygon(0% 0%, 100% 100%, 0% 100%)) {
|
|
||||||
.mx_InteractiveTooltip_chevron_top {
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
background-color: inherit;
|
|
||||||
border: none;
|
|
||||||
clip-path: polygon(0% 0%, 100% 100%, 0% 100%);
|
|
||||||
transform: rotate(135deg);
|
|
||||||
border-radius: 0 0 0 3px;
|
|
||||||
top: calc(-8px / 1.414); // sqrt(2) because of rotation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_bottom {
|
|
||||||
bottom: 10px; // 8px chevron + 2px spacing
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip_chevron_bottom {
|
|
||||||
position: absolute;
|
|
||||||
left: calc(50% - 8px);
|
|
||||||
bottom: -8px;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: 8px solid transparent;
|
|
||||||
border-top: 8px solid $interactive-tooltip-bg-color;
|
|
||||||
border-right: 8px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adapted from https://codyhouse.co/blog/post/css-rounded-triangles-with-clip-path
|
|
||||||
// by Sebastiano Guerriero (@guerriero_se)
|
|
||||||
@supports (clip-path: polygon(0% 0%, 100% 100%, 0% 100%)) {
|
|
||||||
.mx_InteractiveTooltip_chevron_bottom {
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
background-color: inherit;
|
|
||||||
border: none;
|
|
||||||
clip-path: polygon(0% 0%, 100% 100%, 0% 100%);
|
|
||||||
transform: rotate(-45deg);
|
|
||||||
border-radius: 0 0 0 3px;
|
|
||||||
bottom: calc(-8px / 1.414); // sqrt(2) because of rotation
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,336 +0,0 @@
|
||||||
/*
|
|
||||||
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";
|
|
||||||
|
|
||||||
// If the distance from tooltip to window edge is below this value, the tooltip
|
|
||||||
// will flip around to the other side of the target.
|
|
||||||
const MIN_SAFE_DISTANCE_TO_WINDOW_EDGE = 20;
|
|
||||||
|
|
||||||
function getOrCreateContainer() {
|
|
||||||
let container = document.getElementById(InteractiveTooltipContainerId);
|
|
||||||
|
|
||||||
if (!container) {
|
|
||||||
container = document.createElement("div");
|
|
||||||
container.id = InteractiveTooltipContainerId;
|
|
||||||
document.body.appendChild(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInRect(x, y, rect) {
|
|
||||||
const { top, right, bottom, left } = rect;
|
|
||||||
return x >= left && x <= right && y >= top && y <= bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the positive slope of the diagonal of the rect.
|
|
||||||
*
|
|
||||||
* @param {DOMRect} rect
|
|
||||||
* @return {integer}
|
|
||||||
*/
|
|
||||||
function getDiagonalSlope(rect) {
|
|
||||||
const { top, right, bottom, left } = rect;
|
|
||||||
return (bottom - top) / (right - left);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInUpperLeftHalf(x, y, rect) {
|
|
||||||
const { bottom, left } = rect;
|
|
||||||
// Negative slope because Y values grow downwards and for this case, the
|
|
||||||
// diagonal goes from larger to smaller Y values.
|
|
||||||
const diagonalSlope = getDiagonalSlope(rect) * -1;
|
|
||||||
return isInRect(x, y, rect) && (y <= bottom + diagonalSlope * (x - left));
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInLowerRightHalf(x, y, rect) {
|
|
||||||
const { bottom, left } = rect;
|
|
||||||
// Negative slope because Y values grow downwards and for this case, the
|
|
||||||
// diagonal goes from larger to smaller Y values.
|
|
||||||
const diagonalSlope = getDiagonalSlope(rect) * -1;
|
|
||||||
return isInRect(x, y, rect) && (y >= bottom + diagonalSlope * (x - left));
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInUpperRightHalf(x, y, rect) {
|
|
||||||
const { top, left } = rect;
|
|
||||||
// Positive slope because Y values grow downwards and for this case, the
|
|
||||||
// diagonal goes from smaller to larger Y values.
|
|
||||||
const diagonalSlope = getDiagonalSlope(rect) * 1;
|
|
||||||
return isInRect(x, y, rect) && (y <= top + diagonalSlope * (x - left));
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInLowerLeftHalf(x, y, rect) {
|
|
||||||
const { top, left } = rect;
|
|
||||||
// Positive slope because Y values grow downwards and for this case, the
|
|
||||||
// diagonal goes from smaller to larger Y values.
|
|
||||||
const diagonalSlope = getDiagonalSlope(rect) * 1;
|
|
||||||
return isInRect(x, y, rect) && (y >= top + diagonalSlope * (x - left));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This style of tooltip takes a "target" element as its child and centers the
|
|
||||||
* tooltip along one edge of the target.
|
|
||||||
*/
|
|
||||||
export default class InteractiveTooltip extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
// Content to show in the tooltip
|
|
||||||
content: PropTypes.node.isRequired,
|
|
||||||
// Function to call when visibility of the tooltip changes
|
|
||||||
onVisibilityChange: PropTypes.func,
|
|
||||||
// flag to forcefully hide this tooltip
|
|
||||||
forceHidden: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
contentRect: null,
|
|
||||||
visible: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
// Whenever this passthrough component updates, also render the tooltip
|
|
||||||
// in a separate DOM tree. This allows the tooltip content to participate
|
|
||||||
// the normal React rendering cycle: when this component re-renders, the
|
|
||||||
// tooltip content re-renders.
|
|
||||||
// Once we upgrade to React 16, this could be done a bit more naturally
|
|
||||||
// using the portals feature instead.
|
|
||||||
this.renderTooltip();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
document.removeEventListener("mousemove", this.onMouseMove);
|
|
||||||
}
|
|
||||||
|
|
||||||
collectContentRect = (element) => {
|
|
||||||
// We don't need to clean up when unmounting, so ignore
|
|
||||||
if (!element) return;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
contentRect: element.getBoundingClientRect(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
collectTarget = (element) => {
|
|
||||||
this.target = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
canTooltipFitAboveTarget() {
|
|
||||||
const { contentRect } = this.state;
|
|
||||||
const targetRect = this.target.getBoundingClientRect();
|
|
||||||
const targetTop = targetRect.top + window.pageYOffset;
|
|
||||||
return (
|
|
||||||
!contentRect ||
|
|
||||||
(targetTop - contentRect.height > MIN_SAFE_DISTANCE_TO_WINDOW_EDGE)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseMove = (ev) => {
|
|
||||||
const { clientX: x, clientY: y } = ev;
|
|
||||||
const { contentRect } = this.state;
|
|
||||||
const targetRect = this.target.getBoundingClientRect();
|
|
||||||
|
|
||||||
// When moving the mouse from the target to the tooltip, we create a
|
|
||||||
// safe area that includes the tooltip, the target, and the trapezoid
|
|
||||||
// ABCD between them:
|
|
||||||
// ┌───────────┐
|
|
||||||
// │ │
|
|
||||||
// │ │
|
|
||||||
// A └───E───F───┘ B
|
|
||||||
// V
|
|
||||||
// ┌─┐
|
|
||||||
// │ │
|
|
||||||
// C└─┘D
|
|
||||||
//
|
|
||||||
// As long as the mouse remains inside the safe area, the tooltip will
|
|
||||||
// stay open.
|
|
||||||
const buffer = 50;
|
|
||||||
if (isInRect(x, y, targetRect)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.canTooltipFitAboveTarget()) {
|
|
||||||
const contentRectWithBuffer = {
|
|
||||||
top: contentRect.top - buffer,
|
|
||||||
right: contentRect.right + buffer,
|
|
||||||
bottom: contentRect.bottom,
|
|
||||||
left: contentRect.left - buffer,
|
|
||||||
};
|
|
||||||
const trapezoidLeft = {
|
|
||||||
top: contentRect.bottom,
|
|
||||||
right: targetRect.left,
|
|
||||||
bottom: targetRect.bottom,
|
|
||||||
left: contentRect.left - buffer,
|
|
||||||
};
|
|
||||||
const trapezoidCenter = {
|
|
||||||
top: contentRect.bottom,
|
|
||||||
right: targetRect.right,
|
|
||||||
bottom: targetRect.bottom,
|
|
||||||
left: targetRect.left,
|
|
||||||
};
|
|
||||||
const trapezoidRight = {
|
|
||||||
top: contentRect.bottom,
|
|
||||||
right: contentRect.right + buffer,
|
|
||||||
bottom: targetRect.bottom,
|
|
||||||
left: targetRect.right,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
isInRect(x, y, contentRectWithBuffer) ||
|
|
||||||
isInUpperRightHalf(x, y, trapezoidLeft) ||
|
|
||||||
isInRect(x, y, trapezoidCenter) ||
|
|
||||||
isInUpperLeftHalf(x, y, trapezoidRight)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const contentRectWithBuffer = {
|
|
||||||
top: contentRect.top,
|
|
||||||
right: contentRect.right + buffer,
|
|
||||||
bottom: contentRect.bottom + buffer,
|
|
||||||
left: contentRect.left - buffer,
|
|
||||||
};
|
|
||||||
const trapezoidLeft = {
|
|
||||||
top: targetRect.top,
|
|
||||||
right: targetRect.left,
|
|
||||||
bottom: contentRect.top,
|
|
||||||
left: contentRect.left - buffer,
|
|
||||||
};
|
|
||||||
const trapezoidCenter = {
|
|
||||||
top: targetRect.top,
|
|
||||||
right: targetRect.right,
|
|
||||||
bottom: contentRect.top,
|
|
||||||
left: targetRect.left,
|
|
||||||
};
|
|
||||||
const trapezoidRight = {
|
|
||||||
top: targetRect.top,
|
|
||||||
right: contentRect.right + buffer,
|
|
||||||
bottom: contentRect.top,
|
|
||||||
left: targetRect.right,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
isInRect(x, y, contentRectWithBuffer) ||
|
|
||||||
isInLowerRightHalf(x, y, trapezoidLeft) ||
|
|
||||||
isInRect(x, y, trapezoidCenter) ||
|
|
||||||
isInLowerLeftHalf(x, y, trapezoidRight)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hideTooltip();
|
|
||||||
}
|
|
||||||
|
|
||||||
onTargetMouseOver = (ev) => {
|
|
||||||
this.showTooltip();
|
|
||||||
}
|
|
||||||
|
|
||||||
showTooltip() {
|
|
||||||
// Don't enter visible state if we haven't collected the target yet
|
|
||||||
if (!this.target) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
if (this.props.onVisibilityChange) {
|
|
||||||
this.props.onVisibilityChange(true);
|
|
||||||
}
|
|
||||||
document.addEventListener("mousemove", this.onMouseMove);
|
|
||||||
}
|
|
||||||
|
|
||||||
hideTooltip() {
|
|
||||||
this.setState({
|
|
||||||
visible: false,
|
|
||||||
});
|
|
||||||
if (this.props.onVisibilityChange) {
|
|
||||||
this.props.onVisibilityChange(false);
|
|
||||||
}
|
|
||||||
document.removeEventListener("mousemove", this.onMouseMove);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTooltip() {
|
|
||||||
const { contentRect, visible } = this.state;
|
|
||||||
if (this.props.forceHidden === true || !visible) {
|
|
||||||
ReactDOM.render(null, getOrCreateContainer());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetRect = this.target.getBoundingClientRect();
|
|
||||||
|
|
||||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
|
||||||
const targetLeft = targetRect.left + window.pageXOffset;
|
|
||||||
const targetBottom = targetRect.bottom + window.pageYOffset;
|
|
||||||
const targetTop = targetRect.top + window.pageYOffset;
|
|
||||||
|
|
||||||
// Place the tooltip above the target by default. If we find that the
|
|
||||||
// tooltip content would extend past the safe area towards the window
|
|
||||||
// edge, flip around to below the target.
|
|
||||||
const position = {};
|
|
||||||
let chevronFace = null;
|
|
||||||
if (this.canTooltipFitAboveTarget()) {
|
|
||||||
position.bottom = window.innerHeight - targetTop;
|
|
||||||
chevronFace = "bottom";
|
|
||||||
} else {
|
|
||||||
position.top = targetBottom;
|
|
||||||
chevronFace = "top";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Center the tooltip horizontally with the target's center.
|
|
||||||
position.left = targetLeft + targetRect.width / 2;
|
|
||||||
|
|
||||||
const chevron = <div className={"mx_InteractiveTooltip_chevron_" + chevronFace} />;
|
|
||||||
|
|
||||||
const menuClasses = classNames({
|
|
||||||
'mx_InteractiveTooltip': true,
|
|
||||||
'mx_InteractiveTooltip_withChevron_top': chevronFace === 'top',
|
|
||||||
'mx_InteractiveTooltip_withChevron_bottom': chevronFace === 'bottom',
|
|
||||||
});
|
|
||||||
|
|
||||||
const menuStyle = {};
|
|
||||||
if (contentRect) {
|
|
||||||
menuStyle.left = `-${contentRect.width / 2}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tooltip = <div className="mx_InteractiveTooltip_wrapper" style={{...position}}>
|
|
||||||
<div className={menuClasses}
|
|
||||||
style={menuStyle}
|
|
||||||
ref={this.collectContentRect}
|
|
||||||
>
|
|
||||||
{chevron}
|
|
||||||
{this.props.content}
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
|
|
||||||
ReactDOM.render(tooltip, getOrCreateContainer());
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
// We use `cloneElement` here to append some props to the child content
|
|
||||||
// without using a wrapper element which could disrupt layout.
|
|
||||||
return React.cloneElement(this.props.children, {
|
|
||||||
ref: this.collectTarget,
|
|
||||||
onMouseOver: this.onTargetMouseOver,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,9 +17,8 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
|
|
||||||
export default class MessageComposerFormatBar extends React.PureComponent {
|
export default class MessageComposerFormatBar extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -68,28 +67,28 @@ class FormatButton extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const InteractiveTooltip = sdk.getComponent('elements.InteractiveTooltip');
|
|
||||||
const className = `mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIcon${this.props.icon}`;
|
const className = `mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIcon${this.props.icon}`;
|
||||||
let shortcut;
|
let shortcut;
|
||||||
if (this.props.shortcut) {
|
if (this.props.shortcut) {
|
||||||
shortcut = <div className="mx_MessageComposerFormatBar_tooltipShortcut">{this.props.shortcut}</div>;
|
shortcut = <div className="mx_MessageComposerFormatBar_tooltipShortcut">{this.props.shortcut}</div>;
|
||||||
}
|
}
|
||||||
const tooltipContent = (
|
const tooltip = <div>
|
||||||
<div className="mx_MessageComposerFormatBar_buttonTooltip">
|
<div className="mx_Tooltip_title">
|
||||||
<div>{this.props.label}</div>
|
{this.props.label}
|
||||||
|
</div>
|
||||||
|
<div className="mx_Tooltip_sub">
|
||||||
{shortcut}
|
{shortcut}
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InteractiveTooltip content={tooltipContent} forceHidden={!this.props.visible}>
|
<AccessibleTooltipButton
|
||||||
<AccessibleButton
|
as="span"
|
||||||
as="span"
|
role="button"
|
||||||
role="button"
|
onClick={this.props.onClick}
|
||||||
onClick={this.props.onClick}
|
title={this.props.label}
|
||||||
aria-label={this.props.label}
|
tooltip={tooltip}
|
||||||
className={className} />
|
className={className} />
|
||||||
</InteractiveTooltip>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue