MM-15219 Added support for introduction text in interactive dialogs (#3222)
* MM-15219 Added support for optional introduction text in interactive dialogs * Fixed margins in introduction text for interactive dialogs * Created DialogIntroductionText component to render the introduction text. Changed text color * Changed DialogIntroductionText component to only render if a value is supplied
This commit is contained in:
parent
19e6b2a00e
commit
30f91a3814
8 changed files with 321 additions and 2 deletions
|
|
@ -55,7 +55,7 @@ describe('InteractiveDialogController', () => {
|
|||
});
|
||||
});
|
||||
|
||||
function getBaseProps(triggerId, elements) {
|
||||
function getBaseProps(triggerId, elements, introductionText) {
|
||||
const dialogData = {
|
||||
dialog: {
|
||||
callback_id: 'somecallbackid',
|
||||
|
|
@ -65,6 +65,7 @@ function getBaseProps(triggerId, elements) {
|
|||
state: 'somestate',
|
||||
submit_label: 'Submit Test',
|
||||
title: 'Dialog Test',
|
||||
introductionText,
|
||||
},
|
||||
trigger_id: triggerId,
|
||||
url: 'https://localhost:8065/dialog_submit',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DialogIntroductionText should not render the component with an empty value 1`] = `null`;
|
||||
|
||||
exports[`DialogIntroductionText should render the introduction text correctly 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(Markdown)
|
||||
baseTextStyle={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
}
|
||||
}
|
||||
blockStyles={
|
||||
Object {
|
||||
"adjacentParagraph": Object {
|
||||
"marginTop": 6,
|
||||
},
|
||||
"horizontalRule": Object {
|
||||
"backgroundColor": "#3d3c40",
|
||||
"height": 0.5,
|
||||
"marginVertical": 10,
|
||||
},
|
||||
"quoteBlockIcon": Object {
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"padding": 5,
|
||||
},
|
||||
}
|
||||
}
|
||||
disableAtMentions={true}
|
||||
disableChannelLink={true}
|
||||
disableHashtags={true}
|
||||
textStyles={
|
||||
Object {
|
||||
"code": Object {
|
||||
"alignSelf": "center",
|
||||
"backgroundColor": "rgba(61,60,64,0.07)",
|
||||
"fontFamily": "Menlo",
|
||||
},
|
||||
"codeBlock": Object {
|
||||
"fontFamily": "Menlo",
|
||||
},
|
||||
"del": Object {
|
||||
"textDecorationLine": "line-through",
|
||||
},
|
||||
"emph": Object {
|
||||
"fontStyle": "italic",
|
||||
},
|
||||
"error": Object {
|
||||
"color": "#fd5960",
|
||||
},
|
||||
"heading1": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading1Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"heading2": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading2Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"heading3": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading3Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"heading4": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading4Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"heading5": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading5Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"heading6": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading6Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"link": Object {
|
||||
"color": "#2389d7",
|
||||
},
|
||||
"mention": Object {
|
||||
"color": "#2389d7",
|
||||
},
|
||||
"mention_highlight": Object {
|
||||
"backgroundColor": "#ffe577",
|
||||
},
|
||||
"strong": Object {
|
||||
"fontWeight": "bold",
|
||||
},
|
||||
"table_header_row": Object {
|
||||
"fontWeight": "700",
|
||||
},
|
||||
}
|
||||
}
|
||||
value="**bold** *italic* [link](https://mattermost.com/) <br/> [link target blank](!https://mattermost.com/)"
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`InteractiveDialog should display introduction text if present 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.03)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ScrollViewMock
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": 20,
|
||||
"marginTop": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<DialogIntroductionText
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
value="**Some** _introduction_ text"
|
||||
/>
|
||||
</ScrollViewMock>
|
||||
</View>
|
||||
`;
|
||||
58
app/screens/interactive_dialog/dialog_introduction_text.js
Normal file
58
app/screens/interactive_dialog/dialog_introduction_text.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Markdown from 'app/components/markdown';
|
||||
|
||||
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class DialogIntroductionText extends PureComponent {
|
||||
static propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
if (value) {
|
||||
const style = getStyleFromTheme(theme);
|
||||
const blockStyles = getMarkdownBlockStyles(theme);
|
||||
const textStyles = getMarkdownTextStyles(theme);
|
||||
|
||||
return (
|
||||
<View style={style.introductionTextView}>
|
||||
<Markdown
|
||||
baseTextStyle={style.introductionText}
|
||||
textStyles={textStyles}
|
||||
blockStyles={blockStyles}
|
||||
value={value}
|
||||
disableHashtags={true}
|
||||
disableAtMentions={true}
|
||||
disableChannelLink={true}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
introductionTextView: {
|
||||
marginHorizontal: 15,
|
||||
},
|
||||
introductionText: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// 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 DialogIntroductionText from './dialog_introduction_text.js';
|
||||
|
||||
describe('DialogIntroductionText', () => {
|
||||
const baseProps = {
|
||||
theme: Preferences.THEMES.default,
|
||||
value: '**bold** *italic* [link](https://mattermost.com/) <br/> [link target blank](!https://mattermost.com/)',
|
||||
};
|
||||
|
||||
test('should render the introduction text correctly', () => {
|
||||
const wrapper = shallow(
|
||||
<DialogIntroductionText
|
||||
{...baseProps}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should not render the component with an empty value', () => {
|
||||
baseProps.value = '';
|
||||
|
||||
const wrapper = shallow(
|
||||
<DialogIntroductionText
|
||||
{...baseProps}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -19,6 +19,7 @@ function mapStateToProps(state) {
|
|||
callbackId: data.dialog.callback_id,
|
||||
elements: data.dialog.elements,
|
||||
title: data.dialog.title,
|
||||
introductionText: data.dialog.introduction_text,
|
||||
iconUrl: data.dialog.icon_url,
|
||||
submitLabel: data.dialog.submit_label,
|
||||
notifyOnCancel: data.dialog.notify_on_cancel,
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@ import StatusBar from 'app/components/status_bar';
|
|||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
import DialogElement from './dialog_element.js';
|
||||
import DialogIntroductionText from './dialog_introduction_text.js';
|
||||
|
||||
export default class InteractiveDialog extends PureComponent {
|
||||
static propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
callbackId: PropTypes.string,
|
||||
introductionText: PropTypes.string,
|
||||
elements: PropTypes.arrayOf(PropTypes.object),
|
||||
notifyOnCancel: PropTypes.bool,
|
||||
state: PropTypes.string,
|
||||
|
|
@ -150,13 +152,19 @@ export default class InteractiveDialog extends PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {elements, theme} = this.props;
|
||||
const {introductionText, elements, theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<ScrollView style={style.scrollView}>
|
||||
<StatusBar/>
|
||||
{Boolean(introductionText) &&
|
||||
<DialogIntroductionText
|
||||
value={introductionText}
|
||||
theme={theme}
|
||||
/>
|
||||
}
|
||||
{elements && elements.map((e) => {
|
||||
return (
|
||||
<DialogElement
|
||||
|
|
|
|||
|
|
@ -106,4 +106,39 @@ describe('InteractiveDialog', () => {
|
|||
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledWith(dialog);
|
||||
});
|
||||
|
||||
test('should display introduction text if present', async () => {
|
||||
baseProps.introductionText = '**Some** _introduction_ text';
|
||||
|
||||
const wrapper = shallow(
|
||||
<InteractiveDialog
|
||||
{...baseProps}
|
||||
notifyOnCancel={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('introduction text should not affect submission', async () => {
|
||||
baseProps.introductionText = '**Some** _introduction_ text';
|
||||
|
||||
const wrapper = shallow(
|
||||
<InteractiveDialog
|
||||
{...baseProps}
|
||||
notifyOnCancel={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
const dialog = {
|
||||
url: baseProps.url,
|
||||
callback_id: baseProps.callbackId,
|
||||
state: baseProps.state,
|
||||
submission: {},
|
||||
};
|
||||
|
||||
wrapper.instance().handleSubmit();
|
||||
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledWith(dialog);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue