feat(reactions) Open reactions menu on hover instead of click (#11364)

Fixed issue on DialogPortal where the content would flash to the initial position then move to the correct position
This commit is contained in:
Robert Pintilii 2022-04-13 16:18:54 +03:00 committed by GitHub
parent 00bb013373
commit a6ad592d25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 201 additions and 90 deletions

View File

@ -46,18 +46,12 @@
}
.audio-preview > div:nth-child(2),
.video-preview > div:nth-child(2),
.reactions-menu-popup > div:nth-child(2) {
.video-preview > div:nth-child(2) {
margin-bottom: 4px;
outline: none;
padding: 0;
}
.reactions-menu-popup > div:nth-child(2) {
margin-bottom: 6px;
box-shadow: none;
}
/**
* The following selectors keep the chat modal full-size anywhere between 100px
* and 580px for desktop or 680px for mobile.

View File

@ -104,6 +104,10 @@
}
}
.reactions-menu-container {
padding-bottom: 6px;
}
.reactions-animations-container {
position: absolute;
width: 20%;
@ -112,8 +116,7 @@
height: 0;
}
.reactions-menu-popup-container,
.reactions-menu-popup {
.reactions-menu-popup-container {
display: inline-block;
position: relative;
}

View File

@ -60,3 +60,15 @@
}
}
}
.settings-button-small-icon-container {
position: absolute;
right: -4px;
top: -3px;
& .settings-button-small-icon {
position: relative;
top: 0;
right: 0;
}
}

View File

@ -0,0 +1,138 @@
// @flow
import React from 'react';
import { Icon } from '../../../icons';
import { Popover } from '../../../popover';
type Props = {
/**
* Whether the element popup is expanded.
*/
ariaExpanded?: boolean,
/**
* The id of the element this button icon controls.
*/
ariaControls?: string,
/**
* Whether the element has a popup.
*/
ariaHasPopup?: boolean,
/**
* Aria label for the Icon.
*/
ariaLabel?: string,
/**
* The decorated component (ToolboxButton).
*/
children: React$Node,
/**
* Icon of the button.
*/
icon: Function,
/**
* Flag used for disabling the small icon.
*/
iconDisabled: boolean,
/**
* The ID of the icon button.
*/
iconId: string,
/**
* Popover close callback.
*/
onPopoverClose: Function,
/**
* Popover open callback.
*/
onPopoverOpen: Function,
/**
* The content that will be displayed inside the popover.
*/
popoverContent: React$Node,
/**
* Additional styles.
*/
styles?: Object,
/**
* Whether or not the popover is visible.
*/
visible: boolean
};
declare var APP: Object;
/**
* Displays the `ToolboxButtonWithIcon` component.
*
* @param {Object} props - Component's props.
* @returns {ReactElement}
*/
export default function ToolboxButtonWithIconPopup(props: Props) {
const {
ariaControls,
ariaExpanded,
ariaHasPopup,
ariaLabel,
children,
icon,
iconDisabled,
iconId,
onPopoverClose,
onPopoverOpen,
popoverContent,
styles,
visible
} = props;
const iconProps = {};
if (iconDisabled) {
iconProps.className
= 'settings-button-small-icon settings-button-small-icon--disabled';
} else {
iconProps.className = 'settings-button-small-icon';
iconProps.role = 'button';
iconProps.tabIndex = 0;
iconProps.ariaControls = ariaControls;
iconProps.ariaExpanded = ariaExpanded;
iconProps.containerId = iconId;
}
return (
<div
className = 'settings-button-container'
styles = { styles }>
{children}
<div className = 'settings-button-small-icon-container'>
<Popover
content = { popoverContent }
onPopoverClose = { onPopoverClose }
onPopoverOpen = { onPopoverOpen }
position = 'top'
visible = { visible }>
<Icon
{ ...iconProps }
ariaHasPopup = { ariaHasPopup }
ariaLabel = { ariaLabel }
size = { 9 }
src = { icon } />
</Popover>
</div>
</div>
);
}

View File

@ -1,12 +1,13 @@
// @flow
import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';
import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n';
import { IconArrowUp } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { ToolboxButtonWithIcon } from '../../../base/toolbox/components';
import ToolboxButtonWithIconPopup from '../../../base/toolbox/components/web/ToolboxButtonWithIconPopup';
import { toggleReactionsMenuVisibility } from '../../actions.web';
import { type ReactionEmojiProps } from '../../constants';
import { getReactionsQueue, isReactionsEnabled } from '../../functions.any';
@ -14,7 +15,7 @@ import { getReactionsMenuVisibility } from '../../functions.web';
import RaiseHandButton from './RaiseHandButton';
import ReactionEmoji from './ReactionEmoji';
import ReactionsMenuPopup from './ReactionsMenuPopup';
import ReactionsMenu from './ReactionsMenu';
type Props = {
@ -84,20 +85,28 @@ function ReactionsMenuButton({
reactionsQueue,
t
}: Props) {
const visible = useSelector(getReactionsMenuVisibility);
const toggleReactionsMenu = useCallback(() => {
dispatch(toggleReactionsMenuVisibility());
}, [ dispatch ]);
const openReactionsMenu = useCallback(() => {
!visible && toggleReactionsMenu();
}, [ visible, toggleReactionsMenu ]);
const reactionsMenu = (<div className = 'reactions-menu-container'>
<ReactionsMenu />
</div>);
return (
<div className = 'reactions-menu-popup-container'>
<ReactionsMenuPopup>
{!_reactionsEnabled || isMobile ? (
<RaiseHandButton
buttonKey = { buttonKey }
handleClick = { handleClick }
notifyMode = { notifyMode } />)
: (
<ToolboxButtonWithIcon
<ToolboxButtonWithIconPopup
ariaControls = 'reactions-menu-dialog'
ariaExpanded = { isOpen }
ariaHasPopup = { true }
@ -106,16 +115,17 @@ function ReactionsMenuButton({
icon = { IconArrowUp }
iconDisabled = { false }
iconId = 'reactions-menu-button'
iconTooltip = { t(`toolbar.${isOpen ? 'closeReactionsMenu' : 'openReactionsMenu'}`) }
notifyMode = { notifyMode }
onIconClick = { toggleReactionsMenu }>
onPopoverClose = { toggleReactionsMenu }
onPopoverOpen = { openReactionsMenu }
popoverContent = { reactionsMenu }
visible = { visible }>
<RaiseHandButton
buttonKey = { buttonKey }
handleClick = { handleClick }
notifyMode = { notifyMode } />
</ToolboxButtonWithIcon>
</ToolboxButtonWithIconPopup>
)}
</ReactionsMenuPopup>
{reactionsQueue.map(({ reaction, uid }, index) => (<ReactionEmoji
index = { index }
key = { uid }

View File

@ -1,52 +0,0 @@
// @flow
import InlineDialog from '@atlaskit/inline-dialog';
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { toggleReactionsMenuVisibility } from '../../actions.web';
import { getReactionsMenuVisibility } from '../../functions.web';
import ReactionsMenu from './ReactionsMenu';
type Props = {
/**
* Component's children (the reactions menu button).
*/
children: React$Node
}
/**
* Popup with reactions menu.
*
* @returns {ReactElement}
*/
function ReactionsMenuPopup({
children
}: Props) {
/**
* Flag controlling the visibility of the popup.
*/
const isOpen = useSelector(state => getReactionsMenuVisibility(state));
const dispatch = useDispatch();
const onClose = useCallback(() => {
dispatch(toggleReactionsMenuVisibility());
});
return (
<div className = 'reactions-menu-popup'>
<InlineDialog
content = { <ReactionsMenu /> }
isOpen = { isOpen }
onClose = { onClose }
placement = 'top'>
{children}
</InlineDialog>
</div>
);
}
export default ReactionsMenuPopup;

View File

@ -4,4 +4,3 @@ export { default as ReactionButton } from './ReactionButton';
export { default as ReactionEmoji } from './ReactionEmoji';
export { default as ReactionsMenu } from './ReactionsMenu';
export { default as ReactionsMenuButton } from './ReactionsMenuButton';
export { default as ReactionsMenuPopup } from './ReactionsMenuPopup';

View File

@ -1,6 +1,6 @@
// @flow
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
type Props = {
@ -41,8 +41,11 @@ function DialogPortal({ children, className, style, getRef, setSize }: Props) {
const [ portalTarget ] = useState(() => {
const portalDiv = document.createElement('div');
portalDiv.style.visibility = 'hidden';
return portalDiv;
});
const timerRef = useRef();
useEffect(() => {
if (style) {
@ -74,6 +77,10 @@ function DialogPortal({ children, className, style, getRef, setSize }: Props) {
if (contentRect.width !== size.width || contentRect.height !== size.height) {
setSize && setSize(contentRect);
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
portalTarget.style.visibility = 'visible';
}, 100);
}
});