Parcourir la source

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
master
Robert Pintilii il y a 3 ans
Parent
révision
a6ad592d25
Aucun compte lié à l'adresse e-mail de l'auteur

+ 1
- 7
css/_atlaskit_overrides.scss Voir le fichier

@@ -46,18 +46,12 @@
46 46
 }
47 47
 
48 48
 .audio-preview > div:nth-child(2),
49
-.video-preview > div:nth-child(2),
50
-.reactions-menu-popup > div:nth-child(2) {
49
+.video-preview > div:nth-child(2) {
51 50
     margin-bottom: 4px;
52 51
     outline: none;
53 52
     padding: 0;
54 53
 }
55 54
 
56
-.reactions-menu-popup > div:nth-child(2) {
57
-    margin-bottom: 6px;
58
-    box-shadow: none;
59
-}
60
-
61 55
 /**
62 56
  * The following selectors keep the chat modal full-size anywhere between 100px
63 57
  * and 580px for desktop or 680px for mobile.

+ 5
- 2
css/_reactions-menu.scss Voir le fichier

@@ -104,6 +104,10 @@
104 104
 	}
105 105
 }
106 106
 
107
+.reactions-menu-container {
108
+	padding-bottom: 6px;
109
+}
110
+
107 111
 .reactions-animations-container {
108 112
 	position: absolute;
109 113
 	width: 20%;
@@ -112,8 +116,7 @@
112 116
 	height: 0;
113 117
 }
114 118
 
115
-.reactions-menu-popup-container,
116
-.reactions-menu-popup {
119
+.reactions-menu-popup-container {
117 120
 	display: inline-block;
118 121
 	position: relative;
119 122
 }

+ 12
- 0
css/_settings-button.scss Voir le fichier

@@ -60,3 +60,15 @@
60 60
         }
61 61
     }
62 62
 }
63
+
64
+.settings-button-small-icon-container {
65
+    position: absolute;
66
+    right: -4px;
67
+    top: -3px;
68
+
69
+    & .settings-button-small-icon {
70
+        position: relative;
71
+        top: 0;
72
+        right: 0;
73
+    }
74
+}

+ 138
- 0
react/features/base/toolbox/components/web/ToolboxButtonWithIconPopup.js Voir le fichier

@@ -0,0 +1,138 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+
5
+import { Icon } from '../../../icons';
6
+import { Popover } from '../../../popover';
7
+
8
+type Props = {
9
+
10
+    /**
11
+     * Whether the element popup is expanded.
12
+     */
13
+    ariaExpanded?: boolean,
14
+
15
+    /**
16
+     * The id of the element this button icon controls.
17
+     */
18
+    ariaControls?: string,
19
+
20
+    /**
21
+     * Whether the element has a popup.
22
+     */
23
+    ariaHasPopup?: boolean,
24
+
25
+    /**
26
+     * Aria label for the Icon.
27
+     */
28
+    ariaLabel?: string,
29
+
30
+    /**
31
+     * The decorated component (ToolboxButton).
32
+     */
33
+    children: React$Node,
34
+
35
+    /**
36
+     * Icon of the button.
37
+     */
38
+    icon: Function,
39
+
40
+    /**
41
+     * Flag used for disabling the small icon.
42
+     */
43
+    iconDisabled: boolean,
44
+
45
+    /**
46
+     * The ID of the icon button.
47
+     */
48
+    iconId: string,
49
+
50
+    /**
51
+     * Popover close callback.
52
+     */
53
+    onPopoverClose: Function,
54
+
55
+    /**
56
+     * Popover open callback.
57
+     */
58
+    onPopoverOpen: Function,
59
+
60
+    /**
61
+     * The content that will be displayed inside the popover.
62
+     */
63
+    popoverContent: React$Node,
64
+
65
+    /**
66
+     * Additional styles.
67
+     */
68
+    styles?: Object,
69
+
70
+    /**
71
+     * Whether or not the popover is visible.
72
+     */
73
+    visible: boolean
74
+};
75
+
76
+declare var APP: Object;
77
+
78
+/**
79
+ * Displays the `ToolboxButtonWithIcon` component.
80
+ *
81
+ * @param {Object} props - Component's props.
82
+ * @returns {ReactElement}
83
+ */
84
+export default function ToolboxButtonWithIconPopup(props: Props) {
85
+    const {
86
+        ariaControls,
87
+        ariaExpanded,
88
+        ariaHasPopup,
89
+        ariaLabel,
90
+        children,
91
+        icon,
92
+        iconDisabled,
93
+        iconId,
94
+        onPopoverClose,
95
+        onPopoverOpen,
96
+        popoverContent,
97
+        styles,
98
+        visible
99
+    } = props;
100
+
101
+    const iconProps = {};
102
+
103
+    if (iconDisabled) {
104
+        iconProps.className
105
+            = 'settings-button-small-icon settings-button-small-icon--disabled';
106
+    } else {
107
+        iconProps.className = 'settings-button-small-icon';
108
+        iconProps.role = 'button';
109
+        iconProps.tabIndex = 0;
110
+        iconProps.ariaControls = ariaControls;
111
+        iconProps.ariaExpanded = ariaExpanded;
112
+        iconProps.containerId = iconId;
113
+    }
114
+
115
+
116
+    return (
117
+        <div
118
+            className = 'settings-button-container'
119
+            styles = { styles }>
120
+            {children}
121
+            <div className = 'settings-button-small-icon-container'>
122
+                <Popover
123
+                    content = { popoverContent }
124
+                    onPopoverClose = { onPopoverClose }
125
+                    onPopoverOpen = { onPopoverOpen }
126
+                    position = 'top'
127
+                    visible = { visible }>
128
+                    <Icon
129
+                        { ...iconProps }
130
+                        ariaHasPopup = { ariaHasPopup }
131
+                        ariaLabel = { ariaLabel }
132
+                        size = { 9 }
133
+                        src = { icon } />
134
+                </Popover>
135
+            </div>
136
+        </div>
137
+    );
138
+}

