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.
This commit is contained in:
Leonard Kim 2017-04-17 14:02:36 -07:00 committed by yanas
parent 33c92a31bf
commit 3e518e8040
4 changed files with 120 additions and 51 deletions

View File

@ -4,37 +4,58 @@
.device-selectors {
font-size: 14px;
/* ensure all child components do not exceed parent width */
button,
div {
max-width: 100%;
}
> div {
display: block;
margin-bottom: 10px;
}
> div:last-child {
margin-bottom: 5px;
}
.device-selector-icon {
color: inherit;
font-size: 20px;
}
}
.device-selection-column-selectors,
.device-selection-column-video {
padding: 10px;
.device-selection-column {
box-sizing: border-box;
display: inline-block;
vertical-align: top;
}
.device-selection-column-selectors {
width: 46%;
}
.device-selection-column-video {
width: 49%;
padding: 10px 0;
&.column-selectors {
margin-left: 15px;
width: 45%;
}
&.column-video {
width: 50%;
}
}
.device-selection-video-container {
/* TOFIX: to be removed when we move out from muted preview */
background: black;
height: 156px;
margin: 15px 0 5px;
border-radius: 3px;
/* TOFIX-END */
height: 160px;
margin-bottom: 5px;
.video-input-preview {
margin-top: 2px;
position: relative;
> video {
border-radius: 3px;
}
.video-input-preview-muted {
color: $participantNameColor;
display: none;
@ -58,8 +79,8 @@
}
.audio-output-preview {
text-align: right;
font-size: 14px;
margin-top: 10px;
a {
cursor: pointer;
text-decoration: none;

View File

@ -18,9 +18,10 @@
"dependencies": {
"@atlaskit/button": "1.0.3",
"@atlaskit/button-group": "1.0.0",
"@atlaskit/dropdown-menu": "1.1.12",
"@atlaskit/field-text": "2.0.3",
"@atlaskit/icon": "6.0.0",
"@atlaskit/modal-dialog": "1.2.4",
"@atlaskit/single-select": "1.6.1",
"@atlaskit/tabs": "1.2.5",
"@atlassian/aui": "6.0.6",
"async": "0.9.0",

View File

@ -170,13 +170,7 @@ class DeviceSelectionDialog extends Component {
onSubmit = { this._onSubmit }
titleKey = 'deviceSelection.deviceSettings' >
<div className = 'device-selection'>
<div className = 'device-selection-column-selectors'>
<div className = 'device-selectors'>
{ this._renderSelectors() }
</div>
{ this._renderAudioOutputPreview() }
</div>
<div className = 'device-selection-column-video'>
<div className = 'device-selection-column column-video'>
<div className = 'device-selection-video-container'>
<VideoInputPreview
track = { this.state.previewVideoTrack
@ -184,6 +178,12 @@ class DeviceSelectionDialog extends Component {
</div>
{ this._renderAudioInputPreview() }
</div>
<div className = 'device-selection-column column-selectors'>
<div className = 'device-selectors'>
{ this._renderSelectors() }
</div>
{ this._renderAudioOutputPreview() }
</div>
</div>
</Dialog>
);
@ -543,6 +543,7 @@ class DeviceSelectionDialog extends Component {
{
devices: availableDevices.videoInput,
hasPermission: this.props.hasVideoPermission,
icon: 'icon-camera',
isDisabled: this.props.disableDeviceChange,
key: 'videoInput',
label: 'settings.selectCamera',
@ -552,6 +553,7 @@ class DeviceSelectionDialog extends Component {
{
devices: availableDevices.audioInput,
hasPermission: this.props.hasAudioPermission,
icon: 'icon-microphone',
isDisabled: this.props.disableAudioInputChange
|| this.props.disableDeviceChange,
key: 'audioInput',
@ -566,6 +568,7 @@ class DeviceSelectionDialog extends Component {
devices: availableDevices.audioOutput,
hasPermission: this.props.hasAudioPermission
|| this.props.hasVideoPermission,
icon: 'icon-volume',
isDisabled: this.props.disableDeviceChange,
key: 'audioOutput',
label: 'settings.selectAudioOutput',

View File

@ -1,11 +1,15 @@
import Select from '@atlaskit/single-select';
import AKButton from '@atlaskit/button';
import AKDropdownMenu from '@atlaskit/dropdown-menu';
import ExpandIcon from '@atlaskit/icon/glyph/expand';
import React, { Component } from 'react';
import { translate } from '../../base/i18n';
const EXPAND_ICON = <ExpandIcon label = 'expand' />;
/**
* React component for selecting a device from a select element. Wraps Select
* with device selection specific logic.
* React component for selecting a device from a select element. Wraps
* AKDropdownMenu with device selection specific logic.
*
* @extends Component
*/
@ -26,6 +30,11 @@ class DeviceSelector extends Component {
*/
hasPermission: React.PropTypes.bool,
/**
* CSS class for the icon to the left of the dropdown trigger.
*/
icon: React.PropTypes.string,
/**
* If true, will render the selector disabled with a default selection.
*/
@ -79,12 +88,12 @@ class DeviceSelector extends Component {
return this._renderNoDevices();
}
const items = this.props.devices.map(this._createSelectItem);
const items = this.props.devices.map(this._createDropdownItem);
const defaultSelected = items.find(item =>
item.value === this.props.selectedDeviceId
);
return this._createSelector({
return this._createDropdown({
defaultSelected,
isDisabled: this.props.isDisabled,
items,
@ -93,14 +102,44 @@ class DeviceSelector extends Component {
}
/**
* Creates an object in the format expected by Select for an option element.
* Creates an AtlasKit Button.
*
* @param {string} buttonText - The text to display within the button.
* @private
* @returns {ReactElement}
*/
_createDropdownTrigger(buttonText) {
return (
<AKButton
className = 'device-selector-trigger'
iconAfter = { EXPAND_ICON }
iconBefore = { this._createDropdownIcon() }>
{ buttonText }
</AKButton>
);
}
/**
* Creates a ReactComponent for displaying an icon.
*
* @private
* @returns {ReactElement}
*/
_createDropdownIcon() {
return (
<span className = { `device-selector-icon ${this.props.icon}` } />
);
}
/**
* Creates an object in the format expected by AKDropdownMenu for an option.
*
* @param {MediaDeviceInfo} device - An object with a label and a deviceId.
* @private
* @returns {Object} The passed in media device description converted to a
* format recognized as a valid Select item.
* format recognized as a valid AKDropdownMenu item.
*/
_createSelectItem(device) {
_createDropdownItem(device) {
return {
content: device.label,
value: device.deviceId
@ -108,44 +147,49 @@ class DeviceSelector extends Component {
}
/**
* Creates a Select Component using passed in props and options.
* Creates a AKDropdownMenu Component using passed in props and options.
*
* @param {Object} options - Additional configuration for display Select.
* @param {Object} options - Additional configuration for display.
* @param {Object} options.defaultSelected - The option that should be set
* as currently chosen.
* @param {boolean} options.isDisabled - If true Select will not open on
* click.
* @param {boolean} options.isDisabled - If true, AKDropdownMenu will not
* open on click.
* @param {Array} options.items - All the selectable options to display.
* @param {string} options.placeholder - The translation key to display when
* no selection has been made.
* @private
* @returns {ReactElement}
*/
_createSelector(options) {
_createDropdown(options) {
const triggerText
= (options.defaultSelected && options.defaultSelected.content)
|| options.placeholder;
return (
<Select
defaultSelected = { options.defaultSelected }
isDisabled = { options.isDisabled }
isFirstChild = { true }
<AKDropdownMenu
{ ...(options.isDisabled && { isOpen: !options.isDisabled }) }
items = { [ { items: options.items || [] } ] }
label = { this.props.t(this.props.label) }
noMatchesFound
= { this.props.t('deviceSelection.noOtherDevices') }
onSelected = { this._onSelect }
placeholder = { this.props.t(options.placeholder) }
shouldFitContainer = { true } />
onItemActivated = { this._onSelect }>
{ this._createDropdownTrigger(triggerText) }
</AKDropdownMenu>
);
}
/**
* Invokes the passed in callback to notify of selection changes.
*
* @param {Object} selection - Event returned from Select.
* @param {Object} selection - Event from choosing a AKDropdownMenu option.
* @private
* @returns {void}
*/
_onSelect(selection) {
this.props.onSelect(selection.item.value);
const newDeviceId = selection.item.value;
if (this.props.selectedDeviceId !== newDeviceId) {
this.props.onSelect(selection.item.value);
}
}
/**
@ -156,23 +200,23 @@ class DeviceSelector extends Component {
* @returns {ReactElement}
*/
_renderNoDevices() {
return this._createSelector({
return this._createDropdown({
isDisabled: true,
placeholder: 'settings.noDevice'
placeholder: this.props.t('settings.noDevice')
});
}
/**
* Creates a Select Component that is disabled and has a placeholder stating
* there is no permission to display the devices.
* Creates a AKDropdownMenu Component that is disabled and has a placeholder
* stating there is no permission to display the devices.
*
* @private
* @returns {ReactElement}
*/
_renderNoPermission() {
return this._createSelector({
return this._createDropdown({
isDisabled: true,
placeholder: 'settings.noPermission'
placeholder: this.props.t('settings.noPermission')
});
}
}