diff --git a/res/css/_components.scss b/res/css/_components.scss index d0432b2f23..27eebf0ea9 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -108,7 +108,6 @@ @import "./views/elements/_IconButton.scss"; @import "./views/elements/_ImageView.scss"; @import "./views/elements/_InlineSpinner.scss"; -@import "./views/elements/_InteractiveTooltip.scss"; @import "./views/elements/_ManageIntegsButton.scss"; @import "./views/elements/_PowerSelector.scss"; @import "./views/elements/_ProgressBar.scss"; diff --git a/res/css/views/elements/_InteractiveTooltip.scss b/res/css/views/elements/_InteractiveTooltip.scss deleted file mode 100644 index db98d95709..0000000000 --- a/res/css/views/elements/_InteractiveTooltip.scss +++ /dev/null @@ -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 - } -} diff --git a/res/css/views/elements/_Tooltip.scss b/res/css/views/elements/_Tooltip.scss index a3a90e2a4f..45202ff5f9 100644 --- a/res/css/views/elements/_Tooltip.scss +++ b/res/css/views/elements/_Tooltip.scss @@ -51,21 +51,27 @@ limitations under the License. .mx_Tooltip { display: none; position: fixed; - border: 1px solid $menu-border-color; border-radius: 8px; box-shadow: 4px 4px 12px 0 $menu-box-shadow-color; - background-color: $menu-bg-color; z-index: 6000; // Higher than context menu so tooltips can be used everywhere padding: 10px; pointer-events: none; line-height: $font-14px; font-size: $font-12px; font-weight: 500; - color: $primary-fg-color; max-width: 200px; word-break: break-word; margin-right: 50px; + background-color: $inverted-bg-color; + color: $accent-fg-color; + border: 0; + text-align: center; + + .mx_Tooltip_chevron { + display: none; + } + &.mx_Tooltip_visible { animation: mx_fadein 0.2s forwards; } @@ -75,19 +81,6 @@ limitations under the License. } } -.mx_Tooltip_timeline { - &.mx_Tooltip { - background-color: $inverted-bg-color; - color: $accent-fg-color; - border: 0; - text-align: center; - - .mx_Tooltip_chevron { - display: none; - } - } -} - .mx_Tooltip_title { font-weight: 600; } diff --git a/res/css/views/rooms/_RoomBreadcrumbs2.scss b/res/css/views/rooms/_RoomBreadcrumbs2.scss index fd050cfd7c..b4a957cd8d 100644 --- a/res/css/views/rooms/_RoomBreadcrumbs2.scss +++ b/res/css/views/rooms/_RoomBreadcrumbs2.scss @@ -55,14 +55,4 @@ limitations under the License. .mx_RoomBreadcrumbs2_Tooltip { margin-left: -42px; margin-top: -42px; - - &.mx_Tooltip { - background-color: $inverted-bg-color; - color: $accent-fg-color; - border: 0; - - .mx_Tooltip_chevron { - display: none; - } - } } diff --git a/src/accessibility/context_menu/ContextMenuButton.tsx b/src/accessibility/context_menu/ContextMenuButton.tsx index c358155e10..e211a4c933 100644 --- a/src/accessibility/context_menu/ContextMenuButton.tsx +++ b/src/accessibility/context_menu/ContextMenuButton.tsx @@ -18,9 +18,9 @@ limitations under the License. import React from "react"; -import AccessibleButton, {IProps as IAccessibleButtonProps} from "../../components/views/elements/AccessibleButton"; +import AccessibleButton from "../../components/views/elements/AccessibleButton"; -interface IProps extends IAccessibleButtonProps { +interface IProps extends React.ComponentProps { label?: string; // whether or not the context menu is currently open isExpanded: boolean; diff --git a/src/accessibility/context_menu/ContextMenuTooltipButton.tsx b/src/accessibility/context_menu/ContextMenuTooltipButton.tsx new file mode 100644 index 0000000000..abc5412100 --- /dev/null +++ b/src/accessibility/context_menu/ContextMenuTooltipButton.tsx @@ -0,0 +1,47 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2018 New Vector Ltd +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 AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton"; + +interface IProps extends React.ComponentProps { + // whether or not the context menu is currently open + isExpanded: boolean; +} + +// Semantic component for representing the AccessibleButton which launches a +export const ContextMenuTooltipButton: React.FC = ({ + isExpanded, + children, + onClick, + onContextMenu, + ...props +}) => { + return ( + + { children } + + ); +}; diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 62964c5799..f1bd297730 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -461,6 +461,7 @@ export function createMenu(ElementClass, props) { // re-export the semantic helper components for simplicity export {ContextMenuButton} from "../../accessibility/context_menu/ContextMenuButton"; +export {ContextMenuTooltipButton} from "../../accessibility/context_menu/ContextMenuTooltipButton"; export {MenuGroup} from "../../accessibility/context_menu/MenuGroup"; export {MenuItem} from "../../accessibility/context_menu/MenuItem"; export {MenuItemCheckbox} from "../../accessibility/context_menu/MenuItemCheckbox"; diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 7fe1c24062..717ec240ac 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -34,6 +34,7 @@ import SettingsStore from "../../settings/SettingsStore"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore2"; import {Key} from "../../Keyboard"; import IndicatorScrollbar from "../structures/IndicatorScrollbar"; +import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 @@ -347,7 +348,7 @@ export default class LeftPanel2 extends React.Component { onVerticalArrow={this.onKeyDown} onEnter={this.onEnter} /> - | React.KeyboardEvent { +interface IProps extends React.InputHTMLAttributes { inputRef?: React.Ref; element?: string; // The kind of button, similar to how Bootstrap works. @@ -118,7 +118,7 @@ export default function AccessibleButton({ AccessibleButton.defaultProps = { element: 'div', role: 'button', - tabIndex: "0", + tabIndex: 0, }; AccessibleButton.displayName = "AccessibleButton"; diff --git a/src/components/views/elements/AccessibleTooltipButton.tsx b/src/components/views/elements/AccessibleTooltipButton.tsx index 641151bbfd..6bb6f9e529 100644 --- a/src/components/views/elements/AccessibleTooltipButton.tsx +++ b/src/components/views/elements/AccessibleTooltipButton.tsx @@ -19,10 +19,9 @@ import React from 'react'; import classNames from 'classnames'; import AccessibleButton from "./AccessibleButton"; -import {IProps} from "./AccessibleButton"; import Tooltip from './Tooltip'; -interface ITooltipProps extends IProps { +interface ITooltipProps extends React.ComponentProps { title: string; tooltip?: React.ReactNode; tooltipClassName?: string; diff --git a/src/components/views/elements/InteractiveTooltip.js b/src/components/views/elements/InteractiveTooltip.js deleted file mode 100644 index 7f5f24a094..0000000000 --- a/src/components/views/elements/InteractiveTooltip.js +++ /dev/null @@ -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 =
; - - 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 =
-
- {chevron} - {this.props.content} -
-
; - - 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, - }); - } -} diff --git a/src/components/views/elements/ManageIntegsButton.js b/src/components/views/elements/ManageIntegsButton.js index b631ddee73..ac8a98a94a 100644 --- a/src/components/views/elements/ManageIntegsButton.js +++ b/src/components/views/elements/ManageIntegsButton.js @@ -17,10 +17,10 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import SettingsStore from "../../../settings/SettingsStore"; +import AccessibleTooltipButton from "./AccessibleTooltipButton"; export default class ManageIntegsButton extends React.Component { constructor(props) { @@ -45,9 +45,8 @@ export default class ManageIntegsButton extends React.Component { render() { let integrationsButton =
; if (IntegrationManagers.sharedInstance().hasManager()) { - const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); integrationsButton = ( - { // Remove the wrapper element, as the tooltip has finished using it public componentWillUnmount() { - dis.dispatch({ - action: Action.ViewTooltip, - tooltip: null, - parent: null, - }); - ReactDOM.unmountComponentAtNode(this.tooltipContainer); document.body.removeChild(this.tooltipContainer); window.removeEventListener('scroll', this.renderTooltip, true); } - private updatePosition(style: {[key: string]: any}) { + private updatePosition(style: CSSProperties) { const parentBox = this.parent.getBoundingClientRect(); let offset = 0; if (parentBox.height > MIN_TOOLTIP_HEIGHT) { @@ -89,8 +80,14 @@ export default class Tooltip extends React.Component { // we need so that we're still centered. offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT); } + style.top = (parentBox.top - 2) + window.pageYOffset + offset; - style.left = 6 + parentBox.right + window.pageXOffset; + if (parentBox.right > window.innerWidth / 2) { + style.right = window.innerWidth - parentBox.right - window.pageXOffset - 8; + } else { + style.left = parentBox.right + window.pageXOffset + 6; + } + return style; } @@ -99,7 +96,6 @@ export default class Tooltip extends React.Component { // positioned, also taking into account any window zoom // NOTE: The additional 6 pixels for the left position, is to take account of the // tooltips chevron - const parent = ReactDOM.findDOMNode(this).parentNode as Element; const style = this.updatePosition({}); // Hide the entire container when not visible. This prevents flashing of the tooltip // if it is not meant to be visible on first mount. @@ -119,13 +115,6 @@ export default class Tooltip extends React.Component { // Render the tooltip manually, as we wish it not to be rendered within the parent this.tooltip = ReactDOM.render(tooltip, this.tooltipContainer); - - // Tell the roomlist about us so it can manipulate us if it wishes - dis.dispatch({ - action: Action.ViewTooltip, - tooltip: this.tooltip, - parent: parent, - }); }; public render() { diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 7959ad8a93..513f21b8b7 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -22,11 +22,11 @@ import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; -import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu'; +import {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from '../../structures/ContextMenu'; import { isContentActionable, canEditContent } from '../../../utils/EventUtils'; import RoomContext from "../../../contexts/RoomContext"; import Toolbar from "../../../accessibility/Toolbar"; -import {RovingAccessibleButton, useRovingTabIndex} from "../../../accessibility/RovingTabIndex"; +import {RovingAccessibleTooltipButton, useRovingTabIndex} from "../../../accessibility/RovingTabIndex"; const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); @@ -55,9 +55,9 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo } return - { } return - ; + tooltip = ; } return tooltip; diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 5784e36a8b..87df1b8a87 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -391,7 +391,6 @@ export default createReactClass({ onClick={this._openHistoryDialog} title={_t("Edited at %(date)s. Click to view edits.", {date: dateString})} tooltip={tooltip} - tooltipClassName="mx_Tooltip_timeline" > {`(${_t("edited")})`} diff --git a/src/components/views/right_panel/HeaderButton.js b/src/components/views/right_panel/HeaderButton.js index 06b434c1ce..2cfc060bba 100644 --- a/src/components/views/right_panel/HeaderButton.js +++ b/src/components/views/right_panel/HeaderButton.js @@ -22,7 +22,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import Analytics from '../../../Analytics'; -import AccessibleButton from '../elements/AccessibleButton'; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; export default class HeaderButton extends React.Component { constructor() { @@ -42,13 +42,13 @@ export default class HeaderButton extends React.Component { [`mx_RightPanel_${this.props.name}`]: true, }); - return - ; + onClick={this.onClick} + />; } } diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 84a5a3a9a0..bf4700ed97 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -27,7 +27,8 @@ import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks'; import ContentMessages from '../../../ContentMessages'; import E2EIcon from './E2EIcon'; import SettingsStore from "../../../settings/SettingsStore"; -import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from "../../structures/ContextMenu"; +import {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; function ComposerAvatar(props) { const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); @@ -41,7 +42,6 @@ ComposerAvatar.propTypes = { }; function CallButton(props) { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const onVoiceCallClick = (ev) => { dis.dispatch({ action: 'place_call', @@ -50,10 +50,11 @@ function CallButton(props) { }); }; - return (); + return (); } CallButton.propTypes = { @@ -61,7 +62,6 @@ CallButton.propTypes = { }; function VideoCallButton(props) { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const onCallClick = (ev) => { dis.dispatch({ action: 'place_call', @@ -70,7 +70,8 @@ function VideoCallButton(props) { }); }; - return ; @@ -117,14 +118,15 @@ const EmojiButton = ({addEmoji}) => { } return - - + { contextMenu } ; @@ -185,9 +187,9 @@ class UploadButton extends React.Component { render() { const uploadInputStyle = {display: 'none'}; - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return ( - @@ -198,7 +200,7 @@ class UploadButton extends React.Component { multiple onChange={this.onUploadFileInputChange} /> - + ); } } diff --git a/src/components/views/rooms/MessageComposerFormatBar.js b/src/components/views/rooms/MessageComposerFormatBar.js index 42d54f5987..71aef1e833 100644 --- a/src/components/views/rooms/MessageComposerFormatBar.js +++ b/src/components/views/rooms/MessageComposerFormatBar.js @@ -17,9 +17,8 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; -import * as sdk from '../../../index'; import classNames from 'classnames'; -import AccessibleButton from "../elements/AccessibleButton"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; export default class MessageComposerFormatBar extends React.PureComponent { static propTypes = { @@ -68,28 +67,28 @@ class FormatButton extends React.PureComponent { }; render() { - const InteractiveTooltip = sdk.getComponent('elements.InteractiveTooltip'); const className = `mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIcon${this.props.icon}`; let shortcut; if (this.props.shortcut) { shortcut =
{this.props.shortcut}
; } - const tooltipContent = ( -
-
{this.props.label}
+ const tooltip =
+
+ {this.props.label} +
+
{shortcut}
- ); +
; return ( - - - + ); } } diff --git a/src/components/views/rooms/RoomBreadcrumbs2.tsx b/src/components/views/rooms/RoomBreadcrumbs2.tsx index fde24524cd..71e9d9d6e1 100644 --- a/src/components/views/rooms/RoomBreadcrumbs2.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs2.tsx @@ -93,7 +93,7 @@ export default class RoomBreadcrumbs2 extends React.PureComponent this.viewRoom(r, i)} aria-label={_t("Room %(name)s", {name: r.name})} title={r.name} - tooltipClassName={"mx_RoomBreadcrumbs2_Tooltip"} + tooltipClassName="mx_RoomBreadcrumbs2_Tooltip" > - ; + title={_t("Settings")} />; } if (this.props.onPinnedClick && SettingsStore.isFeatureEnabled('feature_pinning')) { @@ -236,55 +235,45 @@ export default createReactClass({ } pinnedEventsButton = - + { pinsIndicator } - ; + ; } -// var leave_button; -// if (this.props.onLeaveClick) { -// leave_button = -//
-// -//
; -// } - let forgetButton; if (this.props.onForgetClick) { forgetButton = - - ; + title={_t("Forget room")} />; } let searchButton; if (this.props.onSearchClick && this.props.inRoom) { searchButton = - - ; + title={_t("Search")} />; } let shareRoomButton; if (this.props.inRoom) { shareRoomButton = - - ; + title={_t('Share room')} />; } let manageIntegsButton; if (this.props.room && this.props.room.roomId && this.props.inRoom) { - manageIntegsButton = ; + manageIntegsButton = ; } const rightRow = diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index e4290f87d9..dee4015003 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -230,9 +230,6 @@ export default createReactClass({ onAction: function(payload) { switch (payload.action) { - case 'view_tooltip': - this.tooltip = payload.tooltip; - break; case 'call_state': var call = CallHandler.getCall(payload.room_id); if (call && call.call_state === 'ringing') { @@ -589,18 +586,6 @@ export default createReactClass({ } }, - _whenScrolling: function(e) { - this._hideTooltip(e); - this._repositionIncomingCallBox(e, false); - }, - - _hideTooltip: function(e) { - // Hide tooltip when scrolling, as we'll no longer be over the one we were on - if (this.tooltip && this.tooltip.style.display !== "none") { - this.tooltip.style.display = "none"; - } - }, - _repositionIncomingCallBox: function(e, firstTime) { const incomingCallBox = document.getElementById("incomingCallBox"); if (incomingCallBox && incomingCallBox.parentElement) { diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 145bd8d68e..4883d4f2a3 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -28,7 +28,7 @@ import { ListLayout } from "../../../stores/room-list/ListLayout"; import { ChevronFace, ContextMenu, - ContextMenuButton, + ContextMenuTooltipButton, StyledMenuItemCheckbox, StyledMenuItemRadio, } from "../../structures/ContextMenu"; @@ -499,10 +499,10 @@ export default class RoomSublist2 extends React.Component { return ( - {contextMenu} @@ -561,6 +561,11 @@ export default class RoomSublist2 extends React.Component {
); + let Button: React.ComponentType> = AccessibleButton; + if (this.props.isMinimized) { + Button = AccessibleTooltipButton; + } + // Note: the addRoomButton conditionally gets moved around // the DOM depending on whether or not the list is minimized. // If we're minimized, we want it below the header so it @@ -569,7 +574,7 @@ export default class RoomSublist2 extends React.Component { return (
- { aria-level={1} onClick={this.onHeaderClick} onContextMenu={this.onContextMenu} + title={this.props.isMinimized ? this.props.label : undefined} > {this.props.label} - + {this.renderMenu()} {this.props.isMinimized ? null : badgeContainer} {this.props.isMinimized ? null : addRoomButton} diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index fe6a19f2ed..b19bb23160 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -29,7 +29,7 @@ import { _t } from "../../../languageHandler"; import { ChevronFace, ContextMenu, - ContextMenuButton, + ContextMenuTooltipButton, MenuItemRadio, MenuItemCheckbox, MenuItem, @@ -54,6 +54,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher"; import {ActionPayload} from "../../../dispatcher/payloads"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import { NotificationState } from "../../../stores/notifications/NotificationState"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 @@ -373,10 +374,10 @@ export default class RoomTile2 extends React.Component { return ( - @@ -441,10 +442,10 @@ export default class RoomTile2 extends React.Component { return ( - {contextMenu} @@ -537,11 +538,16 @@ export default class RoomTile2 extends React.Component { ariaDescribedBy = messagePreviewId(this.props.room.roomId); } + let Button: React.ComponentType> = AccessibleButton; + if (this.props.isMinimized) { + Button = AccessibleTooltipButton; + } + return ( {({onFocus, isActive, ref}) => - { aria-label={ariaLabel} aria-selected={this.state.selected} aria-describedby={ariaDescribedBy} + title={this.props.isMinimized ? name : undefined} > {roomAvatar} {nameContainer} {badge} {this.renderGeneralMenu()} {this.renderNotificationsMenu(isActive)} - + } diff --git a/src/components/views/rooms/RoomTileIcon.tsx b/src/components/views/rooms/RoomTileIcon.tsx index 234840d28d..94833ac818 100644 --- a/src/components/views/rooms/RoomTileIcon.tsx +++ b/src/components/views/rooms/RoomTileIcon.tsx @@ -162,7 +162,6 @@ export default class RoomTileIcon extends React.Component { return ; } diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index fc6e80fc61..2e56e49be1 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -27,6 +27,7 @@ import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import SettingsStore from "../../../settings/SettingsStore"; import {ContextMenu} from "../../structures/ContextMenu"; import {WidgetType} from "../../../widgets/WidgetType"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; // This should be below the dialog level (4000), but above the rest of the UI (1000-2000). // We sit in a context menu, so this should be given to the context menu. @@ -409,14 +410,14 @@ export default class Stickerpicker extends React.Component { } else { // Show show-stickers button stickersButton = - - ; + ; } return { stickersButton } diff --git a/src/components/views/rooms/TemporaryTile.tsx b/src/components/views/rooms/TemporaryTile.tsx index 9baaba817d..990c619918 100644 --- a/src/components/views/rooms/TemporaryTile.tsx +++ b/src/components/views/rooms/TemporaryTile.tsx @@ -16,7 +16,11 @@ limitations under the License. import React from "react"; import classNames from "classnames"; -import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; +import { + RovingAccessibleButton, + RovingAccessibleTooltipButton, + RovingTabIndexWrapper +} from "../../../accessibility/RovingTabIndex"; import AccessibleButton from "../../views/elements/AccessibleButton"; import NotificationBadge from "./NotificationBadge"; import { NotificationState } from "../../../stores/notifications/NotificationState"; @@ -86,30 +90,29 @@ export default class TemporaryTile extends React.Component { ); if (this.props.isMinimized) nameContainer = null; + let Button = RovingAccessibleButton; + if (this.props.isMinimized) { + Button = RovingAccessibleTooltipButton; + } + return ( - - {({onFocus, isActive, ref}) => - -
- {this.props.avatar} -
- {nameContainer} -
- {badge} -
-
- } -
+
); } diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 9be674b59e..519a799e67 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -45,11 +45,6 @@ export enum Action { */ ViewRoomDirectory = "view_room_directory", - /** - * Sets the current tooltip. Should be use with ViewTooltipPayload. - */ - ViewTooltip = "view_tooltip", - /** * Forces the theme to reload. No additional payload information required. */ diff --git a/src/dispatcher/payloads/ViewTooltipPayload.ts b/src/dispatcher/payloads/ViewTooltipPayload.ts deleted file mode 100644 index 069e3a619a..0000000000 --- a/src/dispatcher/payloads/ViewTooltipPayload.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2020 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 { ActionPayload } from "../payloads"; -import { Action } from "../actions"; -import { Component } from "react"; - -export interface ViewTooltipPayload extends ActionPayload { - action: Action.ViewTooltip; - - /* - * The tooltip to render. If it's null the tooltip will not be rendered - * We need the void type because of typescript headaches. - */ - tooltip: null | void | Element | Component; - - /* - * The parent under which to render the tooltip. Can be null to remove - * the parent type. - */ - parent: null | Element; -} \ No newline at end of file diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index b705463965..fac3fa0855 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -45,7 +45,7 @@ async function findTabs(session) { /// XXX delay is needed here, possibly because the header is being rerendered /// click doesn't do anything otherwise await session.delay(1000); - const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[title=Settings]"); + const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[aria-label=Settings]"); await settingsButton.click(); //find tabs