feat(tooltip) Create and move to our component (#13061)

Create Tooltip component
Fix Popover positioning calculations
Add margins to popover
Remove @atlaskit/tooltip
Update all components to use the new Tooltip component

Added tooltip actions and reducers for the following functionality: when a user hovers over an element is sees the tooltip for that element and then hovers another element that has a tooltip, instead of using the delay and animations we just unmount the current tooltip and mount the next one immediately
This commit is contained in:
Robert Pintilii
2023-03-17 12:23:51 +02:00
committed by GitHub
parent a89f762a66
commit 00780929e5
46 changed files with 353 additions and 540 deletions

View File

@@ -1,49 +1,27 @@
/**
* Mousemove padding styles are used to add invisible elements to the popover
* to allow mouse movement from the popover trigger to the popover itself
* without triggering a mouseleave event.
*/
%vertical-popover-padding {
height: 100%;
position: absolute;
top: 0;
width: 20px;
padding: 20px 0;
top: -20px;
}
%horizontal-popover-padding {
height: 25px;
position: absolute;
right: 0;
width: 100%;
padding: 0 35px;
left: -35px;
}
.popover-mousemove-padding-left {
@extend %vertical-popover-padding;
left: -35px;
}
.popover-mousemove-padding-right {
@extend %vertical-popover-padding;
right: -35px;
}
.popover-mousemove-padding-bottom {
@extend %horizontal-popover-padding;
bottom: -40px;
}
.popover-mousemove-padding-top {
@extend %horizontal-popover-padding;
top: -40px;
}
.popover { .popover {
margin: -16px -24px; margin: -16px -24px;
z-index: $popoverZ; z-index: $popoverZ;
.popover-content {
margin: 16px 24px;
position: relative;
&.top {
bottom: 8px;
}
&.bottom {
top: 8px;
}
&.left {
right: 8px;
}
&.right {
left: 8px;
}
}
} }
.excalidraw .popover { .excalidraw .popover {

View File

@@ -4,7 +4,7 @@
width: 280px; width: 280px;
background: $menuBG; background: $menuBG;
box-shadow: 0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25); box-shadow: 0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25);
border-radius: 3px; border-radius: 6px;
padding: 16px; padding: 16px;
&.with-gif { &.with-gif {
@@ -104,10 +104,6 @@
} }
} }
.reactions-menu-container {
padding-bottom: 6px;
}
.reactions-animations-container { .reactions-animations-container {
position: absolute; position: absolute;
width: 20%; width: 20%;

View File

@@ -34,20 +34,20 @@
background: #F2F3F4; background: #F2F3F4;
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.1); box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
& > svg { & svg {
fill: #040404; fill: #040404;
} }
&.settings-button-small-icon--disabled { &.settings-button-small-icon--disabled {
background: #36383C; background: #36383C;
&> svg { & svg {
fill: #929292; fill: #929292;
} }
} }
} }
& > svg { & svg {
fill: #fff; fill: #fff;
} }
@@ -55,7 +55,7 @@
background-color: #36383c; background-color: #36383c;
cursor: default; cursor: default;
&> svg { & svg {
fill: #929292; fill: #929292;
} }
} }

56
package-lock.json generated
View File

@@ -15,7 +15,6 @@
"@atlaskit/inline-message": "11.0.8", "@atlaskit/inline-message": "11.0.8",
"@atlaskit/multi-select": "15.0.5", "@atlaskit/multi-select": "15.0.5",
"@atlaskit/theme": "11.0.2", "@atlaskit/theme": "11.0.2",
"@atlaskit/tooltip": "17.1.2",
"@emotion/react": "11.10.0", "@emotion/react": "11.10.0",
"@emotion/styled": "11.10.0", "@emotion/styled": "11.10.0",
"@giphy/js-fetch-api": "4.7.1", "@giphy/js-fetch-api": "4.7.1",
@@ -599,18 +598,6 @@
"styled-components": "^3.2.6" "styled-components": "^3.2.6"
} }
}, },
"node_modules/@atlaskit/motion": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/@atlaskit/motion/-/motion-0.4.8.tgz",
"integrity": "sha512-+5hABjUNUgl66zNya+EmNXLrbJQ7EgXA+fNQih7qzu7MkIuzJjT5vEtpXrzckLKBu1v1HsbMQW90Wnd8MJugdw==",
"dependencies": {
"@babel/runtime": "^7.0.0",
"@emotion/core": "^10.0.9"
},
"peerDependencies": {
"react": "^16.8.0"
}
},
"node_modules/@atlaskit/multi-select": { "node_modules/@atlaskit/multi-select": {
"version": "15.0.5", "version": "15.0.5",
"resolved": "https://registry.npmjs.org/@atlaskit/multi-select/-/multi-select-15.0.5.tgz", "resolved": "https://registry.npmjs.org/@atlaskit/multi-select/-/multi-select-15.0.5.tgz",
@@ -789,25 +776,6 @@
"@babel/types": "^7.15.0" "@babel/types": "^7.15.0"
} }
}, },
"node_modules/@atlaskit/tooltip": {
"version": "17.1.2",
"resolved": "https://registry.npmjs.org/@atlaskit/tooltip/-/tooltip-17.1.2.tgz",
"integrity": "sha512-0N63l18ZuyL2pjNXwY3pwnNos3HjOsFhSmnj2TLASC+RLz/6/Nk1k9gXuydfMjU2ntsr7xrVY5U7ING0mxAzQQ==",
"dependencies": {
"@atlaskit/analytics-next": "^8.1.0",
"@atlaskit/motion": "^0.4.0",
"@atlaskit/popper": "^5.0.0",
"@atlaskit/portal": "^4.0.0",
"@atlaskit/theme": "^11.0.0",
"@babel/runtime": "^7.0.0",
"@emotion/core": "^10.0.9",
"bind-event-listener": "^1.0.2"
},
"peerDependencies": {
"react": "^16.8.0",
"react-dom": "^16.8.0"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.18.6", "version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@@ -20705,15 +20673,6 @@
"react-scrolllock": "^5.0.1" "react-scrolllock": "^5.0.1"
} }
}, },
"@atlaskit/motion": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/@atlaskit/motion/-/motion-0.4.8.tgz",
"integrity": "sha512-+5hABjUNUgl66zNya+EmNXLrbJQ7EgXA+fNQih7qzu7MkIuzJjT5vEtpXrzckLKBu1v1HsbMQW90Wnd8MJugdw==",
"requires": {
"@babel/runtime": "^7.0.0",
"@emotion/core": "^10.0.9"
}
},
"@atlaskit/multi-select": { "@atlaskit/multi-select": {
"version": "15.0.5", "version": "15.0.5",
"resolved": "https://registry.npmjs.org/@atlaskit/multi-select/-/multi-select-15.0.5.tgz", "resolved": "https://registry.npmjs.org/@atlaskit/multi-select/-/multi-select-15.0.5.tgz",
@@ -20857,21 +20816,6 @@
"@babel/types": "^7.15.0" "@babel/types": "^7.15.0"
} }
}, },
"@atlaskit/tooltip": {
"version": "17.1.2",
"resolved": "https://registry.npmjs.org/@atlaskit/tooltip/-/tooltip-17.1.2.tgz",
"integrity": "sha512-0N63l18ZuyL2pjNXwY3pwnNos3HjOsFhSmnj2TLASC+RLz/6/Nk1k9gXuydfMjU2ntsr7xrVY5U7ING0mxAzQQ==",
"requires": {
"@atlaskit/analytics-next": "^8.1.0",
"@atlaskit/motion": "^0.4.0",
"@atlaskit/popper": "^5.0.0",
"@atlaskit/portal": "^4.0.0",
"@atlaskit/theme": "^11.0.0",
"@babel/runtime": "^7.0.0",
"@emotion/core": "^10.0.9",
"bind-event-listener": "^1.0.2"
}
},
"@babel/code-frame": { "@babel/code-frame": {
"version": "7.18.6", "version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",

View File

@@ -20,7 +20,6 @@
"@atlaskit/inline-message": "11.0.8", "@atlaskit/inline-message": "11.0.8",
"@atlaskit/multi-select": "15.0.5", "@atlaskit/multi-select": "15.0.5",
"@atlaskit/theme": "11.0.2", "@atlaskit/theme": "11.0.2",
"@atlaskit/tooltip": "17.1.2",
"@emotion/react": "11.10.0", "@emotion/react": "11.10.0",
"@emotion/styled": "11.10.0", "@emotion/styled": "11.10.0",
"@giphy/js-fetch-api": "4.7.1", "@giphy/js-fetch-api": "4.7.1",

View File

@@ -1,6 +1,5 @@
// @flow
import '../base/devices/reducer'; import '../base/devices/reducer';
import '../base/tooltip/reducer';
import '../e2ee/reducer'; import '../e2ee/reducer';
import '../face-landmarks/reducer'; import '../face-landmarks/reducer';
import '../feedback/reducer'; import '../feedback/reducer';

View File

@@ -24,6 +24,7 @@ import { IResponsiveUIState } from '../base/responsive-ui/reducer';
import { ISettingsState } from '../base/settings/reducer'; import { ISettingsState } from '../base/settings/reducer';
import { ISoundsState } from '../base/sounds/reducer'; import { ISoundsState } from '../base/sounds/reducer';
import { ITestingState } from '../base/testing/reducer'; import { ITestingState } from '../base/testing/reducer';
import { ITooltipState } from '../base/tooltip/reducer';
import { INoSrcDataState, ITracksState } from '../base/tracks/reducer'; import { INoSrcDataState, ITracksState } from '../base/tracks/reducer';
import { IUserInteractionState } from '../base/user-interaction/reducer'; import { IUserInteractionState } from '../base/user-interaction/reducer';
import { IBreakoutRoomsState } from '../breakout-rooms/reducer'; import { IBreakoutRoomsState } from '../breakout-rooms/reducer';
@@ -111,6 +112,7 @@ export interface IReduxState {
'features/base/responsive-ui': IResponsiveUIState; 'features/base/responsive-ui': IResponsiveUIState;
'features/base/settings': ISettingsState; 'features/base/settings': ISettingsState;
'features/base/sounds': ISoundsState; 'features/base/sounds': ISoundsState;
'features/base/tooltip': ITooltipState;
'features/base/tracks': ITracksState; 'features/base/tracks': ITracksState;
'features/base/user-interaction': IUserInteractionState; 'features/base/user-interaction': IUserInteractionState;
'features/breakout-rooms': IBreakoutRoomsState; 'features/breakout-rooms': IBreakoutRoomsState;

View File

@@ -5,7 +5,6 @@ import { IReduxState } from '../../../app/types';
import DialogPortal from '../../../toolbox/components/web/DialogPortal'; import DialogPortal from '../../../toolbox/components/web/DialogPortal';
import Drawer from '../../../toolbox/components/web/Drawer'; import Drawer from '../../../toolbox/components/web/Drawer';
import JitsiPortal from '../../../toolbox/components/web/JitsiPortal'; import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
import { isMobileBrowser } from '../../environment/utils';
import { connect } from '../../redux/functions'; import { connect } from '../../redux/functions';
import { getContextMenuStyle } from '../functions.web'; import { getContextMenuStyle } from '../functions.web';
@@ -14,6 +13,11 @@ import { getContextMenuStyle } from '../functions.web';
*/ */
interface IProps { interface IProps {
/**
* Whether the child element can be clicked on.
*/
allowClick?: boolean;
/** /**
* A child React Element to use as the trigger for showing the dialog. * A child React Element to use as the trigger for showing the dialog.
*/ */
@@ -245,7 +249,8 @@ class Popover extends Component<IProps, IState> {
<DialogPortal <DialogPortal
getRef = { this._setContextMenuRef } getRef = { this._setContextMenuRef }
setSize = { this._setContextMenuStyle } setSize = { this._setContextMenuStyle }
style = { this.state.contextMenuStyle }> style = { this.state.contextMenuStyle }
targetSelector = '.popover-content'>
<ReactFocusLock <ReactFocusLock
lockProps = {{ lockProps = {{
role: 'dialog', role: 'dialog',
@@ -345,9 +350,11 @@ class Popover extends Component<IProps, IState> {
* @returns {void} * @returns {void}
*/ */
_onClick(event: React.MouseEvent) { _onClick(event: React.MouseEvent) {
const { trigger, visible } = this.props; const { allowClick, trigger, visible } = this.props;
event.stopPropagation(); if (!allowClick) {
event.stopPropagation();
}
if (trigger === 'click') { if (trigger === 'click') {
if (visible) { if (visible) {
this._onHideDialog(); this._onHideDialog();
@@ -417,20 +424,15 @@ class Popover extends Component<IProps, IState> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderContent() { _renderContent() {
const { content } = this.props; const { content, position } = this.props;
return ( return (
<div <div
className = 'popover' className = 'popover'
onKeyDown = { this._onEscKey }> onKeyDown = { this._onEscKey }>
{ content } <div className = { `popover-content ${position.split('-')[0]}` }>
{!isMobileBrowser() && ( { content }
<> </div>
<div className = 'popover-mousemove-padding-top' />
<div className = 'popover-mousemove-padding-right' />
<div className = 'popover-mousemove-padding-left' />
<div className = 'popover-mousemove-padding-bottom' />
</>)}
</div> </div>
); );
} }

View File

@@ -1,43 +1,40 @@
const LEFT_RIGHT_OFFSET = 25;
const TOP_BOTTOM_OFFSET = 20;
const getLeftAlignedStyle = (bounds: DOMRect) => { const getLeftAlignedStyle = (bounds: DOMRect) => {
return { return {
position: 'fixed', position: 'fixed',
right: `${window.innerWidth - bounds.x + LEFT_RIGHT_OFFSET}px` right: `${window.innerWidth - bounds.x}px`
}; };
}; };
const getRightAlignedStyle = (bounds: DOMRect) => { const getRightAlignedStyle = (bounds: DOMRect) => {
return { return {
position: 'fixed', position: 'fixed',
left: `${bounds.x + bounds.width + LEFT_RIGHT_OFFSET}px` left: `${bounds.x + bounds.width}px`
}; };
}; };
const getTopAlignedStyle = (bounds: DOMRect) => { const getTopAlignedStyle = (bounds: DOMRect) => {
return { return {
position: 'fixed', position: 'fixed',
bottom: `${window.innerHeight - bounds.y + TOP_BOTTOM_OFFSET}px` bottom: `${window.innerHeight - bounds.y}px`
}; };
}; };
const getBottomAlignedStyle = (bounds: DOMRect) => { const getBottomAlignedStyle = (bounds: DOMRect) => {
return { return {
position: 'fixed', position: 'fixed',
top: `${bounds.y + bounds.height + TOP_BOTTOM_OFFSET}px` top: `${bounds.y + bounds.height}px`
}; };
}; };
const getLeftRightStartAlign = (bounds: DOMRect, size: DOMRectReadOnly) => { const getLeftRightStartAlign = (bounds: DOMRect, size: DOMRectReadOnly) => {
return { return {
top: `${Math.min(bounds.y + 15, window.innerHeight - size.height - 20)}px` top: `${Math.min(bounds.y, window.innerHeight - size.height - 20)}px`
}; };
}; };
const getLeftRightMidAlign = (bounds: DOMRect, size: DOMRectReadOnly) => { const getLeftRightMidAlign = (bounds: DOMRect, size: DOMRectReadOnly) => {
return { return {
bottom: `${window.innerHeight - bounds.y - bounds.height - (size.height / 2)}px` bottom: `${window.innerHeight - bounds.y - (bounds.height / 2) - (size.height / 2)}px`
}; };
}; };
@@ -49,19 +46,19 @@ const getLeftRightEndAlign = (bounds: DOMRect, size: DOMRectReadOnly) => {
const getTopBotStartAlign = (bounds: DOMRect) => { const getTopBotStartAlign = (bounds: DOMRect) => {
return { return {
right: `${window.innerWidth - bounds.x + 10}px` right: `${window.innerWidth - bounds.x - 6}px`
}; };
}; };
const getTopBotMidAlign = (bounds: DOMRect, size: DOMRectReadOnly) => { const getTopBotMidAlign = (bounds: DOMRect, size: DOMRectReadOnly) => {
return { return {
right: `${window.innerWidth - bounds.x - (size.width / 2)}px` right: `${window.innerWidth - bounds.x - (bounds.width / 2) - (size.width / 2)}px`
}; };
}; };
const getTopBotEndAlign = (bounds: DOMRect) => { const getTopBotEndAlign = (bounds: DOMRect) => {
return { return {
left: `${bounds.x + bounds.width + 10}px` left: `${bounds.x + bounds.width - 6}px`
}; };
}; };

View File

@@ -4,9 +4,7 @@ import { makeStyles } from 'tss-react/mui';
import { translate } from '../../../i18n/functions'; import { translate } from '../../../i18n/functions';
import Icon from '../../../icons/components/Icon'; import Icon from '../../../icons/components/Icon';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../../tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../../tooltip';
/** /**
* The type of the React {@code Component} props of {@link BaseIndicator}. * The type of the React {@code Component} props of {@link BaseIndicator}.
@@ -58,7 +56,7 @@ interface IProps extends WithTranslation {
* From which side of the indicator the tooltip should appear from, * From which side of the indicator the tooltip should appear from,
* defaulting to "top". * defaulting to "top".
*/ */
tooltipPosition: string; tooltipPosition: 'top' | 'bottom' | 'left' | 'right';
} }
const useStyles = makeStyles()(() => { const useStyles = makeStyles()(() => {

View File

@@ -3,7 +3,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { Icon } from '../../icons'; import { Icon } from '../../icons';
import { Tooltip } from '../../tooltip'; import Tooltip from '../../tooltip/components/Tooltip';
import ContextMenuItem from '../../ui/components/web/ContextMenuItem'; import ContextMenuItem from '../../ui/components/web/ContextMenuItem';
import AbstractToolboxItem from './AbstractToolboxItem'; import AbstractToolboxItem from './AbstractToolboxItem';

View File

@@ -1,168 +0,0 @@
// @flow
import React, { Component } from 'react';
import { Icon } from '../../../icons';
import { Tooltip } from '../../../tooltip';
/**
* The type of the React {@code Component} props of {@link OverflowMenuItem}.
*/
type Props = {
/**
* A succinct description of what the item does. Used by accessibility tools
* and torture tests.
*/
accessibilityLabel: string,
/**
* Whether menu item is disabled or not.
*/
disabled: boolean,
/**
* A React Element to display at the end of {@code OverflowMenuItem}.
*/
elementAfter?: React$Node,
/**
* The icon class to use for displaying an icon before the link text.
*/
icon: Object,
/**
* Id of the icon to be rendered.
*/
iconId?: string,
/**
* The callback to invoke when {@code OverflowMenuItem} is clicked.
*/
onClick: Function,
/**
* The text to display in the {@code OverflowMenuItem}.
*/
text: string,
/**
* The text to display in the tooltip.
*/
tooltip?: string,
/**
* From which direction the tooltip should appear, relative to the button.
*/
tooltipPosition: string
};
/**
* A React {@code Component} for displaying a link to interact with other
* features of the application.
*
* @augments Component
*/
class OverflowMenuItem extends Component<Props> {
/**
* Default values for {@code OverflowMenuItem} component's properties.
*
* @static
*/
static defaultProps = {
tooltipPosition: 'left',
disabled: false
};
/**
* Initializes a new {@code OverflowMenuItem} instance.
*
* @param {*} props - The read-only properties with which the new instance
* is to be initialized.
*/
constructor(props: Props) {
super(props);
// Bind event handler so it is only bound once for every instance.
this._onKeyPress = this._onKeyPress.bind(this);
}
_onKeyPress: (Object) => void;
/**
* KeyPress handler for accessibility.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onKeyPress(e) {
if (!this.props.disabled && this.props.onClick && (e.key === ' ' || e.key === 'Enter')) {
e.preventDefault();
this.props.onClick();
}
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { accessibilityLabel, disabled, elementAfter, icon, iconId, onClick } = this.props;
let className = 'overflow-menu-item';
className += this.props.disabled ? ' disabled' : '';
return (
<li
aria-disabled = { disabled }
aria-label = { accessibilityLabel }
className = { className }
onClick = { disabled ? null : onClick }
onKeyPress = { this._onKeyPress }
role = 'button'
tabIndex = { 0 }>
<span className = 'overflow-menu-item-icon'>
<Icon
id = { iconId }
src = { icon } />
</span>
{ this._renderText() }
{
elementAfter || null
}
</li>
);
}
/**
* Renders the text label to display in the {@code OverflowMenuItem}.
*
* @private
* @returns {ReactElement}
*/
_renderText() {
const textElement = (
<span className = 'overflow-menu-item-text'>
{ this.props.text }
</span>
);
if (this.props.tooltip) {
return (
<Tooltip
content = { this.props.tooltip }
position = { this.props.tooltipPosition }>
{ textElement }
</Tooltip>
);
}
return textElement;
}
}
export default OverflowMenuItem;

View File

@@ -4,7 +4,7 @@ import React from 'react';
import { NOTIFY_CLICK_MODE } from '../../../../toolbox/constants'; import { NOTIFY_CLICK_MODE } from '../../../../toolbox/constants';
import { Icon } from '../../../icons'; import { Icon } from '../../../icons';
import { Tooltip } from '../../../tooltip'; import Tooltip from '../../../tooltip/components/Tooltip';
type Props = { type Props = {
@@ -107,12 +107,13 @@ export default function ToolboxButtonWithIcon(props: Props) {
} = props; } = props;
const iconProps = {}; const iconProps = {};
let className = '';
if (iconDisabled) { if (iconDisabled) {
iconProps.className className
= 'settings-button-small-icon settings-button-small-icon--disabled'; = 'settings-button-small-icon settings-button-small-icon--disabled';
} else { } else {
iconProps.className = 'settings-button-small-icon'; className = 'settings-button-small-icon';
iconProps.onClick = () => { iconProps.onClick = () => {
if (typeof APP !== 'undefined' && notifyMode) { if (typeof APP !== 'undefined' && notifyMode) {
APP.API.notifyToolbarButtonClicked( APP.API.notifyToolbarButtonClicked(
@@ -141,6 +142,7 @@ export default function ToolboxButtonWithIcon(props: Props) {
<div> <div>
<Tooltip <Tooltip
containerClassName = { className }
content = { iconTooltip } content = { iconTooltip }
position = 'top'> position = 'top'>
<Icon <Icon

View File

@@ -1,111 +0,0 @@
// @flow
import React, { Fragment } from 'react';
import { Icon } from '../../../icons';
import { Tooltip } from '../../../tooltip';
import AbstractToolboxItem from '../AbstractToolboxItem';
import type { Props } from '../AbstractToolboxItem';
/**
* Web implementation of {@code AbstractToolboxItem}.
*/
export default class ToolboxItem extends AbstractToolboxItem<Props> {
/**
* Initializes a new {@code ToolboxItem} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onKeyPress = this._onKeyPress.bind(this);
}
_onKeyPress: (Object) => void;
/**
* Handles 'Enter' and Space key on the button to trigger onClick for accessibility.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyPress(event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this.props.onClick();
}
}
/**
* Handles rendering of the actual item. If the label is being shown, which
* is controlled with the `showLabel` prop, the item is rendered for its
* display in an overflow menu, otherwise it will only have an icon, which
* can be displayed on any toolbar.
*
* @protected
* @returns {ReactElement}
*/
_renderItem() {
const {
disabled,
elementAfter,
onClick,
showLabel,
tooltipPosition,
toggled
} = this.props;
const className = showLabel ? 'overflow-menu-item' : 'toolbox-button';
const props = {
'aria-pressed': toggled,
'aria-disabled': disabled,
'aria-label': this.accessibilityLabel,
className: className + (disabled ? ' disabled' : ''),
onClick: disabled ? undefined : onClick,
onKeyPress: this._onKeyPress,
tabIndex: 0,
role: 'button'
};
const elementType = showLabel ? 'li' : 'div';
const useTooltip = this.tooltip && this.tooltip.length > 0;
let children = (
<Fragment>
{ this._renderIcon() }
{ showLabel && <span>
{ this.label }
</span> }
{ elementAfter }
</Fragment>
);
if (useTooltip) {
children = (
<Tooltip
content = { this.tooltip }
position = { tooltipPosition }>
{ children }
</Tooltip>
);
}
return React.createElement(elementType, props, children);
}
/**
* Helper function to render the item's icon.
*
* @private
* @returns {ReactElement}
*/
_renderIcon() {
const { customClass, disabled, icon, showLabel, toggled } = this.props;
const iconComponent = <Icon src = { icon } />;
const elementType = showLabel ? 'span' : 'div';
const className = `${showLabel ? 'overflow-menu-item-icon' : 'toolbox-icon'} ${
toggled ? 'toggled' : ''} ${disabled ? 'disabled' : ''} ${customClass ?? ''}`;
return React.createElement(elementType, { className }, iconComponent);
}
}

View File

@@ -1,4 +1,3 @@
// @flow // @flow
export { default as OverflowMenuItem } from './OverflowMenuItem';
export { default as ToolboxButtonWithIcon } from './ToolboxButtonWithIcon'; export { default as ToolboxButtonWithIcon } from './ToolboxButtonWithIcon';

View File

@@ -0,0 +1,19 @@
/**
* The type of the action which signals a tooltip is being displayed.
*
* {
* type: SHOW_TOOLTIP,
* content: string
* }.
*/
export const SHOW_TOOLTIP = 'SHOW_TOOLTIP';
/**
* The type of the action which signals a tooltip should be hidden.
*
* {
* type: SHOW_TOOLTIP,
* content: string
* }.
*/
export const HIDE_TOOLTIP = 'HIDE_TOOLTIP';

View File

@@ -0,0 +1,29 @@
import { HIDE_TOOLTIP, SHOW_TOOLTIP } from './actionTypes';
/**
* Set tooltip state to visible.
*
* @param {string} content - The content of the tooltip.
* Used as unique identifier for tooltip.
* @returns {Object}
*/
export function showTooltip(content: string) {
return {
type: SHOW_TOOLTIP,
content
};
}
/**
* Set tooltip state to hidden.
*
* @param {string} content - The content of the tooltip.
* Used as unique identifier for tooltip.
* @returns {Object}
*/
export function hideTooltip(content: string) {
return {
type: HIDE_TOOLTIP,
content
};
}

View File

@@ -0,0 +1,150 @@
import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { keyframes } from 'tss-react';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../app/types';
import { isMobileBrowser } from '../../environment/utils';
import Popover from '../../popover/components/Popover.web';
import { withPixelLineHeight } from '../../styles/functions.web';
import { hideTooltip, showTooltip } from '../actions';
const TOOLTIP_DELAY = 300;
const ANIMATION_DURATION = 0.2;
interface IProps {
children: ReactElement;
containerClassName?: string;
content: string;
position?: 'top' | 'bottom' | 'left' | 'right';
}
const useStyles = makeStyles()(theme => {
return {
container: {
backgroundColor: theme.palette.uiBackground,
borderRadius: '3px',
padding: theme.spacing(2),
...withPixelLineHeight(theme.typography.labelRegular),
color: theme.palette.text01,
position: 'relative',
'&.mounting-animation': {
animation: `${keyframes`
0% {
opacity: 0;
}
100% {
opacity: 1;
}
`} ${ANIMATION_DURATION}s forwards ease-in`
},
'&.unmounting': {
animation: `${keyframes`
0% {
opacity: 1;
}
100% {
opacity: 0;
}
`} ${ANIMATION_DURATION}s forwards ease-out`
}
}
};
});
const Tooltip = ({ containerClassName, content, children, position = 'top' }: IProps) => {
const dispatch = useDispatch();
const [ visible, setVisible ] = useState(false);
const [ isUnmounting, setIsUnmounting ] = useState(false);
const { classes, cx } = useStyles();
const timeoutID = useRef({
open: 0,
close: 0
});
const {
content: storeContent,
previousContent,
visible: isVisible
} = useSelector((state: IReduxState) => state['features/base/tooltip']);
if (isMobileBrowser()) {
return children;
}
const contentComponent = (
<div
className = { cx(classes.container, previousContent === '' && 'mounting-animation',
isUnmounting && 'unmounting') }>
{content}
</div>
);
const openPopover = () => {
setVisible(true);
dispatch(showTooltip(content));
};
const closePopover = () => {
setVisible(false);
dispatch(hideTooltip(content));
setIsUnmounting(false);
};
const onPopoverOpen = useCallback(() => {
clearTimeout(timeoutID.current.close);
timeoutID.current.close = 0;
if (!visible) {
if (isVisible) {
openPopover();
} else {
timeoutID.current.open = window.setTimeout(() => {
openPopover();
}, TOOLTIP_DELAY);
}
}
}, [ visible, isVisible ]);
const onPopoverClose = useCallback(() => {
clearTimeout(timeoutID.current.open);
if (visible) {
timeoutID.current.close = window.setTimeout(() => {
setIsUnmounting(true);
}, TOOLTIP_DELAY);
}
}, [ visible ]);
useEffect(() => {
if (isUnmounting) {
setTimeout(() => {
if (timeoutID.current.close !== 0) {
closePopover();
}
}, (ANIMATION_DURATION * 1000) + 10);
}
}, [ isUnmounting ]);
useEffect(() => {
if (storeContent !== content) {
closePopover();
clearTimeout(timeoutID.current.close);
timeoutID.current.close = 0;
}
}, [ storeContent ]);
return (
<Popover
allowClick = { true }
className = { containerClassName }
content = { contentComponent }
onPopoverClose = { onPopoverClose }
onPopoverOpen = { onPopoverOpen }
position = { position }
visible = { visible }>
{children}
</Popover>
);
};
export default Tooltip;

View File

@@ -1,50 +0,0 @@
// @flow
import Tooltip from '@atlaskit/tooltip';
import React from 'react';
import { isMobileBrowser } from '../../environment/utils';
type Props = {
/**
* Children of the component.
*/
children: React$Node,
/**
* The text to be displayed in the tooltip.
*/
content?: string | null,
/**
* The position of the tooltip relative to the element it contains.
*/
position?: string
}
/**
* Wrapper of AtlasKit Tooltip that doesn't render the actual tooltip in mobile browsers.
*
* @returns {ReactElement}
*/
function TooltipWrapper({
children,
content,
position
}: Props) {
if (isMobileBrowser()) {
return children;
}
return (
<Tooltip
content = { content }
position = { position }>
{children}
</Tooltip>
);
}
export default TooltipWrapper;

View File

@@ -1,3 +0,0 @@
// @flow
export { default as Tooltip } from './TooltipWrapper';

View File

@@ -1,3 +0,0 @@
// @flow
export * from './components';

View File

@@ -0,0 +1,50 @@
import ReducerRegistry from '../redux/ReducerRegistry';
import { HIDE_TOOLTIP, SHOW_TOOLTIP } from './actionTypes';
export interface ITooltipState {
content: string;
previousContent: string;
visible: boolean;
}
const DEFAULT_STATE = {
content: '',
previousContent: '',
visible: false
};
/**
* Reduces redux actions which mark the tooltip as displayed or hidden.
*
* @param {IDialogState} state - The current redux state.
* @param {Action} action - The redux action to reduce.
* @param {string} action.type - The type of the redux action to reduce..
* @returns {State} The next redux state that is the result of reducing the
* specified action.
*/
ReducerRegistry.register<ITooltipState>('features/base/tooltip', (state = DEFAULT_STATE, action): ITooltipState => {
switch (action.type) {
case SHOW_TOOLTIP:
return {
content: action.content,
previousContent: state.content,
visible: true
};
case HIDE_TOOLTIP: {
// The tooltip can be marked as hidden only if the hide action
// is dispatched by the tooltip that is displayed.
if (action.content === state.content) {
return {
content: '',
previousContent: '',
visible: false
};
}
return state;
}
}
return state;
});

View File

@@ -10,7 +10,7 @@ import {
import { translate } from '../../base/i18n'; import { translate } from '../../base/i18n';
import { Icon, IconPlus } from '../../base/icons'; import { Icon, IconPlus } from '../../base/icons';
import { connect } from '../../base/redux'; import { connect } from '../../base/redux';
import { Tooltip } from '../../base/tooltip'; import Tooltip from '../../base/tooltip/components/Tooltip';
import { updateCalendarEvent } from '../actions'; import { updateCalendarEvent } from '../actions';
/** /**

View File

@@ -4,7 +4,7 @@ import React, { Component } from 'react';
import { translate } from '../../base/i18n'; import { translate } from '../../base/i18n';
import { Icon, IconPlus } from '../../base/icons'; import { Icon, IconPlus } from '../../base/icons';
import { Tooltip } from '../../base/tooltip'; import Tooltip from '../../base/tooltip/components/Tooltip';
/** /**
* The type of the React {@code Component} props of {@link JoinButton}. * The type of the React {@code Component} props of {@link JoinButton}.

View File

@@ -1,4 +1,3 @@
import Tooltip from '@atlaskit/tooltip';
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@@ -6,6 +5,7 @@ import { translate } from '../../../base/i18n/functions';
import { IconExclamationTriangle } from '../../../base/icons/svg'; import { IconExclamationTriangle } from '../../../base/icons/svg';
import Label from '../../../base/label/components/web/Label'; import Label from '../../../base/label/components/web/Label';
import { COLORS } from '../../../base/label/constants'; import { COLORS } from '../../../base/label/constants';
import Tooltip from '../../../base/tooltip/components/Tooltip';
import AbstractInsecureRoomNameLabel, { _mapStateToProps } from '../AbstractInsecureRoomNameLabel'; import AbstractInsecureRoomNameLabel, { _mapStateToProps } from '../AbstractInsecureRoomNameLabel';
/** /**

View File

@@ -6,9 +6,7 @@ import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../app/types'; import { IReduxState } from '../../../app/types';
import { IconRaiseHand } from '../../../base/icons/svg'; import { IconRaiseHand } from '../../../base/icons/svg';
import Label from '../../../base/label/components/web/Label'; import Label from '../../../base/label/components/web/Label';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../../base/tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../../base/tooltip';
import { open as openParticipantsPane } from '../../../participants-pane/actions.web'; import { open as openParticipantsPane } from '../../../participants-pane/actions.web';
const useStyles = makeStyles()(theme => { const useStyles = makeStyles()(theme => {

View File

@@ -5,9 +5,7 @@ import { makeStyles } from 'tss-react/mui';
import { getConferenceName } from '../../../base/conference/functions'; import { getConferenceName } from '../../../base/conference/functions';
import { withPixelLineHeight } from '../../../base/styles/functions.web'; import { withPixelLineHeight } from '../../../base/styles/functions.web';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../../base/tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../../base/tooltip';
const useStyles = makeStyles()(theme => { const useStyles = makeStyles()(theme => {
return { return {
@@ -44,13 +42,13 @@ const SubjectText = () => {
const { classes } = useStyles(); const { classes } = useStyles();
return ( return (
<div className = { classes.container }> <Tooltip
<Tooltip content = { subject }
content = { subject } position = 'bottom'>
position = 'bottom'> <div className = { classes.container }>
<div className = { clsx('subject-text--content', classes.content) }>{subject}</div> <div className = { clsx('subject-text--content', classes.content) }>{subject}</div>
</Tooltip> </div>
</div> </Tooltip>
); );
}; };

View File

@@ -5,9 +5,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types'; import { IReduxState } from '../../../app/types';
import { IconArrowDown } from '../../../base/icons/svg/index'; import { IconArrowDown } from '../../../base/icons/svg/index';
import Label from '../../../base/label/components/web/Label'; import Label from '../../../base/label/components/web/Label';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../../base/tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../../base/tooltip';
import { setTopPanelVisible } from '../../../filmstrip/actions.web'; import { setTopPanelVisible } from '../../../filmstrip/actions.web';
const ToggleTopPanelLabel = () => { const ToggleTopPanelLabel = () => {

View File

@@ -230,12 +230,9 @@ const styles = (theme: Theme) => {
}, },
contextMenu: { contextMenu: {
position: 'relative' as const, position: 'relative' as const,
marginTop: 0, margin: 0,
right: 'auto', right: 'auto',
padding: `${theme.spacing(2)} ${theme.spacing(1)}`, padding: `${theme.spacing(2)} ${theme.spacing(1)}`
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
marginBottom: theme.spacing(1)
}, },
download: {}, download: {},
mobile: { mobile: {

View File

@@ -10,9 +10,7 @@ import {
} from '../../../base/participants/functions'; } from '../../../base/participants/functions';
import { updateSettings } from '../../../base/settings/actions'; import { updateSettings } from '../../../base/settings/actions';
import { withPixelLineHeight } from '../../../base/styles/functions.web'; import { withPixelLineHeight } from '../../../base/styles/functions.web';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../../base/tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../../base/tooltip';
import { getIndicatorsTooltipPosition } from '../../../filmstrip/functions.web'; import { getIndicatorsTooltipPosition } from '../../../filmstrip/functions.web';
import { appendSuffix } from '../../functions'; import { appendSuffix } from '../../functions';

View File

@@ -5,9 +5,7 @@ import { translate } from '../../base/i18n/functions';
import { IconE2EE } from '../../base/icons/svg'; import { IconE2EE } from '../../base/icons/svg';
import Label from '../../base/label/components/web/Label'; import Label from '../../base/label/components/web/Label';
import { COLORS } from '../../base/label/constants'; import { COLORS } from '../../base/label/constants';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../base/tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../base/tooltip';
import { IProps, _mapStateToProps } from './AbstractE2EELabel'; import { IProps, _mapStateToProps } from './AbstractE2EELabel';

View File

@@ -27,7 +27,7 @@ interface IProps {
/** /**
* From which side of the indicator the tooltip should appear from. * From which side of the indicator the tooltip should appear from.
*/ */
tooltipPosition: string; tooltipPosition: 'top' | 'bottom' | 'left' | 'right';
} }
const useStyles = makeStyles()(() => { const useStyles = makeStyles()(() => {

View File

@@ -27,7 +27,7 @@ interface IProps {
/** /**
* From which side of the indicator the tooltip should appear from. * From which side of the indicator the tooltip should appear from.
*/ */
tooltipPosition: string; tooltipPosition: 'top' | 'bottom' | 'left' | 'right';
} }
const useStyles = makeStyles()(theme => { const useStyles = makeStyles()(theme => {

View File

@@ -30,8 +30,7 @@ import {
} from '../../../base/participants/functions'; } from '../../../base/participants/functions';
import { IParticipant } from '../../../base/participants/types'; import { IParticipant } from '../../../base/participants/types';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants'; import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
// @ts-ignore import Tooltip from '../../../base/tooltip/components/Tooltip';
import { Tooltip } from '../../../base/tooltip';
import { trackStreamingStatusChanged } from '../../../base/tracks/actions'; import { trackStreamingStatusChanged } from '../../../base/tracks/actions';
import { import {
getLocalAudioTrack, getLocalAudioTrack,

View File

@@ -222,7 +222,9 @@ export const STATS_POPOVER_POSITION = {
/** /**
* The tooltip position for the indicators on the thumbnail. * The tooltip position for the indicators on the thumbnail.
*/ */
export const INDICATORS_TOOLTIP_POSITION = { export const INDICATORS_TOOLTIP_POSITION: {
[x: string]: 'right' | 'left' | 'top';
} = {
[THUMBNAIL_TYPE.TILE]: 'right', [THUMBNAIL_TYPE.TILE]: 'right',
[THUMBNAIL_TYPE.VERTICAL]: 'left', [THUMBNAIL_TYPE.VERTICAL]: 'left',
[THUMBNAIL_TYPE.HORIZONTAL]: 'top' [THUMBNAIL_TYPE.HORIZONTAL]: 'top'

View File

@@ -11,9 +11,7 @@ import {
IconOffice365, IconOffice365,
IconYahoo IconYahoo
} from '../../../../base/icons/svg'; } from '../../../../base/icons/svg';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../../../base/tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../../../base/tooltip';
import { copyText } from '../../../../base/util/copyText.web'; import { copyText } from '../../../../base/util/copyText.web';
interface IProps { interface IProps {

View File

@@ -2,7 +2,7 @@
import React from 'react'; import React from 'react';
import { Tooltip } from '../../../base/tooltip'; import Tooltip from '../../../base/tooltip/components/Tooltip';
import AbstractToolbarButton from '../../../toolbox/components/AbstractToolbarButton'; import AbstractToolbarButton from '../../../toolbox/components/AbstractToolbarButton';
import type { Props as AbstractToolbarButtonProps } from '../../../toolbox/components/AbstractToolbarButton'; import type { Props as AbstractToolbarButtonProps } from '../../../toolbox/components/AbstractToolbarButton';

View File

@@ -10,9 +10,7 @@ import { IconHighlight } from '../../../../base/icons/svg';
import { MEET_FEATURES } from '../../../../base/jwt/constants'; import { MEET_FEATURES } from '../../../../base/jwt/constants';
import Label from '../../../../base/label/components/web/Label'; import Label from '../../../../base/label/components/web/Label';
import { connect } from '../../../../base/redux/functions'; import { connect } from '../../../../base/redux/functions';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../../../base/tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../../../base/tooltip';
import BaseTheme from '../../../../base/ui/components/BaseTheme.web'; import BaseTheme from '../../../../base/ui/components/BaseTheme.web';
import { maybeShowPremiumFeatureDialog } from '../../../../jaas/actions'; import { maybeShowPremiumFeatureDialog } from '../../../../jaas/actions';
import AbstractHighlightButton, { import AbstractHighlightButton, {

View File

@@ -7,9 +7,7 @@ import { IconRecord, IconSites } from '../../../base/icons/svg';
import Label from '../../../base/label/components/web/Label'; import Label from '../../../base/label/components/web/Label';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet'; import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { connect } from '../../../base/redux/functions'; import { connect } from '../../../base/redux/functions';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../../base/tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../../base/tooltip';
import AbstractRecordingLabel, { import AbstractRecordingLabel, {
_mapStateToProps _mapStateToProps

View File

@@ -14,9 +14,7 @@ import {
IconEmotionsSad, IconEmotionsSad,
IconEmotionsSurprised IconEmotionsSurprised
} from '../../../base/icons/svg'; } from '../../../base/icons/svg';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../../base/tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../../base/tooltip';
import Dialog from '../../../base/ui/components/web/Dialog'; import Dialog from '../../../base/ui/components/web/Dialog';
import { escapeRegexp } from '../../../base/util/helpers'; import { escapeRegexp } from '../../../base/util/helpers';
import { initSearch, resetSearchCriteria, toggleFaceExpressions } from '../../actions.any'; import { initSearch, resetSearchCriteria, toggleFaceExpressions } from '../../actions.any';

View File

@@ -31,6 +31,12 @@ type Props = {
* Custom style to apply to the container div. * Custom style to apply to the container div.
*/ */
style?: any; style?: any;
/**
* The selector for the element we consider the content container.
* This is used to determine the correct size of the portal content.
*/
targetSelector?: string;
}; };
/** /**
@@ -39,7 +45,7 @@ type Props = {
* *
* @returns {ReactElement} * @returns {ReactElement}
*/ */
function DialogPortal({ children, className, style, getRef, setSize }: Props) { function DialogPortal({ children, className, style, getRef, setSize, targetSelector }: Props) {
const clientWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].clientWidth); const clientWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].clientWidth);
const [ portalTarget ] = useState(() => { const [ portalTarget ] = useState(() => {
const portalDiv = document.createElement('div'); const portalDiv = document.createElement('div');
@@ -87,13 +93,15 @@ function DialogPortal({ children, className, style, getRef, setSize }: Props) {
} }
}); });
const target = targetSelector ? portalTarget.querySelector(targetSelector) : portalTarget;
if (document.body) { if (document.body) {
document.body.appendChild(portalTarget); document.body.appendChild(portalTarget);
observer.observe(portalTarget); observer.observe(target ?? portalTarget);
} }
return () => { return () => {
observer.unobserve(portalTarget); observer.unobserve(target ?? portalTarget);
if (document.body) { if (document.body) {
document.body.removeChild(portalTarget); document.body.removeChild(portalTarget);
} }

View File

@@ -3,9 +3,7 @@ import { connect } from 'react-redux';
import { translate } from '../../base/i18n/functions'; import { translate } from '../../base/i18n/functions';
import Label from '../../base/label/components/web/Label'; import Label from '../../base/label/components/web/Label';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../base/tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../base/tooltip';
import { Props, _mapStateToProps } from './AbstractTranscribingLabel'; import { Props, _mapStateToProps } from './AbstractTranscribingLabel';

View File

@@ -8,7 +8,7 @@ import { IconPerformance } from '../../base/icons';
import { Label } from '../../base/label'; import { Label } from '../../base/label';
import { COLORS } from '../../base/label/constants'; import { COLORS } from '../../base/label/constants';
import { connect } from '../../base/redux'; import { connect } from '../../base/redux';
import { Tooltip } from '../../base/tooltip'; import Tooltip from '../../base/tooltip/components/Tooltip';
import { shouldDisplayTileView } from '../../video-layout'; import { shouldDisplayTileView } from '../../video-layout';
import AbstractVideoQualityLabel, { import AbstractVideoQualityLabel, {

View File

@@ -14,8 +14,7 @@ import { translate } from '../../base/i18n/functions';
import Icon from '../../base/icons/components/Icon'; import Icon from '../../base/icons/components/Icon';
import { IconCloseLarge } from '../../base/icons/svg'; import { IconCloseLarge } from '../../base/icons/svg';
import { withPixelLineHeight } from '../../base/styles/functions.web'; import { withPixelLineHeight } from '../../base/styles/functions.web';
// @ts-ignore import Tooltip from '../../base/tooltip/components/Tooltip';
import { Tooltip } from '../../base/tooltip';
import Spinner from '../../base/ui/components/web/Spinner'; import Spinner from '../../base/ui/components/web/Spinner';
import { BACKGROUNDS_LIMIT, IMAGES, type Image, VIRTUAL_BACKGROUND_TYPE } from '../constants'; import { BACKGROUNDS_LIMIT, IMAGES, type Image, VIRTUAL_BACKGROUND_TYPE } from '../constants';
import { toDataURL } from '../functions'; import { toDataURL } from '../functions';
@@ -430,7 +429,7 @@ function VirtualBackgrounds({
</Tooltip> </Tooltip>
{_images.map(image => ( {_images.map(image => (
<Tooltip <Tooltip
content = { image.tooltip && t(`virtualBackground.${image.tooltip}`) } content = { (image.tooltip && t(`virtualBackground.${image.tooltip}`)) ?? '' }
key = { image.id } key = { image.id }
position = { 'top' }> position = { 'top' }>
<img <img

View File

@@ -6,9 +6,7 @@ import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../app/types'; import { IReduxState } from '../../../app/types';
import { IconUsers } from '../../../base/icons/svg'; import { IconUsers } from '../../../base/icons/svg';
import Label from '../../../base/label/components/web/Label'; import Label from '../../../base/label/components/web/Label';
// eslint-disable-next-line lines-around-comment import Tooltip from '../../../base/tooltip/components/Tooltip';
// @ts-ignore
import { Tooltip } from '../../../base/tooltip';
import { getVisitorsShortText, iAmVisitor } from '../../functions'; import { getVisitorsShortText, iAmVisitor } from '../../functions';
const useStyles = makeStyles()(theme => { const useStyles = makeStyles()(theme => {

View File

@@ -18,6 +18,7 @@
"node_modules", "node_modules",
"react/features/analytics/handlers/GoogleAnalyticsHandler.ts", "react/features/analytics/handlers/GoogleAnalyticsHandler.ts",
"react/features/base/components/participants-pane-list", "react/features/base/components/participants-pane-list",
"react/features/base/tooltip",
"react/features/connection-stats", "react/features/connection-stats",
"react/features/desktop-picker", "react/features/desktop-picker",
"react/features/e2ee", "react/features/e2ee",