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:
parent
33c92a31bf
commit
3e518e8040
|
@ -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;
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue