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

View File

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

View File

@ -170,13 +170,7 @@ class DeviceSelectionDialog extends Component {
onSubmit = { this._onSubmit } onSubmit = { this._onSubmit }
titleKey = 'deviceSelection.deviceSettings' > titleKey = 'deviceSelection.deviceSettings' >
<div className = 'device-selection'> <div className = 'device-selection'>
<div className = 'device-selection-column-selectors'> <div className = 'device-selection-column column-video'>
<div className = 'device-selectors'>
{ this._renderSelectors() }
</div>
{ this._renderAudioOutputPreview() }
</div>
<div className = 'device-selection-column-video'>
<div className = 'device-selection-video-container'> <div className = 'device-selection-video-container'>
<VideoInputPreview <VideoInputPreview
track = { this.state.previewVideoTrack track = { this.state.previewVideoTrack
@ -184,6 +178,12 @@ class DeviceSelectionDialog extends Component {
</div> </div>
{ this._renderAudioInputPreview() } { this._renderAudioInputPreview() }
</div> </div>
<div className = 'device-selection-column column-selectors'>
<div className = 'device-selectors'>
{ this._renderSelectors() }
</div>
{ this._renderAudioOutputPreview() }
</div>
</div> </div>
</Dialog> </Dialog>
); );
@ -543,6 +543,7 @@ class DeviceSelectionDialog extends Component {
{ {
devices: availableDevices.videoInput, devices: availableDevices.videoInput,
hasPermission: this.props.hasVideoPermission, hasPermission: this.props.hasVideoPermission,
icon: 'icon-camera',
isDisabled: this.props.disableDeviceChange, isDisabled: this.props.disableDeviceChange,
key: 'videoInput', key: 'videoInput',
label: 'settings.selectCamera', label: 'settings.selectCamera',
@ -552,6 +553,7 @@ class DeviceSelectionDialog extends Component {
{ {
devices: availableDevices.audioInput, devices: availableDevices.audioInput,
hasPermission: this.props.hasAudioPermission, hasPermission: this.props.hasAudioPermission,
icon: 'icon-microphone',
isDisabled: this.props.disableAudioInputChange isDisabled: this.props.disableAudioInputChange
|| this.props.disableDeviceChange, || this.props.disableDeviceChange,
key: 'audioInput', key: 'audioInput',
@ -566,6 +568,7 @@ class DeviceSelectionDialog extends Component {
devices: availableDevices.audioOutput, devices: availableDevices.audioOutput,
hasPermission: this.props.hasAudioPermission hasPermission: this.props.hasAudioPermission
|| this.props.hasVideoPermission, || this.props.hasVideoPermission,
icon: 'icon-volume',
isDisabled: this.props.disableDeviceChange, isDisabled: this.props.disableDeviceChange,
key: 'audioOutput', key: 'audioOutput',
label: 'settings.selectAudioOutput', 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 React, { Component } from 'react';
import { translate } from '../../base/i18n'; import { translate } from '../../base/i18n';
const EXPAND_ICON = <ExpandIcon label = 'expand' />;
/** /**
* React component for selecting a device from a select element. Wraps Select * React component for selecting a device from a select element. Wraps
* with device selection specific logic. * AKDropdownMenu with device selection specific logic.
* *
* @extends Component * @extends Component
*/ */
@ -26,6 +30,11 @@ class DeviceSelector extends Component {
*/ */
hasPermission: React.PropTypes.bool, 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. * If true, will render the selector disabled with a default selection.
*/ */
@ -79,12 +88,12 @@ class DeviceSelector extends Component {
return this._renderNoDevices(); return this._renderNoDevices();
} }
const items = this.props.devices.map(this._createSelectItem); const items = this.props.devices.map(this._createDropdownItem);
const defaultSelected = items.find(item => const defaultSelected = items.find(item =>
item.value === this.props.selectedDeviceId item.value === this.props.selectedDeviceId
); );
return this._createSelector({ return this._createDropdown({
defaultSelected, defaultSelected,
isDisabled: this.props.isDisabled, isDisabled: this.props.isDisabled,
items, 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. * @param {MediaDeviceInfo} device - An object with a label and a deviceId.
* @private * @private
* @returns {Object} The passed in media device description converted to a * @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 { return {
content: device.label, content: device.label,
value: device.deviceId 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 * @param {Object} options.defaultSelected - The option that should be set
* as currently chosen. * as currently chosen.
* @param {boolean} options.isDisabled - If true Select will not open on * @param {boolean} options.isDisabled - If true, AKDropdownMenu will not
* click. * open on click.
* @param {Array} options.items - All the selectable options to display. * @param {Array} options.items - All the selectable options to display.
* @param {string} options.placeholder - The translation key to display when * @param {string} options.placeholder - The translation key to display when
* no selection has been made. * no selection has been made.
* @private * @private
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_createSelector(options) { _createDropdown(options) {
const triggerText
= (options.defaultSelected && options.defaultSelected.content)
|| options.placeholder;
return ( return (
<Select <AKDropdownMenu
defaultSelected = { options.defaultSelected } { ...(options.isDisabled && { isOpen: !options.isDisabled }) }
isDisabled = { options.isDisabled }
isFirstChild = { true }
items = { [ { items: options.items || [] } ] } items = { [ { items: options.items || [] } ] }
label = { this.props.t(this.props.label) }
noMatchesFound noMatchesFound
= { this.props.t('deviceSelection.noOtherDevices') } = { this.props.t('deviceSelection.noOtherDevices') }
onSelected = { this._onSelect } onItemActivated = { this._onSelect }>
placeholder = { this.props.t(options.placeholder) } { this._createDropdownTrigger(triggerText) }
shouldFitContainer = { true } /> </AKDropdownMenu>
); );
} }
/** /**
* Invokes the passed in callback to notify of selection changes. * 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 * @private
* @returns {void} * @returns {void}
*/ */
_onSelect(selection) { _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} * @returns {ReactElement}
*/ */
_renderNoDevices() { _renderNoDevices() {
return this._createSelector({ return this._createDropdown({
isDisabled: true, isDisabled: true,
placeholder: 'settings.noDevice' placeholder: this.props.t('settings.noDevice')
}); });
} }
/** /**
* Creates a Select Component that is disabled and has a placeholder stating * Creates a AKDropdownMenu Component that is disabled and has a placeholder
* there is no permission to display the devices. * stating there is no permission to display the devices.
* *
* @private * @private
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderNoPermission() { _renderNoPermission() {
return this._createSelector({ return this._createDropdown({
isDisabled: true, isDisabled: true,
placeholder: 'settings.noPermission' placeholder: this.props.t('settings.noPermission')
}); });
} }
} }