From 99cc9c94c3e2d5bba76f23337f0e1a6c6e2960e0 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Wed, 26 Feb 2020 19:20:20 -0500 Subject: [PATCH] MM-10160 - support interactive button styles (#3896) * MM-10160 - support interactive button styles * MM-10160 - update action button styles - move status colors to constants * MM-10160 - add unit tests - add button border opacity - fix color accuracy * MM-10160 - change action button padding - edit hardcoded color status * MM-10160 - update styles * MM-10160 - center action button text * MM-10160 - change action button line height again * MM-10160 - improve variable names * MM-10160 - split up tests to be more descriptive --- .../action_button/action_button.js | 35 +++-- .../action_button/action_button.test.js | 127 ++++++++++++++++++ .../message_attachments/attachment_actions.js | 1 + .../message_attachments/message_attachment.js | 9 +- app/constants/colors.js | 13 ++ 5 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 app/components/message_attachments/action_button/action_button.test.js create mode 100644 app/constants/colors.js diff --git a/app/components/message_attachments/action_button/action_button.js b/app/components/message_attachments/action_button/action_button.js index 760a101d8..54c9c2ec7 100644 --- a/app/components/message_attachments/action_button/action_button.js +++ b/app/components/message_attachments/action_button/action_button.js @@ -7,6 +7,7 @@ import Button from 'react-native-button'; import {preventDoubleTap} from 'app/utils/tap'; import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; +import {STATUS_COLORS} from 'app/constants/colors'; import ActionButtonText from './action_button_text'; export default class ActionButton extends PureComponent { @@ -20,6 +21,7 @@ export default class ActionButton extends PureComponent { theme: PropTypes.object.isRequired, cookie: PropTypes.string.isRequired, disabled: PropTypes.bool, + buttonColor: PropTypes.string, }; handleActionPress = preventDoubleTap(() => { @@ -28,19 +30,27 @@ export default class ActionButton extends PureComponent { }, 4000); render() { - const {name, theme, disabled} = this.props; + const {name, theme, disabled, buttonColor} = this.props; const style = getStyleSheet(theme); + let customButtonStyle; + let customButtonTextStyle; + + if (buttonColor) { + const hexColor = STATUS_COLORS[buttonColor] || theme[buttonColor] || buttonColor; + customButtonStyle = {borderColor: changeOpacity(hexColor, 0.25), backgroundColor: '#ffffff'}; + customButtonTextStyle = {color: hexColor}; + } return ( ); @@ -50,24 +60,23 @@ export default class ActionButton extends PureComponent { const getStyleSheet = makeStyleSheetFromTheme((theme) => { return { button: { - borderRadius: 2, - backgroundColor: theme.buttonBg, + borderRadius: 4, + borderColor: changeOpacity(STATUS_COLORS.default, 0.25), + borderWidth: 2, opacity: 1, alignItems: 'center', - marginBottom: 2, - marginRight: 5, - marginTop: 10, - paddingHorizontal: 10, - paddingVertical: 7, + marginTop: 12, + justifyContent: 'center', + height: 36, }, buttonDisabled: { backgroundColor: changeOpacity(theme.buttonBg, 0.3), }, text: { - color: theme.buttonColor, - fontSize: 12, + color: STATUS_COLORS.default, + fontSize: 15, fontWeight: '600', - lineHeight: 13, + lineHeight: 17, }, }; }); diff --git a/app/components/message_attachments/action_button/action_button.test.js b/app/components/message_attachments/action_button/action_button.test.js new file mode 100644 index 000000000..dd797bbf3 --- /dev/null +++ b/app/components/message_attachments/action_button/action_button.test.js @@ -0,0 +1,127 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {shallow} from 'enzyme'; +import ActionButton from './action_button'; +import {changeOpacity} from 'app/utils/theme'; +import {STATUS_COLORS} from 'app/constants/colors'; + +import Preferences from 'mattermost-redux/constants/preferences'; + +describe('ActionButton', () => { + test('correct styles when from global theme', () => { + const buttonConfig = { + id: 'gpc1tihowfabfmmcizddo', + name: 'Option 1', + style: 'onlineIndicator', + }; + + const baseProps = { + id: buttonConfig.id, + cookie: '', + name: buttonConfig.name, + postId: buttonConfig.id, + buttonColor: buttonConfig.style, + theme: Preferences.THEMES.default, + actions: { + doPostActionWithCookie: jest.fn(), + }, + }; + + const wrapper = shallow(); + + const buttonTextChild = wrapper.getElement().props.children; + const dynamicButtonStyles = wrapper.getElement().props.containerStyle[1]; + + expect(dynamicButtonStyles.borderColor).toBe(changeOpacity(Preferences.THEMES.default[buttonConfig.style], 0.25)); + expect(buttonTextChild.props.style.color).toBe(Preferences.THEMES.default[buttonConfig.style]); + }); + + test('correct styles when a status color', () => { + const buttonConfig = { + id: 'gpc1tihowbfmmcizofm7zhr1to', + name: 'Option 2', + style: 'danger', + }; + + const baseProps = { + id: buttonConfig.id, + cookie: '', + name: buttonConfig.name, + postId: buttonConfig.id, + buttonColor: buttonConfig.style, + theme: Preferences.THEMES.default, + actions: { + doPostActionWithCookie: jest.fn(), + }, + }; + + const wrapper = shallow(); + + const buttonTextChild = wrapper.getElement().props.children; + const dynamicButtonStyles = wrapper.getElement().props.containerStyle[1]; + + expect(dynamicButtonStyles.borderColor).toBe(changeOpacity(STATUS_COLORS[buttonConfig.style], 0.25)); + expect(buttonTextChild.props.style.color).toBe(STATUS_COLORS[buttonConfig.style]); + }); + + test('correct styles when a hex style', () => { + const buttonConfig = { + id: 'gpc1tihowbfawemmcizddo', + name: 'Option 3', + style: '#166de0', + }; + + const baseProps = { + id: buttonConfig.id, + cookie: '', + name: buttonConfig.name, + postId: buttonConfig.id, + buttonColor: buttonConfig.style, + theme: Preferences.THEMES.default, + actions: { + doPostActionWithCookie: jest.fn(), + }, + }; + + const wrapper = shallow(); + + const buttonTextChild = wrapper.getElement().props.children; + const dynamicButtonStyles = wrapper.getElement().props.containerStyle[1]; + + expect(dynamicButtonStyles.borderColor).toBe(changeOpacity(buttonConfig.style, 0.25)); + expect(buttonTextChild.props.style.color).toBe(buttonConfig.style); + }); + + test('correct default styles', () => { + const buttonConfig = { + id: 'gpc1tihowbfawemmcizddo', + name: 'Option 4', + }; + + const baseProps = { + id: buttonConfig.id, + cookie: '', + name: buttonConfig.name, + postId: buttonConfig.id, + buttonColor: buttonConfig.style, + theme: Preferences.THEMES.default, + actions: { + doPostActionWithCookie: jest.fn(), + }, + }; + + const wrapper = shallow(); + + const buttonTextChild = wrapper.getElement().props.children; + const baseButtonStyles = wrapper.getElement().props.containerStyle[0]; + const dynamicButtonStyles = wrapper.getElement().props.containerStyle[1]; + + expect(baseButtonStyles.borderColor).toBe(changeOpacity(Preferences.THEMES.default.centerChannelColor, 0.25)); + expect(baseButtonStyles.borderWidth).toBe(2); + expect(baseButtonStyles.borderRadius).toBe(4); + expect(dynamicButtonStyles).toBe(undefined); + expect(buttonTextChild.props.style.color).toBe(Preferences.THEMES.default.centerChannelColor); + }); +}); \ No newline at end of file diff --git a/app/components/message_attachments/attachment_actions.js b/app/components/message_attachments/attachment_actions.js index 361e1c30e..e80f84618 100644 --- a/app/components/message_attachments/attachment_actions.js +++ b/app/components/message_attachments/attachment_actions.js @@ -55,6 +55,7 @@ export default class AttachmentActions extends PureComponent { name={action.name} postId={postId} disabled={action.disabled} + buttonColor={action.style} />, ); break; diff --git a/app/components/message_attachments/message_attachment.js b/app/components/message_attachments/message_attachment.js index 1c750b9b0..e29bf1cae 100644 --- a/app/components/message_attachments/message_attachment.js +++ b/app/components/message_attachments/message_attachment.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import {View} from 'react-native'; import CustomPropTypes from 'app/constants/custom_prop_types'; +import {STATUS_COLORS} from 'app/constants/colors'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import AttachmentActions from './attachment_actions'; @@ -18,12 +19,6 @@ import AttachmentThumbnail from './attachment_thumbnail'; import AttachmentTitle from './attachment_title'; import AttachmentFooter from './attachment_footer'; -const STATUS_COLORS = { - good: '#00c100', - warning: '#dede01', - danger: '#e40303', -}; - export default class MessageAttachment extends PureComponent { static propTypes = { attachment: PropTypes.object.isRequired, @@ -138,7 +133,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { borderRightWidth: 1, borderTopWidth: 1, marginTop: 5, - padding: 10, + padding: 12, }, border: { borderLeftColor: changeOpacity(theme.linkColor, 0.6), diff --git a/app/constants/colors.js b/app/constants/colors.js new file mode 100644 index 000000000..795bb23e4 --- /dev/null +++ b/app/constants/colors.js @@ -0,0 +1,13 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import Preferences from 'mattermost-redux/constants/preferences'; + +export const STATUS_COLORS = { + good: '#00c100', + warning: '#dede01', + danger: Preferences.THEMES.default.errorTextColor, + default: Preferences.THEMES.default.centerChannelColor, + primary: Preferences.THEMES.default.buttonBg, + success: Preferences.THEMES.default.onlineIndicator, +};