MM-14273 Add boolean element support to interactive dialog (#3239)
* add boolean element support to interactive dialog * determine landscape orientation by Dimension
This commit is contained in:
parent
0d1fd78263
commit
4d8fa314d1
5 changed files with 260 additions and 5 deletions
184
app/components/widgets/settings/bool_setting.js
Normal file
184
app/components/widgets/settings/bool_setting.js
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
// 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 {
|
||||
View,
|
||||
Text,
|
||||
Switch,
|
||||
} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {
|
||||
changeOpacity,
|
||||
makeStyleSheetFromTheme,
|
||||
} from 'app/utils/theme';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
|
||||
export default class BoolSetting extends PureComponent {
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
label: PropTypes.oneOfType([
|
||||
PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
}),
|
||||
PropTypes.string,
|
||||
]),
|
||||
value: PropTypes.bool.isRequired,
|
||||
placeholder: PropTypes.string,
|
||||
helpText: PropTypes.node,
|
||||
errorText: PropTypes.node,
|
||||
optional: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
handleChange = (value) => {
|
||||
this.props.onChange(this.props.id, `${value}`);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
label,
|
||||
value,
|
||||
placeholder,
|
||||
helpText,
|
||||
errorText,
|
||||
optional,
|
||||
theme,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let optionalContent;
|
||||
let asterisk;
|
||||
if (optional) {
|
||||
optionalContent = (
|
||||
<FormattedText
|
||||
style={style.optional}
|
||||
id='channel_modal.optional'
|
||||
defaultMessage='(optional)'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
asterisk = <Text style={style.asterisk}>{' *'}</Text>;
|
||||
}
|
||||
|
||||
let labelContent;
|
||||
if (label) {
|
||||
labelContent = (
|
||||
<View style={style.labelContainer}>
|
||||
<Text style={style.label}>
|
||||
{label}
|
||||
</Text>
|
||||
{asterisk}
|
||||
{optionalContent}
|
||||
</View>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
let helpTextContent;
|
||||
if (helpText) {
|
||||
helpTextContent = (
|
||||
<Text style={style.helpText}>
|
||||
{helpText}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
let errorTextContent;
|
||||
if (errorText) {
|
||||
errorTextContent = (
|
||||
<Text style={style.errorText}>
|
||||
{errorText}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={padding(isLandscape)}>
|
||||
{labelContent}
|
||||
<View style={style.separator}/>
|
||||
<View style={[style.inputContainer]}>
|
||||
<Text style={style.placeholderText}>
|
||||
{placeholder}
|
||||
</Text>
|
||||
<Switch
|
||||
onValueChange={this.handleChange}
|
||||
value={value}
|
||||
style={style.inputSwitch}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.separator}/>
|
||||
<View>
|
||||
{helpTextContent}
|
||||
{errorTextContent}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
labelContainer: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 15,
|
||||
marginBottom: 10,
|
||||
},
|
||||
label: {
|
||||
fontSize: 14,
|
||||
color: theme.centerChannelColor,
|
||||
marginLeft: 15,
|
||||
},
|
||||
inputContainer: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 15,
|
||||
height: 40,
|
||||
},
|
||||
placeholderText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 15,
|
||||
},
|
||||
inputSwitch: {
|
||||
position: 'absolute',
|
||||
right: 12,
|
||||
},
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1,
|
||||
width: '100%',
|
||||
},
|
||||
optional: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 14,
|
||||
marginLeft: 5,
|
||||
},
|
||||
helpText: {
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
marginHorizontal: 15,
|
||||
marginTop: 10,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 12,
|
||||
color: theme.errorTextColor,
|
||||
marginHorizontal: 15,
|
||||
marginVertical: 10,
|
||||
},
|
||||
asterisk: {
|
||||
color: theme.errorTextColor,
|
||||
fontSize: 14,
|
||||
},
|
||||
};
|
||||
});
|
||||
35
app/components/widgets/settings/bool_setting.test.js
Normal file
35
app/components/widgets/settings/bool_setting.test.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// 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 BoolSetting from './bool_setting.js';
|
||||
|
||||
describe('components/widgets/settings/TextSetting', () => {
|
||||
const theme = Preferences.THEMES.default;
|
||||
test('onChange', () => {
|
||||
const onChange = jest.fn();
|
||||
const wrapper = shallow(
|
||||
<BoolSetting
|
||||
id='elementid'
|
||||
label='Can you please check below'
|
||||
value={true}
|
||||
placeholder='This is a boolean setting.'
|
||||
optional={false}
|
||||
theme={theme}
|
||||
onChange={onChange}
|
||||
isLandscape={false}
|
||||
/>
|
||||
);
|
||||
|
||||
wrapper.instance().handleChange(false);
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledWith('elementid', 'false');
|
||||
|
||||
wrapper.instance().handleChange(true);
|
||||
expect(onChange).toHaveBeenCalledTimes(2);
|
||||
expect(onChange).toHaveBeenCalledWith('elementid', 'true');
|
||||
});
|
||||
});
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
import React, {PureComponent} from 'react';
|
||||
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';
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ export default class DialogElement extends PureComponent {
|
|||
value: PropTypes.any,
|
||||
onChange: PropTypes.func,
|
||||
theme: PropTypes.object,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -70,6 +72,7 @@ export default class DialogElement extends PureComponent {
|
|||
theme,
|
||||
dataSource,
|
||||
options,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
|
||||
let {maxLength} = this.props;
|
||||
|
|
@ -133,8 +136,23 @@ export default class DialogElement extends PureComponent {
|
|||
roundedBorders={false}
|
||||
/>
|
||||
);
|
||||
} else if (type === 'bool') {
|
||||
return (
|
||||
<BoolSetting
|
||||
id={name}
|
||||
label={displayName}
|
||||
value={value === 'true'}
|
||||
placeholder={placeholder}
|
||||
helpText={helpText}
|
||||
errorText={errorText}
|
||||
optional={optional}
|
||||
theme={theme}
|
||||
onChange={this.onChange}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ describe('DialogElement', () => {
|
|||
displayName: 'Testing',
|
||||
name: 'testing',
|
||||
type: 'text',
|
||||
isLandscape: false,
|
||||
};
|
||||
const theme = Preferences.THEMES.default;
|
||||
test('secureTextEntry is true and multiline is false when subtype is password', () => {
|
||||
|
|
@ -36,4 +37,4 @@ describe('DialogElement', () => {
|
|||
);
|
||||
expect(wrapper.find({secureTextEntry: false}).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {ScrollView, View} from 'react-native';
|
||||
import {Dimensions, ScrollView, View} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import {checkDialogElementForError, checkIfErrorsMatchElements} from 'mattermost-redux/utils/integration_utils';
|
||||
|
|
@ -49,12 +49,27 @@ export default class InteractiveDialog extends PureComponent {
|
|||
this.state = {
|
||||
values,
|
||||
errors: {},
|
||||
isLandscape: this.isLandscape(),
|
||||
submitting: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.navigationEventListener = Navigation.events().bindComponent(this);
|
||||
Dimensions.addEventListener('change', this.orientationDidChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Dimensions.removeEventListener('change', this.orientationDidChange);
|
||||
}
|
||||
|
||||
orientationDidChange = () => {
|
||||
this.setState({isLandscape: this.isLandscape()});
|
||||
}
|
||||
|
||||
isLandscape = () => {
|
||||
const {height, width} = Dimensions.get('window');
|
||||
return width > height;
|
||||
}
|
||||
|
||||
navigationButtonPressed({buttonId}) {
|
||||
|
|
@ -153,6 +168,7 @@ export default class InteractiveDialog extends PureComponent {
|
|||
|
||||
render() {
|
||||
const {introductionText, elements, theme} = this.props;
|
||||
const {errors, isLandscape, values} = this.state;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
|
|
@ -174,16 +190,17 @@ export default class InteractiveDialog extends PureComponent {
|
|||
type={e.type}
|
||||
subtype={e.subtype}
|
||||
helpText={e.help_text}
|
||||
errorText={this.state.errors[e.name]}
|
||||
errorText={errors[e.name]}
|
||||
placeholder={e.placeholder}
|
||||
minLength={e.min_length}
|
||||
maxLength={e.max_length}
|
||||
dataSource={e.data_source}
|
||||
optional={e.optional}
|
||||
options={e.options}
|
||||
value={this.state.values[e.name]}
|
||||
value={values[e.name]}
|
||||
onChange={this.onChange}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
Loading…
Reference in a new issue