+ 37
- 27
react/features/reactions/components/web/ReactionsMenuButton.js Voir le fichier

@@ -1,12 +1,13 @@
1 1
 // @flow
2 2
 
3 3
 import React, { useCallback } from 'react';
4
+import { useSelector } from 'react-redux';
4 5
 
5 6
 import { isMobileBrowser } from '../../../base/environment/utils';
6 7
 import { translate } from '../../../base/i18n';
7 8
 import { IconArrowUp } from '../../../base/icons';
8 9
 import { connect } from '../../../base/redux';
9
-import { ToolboxButtonWithIcon } from '../../../base/toolbox/components';
10
+import ToolboxButtonWithIconPopup from '../../../base/toolbox/components/web/ToolboxButtonWithIconPopup';
10 11
 import { toggleReactionsMenuVisibility } from '../../actions.web';
11 12
 import { type ReactionEmojiProps } from '../../constants';
12 13
 import { getReactionsQueue, isReactionsEnabled } from '../../functions.any';
@@ -14,7 +15,7 @@ import { getReactionsMenuVisibility } from '../../functions.web';
14 15
 
15 16
 import RaiseHandButton from './RaiseHandButton';
16 17
 import ReactionEmoji from './ReactionEmoji';
17
-import ReactionsMenuPopup from './ReactionsMenuPopup';
18
+import ReactionsMenu from './ReactionsMenu';
18 19
 
19 20
 type Props = {
20 21
 
@@ -26,7 +27,7 @@ type Props = {
26 27
     /**
27 28
      * The button's key.
28 29
      */
29
-     buttonKey?: string,
30
+    buttonKey?: string,
30 31
 
31 32
     /**
32 33
      * Redux dispatch function.
@@ -84,38 +85,47 @@ function ReactionsMenuButton({
84 85
     reactionsQueue,
85 86
     t
86 87
 }: Props) {
88
+    const visible = useSelector(getReactionsMenuVisibility);
87 89
     const toggleReactionsMenu = useCallback(() => {
88 90
         dispatch(toggleReactionsMenuVisibility());
89 91
     }, [ dispatch ]);
90 92
 
93
+    const openReactionsMenu = useCallback(() => {
94
+        !visible && toggleReactionsMenu();
95
+    }, [ visible, toggleReactionsMenu ]);
96
+
97
+    const reactionsMenu = (<div className = 'reactions-menu-container'>
98
+        <ReactionsMenu />
99
+    </div>);
100
+
91 101
     return (
92 102
         <div className = 'reactions-menu-popup-container'>
93
-            <ReactionsMenuPopup>
94
-                {!_reactionsEnabled || isMobile ? (
95
-                    <RaiseHandButton
103
+            {!_reactionsEnabled || isMobile ? (
104
+                <RaiseHandButton
105
+                    buttonKey = { buttonKey }
106
+                    handleClick = { handleClick }
107
+                    notifyMode = { notifyMode } />)
108
+                : (
109
+                    <ToolboxButtonWithIconPopup
110
+                        ariaControls = 'reactions-menu-dialog'
111
+                        ariaExpanded = { isOpen }
112
+                        ariaHasPopup = { true }
113
+                        ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
96 114
                         buttonKey = { buttonKey }
97
-                        handleClick = { handleClick }
98
-                        notifyMode = { notifyMode } />)
99
-                    : (
100
-                        <ToolboxButtonWithIcon
101
-                            ariaControls = 'reactions-menu-dialog'
102
-                            ariaExpanded = { isOpen }
103
-                            ariaHasPopup = { true }
104
-                            ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
115
+                        icon = { IconArrowUp }
116
+                        iconDisabled = { false }
117
+                        iconId = 'reactions-menu-button'
118
+                        notifyMode = { notifyMode }
119
+                        onPopoverClose = { toggleReactionsMenu }
120
+                        onPopoverOpen = { openReactionsMenu }
121
+                        popoverContent = { reactionsMenu }
122
+                        visible = { visible }>
123
+                        <RaiseHandButton
105 124
                             buttonKey = { buttonKey }
106
-                            icon = { IconArrowUp }
107
-                            iconDisabled = { false }
108
-                            iconId = 'reactions-menu-button'
109
-                            iconTooltip = { t(`toolbar.${isOpen ? 'closeReactionsMenu' : 'openReactionsMenu'}`) }
110
-                            notifyMode = { notifyMode }
111
-                            onIconClick = { toggleReactionsMenu }>
112
-                            <RaiseHandButton
113
-                                buttonKey = { buttonKey }
114
-                                handleClick = { handleClick }
115
-                                notifyMode = { notifyMode } />
116
-                        </ToolboxButtonWithIcon>
117
-                    )}
118
-            </ReactionsMenuPopup>
125
+                            handleClick = { handleClick }
126
+                            notifyMode = { notifyMode } />
127
+                    </ToolboxButtonWithIconPopup>
128
+                )}
119 129
             {reactionsQueue.map(({ reaction, uid }, index) => (<ReactionEmoji
120 130
                 index = { index }
121 131
                 key = { uid }

+ 0
- 52
react/features/reactions/components/web/ReactionsMenuPopup.js Voir le fichier

@@ -1,52 +0,0 @@
1
-// @flow
2
-
3
-import InlineDialog from '@atlaskit/inline-dialog';
4
-import React, { useCallback } from 'react';
5
-import { useDispatch, useSelector } from 'react-redux';
6
-
7
-import { toggleReactionsMenuVisibility } from '../../actions.web';
8
-import { getReactionsMenuVisibility } from '../../functions.web';
9
-
10
-import ReactionsMenu from './ReactionsMenu';
11
-
12
-
13
-type Props = {
14
-
15
-    /**
16
-    * Component's children (the reactions menu button).
17
-    */
18
-    children: React$Node
19
-}
20
-
21
-/**
22
- * Popup with reactions menu.
23
- *
24
- * @returns {ReactElement}
25
- */
26
-function ReactionsMenuPopup({
27
-    children
28
-}: Props) {
29
-    /**
30
-    * Flag controlling the visibility of the popup.
31
-    */
32
-    const isOpen = useSelector(state => getReactionsMenuVisibility(state));
33
-
34
-    const dispatch = useDispatch();
35
-    const onClose = useCallback(() => {
36
-        dispatch(toggleReactionsMenuVisibility());
37
-    });
38
-
39
-    return (
40
-        <div className = 'reactions-menu-popup'>
41
-            <InlineDialog
42
-                content = { <ReactionsMenu /> }
43
-                isOpen = { isOpen }
44
-                onClose = { onClose }
45
-                placement = 'top'>
46
-                {children}
47
-            </InlineDialog>
48
-        </div>
49
-    );
50
-}
51
-
52
-export default ReactionsMenuPopup;

+ 0
- 1
react/features/reactions/components/web/index.js Voir le fichier

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

+ 8
- 1
react/features/toolbox/components/web/DialogPortal.js Voir le fichier

@@ -1,6 +1,6 @@
1 1
 // @flow
2 2
 
3
-import { useEffect, useState } from 'react';
3
+import { useEffect, useRef, useState } from 'react';
4 4
 import ReactDOM from 'react-dom';
5 5
 
6 6
 type Props = {
@@ -41,8 +41,11 @@ function DialogPortal({ children, className, style, getRef, setSize }: Props) {
41 41
     const [ portalTarget ] = useState(() => {
42 42
         const portalDiv = document.createElement('div');
43 43
 
44
+        portalDiv.style.visibility = 'hidden';
45
+
44 46
         return portalDiv;
45 47
     });
48
+    const timerRef = useRef();
46 49
 
47 50
     useEffect(() => {
48 51
         if (style) {
@@ -74,6 +77,10 @@ function DialogPortal({ children, className, style, getRef, setSize }: Props) {
74 77
 
75 78
             if (contentRect.width !== size.width || contentRect.height !== size.height) {
76 79
                 setSize && setSize(contentRect);
80
+                clearTimeout(timerRef.current);
81
+                timerRef.current = setTimeout(() => {
82
+                    portalTarget.style.visibility = 'visible';
83
+                }, 100);
77 84
             }
78 85
         });
79 86
 

Chargement…
Annuler
Enregistrer