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

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 = {
@ -26,7 +27,7 @@ type Props = {
/**
* The button's key.
*/
buttonKey?: string,
buttonKey?: string,
/**
* Redux dispatch function.
@ -84,38 +85,47 @@ 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
{!_reactionsEnabled || isMobile ? (
<RaiseHandButton
buttonKey = { buttonKey }
handleClick = { handleClick }
notifyMode = { notifyMode } />)
: (
<ToolboxButtonWithIconPopup
ariaControls = 'reactions-menu-dialog'
ariaExpanded = { isOpen }
ariaHasPopup = { true }
ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
buttonKey = { buttonKey }
handleClick = { handleClick }
notifyMode = { notifyMode } />)
: (
<ToolboxButtonWithIcon
ariaControls = 'reactions-menu-dialog'
ariaExpanded = { isOpen }
ariaHasPopup = { true }
ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
icon = { IconArrowUp }
iconDisabled = { false }
iconId = 'reactions-menu-button'
notifyMode = { notifyMode }
onPopoverClose = { toggleReactionsMenu }
onPopoverOpen = { openReactionsMenu }
popoverContent = { reactionsMenu }
visible = { visible }>
<RaiseHandButton
buttonKey = { buttonKey }
icon = { IconArrowUp }
iconDisabled = { false }
iconId = 'reactions-menu-button'
iconTooltip = { t(`toolbar.${isOpen ? 'closeReactionsMenu' : 'openReactionsMenu'}`) }
notifyMode = { notifyMode }
onIconClick = { toggleReactionsMenu }>
<RaiseHandButton
buttonKey = { buttonKey }
handleClick = { handleClick }
notifyMode = { notifyMode } />
</ToolboxButtonWithIcon>
)}
</ReactionsMenuPopup>
handleClick = { handleClick }
notifyMode = { notifyMode } />
</ToolboxButtonWithIconPopup>
)}
{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);
}
});