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
This commit is contained in:
parent
3e2fc1bc67
commit
99cc9c94c3
5 changed files with 165 additions and 20 deletions
|
|
@ -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 (
|
||||
<Button
|
||||
containerStyle={style.button}
|
||||
containerStyle={[style.button, customButtonStyle]}
|
||||
disabledContainerStyle={style.buttonDisabled}
|
||||
onPress={this.handleActionPress}
|
||||
disabled={disabled}
|
||||
>
|
||||
<ActionButtonText
|
||||
message={name}
|
||||
style={style.text}
|
||||
style={{...style.text, ...customButtonTextStyle}}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(<ActionButton {...baseProps}/>);
|
||||
|
||||
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(<ActionButton {...baseProps}/>);
|
||||
|
||||
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(<ActionButton {...baseProps}/>);
|
||||
|
||||
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(<ActionButton {...baseProps}/>);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
@ -55,6 +55,7 @@ export default class AttachmentActions extends PureComponent {
|
|||
name={action.name}
|
||||
postId={postId}
|
||||
disabled={action.disabled}
|
||||
buttonColor={action.style}
|
||||
/>,
|
||||
);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
13
app/constants/colors.js
Normal file
13
app/constants/colors.js
Normal file
|
|
@ -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,
|
||||
};
|
||||
Loading…
Reference in a new issue