Skip to content

Fix TextField color by state logic #631

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 28, 2020
69 changes: 35 additions & 34 deletions src/components/inputs/TextField.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,22 @@ import Text from '../text';
import TouchableOpacity from '../touchableOpacity';

const DEFAULT_COLOR_BY_STATE = {
default: Colors.dark40,
focus: Colors.blue30,
error: Colors.red30
default: Colors.grey10,
focus: Colors.grey10,
error: Colors.grey10,
disabled: Colors.grey50
};
const DEFAULT_UNDERLINE_COLOR_BY_STATE = {
default: Colors.dark70,
default: Colors.grey50,
focus: Colors.blue30,
error: Colors.red30
};

const DEFAULT_PLACEHOLDER_COLOR_BY_STATE = {
default: Colors.grey30,
focus: Colors.blue30
};

const LABEL_TYPOGRAPHY = Typography.text80;
const ICON_SIZE = 24;
const ICON_RIGHT_PADDING = 3;
Expand All @@ -52,7 +59,7 @@ export default class TextField extends BaseInput {
*/
floatingPlaceholder: PropTypes.bool,
/**
* floating placeholder color as a string or object of states, ex. {default: 'black', error: 'red', focus: 'blue'}
* floating placeholder color as a string or object of states, ex. {default: 'black', error: 'red', focus: 'blue', disabled: 'grey'}
*/
floatingPlaceholderColor: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
/**
Expand All @@ -65,7 +72,7 @@ export default class TextField extends BaseInput {
*/
hideUnderline: PropTypes.bool,
/**
* underline color as a string or object of states, ex. {default: 'black', error: 'red', focus: 'blue'}
* underline color as a string or object of states, ex. {default: 'black', error: 'red', focus: 'blue', disabled: 'grey'}
*/
underlineColor: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
/**
Expand Down Expand Up @@ -115,7 +122,7 @@ export default class TextField extends BaseInput {
*/
title: PropTypes.string,
/**
* The title's color as a string or object of states, ex. {default: 'black', error: 'red', focus: 'blue'}
* The title's color as a string or object of states, ex. {default: 'black', error: 'red', focus: 'blue', disabled: 'grey'}
*/
titleColor: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
/**
Expand Down Expand Up @@ -225,34 +232,28 @@ export default class TextField extends BaseInput {
return text;
}

getStateColor(colorProp, isUnderline) {
getStateColor(colorProp = {}) {
const {focused} = this.state;
const {disabledColor} = this.getThemeProps();
const error = this.getErrorMessage();
const colorByState = _.cloneDeep(isUnderline ? DEFAULT_UNDERLINE_COLOR_BY_STATE : DEFAULT_COLOR_BY_STATE);

if (this.isDisabled() && disabledColor) {
return disabledColor;
}
const {disabledColor} = this.getThemeProps();

if (colorProp) {
if (_.isString(colorProp)) {
// use given color for any state
return colorProp;
} else if (_.isObject(colorProp)) {
// set given colors by states
_.merge(colorByState, colorProp);
if (_.isString(colorProp)) {
return colorProp || Colors.dark10;
} else if (_.isPlainObject(colorProp)) {
const mergedColorState = {...DEFAULT_COLOR_BY_STATE, ...colorProp};

if (this.isDisabled()) {
return disabledColor || mergedColorState.disabled;
} else if (error) {
return mergedColorState.error;
} else if (focused) {
return mergedColorState.focus;
} else {
return mergedColorState.default;
}
}

// return the right color for the current state
let color = colorByState.default;
if (error && isUnderline) {
color = colorByState.error;
} else if (focused) {
color = colorByState.focus;
}
return color;
return colorProp || Colors.dark10;
}

getCharCount() {
Expand Down Expand Up @@ -330,7 +331,7 @@ export default class TextField extends BaseInput {
const {expandable, placeholder, placeholderTextColor, floatingPlaceholderColor, multiline}
= this.getThemeProps();
const typography = this.getTypography();
const placeholderColor = this.getStateColor(placeholderTextColor || DEFAULT_COLOR_BY_STATE.default);
const placeholderColor = this.getStateColor(placeholderTextColor || DEFAULT_PLACEHOLDER_COLOR_BY_STATE.default);

if (this.shouldFakePlaceholder()) {
return (
Expand All @@ -351,7 +352,7 @@ export default class TextField extends BaseInput {
}),
color: floatingPlaceholderState.interpolate({
inputRange: [0, 1],
outputRange: [placeholderColor, this.getStateColor(floatingPlaceholderColor)]
outputRange: [placeholderColor, this.getStateColor(floatingPlaceholderColor || DEFAULT_PLACEHOLDER_COLOR_BY_STATE)]
}),
lineHeight: this.shouldFloatPlaceholder() ? LABEL_TYPOGRAPHY.lineHeight : typography.lineHeight
}
Expand All @@ -369,7 +370,7 @@ export default class TextField extends BaseInput {

renderTitle() {
const {floatingPlaceholder, title, titleColor, titleStyle} = this.getThemeProps();
const color = this.getStateColor(titleColor);
const color = this.getStateColor(titleColor || DEFAULT_PLACEHOLDER_COLOR_BY_STATE);

if (!floatingPlaceholder && title) {
return <Text style={[{color}, this.styles.topLabel, this.styles.label, titleStyle]}>{title}</Text>;
Expand Down Expand Up @@ -501,7 +502,7 @@ export default class TextField extends BaseInput {
];

const placeholderText = this.getPlaceholderText();
const placeholderColor = this.getStateColor(placeholderTextColor || DEFAULT_COLOR_BY_STATE.default);
const placeholderColor = this.getStateColor(placeholderTextColor || DEFAULT_PLACEHOLDER_COLOR_BY_STATE.default);
const isEditable = !this.isDisabled() && !expandable;

return (
Expand Down Expand Up @@ -562,7 +563,7 @@ export default class TextField extends BaseInput {

render() {
const {expandable, containerStyle, underlineColor, useTopErrors, hideUnderline} = this.getThemeProps();
const underlineStateColor = this.getStateColor(underlineColor, true);
const underlineStateColor = this.getStateColor(underlineColor || DEFAULT_UNDERLINE_COLOR_BY_STATE);

return (
<View style={[this.styles.container, containerStyle]} collapsable={false}>
Expand Down
57 changes: 39 additions & 18 deletions src/components/inputs/__tests__/TextField.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,54 @@ describe('TextField', () => {
});

describe('getStateColor', () => {
it('should return dark70 when blur (inactive)', () => {
it('should return grey10 when no color state or color was passed', () => {
const uut = new TextField({});
expect(uut.getStateColor(undefined, true)).toEqual(Colors.dark70);
expect(uut.getStateColor(undefined)).toEqual(Colors.grey10);
});
it('should return red30 when error', () => {
const uut = new TextField({error: 'test error'});
expect(uut.getStateColor(undefined, true)).toEqual(Colors.red30);

it('should return the string color given as the first argument ', () => {
const uut = new TextField({});
expect(uut.getStateColor(Colors.blue30)).toEqual(Colors.blue30);
});
it('should return blue30 when focused', () => {

it('should return "default" color from the color states object passed', () => {
const uut = new TextField({});
uut.state = {focused: true};
expect(uut.getStateColor(undefined, true)).toEqual(Colors.blue30);
expect(uut.getStateColor({default: Colors.blue30})).toEqual(Colors.blue30);
});

const underlines = {default: Colors.cyan40, focus: Colors.orange60, error: Colors.purple50};
it('should return cyan40 when passing underlineColor and when blur (inactive)', () => {
const uut = new TextField({underlineColor: underlines});
expect(uut.getStateColor(uut.props.underlineColor, true)).toEqual(Colors.cyan40);
it('should return "focus" color from the color states object passed when input is focused', () => {
const uut = new TextField({});
uut.state = {focused: true};
expect(uut.getStateColor({default: Colors.grey10, focus: Colors.green30})).toEqual(Colors.green30);
});
it('should return purple50 when passing underlineColor and when error', () => {
const uut = new TextField({underlineColor: underlines, error: 'test error'});
expect(uut.getStateColor(uut.props.underlineColor, true)).toEqual(Colors.purple50);

it('should return default component state colors by default', () => {
const uut = new TextField({});
expect(uut.getStateColor()).toEqual(Colors.grey10);
uut.state = {focused: true};
expect(uut.getStateColor()).toEqual(Colors.grey10);
});
it('should return orange60 when passing underlineColor and when focused', () => {
const uut = new TextField({underlineColor: underlines});

it('should return default component state colors by default even when given partial color state', () => {
const uut = new TextField({});
expect(uut.getStateColor({focus: Colors.blue30})).toEqual(Colors.grey10);
uut.state = {focused: true};
expect(uut.getStateColor(uut.props.underlineColor, true)).toEqual(Colors.orange60);
expect(uut.getStateColor({default: Colors.dark20})).toEqual(Colors.grey10);
});

it('should return default "error" state when there is an error', () => {
const uut = new TextField({error: 'error'});
expect(uut.getStateColor()).toEqual(Colors.grey10);
});

it('should return given "error" state when there is an error', () => {
const uut = new TextField({error: 'error'});
expect(uut.getStateColor({default: Colors.grey10, error: Colors.red20})).toEqual(Colors.red20);
});

it('should return disabled color when disabled', () => {
const uut = new TextField({editable: false});
expect(uut.getStateColor()).toEqual(Colors.grey50);
});
});

Expand Down