Merge pull request #5012 from matrix-org/t3chguy/tooltips
Sprinkle and consolidate some tooltips
This commit is contained in:
commit
618ecbd261
30 changed files with 197 additions and 660 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -51,21 +51,27 @@ limitations under the License.
|
||||||
.mx_Tooltip {
|
.mx_Tooltip {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
border: 1px solid $menu-border-color;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 4px 4px 12px 0 $menu-box-shadow-color;
|
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
|
z-index: 6000; // Higher than context menu so tooltips can be used everywhere
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
line-height: $font-14px;
|
line-height: $font-14px;
|
||||||
font-size: $font-12px;
|
font-size: $font-12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $primary-fg-color;
|
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
margin-right: 50px;
|
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 {
|
&.mx_Tooltip_visible {
|
||||||
animation: mx_fadein 0.2s forwards;
|
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 {
|
.mx_Tooltip_title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,14 +55,4 @@ limitations under the License.
|
||||||
.mx_RoomBreadcrumbs2_Tooltip {
|
.mx_RoomBreadcrumbs2_Tooltip {
|
||||||
margin-left: -42px;
|
margin-left: -42px;
|
||||||
margin-top: -42px;
|
margin-top: -42px;
|
||||||
|
|
||||||
&.mx_Tooltip {
|
|
||||||
background-color: $inverted-bg-color;
|
|
||||||
color: $accent-fg-color;
|
|
||||||
border: 0;
|
|
||||||
|
|
||||||
.mx_Tooltip_chevron {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
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<typeof AccessibleButton> {
|
||||||
label?: string;
|
label?: string;
|
||||||
// whether or not the context menu is currently open
|
// whether or not the context menu is currently open
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
|
|
47
src/accessibility/context_menu/ContextMenuTooltipButton.tsx
Normal file
47
src/accessibility/context_menu/ContextMenuTooltipButton.tsx
Normal file
|
@ -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<typeof AccessibleTooltipButton> {
|
||||||
|
// whether or not the context menu is currently open
|
||||||
|
isExpanded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
|
||||||
|
export const ContextMenuTooltipButton: React.FC<IProps> = ({
|
||||||
|
isExpanded,
|
||||||
|
children,
|
||||||
|
onClick,
|
||||||
|
onContextMenu,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<AccessibleTooltipButton
|
||||||
|
{...props}
|
||||||
|
onClick={onClick}
|
||||||
|
onContextMenu={onContextMenu || onClick}
|
||||||
|
aria-haspopup={true}
|
||||||
|
aria-expanded={isExpanded}
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
</AccessibleTooltipButton>
|
||||||
|
);
|
||||||
|
};
|
|
@ -461,6 +461,7 @@ export function createMenu(ElementClass, props) {
|
||||||
|
|
||||||
// re-export the semantic helper components for simplicity
|
// re-export the semantic helper components for simplicity
|
||||||
export {ContextMenuButton} from "../../accessibility/context_menu/ContextMenuButton";
|
export {ContextMenuButton} from "../../accessibility/context_menu/ContextMenuButton";
|
||||||
|
export {ContextMenuTooltipButton} from "../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||||
export {MenuGroup} from "../../accessibility/context_menu/MenuGroup";
|
export {MenuGroup} from "../../accessibility/context_menu/MenuGroup";
|
||||||
export {MenuItem} from "../../accessibility/context_menu/MenuItem";
|
export {MenuItem} from "../../accessibility/context_menu/MenuItem";
|
||||||
export {MenuItemCheckbox} from "../../accessibility/context_menu/MenuItemCheckbox";
|
export {MenuItemCheckbox} from "../../accessibility/context_menu/MenuItemCheckbox";
|
||||||
|
|
|
@ -34,6 +34,7 @@ import SettingsStore from "../../settings/SettingsStore";
|
||||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore2";
|
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore2";
|
||||||
import {Key} from "../../Keyboard";
|
import {Key} from "../../Keyboard";
|
||||||
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
||||||
|
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||||
|
|
||||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
||||||
|
|
||||||
|
@ -347,7 +348,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
onVerticalArrow={this.onKeyDown}
|
onVerticalArrow={this.onKeyDown}
|
||||||
onEnter={this.onEnter}
|
onEnter={this.onEnter}
|
||||||
/>
|
/>
|
||||||
<AccessibleButton
|
<AccessibleTooltipButton
|
||||||
className="mx_LeftPanel2_exploreButton"
|
className="mx_LeftPanel2_exploreButton"
|
||||||
onClick={this.onExplore}
|
onClick={this.onExplore}
|
||||||
title={_t("Explore rooms")}
|
title={_t("Explore rooms")}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Elemen
|
||||||
* onClick: (required) Event handler for button activation. Should be
|
* onClick: (required) Event handler for button activation. Should be
|
||||||
* implemented exactly like a normal onClick handler.
|
* implemented exactly like a normal onClick handler.
|
||||||
*/
|
*/
|
||||||
export interface IProps extends React.InputHTMLAttributes<Element> {
|
interface IProps extends React.InputHTMLAttributes<Element> {
|
||||||
inputRef?: React.Ref<Element>;
|
inputRef?: React.Ref<Element>;
|
||||||
element?: string;
|
element?: string;
|
||||||
// The kind of button, similar to how Bootstrap works.
|
// The kind of button, similar to how Bootstrap works.
|
||||||
|
@ -118,7 +118,7 @@ export default function AccessibleButton({
|
||||||
AccessibleButton.defaultProps = {
|
AccessibleButton.defaultProps = {
|
||||||
element: 'div',
|
element: 'div',
|
||||||
role: 'button',
|
role: 'button',
|
||||||
tabIndex: "0",
|
tabIndex: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
AccessibleButton.displayName = "AccessibleButton";
|
AccessibleButton.displayName = "AccessibleButton";
|
||||||
|
|
|
@ -19,10 +19,9 @@ import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import {IProps} from "./AccessibleButton";
|
|
||||||
import Tooltip from './Tooltip';
|
import Tooltip from './Tooltip';
|
||||||
|
|
||||||
interface ITooltipProps extends IProps {
|
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||||
title: string;
|
title: string;
|
||||||
tooltip?: React.ReactNode;
|
tooltip?: React.ReactNode;
|
||||||
tooltipClassName?: string;
|
tooltipClassName?: string;
|
||||||
|
|
|
@ -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,10 +17,10 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
||||||
|
|
||||||
export default class ManageIntegsButton extends React.Component {
|
export default class ManageIntegsButton extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -45,9 +45,8 @@ export default class ManageIntegsButton extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
let integrationsButton = <div />;
|
let integrationsButton = <div />;
|
||||||
if (IntegrationManagers.sharedInstance().hasManager()) {
|
if (IntegrationManagers.sharedInstance().hasManager()) {
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
|
||||||
integrationsButton = (
|
integrationsButton = (
|
||||||
<AccessibleButton
|
<AccessibleTooltipButton
|
||||||
className='mx_RoomHeader_button mx_RoomHeader_manageIntegsButton'
|
className='mx_RoomHeader_button mx_RoomHeader_manageIntegsButton'
|
||||||
title={_t("Manage Integrations")}
|
title={_t("Manage Integrations")}
|
||||||
onClick={this.onManageIntegrations}
|
onClick={this.onManageIntegrations}
|
||||||
|
|
|
@ -18,12 +18,9 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, {Component, CSSProperties} from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { ViewTooltipPayload } from '../../../dispatcher/payloads/ViewTooltipPayload';
|
|
||||||
import { Action } from '../../../dispatcher/actions';
|
|
||||||
|
|
||||||
const MIN_TOOLTIP_HEIGHT = 25;
|
const MIN_TOOLTIP_HEIGHT = 25;
|
||||||
|
|
||||||
|
@ -68,18 +65,12 @@ export default class Tooltip extends React.Component<IProps> {
|
||||||
|
|
||||||
// Remove the wrapper element, as the tooltip has finished using it
|
// Remove the wrapper element, as the tooltip has finished using it
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
dis.dispatch<ViewTooltipPayload>({
|
|
||||||
action: Action.ViewTooltip,
|
|
||||||
tooltip: null,
|
|
||||||
parent: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
ReactDOM.unmountComponentAtNode(this.tooltipContainer);
|
ReactDOM.unmountComponentAtNode(this.tooltipContainer);
|
||||||
document.body.removeChild(this.tooltipContainer);
|
document.body.removeChild(this.tooltipContainer);
|
||||||
window.removeEventListener('scroll', this.renderTooltip, true);
|
window.removeEventListener('scroll', this.renderTooltip, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updatePosition(style: {[key: string]: any}) {
|
private updatePosition(style: CSSProperties) {
|
||||||
const parentBox = this.parent.getBoundingClientRect();
|
const parentBox = this.parent.getBoundingClientRect();
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
if (parentBox.height > MIN_TOOLTIP_HEIGHT) {
|
if (parentBox.height > MIN_TOOLTIP_HEIGHT) {
|
||||||
|
@ -89,8 +80,14 @@ export default class Tooltip extends React.Component<IProps> {
|
||||||
// we need so that we're still centered.
|
// we need so that we're still centered.
|
||||||
offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT);
|
offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
style.top = (parentBox.top - 2) + window.pageYOffset + offset;
|
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;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +96,6 @@ export default class Tooltip extends React.Component<IProps> {
|
||||||
// positioned, also taking into account any window zoom
|
// positioned, also taking into account any window zoom
|
||||||
// NOTE: The additional 6 pixels for the left position, is to take account of the
|
// NOTE: The additional 6 pixels for the left position, is to take account of the
|
||||||
// tooltips chevron
|
// tooltips chevron
|
||||||
const parent = ReactDOM.findDOMNode(this).parentNode as Element;
|
|
||||||
const style = this.updatePosition({});
|
const style = this.updatePosition({});
|
||||||
// Hide the entire container when not visible. This prevents flashing of the tooltip
|
// Hide the entire container when not visible. This prevents flashing of the tooltip
|
||||||
// if it is not meant to be visible on first mount.
|
// if it is not meant to be visible on first mount.
|
||||||
|
@ -119,13 +115,6 @@ export default class Tooltip extends React.Component<IProps> {
|
||||||
|
|
||||||
// Render the tooltip manually, as we wish it not to be rendered within the parent
|
// Render the tooltip manually, as we wish it not to be rendered within the parent
|
||||||
this.tooltip = ReactDOM.render<Element>(tooltip, this.tooltipContainer);
|
this.tooltip = ReactDOM.render<Element>(tooltip, this.tooltipContainer);
|
||||||
|
|
||||||
// Tell the roomlist about us so it can manipulate us if it wishes
|
|
||||||
dis.dispatch<ViewTooltipPayload>({
|
|
||||||
action: Action.ViewTooltip,
|
|
||||||
tooltip: this.tooltip,
|
|
||||||
parent: parent,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
|
|
@ -22,11 +22,11 @@ import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
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 { isContentActionable, canEditContent } from '../../../utils/EventUtils';
|
||||||
import RoomContext from "../../../contexts/RoomContext";
|
import RoomContext from "../../../contexts/RoomContext";
|
||||||
import Toolbar from "../../../accessibility/Toolbar";
|
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 OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
|
||||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||||
|
@ -55,9 +55,9 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo
|
||||||
}
|
}
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<ContextMenuButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
||||||
label={_t("Options")}
|
title={_t("Options")}
|
||||||
onClick={openMenu}
|
onClick={openMenu}
|
||||||
isExpanded={menuDisplayed}
|
isExpanded={menuDisplayed}
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
|
@ -86,9 +86,9 @@ const ReactButton = ({mxEvent, reactions, onFocusChange}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<ContextMenuButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_reactButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_reactButton"
|
||||||
label={_t("React")}
|
title={_t("React")}
|
||||||
onClick={openMenu}
|
onClick={openMenu}
|
||||||
isExpanded={menuDisplayed}
|
isExpanded={menuDisplayed}
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
|
@ -167,7 +167,7 @@ export default class MessageActionBar extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.context.canReply) {
|
if (this.context.canReply) {
|
||||||
replyButton = <RovingAccessibleButton
|
replyButton = <RovingAccessibleTooltipButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
|
||||||
title={_t("Reply")}
|
title={_t("Reply")}
|
||||||
onClick={this.onReplyClick}
|
onClick={this.onReplyClick}
|
||||||
|
@ -175,7 +175,7 @@ export default class MessageActionBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (canEditContent(this.props.mxEvent)) {
|
if (canEditContent(this.props.mxEvent)) {
|
||||||
editButton = <RovingAccessibleButton
|
editButton = <RovingAccessibleTooltipButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_editButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_editButton"
|
||||||
title={_t("Edit")}
|
title={_t("Edit")}
|
||||||
onClick={this.onEditClick}
|
onClick={this.onEditClick}
|
||||||
|
|
|
@ -73,11 +73,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent {
|
||||||
|
|
||||||
let tooltip;
|
let tooltip;
|
||||||
if (tooltipLabel) {
|
if (tooltipLabel) {
|
||||||
tooltip = <Tooltip
|
tooltip = <Tooltip visible={visible} label={tooltipLabel} />;
|
||||||
tooltipClassName="mx_Tooltip_timeline"
|
|
||||||
visible={visible}
|
|
||||||
label={tooltipLabel}
|
|
||||||
/>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tooltip;
|
return tooltip;
|
||||||
|
|
|
@ -391,7 +391,6 @@ export default createReactClass({
|
||||||
onClick={this._openHistoryDialog}
|
onClick={this._openHistoryDialog}
|
||||||
title={_t("Edited at %(date)s. Click to view edits.", {date: dateString})}
|
title={_t("Edited at %(date)s. Click to view edits.", {date: dateString})}
|
||||||
tooltip={tooltip}
|
tooltip={tooltip}
|
||||||
tooltipClassName="mx_Tooltip_timeline"
|
|
||||||
>
|
>
|
||||||
<span>{`(${_t("edited")})`}</span>
|
<span>{`(${_t("edited")})`}</span>
|
||||||
</AccessibleTooltipButton>
|
</AccessibleTooltipButton>
|
||||||
|
|
|
@ -22,7 +22,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Analytics from '../../../Analytics';
|
import Analytics from '../../../Analytics';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
|
|
||||||
export default class HeaderButton extends React.Component {
|
export default class HeaderButton extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -42,13 +42,13 @@ export default class HeaderButton extends React.Component {
|
||||||
[`mx_RightPanel_${this.props.name}`]: true,
|
[`mx_RightPanel_${this.props.name}`]: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return <AccessibleButton
|
return <AccessibleTooltipButton
|
||||||
aria-selected={this.props.isHighlighted}
|
aria-selected={this.props.isHighlighted}
|
||||||
role="tab"
|
role="tab"
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
className={classes}
|
className={classes}
|
||||||
onClick={this.onClick}>
|
onClick={this.onClick}
|
||||||
</AccessibleButton>;
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,8 @@ import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
|
||||||
import ContentMessages from '../../../ContentMessages';
|
import ContentMessages from '../../../ContentMessages';
|
||||||
import E2EIcon from './E2EIcon';
|
import E2EIcon from './E2EIcon';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
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) {
|
function ComposerAvatar(props) {
|
||||||
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
|
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
|
||||||
|
@ -41,7 +42,6 @@ ComposerAvatar.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function CallButton(props) {
|
function CallButton(props) {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
const onVoiceCallClick = (ev) => {
|
const onVoiceCallClick = (ev) => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'place_call',
|
action: 'place_call',
|
||||||
|
@ -50,10 +50,11 @@ function CallButton(props) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_voicecall"
|
return (<AccessibleTooltipButton
|
||||||
onClick={onVoiceCallClick}
|
className="mx_MessageComposer_button mx_MessageComposer_voicecall"
|
||||||
title={_t('Voice call')}
|
onClick={onVoiceCallClick}
|
||||||
/>);
|
title={_t('Voice call')}
|
||||||
|
/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
CallButton.propTypes = {
|
CallButton.propTypes = {
|
||||||
|
@ -61,7 +62,6 @@ CallButton.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function VideoCallButton(props) {
|
function VideoCallButton(props) {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
const onCallClick = (ev) => {
|
const onCallClick = (ev) => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'place_call',
|
action: 'place_call',
|
||||||
|
@ -70,7 +70,8 @@ function VideoCallButton(props) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return <AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_videocall"
|
return <AccessibleTooltipButton
|
||||||
|
className="mx_MessageComposer_button mx_MessageComposer_videocall"
|
||||||
onClick={onCallClick}
|
onClick={onCallClick}
|
||||||
title={_t('Video call')}
|
title={_t('Video call')}
|
||||||
/>;
|
/>;
|
||||||
|
@ -117,14 +118,15 @@ const EmojiButton = ({addEmoji}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<ContextMenuButton className="mx_MessageComposer_button mx_MessageComposer_emoji"
|
<ContextMenuTooltipButton
|
||||||
onClick={openMenu}
|
className="mx_MessageComposer_button mx_MessageComposer_emoji"
|
||||||
isExpanded={menuDisplayed}
|
onClick={openMenu}
|
||||||
label={_t('Emoji picker')}
|
isExpanded={menuDisplayed}
|
||||||
inputRef={button}
|
title={_t('Emoji picker')}
|
||||||
|
inputRef={button}
|
||||||
>
|
>
|
||||||
|
|
||||||
</ContextMenuButton>
|
</ContextMenuTooltipButton>
|
||||||
|
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
@ -185,9 +187,9 @@ class UploadButton extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const uploadInputStyle = {display: 'none'};
|
const uploadInputStyle = {display: 'none'};
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
return (
|
return (
|
||||||
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_upload"
|
<AccessibleTooltipButton
|
||||||
|
className="mx_MessageComposer_button mx_MessageComposer_upload"
|
||||||
onClick={this.onUploadClick}
|
onClick={this.onUploadClick}
|
||||||
title={_t('Upload file')}
|
title={_t('Upload file')}
|
||||||
>
|
>
|
||||||
|
@ -198,7 +200,7 @@ class UploadButton extends React.Component {
|
||||||
multiple
|
multiple
|
||||||
onChange={this.onUploadFileInputChange}
|
onChange={this.onUploadFileInputChange}
|
||||||
/>
|
/>
|
||||||
</AccessibleButton>
|
</AccessibleTooltipButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ export default class RoomBreadcrumbs2 extends React.PureComponent<IProps, IState
|
||||||
onClick={() => this.viewRoom(r, i)}
|
onClick={() => this.viewRoom(r, i)}
|
||||||
aria-label={_t("Room %(name)s", {name: r.name})}
|
aria-label={_t("Room %(name)s", {name: r.name})}
|
||||||
title={r.name}
|
title={r.name}
|
||||||
tooltipClassName={"mx_RoomBreadcrumbs2_Tooltip"}
|
tooltipClassName="mx_RoomBreadcrumbs2_Tooltip"
|
||||||
>
|
>
|
||||||
<DecoratedRoomAvatar
|
<DecoratedRoomAvatar
|
||||||
room={r}
|
room={r}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import Modal from "../../../Modal";
|
||||||
import RateLimitedFunc from '../../../ratelimitedfunc';
|
import RateLimitedFunc from '../../../ratelimitedfunc';
|
||||||
|
|
||||||
import { linkifyElement } from '../../../HtmlUtils';
|
import { linkifyElement } from '../../../HtmlUtils';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
|
||||||
import ManageIntegsButton from '../elements/ManageIntegsButton';
|
import ManageIntegsButton from '../elements/ManageIntegsButton';
|
||||||
import {CancelButton} from './SimpleRoomHeader';
|
import {CancelButton} from './SimpleRoomHeader';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
@ -34,6 +33,7 @@ import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
||||||
import E2EIcon from './E2EIcon';
|
import E2EIcon from './E2EIcon';
|
||||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||||
import {DefaultTagID} from "../../../stores/room-list/models";
|
import {DefaultTagID} from "../../../stores/room-list/models";
|
||||||
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'RoomHeader',
|
displayName: 'RoomHeader',
|
||||||
|
@ -220,11 +220,10 @@ export default createReactClass({
|
||||||
|
|
||||||
if (this.props.onSettingsClick) {
|
if (this.props.onSettingsClick) {
|
||||||
settingsButton =
|
settingsButton =
|
||||||
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_settingsButton"
|
<AccessibleTooltipButton
|
||||||
|
className="mx_RoomHeader_button mx_RoomHeader_settingsButton"
|
||||||
onClick={this.props.onSettingsClick}
|
onClick={this.props.onSettingsClick}
|
||||||
title={_t("Settings")}
|
title={_t("Settings")} />;
|
||||||
>
|
|
||||||
</AccessibleButton>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.onPinnedClick && SettingsStore.isFeatureEnabled('feature_pinning')) {
|
if (this.props.onPinnedClick && SettingsStore.isFeatureEnabled('feature_pinning')) {
|
||||||
|
@ -236,55 +235,45 @@ export default createReactClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
pinnedEventsButton =
|
pinnedEventsButton =
|
||||||
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_pinnedButton"
|
<AccessibleTooltipButton
|
||||||
onClick={this.props.onPinnedClick} title={_t("Pinned Messages")}>
|
className="mx_RoomHeader_button mx_RoomHeader_pinnedButton"
|
||||||
|
onClick={this.props.onPinnedClick}
|
||||||
|
title={_t("Pinned Messages")}
|
||||||
|
>
|
||||||
{ pinsIndicator }
|
{ pinsIndicator }
|
||||||
</AccessibleButton>;
|
</AccessibleTooltipButton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// var leave_button;
|
|
||||||
// if (this.props.onLeaveClick) {
|
|
||||||
// leave_button =
|
|
||||||
// <div className="mx_RoomHeader_button" onClick={this.props.onLeaveClick} title="Leave room">
|
|
||||||
// <TintableSvg src={require("../../../../res/img/leave.svg")} width="26" height="20"/>
|
|
||||||
// </div>;
|
|
||||||
// }
|
|
||||||
|
|
||||||
let forgetButton;
|
let forgetButton;
|
||||||
if (this.props.onForgetClick) {
|
if (this.props.onForgetClick) {
|
||||||
forgetButton =
|
forgetButton =
|
||||||
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_forgetButton"
|
<AccessibleTooltipButton
|
||||||
|
className="mx_RoomHeader_button mx_RoomHeader_forgetButton"
|
||||||
onClick={this.props.onForgetClick}
|
onClick={this.props.onForgetClick}
|
||||||
title={_t("Forget room")}
|
title={_t("Forget room")} />;
|
||||||
>
|
|
||||||
</AccessibleButton>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let searchButton;
|
let searchButton;
|
||||||
if (this.props.onSearchClick && this.props.inRoom) {
|
if (this.props.onSearchClick && this.props.inRoom) {
|
||||||
searchButton =
|
searchButton =
|
||||||
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_searchButton"
|
<AccessibleTooltipButton
|
||||||
|
className="mx_RoomHeader_button mx_RoomHeader_searchButton"
|
||||||
onClick={this.props.onSearchClick}
|
onClick={this.props.onSearchClick}
|
||||||
title={_t("Search")}
|
title={_t("Search")} />;
|
||||||
>
|
|
||||||
</AccessibleButton>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let shareRoomButton;
|
let shareRoomButton;
|
||||||
if (this.props.inRoom) {
|
if (this.props.inRoom) {
|
||||||
shareRoomButton =
|
shareRoomButton =
|
||||||
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_shareButton"
|
<AccessibleTooltipButton
|
||||||
|
className="mx_RoomHeader_button mx_RoomHeader_shareButton"
|
||||||
onClick={this.onShareRoomClick}
|
onClick={this.onShareRoomClick}
|
||||||
title={_t('Share room')}
|
title={_t('Share room')} />;
|
||||||
>
|
|
||||||
</AccessibleButton>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let manageIntegsButton;
|
let manageIntegsButton;
|
||||||
if (this.props.room && this.props.room.roomId && this.props.inRoom) {
|
if (this.props.room && this.props.room.roomId && this.props.inRoom) {
|
||||||
manageIntegsButton = <ManageIntegsButton
|
manageIntegsButton = <ManageIntegsButton room={this.props.room} />;
|
||||||
room={this.props.room}
|
|
||||||
/>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rightRow =
|
const rightRow =
|
||||||
|
|
|
@ -230,9 +230,6 @@ export default createReactClass({
|
||||||
|
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case 'view_tooltip':
|
|
||||||
this.tooltip = payload.tooltip;
|
|
||||||
break;
|
|
||||||
case 'call_state':
|
case 'call_state':
|
||||||
var call = CallHandler.getCall(payload.room_id);
|
var call = CallHandler.getCall(payload.room_id);
|
||||||
if (call && call.call_state === 'ringing') {
|
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) {
|
_repositionIncomingCallBox: function(e, firstTime) {
|
||||||
const incomingCallBox = document.getElementById("incomingCallBox");
|
const incomingCallBox = document.getElementById("incomingCallBox");
|
||||||
if (incomingCallBox && incomingCallBox.parentElement) {
|
if (incomingCallBox && incomingCallBox.parentElement) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { ListLayout } from "../../../stores/room-list/ListLayout";
|
||||||
import {
|
import {
|
||||||
ChevronFace,
|
ChevronFace,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuButton,
|
ContextMenuTooltipButton,
|
||||||
StyledMenuItemCheckbox,
|
StyledMenuItemCheckbox,
|
||||||
StyledMenuItemRadio,
|
StyledMenuItemRadio,
|
||||||
} from "../../structures/ContextMenu";
|
} from "../../structures/ContextMenu";
|
||||||
|
@ -499,10 +499,10 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ContextMenuButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_RoomSublist2_menuButton"
|
className="mx_RoomSublist2_menuButton"
|
||||||
onClick={this.onOpenMenuClick}
|
onClick={this.onOpenMenuClick}
|
||||||
label={_t("List options")}
|
title={_t("List options")}
|
||||||
isExpanded={!!this.state.contextMenuPosition}
|
isExpanded={!!this.state.contextMenuPosition}
|
||||||
/>
|
/>
|
||||||
{contextMenu}
|
{contextMenu}
|
||||||
|
@ -561,6 +561,11 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let Button: React.ComponentType<React.ComponentProps<typeof AccessibleButton>> = AccessibleButton;
|
||||||
|
if (this.props.isMinimized) {
|
||||||
|
Button = AccessibleTooltipButton;
|
||||||
|
}
|
||||||
|
|
||||||
// Note: the addRoomButton conditionally gets moved around
|
// Note: the addRoomButton conditionally gets moved around
|
||||||
// the DOM depending on whether or not the list is minimized.
|
// the DOM depending on whether or not the list is minimized.
|
||||||
// If we're minimized, we want it below the header so it
|
// If we're minimized, we want it below the header so it
|
||||||
|
@ -569,7 +574,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
return (
|
return (
|
||||||
<div className={classes} onKeyDown={this.onHeaderKeyDown} onFocus={onFocus} aria-label={this.props.label}>
|
<div className={classes} onKeyDown={this.onHeaderKeyDown} onFocus={onFocus} aria-label={this.props.label}>
|
||||||
<div className="mx_RoomSublist2_stickable">
|
<div className="mx_RoomSublist2_stickable">
|
||||||
<AccessibleButton
|
<Button
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
|
@ -579,10 +584,11 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
aria-level={1}
|
aria-level={1}
|
||||||
onClick={this.onHeaderClick}
|
onClick={this.onHeaderClick}
|
||||||
onContextMenu={this.onContextMenu}
|
onContextMenu={this.onContextMenu}
|
||||||
|
title={this.props.isMinimized ? this.props.label : undefined}
|
||||||
>
|
>
|
||||||
<span className={collapseClasses} />
|
<span className={collapseClasses} />
|
||||||
<span>{this.props.label}</span>
|
<span>{this.props.label}</span>
|
||||||
</AccessibleButton>
|
</Button>
|
||||||
{this.renderMenu()}
|
{this.renderMenu()}
|
||||||
{this.props.isMinimized ? null : badgeContainer}
|
{this.props.isMinimized ? null : badgeContainer}
|
||||||
{this.props.isMinimized ? null : addRoomButton}
|
{this.props.isMinimized ? null : addRoomButton}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import { _t } from "../../../languageHandler";
|
||||||
import {
|
import {
|
||||||
ChevronFace,
|
ChevronFace,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuButton,
|
ContextMenuTooltipButton,
|
||||||
MenuItemRadio,
|
MenuItemRadio,
|
||||||
MenuItemCheckbox,
|
MenuItemCheckbox,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
@ -54,6 +54,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
import {ActionPayload} from "../../../dispatcher/payloads";
|
import {ActionPayload} from "../../../dispatcher/payloads";
|
||||||
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
||||||
import { NotificationState } from "../../../stores/notifications/NotificationState";
|
import { NotificationState } from "../../../stores/notifications/NotificationState";
|
||||||
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
|
|
||||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
||||||
|
|
||||||
|
@ -373,10 +374,10 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ContextMenuButton
|
<ContextMenuTooltipButton
|
||||||
className={classes}
|
className={classes}
|
||||||
onClick={this.onNotificationsMenuOpenClick}
|
onClick={this.onNotificationsMenuOpenClick}
|
||||||
label={_t("Notification options")}
|
title={_t("Notification options")}
|
||||||
isExpanded={!!this.state.notificationsMenuPosition}
|
isExpanded={!!this.state.notificationsMenuPosition}
|
||||||
tabIndex={isActive ? 0 : -1}
|
tabIndex={isActive ? 0 : -1}
|
||||||
/>
|
/>
|
||||||
|
@ -441,10 +442,10 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ContextMenuButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_RoomTile2_menuButton"
|
className="mx_RoomTile2_menuButton"
|
||||||
onClick={this.onGeneralMenuOpenClick}
|
onClick={this.onGeneralMenuOpenClick}
|
||||||
label={_t("Room options")}
|
title={_t("Room options")}
|
||||||
isExpanded={!!this.state.generalMenuPosition}
|
isExpanded={!!this.state.generalMenuPosition}
|
||||||
/>
|
/>
|
||||||
{contextMenu}
|
{contextMenu}
|
||||||
|
@ -537,11 +538,16 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
ariaDescribedBy = messagePreviewId(this.props.room.roomId);
|
ariaDescribedBy = messagePreviewId(this.props.room.roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Button: React.ComponentType<React.ComponentProps<typeof AccessibleButton>> = AccessibleButton;
|
||||||
|
if (this.props.isMinimized) {
|
||||||
|
Button = AccessibleTooltipButton;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<RovingTabIndexWrapper inputRef={this.roomTileRef}>
|
<RovingTabIndexWrapper inputRef={this.roomTileRef}>
|
||||||
{({onFocus, isActive, ref}) =>
|
{({onFocus, isActive, ref}) =>
|
||||||
<AccessibleButton
|
<Button
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
tabIndex={isActive ? 0 : -1}
|
tabIndex={isActive ? 0 : -1}
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
|
@ -554,13 +560,14 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
aria-selected={this.state.selected}
|
aria-selected={this.state.selected}
|
||||||
aria-describedby={ariaDescribedBy}
|
aria-describedby={ariaDescribedBy}
|
||||||
|
title={this.props.isMinimized ? name : undefined}
|
||||||
>
|
>
|
||||||
{roomAvatar}
|
{roomAvatar}
|
||||||
{nameContainer}
|
{nameContainer}
|
||||||
{badge}
|
{badge}
|
||||||
{this.renderGeneralMenu()}
|
{this.renderGeneralMenu()}
|
||||||
{this.renderNotificationsMenu(isActive)}
|
{this.renderNotificationsMenu(isActive)}
|
||||||
</AccessibleButton>
|
</Button>
|
||||||
}
|
}
|
||||||
</RovingTabIndexWrapper>
|
</RovingTabIndexWrapper>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
@ -162,7 +162,6 @@ export default class RoomTileIcon extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
return <TextWithTooltip
|
return <TextWithTooltip
|
||||||
tooltip={tooltipText(this.state.icon)}
|
tooltip={tooltipText(this.state.icon)}
|
||||||
tooltipClass="mx_Tooltip_timeline"
|
|
||||||
class={`mx_RoomTileIcon mx_RoomTileIcon_${this.state.icon.toLowerCase()}`}
|
class={`mx_RoomTileIcon mx_RoomTileIcon_${this.state.icon.toLowerCase()}`}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import {ContextMenu} from "../../structures/ContextMenu";
|
import {ContextMenu} from "../../structures/ContextMenu";
|
||||||
import {WidgetType} from "../../../widgets/WidgetType";
|
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).
|
// 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.
|
// 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 {
|
} else {
|
||||||
// Show show-stickers button
|
// Show show-stickers button
|
||||||
stickersButton =
|
stickersButton =
|
||||||
<AccessibleButton
|
<AccessibleTooltipButton
|
||||||
id='stickersButton'
|
id='stickersButton'
|
||||||
key="controls_show_stickers"
|
key="controls_show_stickers"
|
||||||
className="mx_MessageComposer_button mx_MessageComposer_stickers"
|
className="mx_MessageComposer_button mx_MessageComposer_stickers"
|
||||||
onClick={this._onShowStickersClick}
|
onClick={this._onShowStickersClick}
|
||||||
title={_t("Show Stickers")}
|
title={_t("Show Stickers")}
|
||||||
>
|
>
|
||||||
</AccessibleButton>;
|
</AccessibleTooltipButton>;
|
||||||
}
|
}
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
{ stickersButton }
|
{ stickersButton }
|
||||||
|
|
|
@ -16,7 +16,11 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
import {
|
||||||
|
RovingAccessibleButton,
|
||||||
|
RovingAccessibleTooltipButton,
|
||||||
|
RovingTabIndexWrapper
|
||||||
|
} from "../../../accessibility/RovingTabIndex";
|
||||||
import AccessibleButton from "../../views/elements/AccessibleButton";
|
import AccessibleButton from "../../views/elements/AccessibleButton";
|
||||||
import NotificationBadge from "./NotificationBadge";
|
import NotificationBadge from "./NotificationBadge";
|
||||||
import { NotificationState } from "../../../stores/notifications/NotificationState";
|
import { NotificationState } from "../../../stores/notifications/NotificationState";
|
||||||
|
@ -86,30 +90,29 @@ export default class TemporaryTile extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
if (this.props.isMinimized) nameContainer = null;
|
if (this.props.isMinimized) nameContainer = null;
|
||||||
|
|
||||||
|
let Button = RovingAccessibleButton;
|
||||||
|
if (this.props.isMinimized) {
|
||||||
|
Button = RovingAccessibleTooltipButton;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<RovingTabIndexWrapper>
|
<Button
|
||||||
{({onFocus, isActive, ref}) =>
|
className={classes}
|
||||||
<AccessibleButton
|
onMouseEnter={this.onTileMouseEnter}
|
||||||
onFocus={onFocus}
|
onMouseLeave={this.onTileMouseLeave}
|
||||||
tabIndex={isActive ? 0 : -1}
|
onClick={this.props.onClick}
|
||||||
inputRef={ref}
|
role="treeitem"
|
||||||
className={classes}
|
title={this.props.isMinimized ? name : undefined}
|
||||||
onMouseEnter={this.onTileMouseEnter}
|
>
|
||||||
onMouseLeave={this.onTileMouseLeave}
|
<div className="mx_RoomTile2_avatarContainer">
|
||||||
onClick={this.props.onClick}
|
{this.props.avatar}
|
||||||
role="treeitem"
|
</div>
|
||||||
>
|
{nameContainer}
|
||||||
<div className="mx_RoomTile2_avatarContainer">
|
<div className="mx_RoomTile2_badgeContainer">
|
||||||
{this.props.avatar}
|
{badge}
|
||||||
</div>
|
</div>
|
||||||
{nameContainer}
|
</Button>
|
||||||
<div className="mx_RoomTile2_badgeContainer">
|
|
||||||
{badge}
|
|
||||||
</div>
|
|
||||||
</AccessibleButton>
|
|
||||||
}
|
|
||||||
</RovingTabIndexWrapper>
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,11 +45,6 @@ export enum Action {
|
||||||
*/
|
*/
|
||||||
ViewRoomDirectory = "view_room_directory",
|
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.
|
* Forces the theme to reload. No additional payload information required.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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<Element, any, any>;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The parent under which to render the tooltip. Can be null to remove
|
|
||||||
* the parent type.
|
|
||||||
*/
|
|
||||||
parent: null | Element;
|
|
||||||
}
|
|
|
@ -45,7 +45,7 @@ async function findTabs(session) {
|
||||||
/// XXX delay is needed here, possibly because the header is being rerendered
|
/// XXX delay is needed here, possibly because the header is being rerendered
|
||||||
/// click doesn't do anything otherwise
|
/// click doesn't do anything otherwise
|
||||||
await session.delay(1000);
|
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();
|
await settingsButton.click();
|
||||||
|
|
||||||
//find tabs
|
//find tabs
|
||||||
|
|
Loading…
Reference in a new issue