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:
parent
00bb013373
commit
a6ad592d25
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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 }
|
||||
|
|
|
@ -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;
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue