瀏覽代碼

feat(overflow): Add responsive drawer at small screen width.

* Implement opening toolbar and participant overflows as drawers when below certain width.
* Fix dial-in copy button displaying incorrectly.
j8
Mihai-Andrei Uscat 4 年之前
父節點
當前提交
c752ea13f1

+ 16
- 0
css/_atlaskit_overrides.scss 查看文件

@@ -48,3 +48,19 @@
48 48
 .toolbox-button-wth-dialog .eYJELv {
49 49
     max-height: initial;
50 50
 }
51
+
52
+/**
53
+ * Override @atlaskit/InlineDialog styling for the overflowmenu so it displays
54
+ * a scrollable list of elements at small screen widths.
55
+ */
56
+.sc-eNQAEJ {
57
+    overflow-y: auto;
58
+}
59
+
60
+/**
61
+ * Keep overflow menu within screen vertical bounds and make it scrollable.
62
+ */
63
+.toolbox-button-wth-dialog .sc-ckVGcZ.fdAqDG > :first-child {
64
+    max-height: calc(100vh - #{$newToolbarSizeWithPadding} - 16px);
65
+    overflow-y: auto;
66
+}

+ 124
- 0
css/_drawer.scss 查看文件

@@ -0,0 +1,124 @@
1
+.drawer-portal {
2
+    position: absolute;
3
+    left: 0;
4
+    right: 0;
5
+    bottom: 0;
6
+    z-index: $drawerZ;
7
+}
8
+
9
+.drawer-menu {
10
+    padding: 12px 16px;
11
+    max-height: 50vh;
12
+    background: #242528;
13
+    border-radius: 16px 16px 0 0;
14
+    overflow-y: auto;
15
+
16
+    &.expanded {
17
+        max-height: 80vh;
18
+    }
19
+
20
+    .drawer-toggle {
21
+        display: flex;
22
+        justify-content: center;
23
+        align-items: center;
24
+        height: 44px;
25
+        cursor: pointer;
26
+
27
+        &:hover {
28
+            background-color: $overflowMenuItemHoverBG;
29
+        }
30
+
31
+        svg, path {
32
+            fill: #b8c7e0;
33
+        }
34
+    }
35
+
36
+    .popupmenu {
37
+        margin: auto;
38
+        width: 100%;
39
+    }
40
+
41
+    .popupmenu__item {
42
+        height: 48px;
43
+    }
44
+
45
+    &#{&} .overflow-menu {
46
+        margin: auto;
47
+        font-size: 1.2em;
48
+        list-style-type: none;
49
+        padding: 0;
50
+
51
+        .overflow-menu-item {
52
+            box-sizing: border-box;
53
+            height: 48px;
54
+            padding: 12px 16px;
55
+
56
+            align-items: center;
57
+            color: $overflowMenuItemColor;
58
+            cursor: pointer;
59
+            display: flex;
60
+            font-size: 14px;
61
+
62
+            div {
63
+                display: flex;
64
+                flex-direction: row;
65
+                align-items: center;
66
+            }
67
+
68
+            &:hover {
69
+                background-color: $overflowMenuItemHoverBG;
70
+                color: $overflowMenuItemHoverColor;
71
+            }
72
+
73
+            &.unclickable {
74
+                cursor: default;
75
+            }
76
+            &.unclickable:hover {
77
+                background: inherit;
78
+            }
79
+            &.disabled {
80
+                cursor: initial;
81
+                color: #3b475c;
82
+            }
83
+        }
84
+
85
+        .beta-tag {
86
+            background: $overflowMenuItemColor;
87
+            border-radius: 2px;
88
+            color: $overflowMenuBG;
89
+            font-size: 11px;
90
+            font-weight: bold;
91
+            margin-left: 8px;
92
+            padding: 0 6px;
93
+        }
94
+
95
+        .overflow-menu-item-icon {
96
+            margin-right: 10px;
97
+
98
+            i {
99
+                display: inline;
100
+                font-size: 24px;
101
+            }
102
+
103
+            i:hover {
104
+                background-color: initial;
105
+            }
106
+
107
+            img {
108
+                max-width: 24px;
109
+                max-height: 24px;
110
+            }
111
+
112
+            svg {
113
+                fill: #B8C7E0 !important;
114
+            }
115
+        }
116
+
117
+        .profile-text {
118
+            max-width: 150px;
119
+            text-overflow: ellipsis;
120
+            overflow: hidden;
121
+            white-space: nowrap;
122
+        }
123
+    }
124
+}

+ 1
- 0
css/_variables.scss 查看文件

@@ -121,6 +121,7 @@ $poweredByZ: 100;
121 121
 $ringingZ: 300;
122 122
 $sideToolbarContainerZ: 300;
123 123
 $toolbarZ: 350;
124
+$drawerZ: 351;
124 125
 $tooltipsZ: 401;
125 126
 $dropdownMaskZ: 900;
126 127
 $dropdownZ: 901;

+ 1
- 0
css/main.scss 查看文件

@@ -103,5 +103,6 @@ $flagsImagePath: "../images/";
103 103
 @import 'e2ee';
104 104
 @import 'responsive';
105 105
 @import 'connection-status';
106
+@import 'drawer';
106 107
 
107 108
 /* Modules END */

+ 6
- 0
css/modals/invite/_info.scss 查看文件

@@ -50,6 +50,12 @@
50 50
     }
51 51
 }
52 52
 
53
+.dial-in-number {
54
+    display: flex;
55
+    justify-content: space-between;
56
+    padding-right: 8px;
57
+}
58
+
53 59
 .dial-in-numbers-list {
54 60
     margin-top: 20px;
55 61
     font-size: 12px;

+ 0
- 1
css/modals/invite/_invite_more.scss 查看文件

@@ -135,7 +135,6 @@
135 135
             .dial-in-copy {
136 136
                 display: inline-block;
137 137
                 vertical-align: middle;
138
-                margin-left: 21px;
139 138
                 cursor: pointer;
140 139
             }
141 140
         }

+ 84
- 5
react/features/base/popover/components/Popover.web.js 查看文件

@@ -3,6 +3,8 @@
3 3
 import InlineDialog from '@atlaskit/inline-dialog';
4 4
 import React, { Component } from 'react';
5 5
 
6
+import { Drawer, DrawerPortal } from '../../../toolbox/components/web';
7
+
6 8
 /**
7 9
  * A map of dialog positions, relative to trigger, to css classes used to
8 10
  * manipulate elements for handling mouse events.
@@ -66,6 +68,11 @@ type Props = {
66 68
      */
67 69
     onPopoverOpen: Function,
68 70
 
71
+    /**
72
+     * Whether to display the Popover as a drawer.
73
+     */
74
+    overflowDrawer: boolean,
75
+
69 76
     /**
70 77
      * From which side of the dialog trigger the dialog should display. The
71 78
      * value will be passed to {@code InlineDialog}.
@@ -101,6 +108,11 @@ class Popover extends Component<Props, State> {
101 108
         id: ''
102 109
     };
103 110
 
111
+    /**
112
+     * Reference to the Popover that is meant to open as a drawer.
113
+     */
114
+    _drawerContainerRef: Object;
115
+
104 116
     /**
105 117
      * Initializes a new {@code Popover} instance.
106 118
      *
@@ -117,6 +129,51 @@ class Popover extends Component<Props, State> {
117 129
         // Bind event handlers so they are only bound once for every instance.
118 130
         this._onHideDialog = this._onHideDialog.bind(this);
119 131
         this._onShowDialog = this._onShowDialog.bind(this);
132
+        this._drawerContainerRef = React.createRef();
133
+    }
134
+
135
+    /**
136
+     * Sets up an event listener to open a drawer when clicking, rather than entering the
137
+     * overflow area.
138
+     *
139
+     * TODO: This should be done by setting an {@code onClick} handler on the div, but for some
140
+     * reason that doesn't seem to work whatsoever.
141
+     *
142
+     * @inheritdoc
143
+     * @returns {void}
144
+     */
145
+    componentDidMount() {
146
+        if (this._drawerContainerRef && this._drawerContainerRef.current) {
147
+            this._drawerContainerRef.current.addEventListener('click', this._onShowDialog);
148
+        }
149
+    }
150
+
151
+    /**
152
+     * Removes the listener set up in the {@code componentDidMount} method.
153
+     *
154
+     * @inheritdoc
155
+     * @returns {void}
156
+     */
157
+    componentWillUnmount() {
158
+        if (this._drawerContainerRef && this._drawerContainerRef.current) {
159
+            this._drawerContainerRef.current.removeEventListener('click', this._onShowDialog);
160
+        }
161
+    }
162
+
163
+    /**
164
+     * Implements React Component's componentDidUpdate.
165
+     *
166
+     * @inheritdoc
167
+     */
168
+    componentDidUpdate(prevProps: Props) {
169
+        if (prevProps.overflowDrawer !== this.props.overflowDrawer) {
170
+            // Make sure the listeners are set up when resizing the screen past the drawer threshold.
171
+            if (this.props.overflowDrawer) {
172
+                this.componentDidMount();
173
+            } else {
174
+                this.componentWillUnmount();
175
+            }
176
+        }
120 177
     }
121 178
 
122 179
     /**
@@ -126,17 +183,37 @@ class Popover extends Component<Props, State> {
126 183
      * @returns {ReactElement}
127 184
      */
128 185
     render() {
186
+        const { children, className, content, id, overflowDrawer, position } = this.props;
187
+
188
+        if (overflowDrawer) {
189
+            return (
190
+                <div
191
+                    className = { className }
192
+                    id = { id }
193
+                    ref = { this._drawerContainerRef }>
194
+                    { children }
195
+                    <DrawerPortal>
196
+                        <Drawer
197
+                            isOpen = { this.state.showDialog }
198
+                            onClose = { this._onHideDialog }>
199
+                            { content }
200
+                        </Drawer>
201
+                    </DrawerPortal>
202
+                </div>
203
+            );
204
+        }
205
+
129 206
         return (
130 207
             <div
131
-                className = { this.props.className }
132
-                id = { this.props.id }
208
+                className = { className }
209
+                id = { id }
133 210
                 onMouseEnter = { this._onShowDialog }
134 211
                 onMouseLeave = { this._onHideDialog }>
135 212
                 <InlineDialog
136 213
                     content = { this._renderContent() }
137 214
                     isOpen = { this.state.showDialog }
138
-                    position = { this.props.position }>
139
-                    { this.props.children }
215
+                    position = { position }>
216
+                    { children }
140 217
                 </InlineDialog>
141 218
             </div>
142 219
         );
@@ -160,10 +237,12 @@ class Popover extends Component<Props, State> {
160 237
      * Displays the {@code InlineDialog} and calls any registered onPopoverOpen
161 238
      * callbacks.
162 239
      *
240
+     * @param {MouseEvent} event - The mouse event to intercept.
163 241
      * @private
164 242
      * @returns {void}
165 243
      */
166
-    _onShowDialog() {
244
+    _onShowDialog(event) {
245
+        event.stopPropagation();
167 246
         if (!this.props.disablePopover) {
168 247
             this.setState({ showDialog: true });
169 248
 

+ 5
- 0
react/features/filmstrip/constants.js 查看文件

@@ -9,3 +9,8 @@ export const FILMSTRIP_SIZE = 90;
9 9
  * The aspect ratio of a tile in tile view.
10 10
  */
11 11
 export const TILE_ASPECT_RATIO = 16 / 9;
12
+
13
+/**
14
+ * Width below which the overflow menu(s) will be displayed as drawer(s).
15
+ */
16
+export const DISPLAY_DRAWER_THRESHOLD = 512;

+ 11
- 0
react/features/filmstrip/subscriber.web.js 查看文件

@@ -3,9 +3,11 @@
3 3
 import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
4 4
 import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
5 5
 import { StateListenerRegistry, equals } from '../base/redux';
6
+import { setOverflowDrawer } from '../toolbox/actions.web';
6 7
 import { getCurrentLayout, getTileViewGridDimensions, shouldDisplayTileView, LAYOUTS } from '../video-layout';
7 8
 
8 9
 import { setHorizontalViewDimensions, setTileViewDimensions } from './actions.web';
10
+import { DISPLAY_DRAWER_THRESHOLD } from './constants';
9 11
 
10 12
 /**
11 13
  * Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
@@ -123,3 +125,12 @@ StateListenerRegistry.register(
123 125
             );
124 126
         }
125 127
     });
128
+
129
+/**
130
+ * Listens for changes in the client width to determine whether the overflow menu(s) should be displayed as drawers.
131
+ */
132
+StateListenerRegistry.register(
133
+    /* selector */ state => state['features/base/responsive-ui'].clientWidth < DISPLAY_DRAWER_THRESHOLD,
134
+    /* listener */ (widthBelowThreshold, store) => {
135
+        store.dispatch(setOverflowDrawer(widthBelowThreshold));
136
+    });

+ 18
- 16
react/features/invite/components/add-people-dialog/web/DialInNumber.js 查看文件

@@ -79,25 +79,27 @@ class DialInNumber extends Component<Props> {
79 79
 
80 80
         return (
81 81
             <div className = 'dial-in-number'>
82
-                <span className = 'phone-number'>
83
-                    <span className = 'info-label'>
84
-                        { t('info.dialInNumber') }
82
+                <div>
83
+                    <span className = 'phone-number'>
84
+                        <span className = 'info-label'>
85
+                            { t('info.dialInNumber') }
86
+                        </span>
87
+                        <span className = 'spacer'>&nbsp;</span>
88
+                        <span className = 'info-value'>
89
+                            { phoneNumber }
90
+                        </span>
85 91
                     </span>
86 92
                     <span className = 'spacer'>&nbsp;</span>
87
-                    <span className = 'info-value'>
88
-                        { phoneNumber }
93
+                    <span className = 'conference-id'>
94
+                        <span className = 'info-label'>
95
+                            { t('info.dialInConferenceID') }
96
+                        </span>
97
+                        <span className = 'spacer'>&nbsp;</span>
98
+                        <span className = 'info-value'>
99
+                            { `${_formatConferenceIDPin(conferenceID)}#` }
100
+                        </span>
89 101
                     </span>
90
-                </span>
91
-                <span className = 'spacer'>&nbsp;</span>
92
-                <span className = 'conference-id'>
93
-                    <span className = 'info-label'>
94
-                        { t('info.dialInConferenceID') }
95
-                    </span>
96
-                    <span className = 'spacer'>&nbsp;</span>
97
-                    <span className = 'info-value'>
98
-                        { `${_formatConferenceIDPin(conferenceID)}#` }
99
-                    </span>
100
-                </span>
102
+                </div>
101 103
                 <a
102 104
                     className = 'dial-in-copy'
103 105
                     onClick = { this._onCopyText }>

+ 10
- 9
react/features/remote-video-menu/components/web/RemoteVideoMenuTriggerButton.js 查看文件

@@ -60,6 +60,11 @@ type Props = {
60 60
      */
61 61
     _menuPosition: string,
62 62
 
63
+    /**
64
+     * Whether to display the Popover as a drawer.
65
+     */
66
+    _overflowDrawer: boolean,
67
+
63 68
     /**
64 69
      * The current state of the participant's remote control session.
65 70
      */
@@ -122,6 +127,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
122 127
         return (
123 128
             <Popover
124 129
                 content = { content }
130
+                overflowDrawer = { this.props._overflowDrawer }
125 131
                 position = { this.props._menuPosition }>
126 132
                 <span
127 133
                     className = 'popover-trigger remote-video-menu-trigger'>
@@ -237,14 +243,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
237 243
  * @param {Object} state - The Redux state.
238 244
  * @param {Object} ownProps - The own props of the component.
239 245
  * @private
240
- * @returns {{
241
- *     _isAudioMuted: boolean,
242
- *     _isModerator: boolean,
243
- *     _disableKick: boolean,
244
- *     _disableRemoteMute: boolean,
245
- *     _menuPosition: string,
246
- *     _remoteControlState: number
247
- * }}
246
+ * @returns {Props}
248 247
  */
249 248
 function _mapStateToProps(state, ownProps) {
250 249
     const { participantID } = ownProps;
@@ -259,6 +258,7 @@ function _mapStateToProps(state, ownProps) {
259 258
     const { active, controller } = state['features/remote-control'];
260 259
     const { requestedParticipant, controlled } = controller;
261 260
     const activeParticipant = requestedParticipant || controlled;
261
+    const { overflowDrawer } = state['features/toolbox'];
262 262
 
263 263
     if (_supportsRemoteControl
264 264
             && ((!active && !_isRemoteControlSessionActive) || activeParticipant === participantID)) {
@@ -291,7 +291,8 @@ function _mapStateToProps(state, ownProps) {
291 291
         _disableKick: Boolean(disableKick),
292 292
         _disableRemoteMute: Boolean(disableRemoteMute),
293 293
         _remoteControlState,
294
-        _menuPosition
294
+        _menuPosition,
295
+        _overflowDrawer: overflowDrawer
295 296
     };
296 297
 }
297 298
 

+ 5
- 0
react/features/toolbox/actionTypes.js 查看文件

@@ -29,6 +29,11 @@ export const FULL_SCREEN_CHANGED = 'FULL_SCREEN_CHANGED';
29 29
  */
30 30
 export const SET_FULL_SCREEN = 'SET_FULL_SCREEN';
31 31
 
32
+/**
33
+ * The type of the redux action that toggles whether the overflow menu(s) should be shown as drawers.
34
+ */
35
+export const SET_OVERFLOW_DRAWER = 'SET_OVERFLOW_DRAWER';
36
+
32 37
 /**
33 38
  * The type of the (redux) action which shows/hides the OverflowMenu.
34 39
  *

+ 18
- 1
react/features/toolbox/actions.web.js 查看文件

@@ -4,7 +4,8 @@ import type { Dispatch } from 'redux';
4 4
 
5 5
 import {
6 6
     FULL_SCREEN_CHANGED,
7
-    SET_FULL_SCREEN
7
+    SET_FULL_SCREEN,
8
+    SET_OVERFLOW_DRAWER
8 9
 } from './actionTypes';
9 10
 import {
10 11
     clearToolboxTimeout,
@@ -143,3 +144,19 @@ export function showToolbox(timeout: number = 0): Object {
143 144
         }
144 145
     };
145 146
 }
147
+
148
+/**
149
+ * Signals a request to display overflow as drawer.
150
+ *
151
+ * @param {boolean} displayAsDrawer - True to display overflow as drawer, false to preserve original behaviour.
152
+ * @returns {{
153
+ *     type: SET_OVERFLOW_DRAWER,
154
+ *     displayAsDrawer: boolean
155
+ * }}
156
+ */
157
+export function setOverflowDrawer(displayAsDrawer: boolean) {
158
+    return {
159
+        type: SET_OVERFLOW_DRAWER,
160
+        displayAsDrawer
161
+    };
162
+}

+ 90
- 0
react/features/toolbox/components/web/Drawer.js 查看文件

@@ -0,0 +1,90 @@
1
+// @flow
2
+
3
+import React, { useEffect, useRef, useState } from 'react';
4
+
5
+import { Icon, IconArrowUp, IconArrowDown } from '../../../base/icons';
6
+
7
+type Props = {
8
+
9
+    /**
10
+     * Whether the drawer should have a button that expands its size or not.
11
+     */
12
+    canExpand: ?boolean,
13
+
14
+    /**
15
+     * The component(s) to be displayed within the drawer menu.
16
+     */
17
+    children: React$Node,
18
+
19
+    /**
20
+     Whether the drawer should be shown or not.
21
+     */
22
+    isOpen: boolean,
23
+
24
+    /**
25
+     Function that hides the drawer.
26
+     */
27
+    onClose: Function
28
+};
29
+
30
+/**
31
+ * Component that displays the mobile friendly drawer on web.
32
+ *
33
+ * @returns {ReactElement}
34
+ */
35
+function Drawer({
36
+    canExpand,
37
+    children,
38
+    isOpen,
39
+    onClose }: Props) {
40
+    const [ expanded, setExpanded ] = useState(false);
41
+    const drawerRef: Object = useRef(null);
42
+
43
+    /**
44
+     * Closes the drawer when clicking outside of it.
45
+     *
46
+     * @param {Event} event - Mouse down event object.
47
+     * @returns {void}
48
+     */
49
+    function handleOutsideClick(event: MouseEvent) {
50
+        if (drawerRef.current && !drawerRef.current.contains(event.target)) {
51
+            onClose();
52
+        }
53
+    }
54
+
55
+    useEffect(() => {
56
+        window.addEventListener('mousedown', handleOutsideClick);
57
+
58
+        return () => {
59
+            window.removeEventListener('mousedown', handleOutsideClick);
60
+        };
61
+    }, [ drawerRef ]);
62
+
63
+    /**
64
+     * Toggles the menu state between expanded/collapsed.
65
+     *
66
+     * @returns {void}
67
+     */
68
+    function toggleExpanded() {
69
+        setExpanded(!expanded);
70
+    }
71
+
72
+    return (
73
+        isOpen ? (
74
+            <div
75
+                className = { `drawer-menu${expanded ? ' expanded' : ''}` }
76
+                ref = { drawerRef }>
77
+                {canExpand && (
78
+                    <div
79
+                        className = 'drawer-toggle'
80
+                        onClick = { toggleExpanded }>
81
+                        <Icon src = { expanded ? IconArrowDown : IconArrowUp } />
82
+                    </div>
83
+                )}
84
+                {children}
85
+            </div>
86
+        ) : null
87
+    );
88
+}
89
+
90
+export default Drawer;

+ 47
- 0
react/features/toolbox/components/web/DrawerPortal.js 查看文件

@@ -0,0 +1,47 @@
1
+// @flow
2
+
3
+import { useEffect, useState } from 'react';
4
+import ReactDOM from 'react-dom';
5
+
6
+type Props = {
7
+
8
+    /**
9
+     * The component(s) to be displayed within the drawer portal.
10
+     */
11
+    children: React$Node
12
+};
13
+
14
+/**
15
+ * Component meant to render a drawer at the bottom of the screen,
16
+ * by creating a portal containing the component's children.
17
+ *
18
+ * @returns {ReactElement}
19
+ */
20
+function DrawerPortal({ children }: Props) {
21
+    const [ portalTarget ] = useState(() => {
22
+        const portalDiv = document.createElement('div');
23
+
24
+        portalDiv.className = 'drawer-portal';
25
+
26
+        return portalDiv;
27
+    });
28
+
29
+    useEffect(() => {
30
+        if (document.body) {
31
+            document.body.appendChild(portalTarget);
32
+        }
33
+
34
+        return () => {
35
+            if (document.body) {
36
+                document.body.removeChild(portalTarget);
37
+            }
38
+        };
39
+    }, []);
40
+
41
+    return ReactDOM.createPortal(
42
+      children,
43
+      portalTarget
44
+    );
45
+}
46
+
47
+export default DrawerPortal;

+ 69
- 15
react/features/toolbox/components/web/OverflowMenuButton.js 查看文件

@@ -6,7 +6,10 @@ import React, { Component } from 'react';
6 6
 import { createToolbarEvent, sendAnalytics } from '../../../analytics';
7 7
 import { translate } from '../../../base/i18n';
8 8
 import { IconMenuThumb } from '../../../base/icons';
9
+import { connect } from '../../../base/redux';
9 10
 
11
+import Drawer from './Drawer';
12
+import DrawerPortal from './DrawerPortal';
10 13
 import ToolbarButton from './ToolbarButton';
11 14
 
12 15
 /**
@@ -29,6 +32,11 @@ type Props = {
29 32
      */
30 33
     onVisibilityChange: Function,
31 34
 
35
+    /**
36
+     * Whether to display the OverflowMenu as a drawer.
37
+     */
38
+    overflowDrawer: boolean,
39
+
32 40
     /**
33 41
      * Invoked to obtain translated strings.
34 42
      */
@@ -63,27 +71,58 @@ class OverflowMenuButton extends Component<Props> {
63 71
      * @returns {ReactElement}
64 72
      */
65 73
     render() {
66
-        const { children, isOpen, t } = this.props;
74
+        const { children, isOpen, overflowDrawer } = this.props;
67 75
 
68 76
         return (
69 77
             <div className = 'toolbox-button-wth-dialog'>
70
-                <InlineDialog
71
-                    content = { children }
72
-                    isOpen = { isOpen }
73
-                    onClose = { this._onCloseDialog }
74
-                    position = { 'top right' }>
75
-                    <ToolbarButton
76
-                        accessibilityLabel =
77
-                            { t('toolbar.accessibilityLabel.moreActions') }
78
-                        icon = { IconMenuThumb }
79
-                        onClick = { this._onToggleDialogVisibility }
80
-                        toggled = { isOpen }
81
-                        tooltip = { t('toolbar.moreActions') } />
82
-                </InlineDialog>
78
+                {
79
+                    overflowDrawer ? (
80
+                        <>
81
+                            {this._renderToolbarButton()}
82
+                            <DrawerPortal>
83
+                                <Drawer
84
+                                    canExpand = { true }
85
+                                    isOpen = { isOpen }
86
+                                    onClose = { this._onCloseDialog }>
87
+                                    {children}
88
+                                </Drawer>
89
+                            </DrawerPortal>
90
+                        </>
91
+                    ) : (
92
+                        <InlineDialog
93
+                            content = { children }
94
+                            isOpen = { isOpen }
95
+                            onClose = { this._onCloseDialog }
96
+                            position = { 'top right' }>
97
+                            {this._renderToolbarButton()}
98
+                        </InlineDialog>
99
+                    )
100
+                }
83 101
             </div>
84 102
         );
85 103
     }
86 104
 
105
+    _renderToolbarButton: () => React$Node;
106
+
107
+    /**
108
+     * Renders the actual toolbar overflow menu button.
109
+     *
110
+     * @returns {ReactElement}
111
+     */
112
+    _renderToolbarButton() {
113
+        const { isOpen, t } = this.props;
114
+
115
+        return (
116
+            <ToolbarButton
117
+                accessibilityLabel =
118
+                    { t('toolbar.accessibilityLabel.moreActions') }
119
+                icon = { IconMenuThumb }
120
+                onClick = { this._onToggleDialogVisibility }
121
+                toggled = { isOpen }
122
+                tooltip = { t('toolbar.moreActions') } />
123
+        );
124
+    }
125
+
87 126
     _onCloseDialog: () => void;
88 127
 
89 128
     /**
@@ -113,4 +152,19 @@ class OverflowMenuButton extends Component<Props> {
113 152
     }
114 153
 }
115 154
 
116
-export default translate(OverflowMenuButton);
155
+/**
156
+ * Maps (parts of) the Redux state to the associated props for the
157
+ * {@code OverflowMenuButton} component.
158
+ *
159
+ * @param {Object} state - The Redux state.
160
+ * @returns {Props}
161
+ */
162
+function mapStateToProps(state) {
163
+    const { overflowDrawer } = state['features/toolbox'];
164
+
165
+    return {
166
+        overflowDrawer
167
+    };
168
+}
169
+
170
+export default translate(connect(mapStateToProps)(OverflowMenuButton));

+ 2
- 0
react/features/toolbox/components/web/index.js 查看文件

@@ -2,3 +2,5 @@ export { default as AudioSettingsButton } from './AudioSettingsButton';
2 2
 export { default as VideoSettingsButton } from './VideoSettingsButton';
3 3
 export { default as ToolbarButton } from './ToolbarButton';
4 4
 export { default as Toolbox } from './Toolbox';
5
+export { default as Drawer } from './Drawer';
6
+export { default as DrawerPortal } from './DrawerPortal';

+ 16
- 1
react/features/toolbox/reducer.js 查看文件

@@ -5,6 +5,7 @@ import { ReducerRegistry, set } from '../base/redux';
5 5
 import {
6 6
     CLEAR_TOOLBOX_TIMEOUT,
7 7
     FULL_SCREEN_CHANGED,
8
+    SET_OVERFLOW_DRAWER,
8 9
     SET_OVERFLOW_MENU_VISIBLE,
9 10
     SET_TOOLBAR_HOVERED,
10 11
     SET_TOOLBOX_ALWAYS_VISIBLE,
@@ -25,6 +26,7 @@ declare var interfaceConfig: Object;
25 26
  *     alwaysVisible: boolean,
26 27
  *     enabled: boolean,
27 28
  *     hovered: boolean,
29
+ *     overflowDrawer: boolean,
28 30
  *     overflowMenuVisible: boolean,
29 31
  *     timeoutID: number,
30 32
  *     timeoutMS: number,
@@ -79,6 +81,13 @@ function _getInitialState() {
79 81
          */
80 82
         hovered: false,
81 83
 
84
+        /**
85
+         * The indicator which determines whether the overflow menu(s) are to be displayed as drawers.
86
+         *
87
+         * @type {boolean}
88
+         */
89
+        overflowDrawer: false,
90
+
82 91
         /**
83 92
          * The indicator which determines whether the OverflowMenu is visible.
84 93
          *
@@ -103,7 +112,7 @@ function _getInitialState() {
103 112
         timeoutMS,
104 113
 
105 114
         /**
106
-         * The indicator which determines whether the Toolbox is visible.
115
+         * The indicator that determines whether the Toolbox is visible.
107 116
          *
108 117
          * @type {boolean}
109 118
          */
@@ -127,6 +136,12 @@ ReducerRegistry.register(
127 136
                 fullScreen: action.fullScreen
128 137
             };
129 138
 
139
+        case SET_OVERFLOW_DRAWER:
140
+            return {
141
+                ...state,
142
+                overflowDrawer: action.displayAsDrawer
143
+            };
144
+
130 145
         case SET_OVERFLOW_MENU_VISIBLE:
131 146
             return {
132 147
                 ...state,

Loading…
取消
儲存