瀏覽代碼

feat: convert device selection modal to use AtlasKit Dropdown

Instead of using AtlasKit Single-Select, use Dropdown. Dropdown
differs in that an icon can be specified for the trigger element,
whereas Single-Select currently supports icons for all elements,
and Dropdown can show all options incuding the already-selected
option.

This change does introduce the issue of the trigger element not
taking up 100% width of the parent. Supporting such would involve
overriding AtlasKit CSS. The compromise made here was to do a
generic override of max-width so the trigger elements at least
stay within the parent and aligning the trigger elements to the
right.
j8
Leonard Kim 8 年之前
父節點
當前提交
3e518e8040

+ 35
- 14
css/modals/device-selection/_device-selection.scss 查看文件

4
     .device-selectors {
4
     .device-selectors {
5
         font-size: 14px;
5
         font-size: 14px;
6
 
6
 
7
+        /* ensure all child components do not exceed parent width */
8
+        button,
9
+        div {
10
+            max-width: 100%;
11
+        }
12
+
7
         > div {
13
         > div {
14
+            display: block;
8
             margin-bottom: 10px;
15
             margin-bottom: 10px;
9
         }
16
         }
10
 
17
 
11
         > div:last-child {
18
         > div:last-child {
12
             margin-bottom: 5px;
19
             margin-bottom: 5px;
13
         }
20
         }
21
+
22
+        .device-selector-icon {
23
+            color: inherit;
24
+            font-size: 20px;
25
+        }
14
     }
26
     }
15
 
27
 
16
-    .device-selection-column-selectors,
17
-    .device-selection-column-video {
18
-        padding: 10px;
28
+    .device-selection-column {
29
+        box-sizing: border-box;
19
         display: inline-block;
30
         display: inline-block;
20
         vertical-align: top;
31
         vertical-align: top;
21
-    }
22
-    .device-selection-column-selectors {
23
-        width: 46%;
24
-    }
25
-    .device-selection-column-video {
26
-        width: 49%;
27
-        padding: 10px 0;
32
+
33
+        &.column-selectors {
34
+            margin-left: 15px;
35
+            width: 45%;
36
+        }
37
+
38
+        &.column-video {
39
+            width: 50%;
40
+        }
28
     }
41
     }
29
 
42
 
30
     .device-selection-video-container {
43
     .device-selection-video-container {
44
+        /* TOFIX: to be removed when we move out from muted preview */
31
         background: black;
45
         background: black;
32
-        height: 156px;
33
-        margin: 15px 0 5px;
46
+        border-radius: 3px;
47
+        /* TOFIX-END */
48
+        height: 160px;
49
+        margin-bottom: 5px;
34
 
50
 
35
         .video-input-preview {
51
         .video-input-preview {
52
+            margin-top: 2px;
36
             position: relative;
53
             position: relative;
37
 
54
 
55
+            > video {
56
+                border-radius: 3px;
57
+            }
58
+
38
             .video-input-preview-muted {
59
             .video-input-preview-muted {
39
                 color: $participantNameColor;
60
                 color: $participantNameColor;
40
                 display: none;
61
                 display: none;
58
     }
79
     }
59
 
80
 
60
     .audio-output-preview {
81
     .audio-output-preview {
61
-        text-align: right;
62
-
82
+        font-size: 14px;
83
+        margin-top: 10px;
63
         a {
84
         a {
64
             cursor: pointer;
85
             cursor: pointer;
65
             text-decoration: none;
86
             text-decoration: none;

+ 2
- 1
package.json 查看文件

18
   "dependencies": {
18
   "dependencies": {
19
     "@atlaskit/button": "1.0.3",
19
     "@atlaskit/button": "1.0.3",
20
     "@atlaskit/button-group": "1.0.0",
20
     "@atlaskit/button-group": "1.0.0",
21
+    "@atlaskit/dropdown-menu": "1.1.12",
21
     "@atlaskit/field-text": "2.0.3",
22
     "@atlaskit/field-text": "2.0.3",
23
+    "@atlaskit/icon": "6.0.0",
22
     "@atlaskit/modal-dialog": "1.2.4",
24
     "@atlaskit/modal-dialog": "1.2.4",
23
-    "@atlaskit/single-select": "1.6.1",
24
     "@atlaskit/tabs": "1.2.5",
25
     "@atlaskit/tabs": "1.2.5",
25
     "@atlassian/aui": "6.0.6",
26
     "@atlassian/aui": "6.0.6",
26
     "async": "0.9.0",
27
     "async": "0.9.0",

+ 10
- 7
react/features/device-selection/components/DeviceSelectionDialog.js 查看文件

170
                 onSubmit = { this._onSubmit }
170
                 onSubmit = { this._onSubmit }
171
                 titleKey = 'deviceSelection.deviceSettings' >
171
                 titleKey = 'deviceSelection.deviceSettings' >
172
                 <div className = 'device-selection'>
172
                 <div className = 'device-selection'>
173
-                    <div className = 'device-selection-column-selectors'>
174
-                        <div className = 'device-selectors'>
175
-                            { this._renderSelectors() }
176
-                        </div>
177
-                        { this._renderAudioOutputPreview() }
178
-                    </div>
179
-                    <div className = 'device-selection-column-video'>
173
+                    <div className = 'device-selection-column column-video'>
180
                         <div className = 'device-selection-video-container'>
174
                         <div className = 'device-selection-video-container'>
181
                             <VideoInputPreview
175
                             <VideoInputPreview
182
                                 track = { this.state.previewVideoTrack
176
                                 track = { this.state.previewVideoTrack
184
                         </div>
178
                         </div>
185
                         { this._renderAudioInputPreview() }
179
                         { this._renderAudioInputPreview() }
186
                     </div>
180
                     </div>
181
+                    <div className = 'device-selection-column column-selectors'>
182
+                        <div className = 'device-selectors'>
183
+                            { this._renderSelectors() }
184
+                        </div>
185
+                        { this._renderAudioOutputPreview() }
186
+                    </div>
187
                 </div>
187
                 </div>
188
             </Dialog>
188
             </Dialog>
189
         );
189
         );
543
             {
543
             {
544
                 devices: availableDevices.videoInput,
544
                 devices: availableDevices.videoInput,
545
                 hasPermission: this.props.hasVideoPermission,
545
                 hasPermission: this.props.hasVideoPermission,
546
+                icon: 'icon-camera',
546
                 isDisabled: this.props.disableDeviceChange,
547
                 isDisabled: this.props.disableDeviceChange,
547
                 key: 'videoInput',
548
                 key: 'videoInput',
548
                 label: 'settings.selectCamera',
549
                 label: 'settings.selectCamera',
552
             {
553
             {
553
                 devices: availableDevices.audioInput,
554
                 devices: availableDevices.audioInput,
554
                 hasPermission: this.props.hasAudioPermission,
555
                 hasPermission: this.props.hasAudioPermission,
556
+                icon: 'icon-microphone',
555
                 isDisabled: this.props.disableAudioInputChange
557
                 isDisabled: this.props.disableAudioInputChange
556
                     || this.props.disableDeviceChange,
558
                     || this.props.disableDeviceChange,
557
                 key: 'audioInput',
559
                 key: 'audioInput',
566
                 devices: availableDevices.audioOutput,
568
                 devices: availableDevices.audioOutput,
567
                 hasPermission: this.props.hasAudioPermission
569
                 hasPermission: this.props.hasAudioPermission
568
                     || this.props.hasVideoPermission,
570
                     || this.props.hasVideoPermission,
571
+                icon: 'icon-volume',
569
                 isDisabled: this.props.disableDeviceChange,
572
                 isDisabled: this.props.disableDeviceChange,
570
                 key: 'audioOutput',
573
                 key: 'audioOutput',
571
                 label: 'settings.selectAudioOutput',
574
                 label: 'settings.selectAudioOutput',

+ 73
- 29
react/features/device-selection/components/DeviceSelector.js 查看文件

1
-import Select from '@atlaskit/single-select';
1
+import AKButton from '@atlaskit/button';
2
+import AKDropdownMenu from '@atlaskit/dropdown-menu';
3
+import ExpandIcon from '@atlaskit/icon/glyph/expand';
2
 import React, { Component } from 'react';
4
 import React, { Component } from 'react';
3
 
5
 
4
 import { translate } from '../../base/i18n';
6
 import { translate } from '../../base/i18n';
5
 
7
 
8
+const EXPAND_ICON = <ExpandIcon label = 'expand' />;
9
+
6
 /**
10
 /**
7
- * React component for selecting a device from a select element. Wraps Select
8
- * with device selection specific logic.
11
+ * React component for selecting a device from a select element. Wraps
12
+ * AKDropdownMenu with device selection specific logic.
9
  *
13
  *
10
  * @extends Component
14
  * @extends Component
11
  */
15
  */
26
          */
30
          */
27
         hasPermission: React.PropTypes.bool,
31
         hasPermission: React.PropTypes.bool,
28
 
32
 
33
+        /**
34
+         * CSS class for the icon to the left of the dropdown trigger.
35
+         */
36
+        icon: React.PropTypes.string,
37
+
29
         /**
38
         /**
30
          * If true, will render the selector disabled with a default selection.
39
          * If true, will render the selector disabled with a default selection.
31
          */
40
          */
79
             return this._renderNoDevices();
88
             return this._renderNoDevices();
80
         }
89
         }
81
 
90
 
82
-        const items = this.props.devices.map(this._createSelectItem);
91
+        const items = this.props.devices.map(this._createDropdownItem);
83
         const defaultSelected = items.find(item =>
92
         const defaultSelected = items.find(item =>
84
             item.value === this.props.selectedDeviceId
93
             item.value === this.props.selectedDeviceId
85
         );
94
         );
86
 
95
 
87
-        return this._createSelector({
96
+        return this._createDropdown({
88
             defaultSelected,
97
             defaultSelected,
89
             isDisabled: this.props.isDisabled,
98
             isDisabled: this.props.isDisabled,
90
             items,
99
             items,
93
     }
102
     }
94
 
103
 
95
     /**
104
     /**
96
-     * Creates an object in the format expected by Select for an option element.
105
+     * Creates an AtlasKit Button.
106
+     *
107
+     * @param {string} buttonText - The text to display within the button.
108
+     * @private
109
+     * @returns {ReactElement}
110
+     */
111
+    _createDropdownTrigger(buttonText) {
112
+        return (
113
+            <AKButton
114
+                className = 'device-selector-trigger'
115
+                iconAfter = { EXPAND_ICON }
116
+                iconBefore = { this._createDropdownIcon() }>
117
+                { buttonText }
118
+            </AKButton>
119
+        );
120
+    }
121
+
122
+    /**
123
+     * Creates a ReactComponent for displaying an icon.
124
+     *
125
+     * @private
126
+     * @returns {ReactElement}
127
+     */
128
+    _createDropdownIcon() {
129
+        return (
130
+            <span className = { `device-selector-icon ${this.props.icon}` } />
131
+        );
132
+    }
133
+
134
+    /**
135
+     * Creates an object in the format expected by AKDropdownMenu for an option.
97
      *
136
      *
98
      * @param {MediaDeviceInfo} device - An object with a label and a deviceId.
137
      * @param {MediaDeviceInfo} device - An object with a label and a deviceId.
99
      * @private
138
      * @private
100
      * @returns {Object} The passed in media device description converted to a
139
      * @returns {Object} The passed in media device description converted to a
101
-     * format recognized as a valid Select item.
140
+     * format recognized as a valid AKDropdownMenu item.
102
      */
141
      */
103
-    _createSelectItem(device) {
142
+    _createDropdownItem(device) {
104
         return {
143
         return {
105
             content: device.label,
144
             content: device.label,
106
             value: device.deviceId
145
             value: device.deviceId
108
     }
147
     }
109
 
148
 
110
     /**
149
     /**
111
-     * Creates a Select Component using passed in props and options.
150
+     * Creates a AKDropdownMenu Component using passed in props and options.
112
      *
151
      *
113
-     * @param {Object} options - Additional configuration for display Select.
152
+     * @param {Object} options - Additional configuration for display.
114
      * @param {Object} options.defaultSelected - The option that should be set
153
      * @param {Object} options.defaultSelected - The option that should be set
115
      * as currently chosen.
154
      * as currently chosen.
116
-     * @param {boolean} options.isDisabled - If true Select will not open on
117
-     * click.
155
+     * @param {boolean} options.isDisabled - If true, AKDropdownMenu will not
156
+     * open on click.
118
      * @param {Array} options.items - All the selectable options to display.
157
      * @param {Array} options.items - All the selectable options to display.
119
      * @param {string} options.placeholder - The translation key to display when
158
      * @param {string} options.placeholder - The translation key to display when
120
      * no selection has been made.
159
      * no selection has been made.
121
      * @private
160
      * @private
122
      * @returns {ReactElement}
161
      * @returns {ReactElement}
123
      */
162
      */
124
-    _createSelector(options) {
163
+    _createDropdown(options) {
164
+        const triggerText
165
+            = (options.defaultSelected && options.defaultSelected.content)
166
+                || options.placeholder;
167
+
125
         return (
168
         return (
126
-            <Select
127
-                defaultSelected = { options.defaultSelected }
128
-                isDisabled = { options.isDisabled }
129
-                isFirstChild = { true }
169
+            <AKDropdownMenu
170
+                { ...(options.isDisabled && { isOpen: !options.isDisabled }) }
130
                 items = { [ { items: options.items || [] } ] }
171
                 items = { [ { items: options.items || [] } ] }
131
-                label = { this.props.t(this.props.label) }
132
                 noMatchesFound
172
                 noMatchesFound
133
                     = { this.props.t('deviceSelection.noOtherDevices') }
173
                     = { this.props.t('deviceSelection.noOtherDevices') }
134
-                onSelected = { this._onSelect }
135
-                placeholder = { this.props.t(options.placeholder) }
136
-                shouldFitContainer = { true } />
174
+                onItemActivated = { this._onSelect }>
175
+                { this._createDropdownTrigger(triggerText) }
176
+            </AKDropdownMenu>
137
         );
177
         );
138
     }
178
     }
139
 
179
 
140
     /**
180
     /**
141
      * Invokes the passed in callback to notify of selection changes.
181
      * Invokes the passed in callback to notify of selection changes.
142
      *
182
      *
143
-     * @param {Object} selection - Event returned from Select.
183
+     * @param {Object} selection - Event from choosing a AKDropdownMenu option.
144
      * @private
184
      * @private
145
      * @returns {void}
185
      * @returns {void}
146
      */
186
      */
147
     _onSelect(selection) {
187
     _onSelect(selection) {
148
-        this.props.onSelect(selection.item.value);
188
+        const newDeviceId = selection.item.value;
189
+
190
+        if (this.props.selectedDeviceId !== newDeviceId) {
191
+            this.props.onSelect(selection.item.value);
192
+        }
149
     }
193
     }
150
 
194
 
151
     /**
195
     /**
156
      * @returns {ReactElement}
200
      * @returns {ReactElement}
157
      */
201
      */
158
     _renderNoDevices() {
202
     _renderNoDevices() {
159
-        return this._createSelector({
203
+        return this._createDropdown({
160
             isDisabled: true,
204
             isDisabled: true,
161
-            placeholder: 'settings.noDevice'
205
+            placeholder: this.props.t('settings.noDevice')
162
         });
206
         });
163
     }
207
     }
164
 
208
 
165
     /**
209
     /**
166
-     * Creates a Select Component that is disabled and has a placeholder stating
167
-     * there is no permission to display the devices.
210
+     * Creates a AKDropdownMenu Component that is disabled and has a placeholder
211
+     * stating there is no permission to display the devices.
168
      *
212
      *
169
      * @private
213
      * @private
170
      * @returns {ReactElement}
214
      * @returns {ReactElement}
171
      */
215
      */
172
     _renderNoPermission() {
216
     _renderNoPermission() {
173
-        return this._createSelector({
217
+        return this._createDropdown({
174
             isDisabled: true,
218
             isDisabled: true,
175
-            placeholder: 'settings.noPermission'
219
+            placeholder: this.props.t('settings.noPermission')
176
         });
220
         });
177
     }
221
     }
178
 }
222
 }

Loading…
取消
儲存