Fix tooltip bugs

This commit is contained in:
Josh Perez 2020-11-19 13:11:35 -05:00 committed by Josh Perez
parent 6398a01852
commit c6eafbb8d5
16 changed files with 339 additions and 235 deletions

View file

@ -2223,30 +2223,6 @@ Signal Desktop makes use of the following open source projects.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## react-tooltip-lite
MIT License
Copyright (c) 2019 Benny Sidelinger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## react-virtualized
The MIT License (MIT)

View file

@ -126,7 +126,6 @@
"react-redux": "7.1.0",
"react-router-dom": "5.0.1",
"react-sortable-hoc": "1.9.1",
"react-tooltip-lite": "1.12.0",
"react-virtualized": "9.21.0",
"read-last-lines": "1.3.0",
"redux": "4.0.1",

View file

@ -1,60 +0,0 @@
diff --git a/node_modules/react-tooltip-lite/dist/index.js b/node_modules/react-tooltip-lite/dist/index.js
index 32ce07d..6461913 100644
--- a/node_modules/react-tooltip-lite/dist/index.js
+++ b/node_modules/react-tooltip-lite/dist/index.js
@@ -80,7 +80,7 @@ function (_React$Component) {
_this.state = {
showTip: false,
- hasHover: false,
+ hasHover: 0,
ignoreShow: false,
hasBeenShown: false
};
@@ -232,7 +232,7 @@ function (_React$Component) {
var _this3 = this;
this.setState({
- hasHover: false
+ hasHover: 0
});
if (this.state.showTip) {
@@ -250,7 +250,7 @@ function (_React$Component) {
value: function startHover() {
if (!this.state.ignoreShow) {
this.setState({
- hasHover: true
+ hasHover: (this.state.hasHover || 0) + 1,
});
clearTimeout(this.hoverTimeout);
this.hoverTimeout = setTimeout(this.checkHover, this.props.hoverDelay);
@@ -260,7 +260,7 @@ function (_React$Component) {
key: "endHover",
value: function endHover() {
this.setState({
- hasHover: false
+ hasHover: Math.max((this.state.hasHover || 0) - 1, 0),
});
clearTimeout(this.hoverTimeout);
this.hoverTimeout = setTimeout(this.checkHover, this.props.mouseOutDelay || this.props.hoverDelay);
@@ -268,7 +268,7 @@ function (_React$Component) {
}, {
key: "checkHover",
value: function checkHover() {
- this.state.hasHover ? this.showTip() : this.hideTip();
+ this.state.hasHover > 0 ? this.showTip() : this.hideTip();
}
}, {
key: "render",
@@ -330,7 +330,9 @@ function (_React$Component) {
props[eventToggle] = this.toggleTip; // only use hover if they don't have a toggle event
} else if (useHover && !isControlledByProps) {
props.onMouseEnter = this.startHover;
- props.onMouseLeave = tipContentHover || mouseOutDelay ? this.endHover : this.hideTip;
+ props.onMouseLeave = this.endHover;
+ props.onFocus = this.startHover;
+ props.onBlur = this.endHover;
props.onTouchStart = this.targetTouchStart;
props.onTouchEnd = this.targetTouchEnd;

View file

@ -2782,24 +2782,34 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
}
}
.module-in-contacts-icon__tooltip {
.react-tooltip-lite {
span.module-in-contacts-icon__tooltip {
/* Written in this way to add more specificity and avoid !important */
.module-tooltip {
color: $color-white;
background-color: $ultramarine-ui-light;
}
.react-tooltip-lite-arrow {
border-color: $ultramarine-ui-light;
}
@include dark-theme {
.react-tooltip-lite {
color: $color-white;
background-color: $ultramarine-ui-light;
&[data-placement='top'] {
.module-tooltip-arrow::after {
border-top-color: $ultramarine-ui-light;
}
}
.react-tooltip-lite-arrow {
border-color: $ultramarine-ui-light;
&[data-placement='right'] {
.module-tooltip-arrow::after {
border-right-color: $ultramarine-ui-light;
}
}
&[data-placement='bottom'] {
.module-tooltip-arrow::after {
border-bottom-color: $ultramarine-ui-light;
}
}
&[data-placement='left'] {
.module-tooltip-arrow::after {
border-left-color: $ultramarine-ui-light;
}
}
}
}
@ -6522,8 +6532,8 @@ button.module-image__border-overlay:focus {
margin-top: 54px;
overflow: scroll;
padding: 14px;
padding-bottom: 0;
width: 280px;
padding-bottom: 0;
&__overlay {
display: flex;
@ -10183,10 +10193,13 @@ $contact-modal-padding: 18px;
}
}
/* Third-party module: react-tooltip-lite */
.react-tooltip-lite {
.module-tooltip {
border-radius: 8px;
display: inline-block;
padding: 8px 21px;
position: fixed;
text-align: center;
z-index: 999;
@include light-theme {
background-color: $color-gray-02;
@ -10196,14 +10209,96 @@ $contact-modal-padding: 18px;
background-color: $color-gray-65;
color: $color-gray-05;
}
}
.react-tooltip-lite-arrow {
@include light-theme {
border-color: $color-gray-02;
.module-tooltip-arrow {
position: absolute;
}
@include dark-theme {
border-color: $color-gray-65;
.module-tooltip-arrow::after {
border: solid 6px transparent;
content: '';
display: block;
height: 0;
margin-left: -6px;
margin-top: -6px;
position: absolute;
width: 0;
}
&[data-placement='top'] {
margin-bottom: 12px;
.module-tooltip-arrow {
bottom: 0;
}
.module-tooltip-arrow::after {
bottom: -12px;
@include light-theme {
border-top-color: $color-gray-02;
}
@include dark-theme {
border-top-color: $color-gray-65;
}
}
}
&[data-placement='right'] {
margin-left: 12px;
.module-tooltip-arrow {
left: 0;
}
.module-tooltip-arrow::after {
left: -6px;
@include light-theme {
border-right-color: $color-gray-02;
}
@include dark-theme {
border-right-color: $color-gray-65;
}
}
}
&[data-placement='bottom'] {
margin-top: 12px;
.module-tooltip-arrow {
top: 0;
}
.module-tooltip-arrow::after {
top: -6px;
@include light-theme {
border-bottom-color: $color-gray-02;
}
@include dark-theme {
border-bottom-color: $color-gray-65;
}
}
}
&[data-placement='left'] {
margin-right: 12px;
.module-tooltip-arrow {
right: 0;
}
.module-tooltip-arrow::after {
right: -12px;
@include light-theme {
border-left-color: $color-gray-02;
}
@include dark-theme {
border-left-color: $color-gray-65;
}
}
}
}

View file

@ -253,13 +253,11 @@ export const CallScreen: React.FC<PropsType> = ({
buttonType={videoButtonType}
i18n={i18n}
onClick={toggleVideo}
tooltipDistance={24}
/>
<CallingButton
buttonType={audioButtonType}
i18n={i18n}
onClick={toggleAudio}
tooltipDistance={24}
/>
<CallingButton
buttonType={CallingButtonType.HANG_UP}
@ -267,7 +265,6 @@ export const CallScreen: React.FC<PropsType> = ({
onClick={() => {
hangUp({ conversationId: conversation.id });
}}
tooltipDistance={24}
/>
</div>
<div

View file

@ -3,15 +3,11 @@
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { number, select } from '@storybook/addon-knobs';
import { select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import {
CallingButton,
CallingButtonType,
PropsType,
TooltipDirection,
} from './CallingButton';
import { CallingButton, CallingButtonType, PropsType } from './CallingButton';
import { TooltipPlacement } from './Tooltip';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
@ -27,12 +23,8 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
onClick: action('on-click'),
tooltipDirection: select(
'tooltipDirection',
TooltipDirection,
overrideProps.tooltipDirection || TooltipDirection.DOWN
),
tooltipDistance: number(
'tooltipDistance',
overrideProps.tooltipDistance || 16
TooltipPlacement,
overrideProps.tooltipDirection || TooltipPlacement.Bottom
),
});
@ -87,7 +79,7 @@ story.add('Video Disabled', () => {
story.add('Tooltip right', () => {
const props = createProps({
tooltipDirection: TooltipDirection.RIGHT,
tooltipDirection: TooltipPlacement.Right,
});
return <CallingButton {...props} />;
});

View file

@ -3,16 +3,9 @@
import React from 'react';
import classNames from 'classnames';
import Tooltip from 'react-tooltip-lite';
import { Tooltip, TooltipPlacement } from './Tooltip';
import { LocalizerType } from '../types/Util';
export enum TooltipDirection {
UP = 'up',
RIGHT = 'right',
DOWN = 'down',
LEFT = 'left',
}
export enum CallingButtonType {
AUDIO_DISABLED = 'AUDIO_DISABLED',
AUDIO_OFF = 'AUDIO_OFF',
@ -27,16 +20,14 @@ export type PropsType = {
buttonType: CallingButtonType;
i18n: LocalizerType;
onClick: () => void;
tooltipDirection?: TooltipDirection;
tooltipDistance?: number;
tooltipDirection?: TooltipPlacement;
};
export const CallingButton = ({
buttonType,
i18n,
onClick,
tooltipDirection = TooltipDirection.DOWN,
tooltipDistance = 16,
tooltipDirection,
}: PropsType): JSX.Element => {
let classNameSuffix = '';
let tooltipContent = '';
@ -69,21 +60,15 @@ export const CallingButton = ({
);
return (
<button
aria-label={tooltipContent}
type="button"
className={className}
onClick={onClick}
>
<Tooltip
arrowSize={6}
content={tooltipContent}
direction={tooltipDirection}
distance={tooltipDistance}
hoverDelay={0}
<Tooltip content={tooltipContent} direction={tooltipDirection}>
<button
aria-label={tooltipContent}
type="button"
className={className}
onClick={onClick}
>
<div />
</Tooltip>
</button>
</button>
</Tooltip>
);
};

View file

@ -2,8 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import Tooltip from 'react-tooltip-lite';
import { LocalizerType } from '../types/Util';
import { Tooltip } from './Tooltip';
export type PropsType = {
canPip?: boolean;
@ -34,52 +34,39 @@ export const CallingHeader = ({
{isGroupCall ? (
<div className="module-calling-tools__button">
<Tooltip
arrowSize={6}
content={i18n('calling__participants', [
String(remoteParticipants),
])}
direction="down"
hoverDelay={0}
>
<button
type="button"
aria-label={i18n('calling__participants', [
String(remoteParticipants),
])}
className="module-calling-button__participants"
onClick={toggleParticipants}
type="button"
/>
</Tooltip>
</div>
) : null}
<div className="module-calling-tools__button">
<Tooltip
arrowSize={6}
content={i18n('callingDeviceSelection__settings')}
direction="down"
hoverDelay={0}
>
<Tooltip content={i18n('callingDeviceSelection__settings')}>
<button
type="button"
aria-label={i18n('callingDeviceSelection__settings')}
className="module-calling-button__settings"
onClick={toggleSettings}
type="button"
/>
</Tooltip>
</div>
{canPip && (
<div className="module-calling-tools__button">
<Tooltip
arrowSize={6}
content={i18n('calling__pip--on')}
direction="down"
hoverDelay={0}
>
<Tooltip content={i18n('calling__pip--on')}>
<button
type="button"
aria-label={i18n('calling__pip--on')}
className="module-calling-button__pip"
onClick={togglePip}
type="button"
/>
</Tooltip>
</div>

View file

@ -7,11 +7,8 @@ import {
SetLocalPreviewType,
SetLocalVideoType,
} from '../state/ducks/calling';
import {
CallingButton,
CallingButtonType,
TooltipDirection,
} from './CallingButton';
import { CallingButton, CallingButtonType } from './CallingButton';
import { TooltipPlacement } from './Tooltip';
import { CallBackgroundBlur } from './CallBackgroundBlur';
import { CallingHeader } from './CallingHeader';
import { Spinner } from './Spinner';
@ -145,15 +142,13 @@ export const CallingLobby = ({
buttonType={videoButtonType}
i18n={i18n}
onClick={toggleVideo}
tooltipDirection={TooltipDirection.UP}
tooltipDistance={24}
tooltipDirection={TooltipPlacement.Top}
/>
<CallingButton
buttonType={audioButtonType}
i18n={i18n}
onClick={toggleAudio}
tooltipDirection={TooltipDirection.UP}
tooltipDistance={24}
tooltipDirection={TooltipPlacement.Top}
/>
</div>
</div>

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import Tooltip from 'react-tooltip-lite';
import { Tooltip } from './Tooltip';
import { CallingPipRemoteVideo } from './CallingPipRemoteVideo';
import { LocalizerType } from '../types/Util';
import { ConversationType } from '../state/ducks/conversations';
@ -182,27 +182,23 @@ export const CallingPip = ({
) : null}
<div className="module-calling-pip__actions">
<button
type="button"
aria-label={i18n('calling__hangup')}
className="module-calling-pip__button--hangup"
onClick={() => {
hangUp({ conversationId: conversation.id });
}}
/>
<button
type="button"
aria-label={i18n('calling__pip--off')}
className="module-calling-pip__button--pip"
onClick={togglePip}
>
<Tooltip
arrowSize={6}
content={i18n('calling__pip--off')}
hoverDelay={0}
/>
<Tooltip content={i18n('calling__pip--off')}>
<button
aria-label={i18n('calling__pip--off')}
className="module-calling-pip__button--pip"
onClick={togglePip}
type="button"
>
<div />
</Tooltip>
</button>
</button>
</Tooltip>
</div>
</div>
);

View file

@ -2,8 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import Tooltip from 'react-tooltip-lite';
import { Tooltip } from './Tooltip';
import { LocalizerType } from '../types/Util';
type PropsType = {
@ -15,22 +15,16 @@ export const InContactsIcon = (props: PropsType): JSX.Element => {
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
return (
<Tooltip
tagName="span"
direction="bottom"
className="module-in-contacts-icon__tooltip"
arrowSize={8}
content={i18n('contactInAddressBook')}
distance={13}
hoverDelay={0}
>
<span
tabIndex={0}
role="img"
aria-label={i18n('contactInAddressBook')}
className="module-in-contacts-icon__icon"
/>
</Tooltip>
<span className="module-in-contacts-icon__tooltip">
<Tooltip content={i18n('contactInAddressBook')}>
<span
tabIndex={0}
role="img"
aria-label={i18n('contactInAddressBook')}
className="module-in-contacts-icon__icon"
/>
</Tooltip>
</span>
);
/* eslint-enable jsx-a11y/no-noninteractive-tabindex */
};

View file

@ -2,8 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import Tooltip from 'react-tooltip-lite';
import { Avatar } from './Avatar';
import { Tooltip } from './Tooltip';
import { ContactName } from './conversation/ContactName';
import { LocalizerType } from '../types/Util';
import { ColorType } from '../types/Colors';
@ -41,22 +41,16 @@ const CallButton = ({
tooltipContent,
}: CallButtonProps): JSX.Element => {
return (
<button
className={`module-incoming-call__button module-incoming-call__button--${classSuffix}`}
onClick={onClick}
tabIndex={tabIndex}
type="button"
>
<Tooltip
arrowSize={6}
content={tooltipContent}
direction="bottom"
distance={16}
hoverDelay={0}
<Tooltip content={tooltipContent}>
<button
className={`module-incoming-call__button module-incoming-call__button--${classSuffix}`}
onClick={onClick}
tabIndex={tabIndex}
type="button"
>
<div />
</Tooltip>
</button>
</button>
</Tooltip>
);
};

View file

@ -0,0 +1,80 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { select } from '@storybook/addon-knobs';
import { Tooltip, TooltipPlacement, PropsType } from './Tooltip';
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
content: overrideProps.content || 'Hello World',
direction: select('direction', TooltipPlacement, overrideProps.direction),
sticky: overrideProps.sticky,
});
const story = storiesOf('Components/Tooltip', module);
const Trigger = (
<span
style={{
display: 'inline-block',
marginTop: 200,
marginBottom: 200,
marginLeft: 200,
marginRight: 200,
}}
>
Trigger
</span>
);
story.add('Top', () => (
<Tooltip
{...createProps({
direction: TooltipPlacement.Top,
})}
>
{Trigger}
</Tooltip>
));
story.add('Right', () => (
<Tooltip
{...createProps({
direction: TooltipPlacement.Right,
})}
>
{Trigger}
</Tooltip>
));
story.add('Bottom', () => (
<Tooltip
{...createProps({
direction: TooltipPlacement.Bottom,
})}
>
{Trigger}
</Tooltip>
));
story.add('Left', () => (
<Tooltip
{...createProps({
direction: TooltipPlacement.Left,
})}
>
{Trigger}
</Tooltip>
));
story.add('Sticky', () => (
<Tooltip
{...createProps({
sticky: true,
})}
>
{Trigger}
</Tooltip>
));

81
ts/components/Tooltip.tsx Normal file
View file

@ -0,0 +1,81 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { Manager, Reference, Popper } from 'react-popper';
export enum TooltipPlacement {
Top = 'top',
Right = 'right',
Bottom = 'bottom',
Left = 'left',
}
export type PropsType = {
content: string | JSX.Element;
direction?: TooltipPlacement;
sticky?: boolean;
};
export const Tooltip: React.FC<PropsType> = ({
children,
content,
direction,
sticky,
}) => {
const isSticky = Boolean(sticky);
const [showTooltip, setShowTooltip] = React.useState(isSticky);
return (
<Manager>
<Reference>
{({ ref }) => (
<span
onBlur={() => {
if (!isSticky) {
setShowTooltip(false);
}
}}
onFocus={() => {
if (!isSticky) {
setShowTooltip(true);
}
}}
onMouseEnter={() => {
if (!isSticky) {
setShowTooltip(true);
}
}}
onMouseLeave={() => {
if (!isSticky) {
setShowTooltip(false);
}
}}
ref={ref}
>
{children}
</span>
)}
</Reference>
<Popper placement={direction}>
{({ arrowProps, placement, ref, style }) =>
showTooltip && (
<div
className="module-tooltip"
ref={ref}
style={style}
data-placement={placement}
>
{content}
<div
className="module-tooltip-arrow"
ref={arrowProps.ref}
style={arrowProps.style}
/>
</div>
)
}
</Popper>
</Manager>
);
};

View file

@ -14391,7 +14391,7 @@
"rule": "React-useRef",
"path": "ts/components/CallingLobby.js",
"line": " const localVideoRef = react_1.default.useRef(null);",
"lineNumber": 14,
"lineNumber": 15,
"reasonCategory": "usageTrusted",
"updated": "2020-10-26T19:12:24.410Z",
"reasonDetail": "Used to get the local video element for rendering."
@ -14400,7 +14400,7 @@
"rule": "React-useRef",
"path": "ts/components/CallingLobby.tsx",
"line": " const localVideoRef = React.useRef(null);",
"lineNumber": 61,
"lineNumber": 58,
"reasonCategory": "usageTrusted",
"updated": "2020-10-26T19:12:24.410Z",
"reasonDetail": "Used to get the local video element for rendering."
@ -15176,4 +15176,4 @@
"reasonCategory": "falseMatch",
"updated": "2020-09-08T23:07:22.682Z"
}
]
]

View file

@ -13508,13 +13508,6 @@ react-textarea-autosize@^7.1.0:
"@babel/runtime" "^7.1.2"
prop-types "^15.6.0"
react-tooltip-lite@1.12.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/react-tooltip-lite/-/react-tooltip-lite-1.12.0.tgz#f6cd1323cdd9f5f80dd0e71a30ef59f401dee9ba"
integrity sha512-QjDnmDmjtLNKvLY6bzUOG8W6ZDBTiE4UXugGzClOQEGvMvbkJn2GvZvLwRaxsN/GCx7589RgbGaESMiJAm+zWg==
dependencies:
prop-types "^15.5.8"
react-transition-group@^2.2.1:
version "2.9.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"