Commit d5455408 by Michael Roytman Committed by jaebradley

fix(asinput): Add danger theme to asInput component

Add danger theme to asInput component to add alert icon, red text for validation message, and red border to input element
parent 1d25dfca
...@@ -499,6 +499,7 @@ exports[`Storyshots InputText focus test 1`] = ` ...@@ -499,6 +499,7 @@ exports[`Storyshots InputText focus test 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder={undefined} placeholder={undefined}
required={false} required={false}
themes={Array []}
type="text" type="text"
value="" value=""
/> />
...@@ -527,6 +528,7 @@ exports[`Storyshots InputText minimal usage 1`] = ` ...@@ -527,6 +528,7 @@ exports[`Storyshots InputText minimal usage 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder={undefined} placeholder={undefined}
required={false} required={false}
themes={Array []}
type="text" type="text"
value="Foo Bar" value="Foo Bar"
/> />
...@@ -554,6 +556,7 @@ exports[`Storyshots InputText validation 1`] = ` ...@@ -554,6 +556,7 @@ exports[`Storyshots InputText validation 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder={undefined} placeholder={undefined}
required={false} required={false}
themes={Array []}
type="text" type="text"
value="" value=""
/> />
...@@ -566,6 +569,44 @@ exports[`Storyshots InputText validation 1`] = ` ...@@ -566,6 +569,44 @@ exports[`Storyshots InputText validation 1`] = `
</div> </div>
`; `;
exports[`Storyshots InputText validation with danger theme 1`] = `
<div
className="form-group"
>
<label
htmlFor="asInput3"
id="label-asInput3"
>
Username
</label>
<input
aria-describedby="undefined description-asInput3"
aria-invalid={false}
className="form-control"
disabled={false}
id="asInput3"
name="username"
onBlur={[Function]}
onChange={[Function]}
placeholder={undefined}
required={false}
themes={
Array [
"danger",
]
}
type="text"
value=""
/>
<small
className="form-text"
id="description-asInput3"
>
The unique name that identifies you throughout the site.
</small>
</div>
`;
exports[`Storyshots Modal basic usage 1`] = ` exports[`Storyshots Modal basic usage 1`] = `
<div <div
aria-labelledby="id5" aria-labelledby="id5"
...@@ -1058,6 +1099,7 @@ exports[`Storyshots Modal modal with element body 1`] = ` ...@@ -1058,6 +1099,7 @@ exports[`Storyshots Modal modal with element body 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder={undefined} placeholder={undefined}
required={false} required={false}
themes={Array []}
type="text" type="text"
value="" value=""
/> />
...@@ -2044,6 +2086,11 @@ exports[`Storyshots Textarea minimal usage 1`] = ` ...@@ -2044,6 +2086,11 @@ exports[`Storyshots Textarea minimal usage 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder={undefined} placeholder={undefined}
required={false} required={false}
themes={
Array [
"danger",
]
}
value="Foo Bar" value="Foo Bar"
/> />
</div> </div>
...@@ -2070,6 +2117,11 @@ exports[`Storyshots Textarea scrollable 1`] = ` ...@@ -2070,6 +2117,11 @@ exports[`Storyshots Textarea scrollable 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder={undefined} placeholder={undefined}
required={false} required={false}
themes={
Array [
"danger",
]
}
value="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." value="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
/> />
</div> </div>
...@@ -2096,6 +2148,11 @@ exports[`Storyshots Textarea validation 1`] = ` ...@@ -2096,6 +2148,11 @@ exports[`Storyshots Textarea validation 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder={undefined} placeholder={undefined}
required={false} required={false}
themes={
Array [
"danger",
]
}
value="" value=""
/> />
<small <small
......
...@@ -65,6 +65,24 @@ storiesOf('InputText', module) ...@@ -65,6 +65,24 @@ storiesOf('InputText', module)
}} }}
/> />
)) ))
.add('validation with danger theme', () => (
<InputText
name="username"
label="Username"
description="The unique name that identifies you throughout the site."
validator={(value) => {
let feedback = { isValid: true };
if (value.length < 3) {
feedback = {
isValid: false,
validationMessage: 'Username must be at least 3 characters in length.',
};
}
return feedback;
}}
themes={['danger']}
/>
))
.add('focus test', () => ( .add('focus test', () => (
<FocusInputWrapper /> <FocusInputWrapper />
)); ));
...@@ -19,6 +19,7 @@ function Text(props) { ...@@ -19,6 +19,7 @@ function Text(props) {
disabled={props.disabled} disabled={props.disabled}
required={props.required} required={props.required}
ref={props.inputRef} ref={props.inputRef}
themes={props.themes}
/> />
); );
} }
......
...@@ -18,6 +18,7 @@ function Text(props) { ...@@ -18,6 +18,7 @@ function Text(props) {
disabled={props.disabled} disabled={props.disabled}
required={props.required} required={props.required}
ref={props.inputRef} ref={props.inputRef}
themes={['danger']}
/> />
); );
} }
......
@import "~bootstrap/scss/_forms"; @import "~bootstrap/scss/_forms";
@import "~bootstrap/scss/mixins/_forms";
.fa-icon-spacing {
padding: 0px 5px 0px 0px;
}
...@@ -117,23 +117,47 @@ describe('asInput()', () => { ...@@ -117,23 +117,47 @@ describe('asInput()', () => {
expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1);
}); });
it('and displays error message when invalid', () => { describe('and display error message when invalid', () => {
const spy = jest.fn(); let spy;
const validationResult = { let validationResult;
isValid: false, let wrapper;
validationMessage: 'Invalid!!1',
}; beforeEach(() => {
spy.mockReturnValueOnce(validationResult); spy = jest.fn();
const props = { validationResult = {
...baseProps, isValid: false,
validator: spy, validationMessage: 'Invalid!!1',
}; };
const wrapper = mount(<InputTestComponent {...props} />); spy.mockReturnValueOnce(validationResult);
wrapper.find('input').simulate('blur'); const props = {
expect(spy).toHaveBeenCalledTimes(1); ...baseProps,
const err = wrapper.find('.form-control-feedback'); validator: spy,
expect(err.exists()).toEqual(true); };
expect(err.text()).toEqual(validationResult.validationMessage); wrapper = mount(<InputTestComponent {...props} />);
});
it('without theme', () => {
wrapper.find('input').simulate('blur');
expect(spy).toHaveBeenCalledTimes(1);
const err = wrapper.find('.form-control-feedback');
expect(err.exists()).toEqual(true);
expect(err.text()).toEqual(validationResult.validationMessage);
});
it('with danger theme', () => {
wrapper.setProps({ themes: ['danger'] });
wrapper.find('input').simulate('blur');
expect(spy).toHaveBeenCalledTimes(1);
const err = wrapper.find('.form-control-feedback');
expect(err.exists()).toEqual(true);
expect(err.text()).toEqual(validationResult.validationMessage);
// expect(err.hasClass('invalid-feedback')).toEqual(true);
const dangerIcon = wrapper.find('.fa-exclamation-circle');
expect(dangerIcon.exists()).toEqual(true);
expect(dangerIcon.hasClass('fa')).toEqual(true);
const inputElement = wrapper.find('.form-control');
expect(inputElement.hasClass('is-invalid')).toEqual(true);
});
}); });
}); });
}); });
......
/* eslint-disable react/no-unused-prop-types */ /* eslint-disable react/no-unused-prop-types */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import newId from '../utils/newId'; import newId from '../utils/newId';
import styles from './asInput.scss'; import styles from './asInput.scss';
...@@ -23,6 +25,7 @@ export const inputProps = { ...@@ -23,6 +25,7 @@ export const inputProps = {
onBlur: PropTypes.func, onBlur: PropTypes.func,
validator: PropTypes.func, validator: PropTypes.func,
className: PropTypes.arrayOf(PropTypes.string), className: PropTypes.arrayOf(PropTypes.string),
themes: PropTypes.arrayOf(PropTypes.string),
}; };
const asInput = (WrappedComponent, labelFirst = true) => { const asInput = (WrappedComponent, labelFirst = true) => {
...@@ -50,9 +53,19 @@ const asInput = (WrappedComponent, labelFirst = true) => { ...@@ -50,9 +53,19 @@ const asInput = (WrappedComponent, labelFirst = true) => {
const desc = {}; const desc = {};
if (!this.state.isValid) { if (!this.state.isValid) {
const hasDangerTheme = this.hasDangerTheme();
desc.error = ( desc.error = (
<div className={styles['form-control-feedback']} id={errorId} key="0"> <div className={classNames(styles['form-control-feedback'], { [styles['invalid-feedback']]: hasDangerTheme })} id={errorId} key="0">
{this.state.validationMessage} { hasDangerTheme &&
<span
className={classNames(FontAwesomeStyles.fa, FontAwesomeStyles['fa-exclamation-circle'], styles['fa-icon-spacing'])}
aria-hidden
/>
}
<span>
{this.state.validationMessage}
</span>
</div> </div>
); );
desc.describedBy = errorId; desc.describedBy = errorId;
...@@ -70,6 +83,10 @@ const asInput = (WrappedComponent, labelFirst = true) => { ...@@ -70,6 +83,10 @@ const asInput = (WrappedComponent, labelFirst = true) => {
return desc; return desc;
} }
hasDangerTheme() {
return this.props.themes.includes('danger');
}
handleBlur(event) { handleBlur(event) {
const val = event.target.value; const val = event.target.value;
...@@ -95,10 +112,11 @@ const asInput = (WrappedComponent, labelFirst = true) => { ...@@ -95,10 +112,11 @@ const asInput = (WrappedComponent, labelFirst = true) => {
<WrappedComponent <WrappedComponent
{...this.props} {...this.props}
{...this.state} {...this.state}
className={[ className={[classNames(
styles['form-control'], styles['form-control'],
...this.props.className, { [styles['is-invalid']]: !this.state.isValid && this.hasDangerTheme() },
]} { ...this.props.className },
)]}
describedBy={describedBy} describedBy={describedBy}
onChange={this.handleChange} onChange={this.handleChange}
onBlur={this.handleBlur} onBlur={this.handleBlur}
...@@ -125,6 +143,7 @@ const asInput = (WrappedComponent, labelFirst = true) => { ...@@ -125,6 +143,7 @@ const asInput = (WrappedComponent, labelFirst = true) => {
required: false, required: false,
validator: undefined, validator: undefined,
className: [], className: [],
themes: [],
}; };
return NewComponent; return NewComponent;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment