瀏覽代碼

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
 .toolbox-button-wth-dialog .eYJELv {
48
 .toolbox-button-wth-dialog .eYJELv {
49
     max-height: initial;
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 查看文件

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
 $ringingZ: 300;
121
 $ringingZ: 300;
122
 $sideToolbarContainerZ: 300;
122
 $sideToolbarContainerZ: 300;
123
 $toolbarZ: 350;
123
 $toolbarZ: 350;
124
+$drawerZ: 351;
124
 $tooltipsZ: 401;
125
 $tooltipsZ: 401;
125
 $dropdownMaskZ: 900;
126
 $dropdownMaskZ: 900;
126
 $dropdownZ: 901;
127
 $dropdownZ: 901;

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

103
 @import 'e2ee';
103
 @import 'e2ee';
104
 @import 'responsive';
104
 @import 'responsive';
105
 @import 'connection-status';
105
 @import 'connection-status';
106
+@import 'drawer';
106
 
107
 
107
 /* Modules END */
108
 /* Modules END */

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

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
 .dial-in-numbers-list {
59
 .dial-in-numbers-list {
54
     margin-top: 20px;
60
     margin-top: 20px;
55
     font-size: 12px;
61
     font-size: 12px;

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

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

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

3
 import InlineDialog from '@atlaskit/inline-dialog';
3
 import InlineDialog from '@atlaskit/inline-dialog';
4
 import React, { Component } from 'react';
4
 import React, { Component } from 'react';
5
 
5
 
6
+import { Drawer, DrawerPortal } from '../../../toolbox/components/web';
7
+
6
 /**
8
 /**
7
  * A map of dialog positions, relative to trigger, to css classes used to
9
  * A map of dialog positions, relative to trigger, to css classes used to
8
  * manipulate elements for handling mouse events.
10
  * manipulate elements for handling mouse events.
66
      */
68
      */
67
     onPopoverOpen: Function,
69
     onPopoverOpen: Function,
68
 
70
 
71
+    /**
72
+     * Whether to display the Popover as a drawer.
73
+     */
74
+    overflowDrawer: boolean,
75
+
69
     /**
76
     /**
70
      * From which side of the dialog trigger the dialog should display. The
77
      * From which side of the dialog trigger the dialog should display. The
71
      * value will be passed to {@code InlineDialog}.
78
      * value will be passed to {@code InlineDialog}.
101
         id: ''
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
      * Initializes a new {@code Popover} instance.
117
      * Initializes a new {@code Popover} instance.
106
      *
118
      *
117
         // Bind event handlers so they are only bound once for every instance.
129
         // Bind event handlers so they are only bound once for every instance.
118
         this._onHideDialog = this._onHideDialog.bind(this);
130
         this._onHideDialog = this._onHideDialog.bind(this);
119
         this._onShowDialog = this._onShowDialog.bind(this);
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
      * @returns {ReactElement}
183
      * @returns {ReactElement}
127
      */
184
      */
128
     render() {
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
         return (
206
         return (
130
             <div
207
             <div
131
-                className = { this.props.className }
132
-                id = { this.props.id }
208
+                className = { className }
209
+                id = { id }
133
                 onMouseEnter = { this._onShowDialog }
210
                 onMouseEnter = { this._onShowDialog }
134
                 onMouseLeave = { this._onHideDialog }>
211
                 onMouseLeave = { this._onHideDialog }>
135
                 <InlineDialog
212
                 <InlineDialog
136
                     content = { this._renderContent() }
213
                     content = { this._renderContent() }
137
                     isOpen = { this.state.showDialog }
214
                     isOpen = { this.state.showDialog }
138
-                    position = { this.props.position }>
139
-                    { this.props.children }
215
+                    position = { position }>
216
+                    { children }
140
                 </InlineDialog>
217
                 </InlineDialog>
141
             </div>
218
             </div>
142
         );
219
         );
160
      * Displays the {@code InlineDialog} and calls any registered onPopoverOpen
237
      * Displays the {@code InlineDialog} and calls any registered onPopoverOpen
161
      * callbacks.
238
      * callbacks.
162
      *
239
      *
240
+     * @param {MouseEvent} event - The mouse event to intercept.
163
      * @private
241
      * @private
164
      * @returns {void}
242
      * @returns {void}
165
      */
243
      */
166
-    _onShowDialog() {
244
+    _onShowDialog(event) {
245
+        event.stopPropagation();
167
         if (!this.props.disablePopover) {
246
         if (!this.props.disablePopover) {
168
             this.setState({ showDialog: true });
247
             this.setState({ showDialog: true });
169
 
248
 

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

9
  * The aspect ratio of a tile in tile view.
9
  * The aspect ratio of a tile in tile view.
10
  */
10
  */
11
 export const TILE_ASPECT_RATIO = 16 / 9;
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
 import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
3
 import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
4
 import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
4
 import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
5
 import { StateListenerRegistry, equals } from '../base/redux';
5
 import { StateListenerRegistry, equals } from '../base/redux';
6
+import { setOverflowDrawer } from '../toolbox/actions.web';
6
 import { getCurrentLayout, getTileViewGridDimensions, shouldDisplayTileView, LAYOUTS } from '../video-layout';
7
 import { getCurrentLayout, getTileViewGridDimensions, shouldDisplayTileView, LAYOUTS } from '../video-layout';
7
 
8
 
8
 import { setHorizontalViewDimensions, setTileViewDimensions } from './actions.web';
9
 import { setHorizontalViewDimensions, setTileViewDimensions } from './actions.web';
10
+import { DISPLAY_DRAWER_THRESHOLD } from './constants';
9
 
11
 
10
 /**
12
 /**
11
  * Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
13
  * Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
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
 
79
 
80
         return (
80
         return (
81
             <div className = 'dial-in-number'>
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
                     </span>
91
                     </span>
86
                     <span className = 'spacer'>&nbsp;</span>
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
                     </span>
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
                 <a
103
                 <a
102
                     className = 'dial-in-copy'
104
                     className = 'dial-in-copy'
103
                     onClick = { this._onCopyText }>
105
                     onClick = { this._onCopyText }>

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

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

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

29
  */
29
  */
30
 export const SET_FULL_SCREEN = 'SET_FULL_SCREEN';
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
  * The type of the (redux) action which shows/hides the OverflowMenu.
38
  * The type of the (redux) action which shows/hides the OverflowMenu.
34
  *
39
  *

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

4
 
4
 
5
 import {
5
 import {
6
     FULL_SCREEN_CHANGED,
6
     FULL_SCREEN_CHANGED,
7
-    SET_FULL_SCREEN
7
+    SET_FULL_SCREEN,
8
+    SET_OVERFLOW_DRAWER
8
 } from './actionTypes';
9
 } from './actionTypes';
9
 import {
10
 import {
10
     clearToolboxTimeout,
11
     clearToolboxTimeout,
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 查看文件

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 查看文件

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
 import { createToolbarEvent, sendAnalytics } from '../../../analytics';
6
 import { createToolbarEvent, sendAnalytics } from '../../../analytics';
7
 import { translate } from '../../../base/i18n';
7
 import { translate } from '../../../base/i18n';
8
 import { IconMenuThumb } from '../../../base/icons';
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
 import ToolbarButton from './ToolbarButton';
13
 import ToolbarButton from './ToolbarButton';
11
 
14
 
12
 /**
15
 /**
29
      */
32
      */
30
     onVisibilityChange: Function,
33
     onVisibilityChange: Function,
31
 
34
 
35
+    /**
36
+     * Whether to display the OverflowMenu as a drawer.
37
+     */
38
+    overflowDrawer: boolean,
39
+
32
     /**
40
     /**
33
      * Invoked to obtain translated strings.
41
      * Invoked to obtain translated strings.
34
      */
42
      */
63
      * @returns {ReactElement}
71
      * @returns {ReactElement}
64
      */
72
      */
65
     render() {
73
     render() {
66
-        const { children, isOpen, t } = this.props;
74
+        const { children, isOpen, overflowDrawer } = this.props;
67
 
75
 
68
         return (
76
         return (
69
             <div className = 'toolbox-button-wth-dialog'>
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
             </div>
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
     _onCloseDialog: () => void;
126
     _onCloseDialog: () => void;
88
 
127
 
89
     /**
128
     /**
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
 export { default as VideoSettingsButton } from './VideoSettingsButton';
2
 export { default as VideoSettingsButton } from './VideoSettingsButton';
3
 export { default as ToolbarButton } from './ToolbarButton';
3
 export { default as ToolbarButton } from './ToolbarButton';
4
 export { default as Toolbox } from './Toolbox';
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
 import {
5
 import {
6
     CLEAR_TOOLBOX_TIMEOUT,
6
     CLEAR_TOOLBOX_TIMEOUT,
7
     FULL_SCREEN_CHANGED,
7
     FULL_SCREEN_CHANGED,
8
+    SET_OVERFLOW_DRAWER,
8
     SET_OVERFLOW_MENU_VISIBLE,
9
     SET_OVERFLOW_MENU_VISIBLE,
9
     SET_TOOLBAR_HOVERED,
10
     SET_TOOLBAR_HOVERED,
10
     SET_TOOLBOX_ALWAYS_VISIBLE,
11
     SET_TOOLBOX_ALWAYS_VISIBLE,
25
  *     alwaysVisible: boolean,
26
  *     alwaysVisible: boolean,
26
  *     enabled: boolean,
27
  *     enabled: boolean,
27
  *     hovered: boolean,
28
  *     hovered: boolean,
29
+ *     overflowDrawer: boolean,
28
  *     overflowMenuVisible: boolean,
30
  *     overflowMenuVisible: boolean,
29
  *     timeoutID: number,
31
  *     timeoutID: number,
30
  *     timeoutMS: number,
32
  *     timeoutMS: number,
79
          */
81
          */
80
         hovered: false,
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
          * The indicator which determines whether the OverflowMenu is visible.
92
          * The indicator which determines whether the OverflowMenu is visible.
84
          *
93
          *
103
         timeoutMS,
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
          * @type {boolean}
117
          * @type {boolean}
109
          */
118
          */
127
                 fullScreen: action.fullScreen
136
                 fullScreen: action.fullScreen
128
             };
137
             };
129
 
138
 
139
+        case SET_OVERFLOW_DRAWER:
140
+            return {
141
+                ...state,
142
+                overflowDrawer: action.displayAsDrawer
143
+            };
144
+
130
         case SET_OVERFLOW_MENU_VISIBLE:
145
         case SET_OVERFLOW_MENU_VISIBLE:
131
             return {
146
             return {
132
                 ...state,
147
                 ...state,

Loading…
取消
儲存