diff --git a/app/components/widgets/settings/__snapshots__/radio_setting.test.js.snap b/app/components/widgets/settings/__snapshots__/radio_setting.test.js.snap
new file mode 100644
index 000000000..b9053a11d
--- /dev/null
+++ b/app/components/widgets/settings/__snapshots__/radio_setting.test.js.snap
@@ -0,0 +1,189 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`components/widgets/settings/RadioSetting should match snapshot when error is present 1`] = `
+
+
+
+ some label
+
+
+ *
+
+
+
+
+
+
+
+ this is engineering
+
+
+
+
+
+
+
+
+
+ this is sales
+
+
+
+
+
+
+
+
+
+ this is administration
+
+
+
+
+
+
+
+
+ some error message
+
+
+
+`;
diff --git a/app/components/widgets/settings/radio_setting.js b/app/components/widgets/settings/radio_setting.js
new file mode 100644
index 000000000..c312d3d79
--- /dev/null
+++ b/app/components/widgets/settings/radio_setting.js
@@ -0,0 +1,191 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {Text, TouchableOpacity, View} from 'react-native';
+import CheckMark from 'app/components/checkmark';
+import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
+import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
+
+export default class RadioSetting extends PureComponent {
+ static propTypes = {
+ id: PropTypes.string.isRequired,
+ label: PropTypes.node.isRequired,
+ options: PropTypes.array.isRequired,
+ default: PropTypes.string,
+ onChange: PropTypes.func.isRequired,
+ theme: PropTypes.object.isRequired,
+ isLandscape: PropTypes.bool.isRequired,
+ helpText: PropTypes.node,
+ errorText: PropTypes.node,
+ };
+
+ static defaultProps = {
+ isLandscape: false,
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ value: typeof props.default === 'undefined' ? props.options[0] : props.default,
+ };
+ }
+
+ handleChange = (item) => {
+ const {onChange, id} = this.props;
+ onChange(id, item);
+ this.setState({value: item});
+ }
+
+ renderCheckMark = (value, {width, height, color}) => {
+ if (value === this.state.value) {
+ return (
+
+ );
+ }
+ return null;
+ }
+
+ renderRowSeparator = (idx, separatorStyle) => {
+ const {options} = this.props;
+ if (idx === options.length - 1) {
+ return null;
+ }
+ return ;
+ }
+
+ render() {
+ const {
+ theme,
+ label,
+ helpText,
+ errorText,
+ isLandscape,
+ } = this.props;
+ const style = getStyleSheet(theme);
+
+ let helpTextContent;
+ if (helpText) {
+ helpTextContent = (
+
+ {helpText}
+
+ );
+ }
+ let errorTextContent;
+ if (errorText) {
+ errorTextContent = (
+
+ {errorText}
+
+ );
+ }
+
+ let additionalTextContent;
+ if (errorText || helpText) {
+ additionalTextContent = (
+
+ {helpTextContent}
+ {errorTextContent}
+
+ );
+ }
+
+ const options = [];
+ for (const [i, {value, text}] of this.props.options.entries()) {
+ options.push(
+ this.handleChange(value)}
+ key={value}
+ >
+
+
+ {text}
+
+ {this.renderCheckMark(value, style.checkMark)}
+
+ {this.renderRowSeparator(i, style.separator)}
+
+ );
+ }
+ return (
+
+
+ {label}
+ {' *'}
+
+
+
+ {options}
+
+ {additionalTextContent}
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: 15,
+ },
+ rowContainer: {
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ height: 45,
+ },
+ items: {
+ backgroundColor: theme.centerChannelBg,
+ borderTopWidth: 1,
+ borderBottomWidth: 1,
+ borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
+ borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1),
+ },
+ helpText: {
+ fontSize: 12,
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ marginHorizontal: 15,
+ marginVertical: 10,
+ },
+ errorText: {
+ fontSize: 12,
+ color: theme.errorTextColor,
+ marginHorizontal: 15,
+ marginTop: 10,
+ },
+ asterisk: {
+ color: theme.errorTextColor,
+ fontSize: 14,
+ },
+ title: {
+ fontSize: 14,
+ color: theme.centerChannelColor,
+ marginLeft: 15,
+ },
+ titleContainer: {
+ flexDirection: 'row',
+ marginTop: 15,
+ marginBottom: 10,
+ },
+ separator: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
+ flex: 1,
+ height: 1,
+ marginLeft: 15,
+ },
+ checkMark: {
+ width: 12,
+ height: 12,
+ color: theme.linkColor,
+ },
+ };
+});
diff --git a/app/components/widgets/settings/radio_setting.test.js b/app/components/widgets/settings/radio_setting.test.js
new file mode 100644
index 000000000..fc7aa3f36
--- /dev/null
+++ b/app/components/widgets/settings/radio_setting.test.js
@@ -0,0 +1,69 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+import React from 'react';
+import {shallow} from 'enzyme';
+
+import Preferences from 'mattermost-redux/constants/preferences';
+import {TouchableOpacity} from 'react-native';
+import RadioSetting from './radio_setting.js';
+import CheckMark from 'app/components/checkmark';
+
+describe('components/widgets/settings/RadioSetting', () => {
+ const theme = Preferences.THEMES.default;
+ const options = [
+ {text: 'this is engineering', value: 'Engineering'},
+ {text: 'this is sales', value: 'Sales'},
+ {text: 'this is administration', value: 'Administration'},
+ ];
+
+ test('onChange', () => {
+ const onChange = jest.fn();
+ const wrapper = shallow(
+
+ );
+ wrapper.find(TouchableOpacity).at(1).props().onPress();
+
+ expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledWith('string.id', 'Sales');
+ });
+
+ test('should match snapshot when error is present', () => {
+ const onChange = jest.fn();
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.getElement()).toMatchSnapshot();
+ });
+
+ test('no option should be checked when the value is not in provided options', () => {
+ const onChange = jest.fn();
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.find(CheckMark)).toHaveLength(0);
+ });
+});
diff --git a/app/components/widgets/settings/text_setting.js b/app/components/widgets/settings/text_setting.js
index 5c0567917..1775e4ede 100644
--- a/app/components/widgets/settings/text_setting.js
+++ b/app/components/widgets/settings/text_setting.js
@@ -39,7 +39,6 @@ export default class TextSetting extends PureComponent {
onChange: PropTypes.func.isRequired,
value: PropTypes.string.isRequired,
multiline: PropTypes.bool,
- showRequiredAsterisk: PropTypes.bool,
isLandscape: PropTypes.bool.isRequired,
keyboardType: PropTypes.oneOf([
'default',
@@ -57,7 +56,6 @@ export default class TextSetting extends PureComponent {
optional: false,
disabled: false,
multiline: false,
- showRequiredAsterisk: false,
keyboardType: 'default',
isLandscape: false,
secureTextEntry: false,
@@ -80,7 +78,6 @@ export default class TextSetting extends PureComponent {
errorText,
value,
multiline,
- showRequiredAsterisk,
isLandscape,
secureTextEntry,
} = this.props;
@@ -109,7 +106,7 @@ export default class TextSetting extends PureComponent {
defaultMessage='(optional)'
/>
);
- } else if (showRequiredAsterisk) {
+ } else {
asterisk = {' *'};
}
diff --git a/app/screens/interactive_dialog/dialog_element.js b/app/screens/interactive_dialog/dialog_element.js
index 89b5f734e..85f90c4d6 100644
--- a/app/screens/interactive_dialog/dialog_element.js
+++ b/app/screens/interactive_dialog/dialog_element.js
@@ -7,6 +7,7 @@ import PropTypes from 'prop-types';
import BoolSetting from 'app/components/widgets/settings/bool_setting';
import TextSetting from 'app/components/widgets/settings/text_setting';
import AutocompleteSelector from 'app/components/autocomplete_selector';
+import RadioSetting from 'app/components/widgets/settings/radio_setting';
const TEXT_DEFAULT_MAX_LENGTH = 150;
const TEXTAREA_DEFAULT_MAX_LENGTH = 3000;
@@ -136,6 +137,19 @@ export default class DialogElement extends PureComponent {
roundedBorders={false}
/>
);
+ } else if (type === 'radio') {
+ return (
+
+ );
} else if (type === 'bool') {
return (
{
@@ -15,6 +15,7 @@ describe('DialogElement', () => {
type: 'text',
isLandscape: false,
};
+
const theme = Preferences.THEMES.default;
test('secureTextEntry is true and multiline is false when subtype is password', () => {
const wrapper = shallow(
@@ -37,4 +38,24 @@ describe('DialogElement', () => {
);
expect(wrapper.find({secureTextEntry: false}).exists()).toBe(true);
});
+
+ describe('radioSetting', () => {
+ const radioOptions = [
+ {value: 'foo', text: 'foo-text'},
+ {value: 'bar', text: 'bar-text'},
+ ];
+
+ test('The default value can be specified from the list', () => {
+ const wrapper = shallow(
+
+ );
+ expect(wrapper.find(RadioSetting).find({options: radioOptions, default: radioOptions[1].value}).exists()).toBe(true);
+ });
+ });
});
diff --git a/package-lock.json b/package-lock.json
index 0a17b5acb..220211ee0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4490,7 +4490,7 @@
},
"ansi-escapes": {
"version": "3.1.0",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
"integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw=="
},
"ansi-gray": {
@@ -7473,7 +7473,7 @@
},
"external-editor": {
"version": "2.2.0",
- "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
+ "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
"integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
"requires": {
"chardet": "^0.4.0",
@@ -7673,7 +7673,7 @@
"dependencies": {
"json5": {
"version": "0.5.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
+ "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
"dev": true
}
@@ -14056,8 +14056,8 @@
"integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A=="
},
"mattermost-redux": {
- "version": "github:mattermost/mattermost-redux#6ea71dc77808041d7e953ac15cbc12e1c483ac69",
- "from": "github:mattermost/mattermost-redux#6ea71dc77808041d7e953ac15cbc12e1c483ac69",
+ "version": "github:mattermost/mattermost-redux#9800f16a94b46fd77e95132c1c33f18752bf8a98",
+ "from": "github:mattermost/mattermost-redux#9800f16a94b46fd77e95132c1c33f18752bf8a98",
"requires": {
"deep-equal": "1.0.1",
"eslint-plugin-header": "3.0.0",
@@ -14070,7 +14070,7 @@
"redux": "4.0.4",
"redux-action-buffer": "1.2.0",
"redux-batched-actions": "0.4.1",
- "redux-offline": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5",
+ "redux-offline": "git+https://github.com/enahum/redux-offline.git#885024de96b6ec73650c340c8928066585c413df",
"redux-persist": "4.9.1",
"redux-thunk": "2.3.0",
"reselect": "4.0.0",
@@ -17064,7 +17064,7 @@
},
"readable-stream": {
"version": "2.3.6",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
@@ -17451,10 +17451,18 @@
}
},
"redux-offline": {
- "version": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5",
- "from": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5",
+ "version": "git+https://github.com/enahum/redux-offline.git#885024de96b6ec73650c340c8928066585c413df",
+ "from": "git+https://github.com/enahum/redux-offline.git#885024de96b6ec73650c340c8928066585c413df",
"requires": {
+ "@react-native-community/netinfo": "^4.1.3",
"redux-persist": "^4.5.0"
+ },
+ "dependencies": {
+ "@react-native-community/netinfo": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-4.2.1.tgz",
+ "integrity": "sha512-kAnmYp8vXpZToPw8rgE7uO+MqmqHSR9VEDPkuZT0DnFMBJmIXCSD2NLAD28HGKVY/kujVWCknC/FuVWr5/A3uA=="
+ }
}
},
"redux-persist": {
@@ -18263,7 +18271,7 @@
"dependencies": {
"async": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/async/-/async-2.0.0.tgz",
+ "resolved": "http://registry.npmjs.org/async/-/async-2.0.0.tgz",
"integrity": "sha1-0JAK04WvE4BFQKEJxCFm4657K50=",
"dev": true,
"requires": {
@@ -18685,7 +18693,7 @@
},
"inquirer": {
"version": "5.2.0",
- "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz",
+ "resolved": "http://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz",
"integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==",
"dev": true,
"requires": {
@@ -19110,7 +19118,7 @@
},
"strip-ansi": {
"version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "^2.0.0"
diff --git a/package.json b/package.json
index f44c12ed1..04754fb5e 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
"intl": "1.2.5",
"jail-monkey": "2.2.0",
"jsc-android": "241213.2.0",
- "mattermost-redux": "github:mattermost/mattermost-redux#6ea71dc77808041d7e953ac15cbc12e1c483ac69",
+ "mattermost-redux": "github:mattermost/mattermost-redux#9800f16a94b46fd77e95132c1c33f18752bf8a98",
"mime-db": "1.40.0",
"moment-timezone": "0.5.25",
"prop-types": "15.7.2",