From d5455408fb9022dc499b8e5e59245a59227fc942 Mon Sep 17 00:00:00 2001 From: Michael Roytman Date: Tue, 5 Dec 2017 15:16:28 -0500 Subject: [PATCH] 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 --- .../__snapshots__/Storyshots.test.js.snap | 57 ++++++++++++++++++ src/InputText/InputText.stories.jsx | 18 ++++++ src/InputText/index.jsx | 1 + src/TextArea/index.jsx | 1 + src/asInput/asInput.scss | 5 ++ src/asInput/asInput.test.jsx | 58 +++++++++++++------ src/asInput/index.jsx | 29 ++++++++-- 7 files changed, 147 insertions(+), 22 deletions(-) diff --git a/.storybook/__snapshots__/Storyshots.test.js.snap b/.storybook/__snapshots__/Storyshots.test.js.snap index 995f8eb9b1..c71d2935d6 100644 --- a/.storybook/__snapshots__/Storyshots.test.js.snap +++ b/.storybook/__snapshots__/Storyshots.test.js.snap @@ -499,6 +499,7 @@ exports[`Storyshots InputText focus test 1`] = ` onChange={[Function]} placeholder={undefined} required={false} + themes={Array []} type="text" value="" /> @@ -527,6 +528,7 @@ exports[`Storyshots InputText minimal usage 1`] = ` onChange={[Function]} placeholder={undefined} required={false} + themes={Array []} type="text" value="Foo Bar" /> @@ -554,6 +556,7 @@ exports[`Storyshots InputText validation 1`] = ` onChange={[Function]} placeholder={undefined} required={false} + themes={Array []} type="text" value="" /> @@ -566,6 +569,44 @@ exports[`Storyshots InputText validation 1`] = ` `; +exports[`Storyshots InputText validation with danger theme 1`] = ` +
+ + + + The unique name that identifies you throughout the site. + +
+`; + exports[`Storyshots Modal basic usage 1`] = `
@@ -2044,6 +2086,11 @@ exports[`Storyshots Textarea minimal usage 1`] = ` onChange={[Function]} placeholder={undefined} required={false} + themes={ + Array [ + "danger", + ] + } value="Foo Bar" />
@@ -2070,6 +2117,11 @@ exports[`Storyshots Textarea scrollable 1`] = ` onChange={[Function]} placeholder={undefined} 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." /> @@ -2096,6 +2148,11 @@ exports[`Storyshots Textarea validation 1`] = ` onChange={[Function]} placeholder={undefined} required={false} + themes={ + Array [ + "danger", + ] + } value="" /> )) + .add('validation with danger theme', () => ( + { + 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', () => ( )); diff --git a/src/InputText/index.jsx b/src/InputText/index.jsx index 0c145a1263..40b0c1187a 100644 --- a/src/InputText/index.jsx +++ b/src/InputText/index.jsx @@ -19,6 +19,7 @@ function Text(props) { disabled={props.disabled} required={props.required} ref={props.inputRef} + themes={props.themes} /> ); } diff --git a/src/TextArea/index.jsx b/src/TextArea/index.jsx index cedcbc28df..4171ce97fa 100644 --- a/src/TextArea/index.jsx +++ b/src/TextArea/index.jsx @@ -18,6 +18,7 @@ function Text(props) { disabled={props.disabled} required={props.required} ref={props.inputRef} + themes={['danger']} /> ); } diff --git a/src/asInput/asInput.scss b/src/asInput/asInput.scss index e693ffa3d7..361f655e08 100644 --- a/src/asInput/asInput.scss +++ b/src/asInput/asInput.scss @@ -1 +1,6 @@ @import "~bootstrap/scss/_forms"; +@import "~bootstrap/scss/mixins/_forms"; + +.fa-icon-spacing { + padding: 0px 5px 0px 0px; +} diff --git a/src/asInput/asInput.test.jsx b/src/asInput/asInput.test.jsx index c8c1e81e2c..ab035c8f00 100644 --- a/src/asInput/asInput.test.jsx +++ b/src/asInput/asInput.test.jsx @@ -117,23 +117,47 @@ describe('asInput()', () => { expect(spy).toHaveBeenCalledTimes(1); }); - it('and displays error message when invalid', () => { - const spy = jest.fn(); - const validationResult = { - isValid: false, - validationMessage: 'Invalid!!1', - }; - spy.mockReturnValueOnce(validationResult); - const props = { - ...baseProps, - validator: spy, - }; - const wrapper = mount(); - 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); + describe('and display error message when invalid', () => { + let spy; + let validationResult; + let wrapper; + + beforeEach(() => { + spy = jest.fn(); + validationResult = { + isValid: false, + validationMessage: 'Invalid!!1', + }; + spy.mockReturnValueOnce(validationResult); + const props = { + ...baseProps, + validator: spy, + }; + wrapper = mount(); + }); + 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); + }); }); }); }); diff --git a/src/asInput/index.jsx b/src/asInput/index.jsx index da9f3d5091..3a3c6f4a27 100644 --- a/src/asInput/index.jsx +++ b/src/asInput/index.jsx @@ -1,6 +1,8 @@ /* eslint-disable react/no-unused-prop-types */ import React from 'react'; 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 styles from './asInput.scss'; @@ -23,6 +25,7 @@ export const inputProps = { onBlur: PropTypes.func, validator: PropTypes.func, className: PropTypes.arrayOf(PropTypes.string), + themes: PropTypes.arrayOf(PropTypes.string), }; const asInput = (WrappedComponent, labelFirst = true) => { @@ -50,9 +53,19 @@ const asInput = (WrappedComponent, labelFirst = true) => { const desc = {}; if (!this.state.isValid) { + const hasDangerTheme = this.hasDangerTheme(); + desc.error = ( -
- {this.state.validationMessage} +
+ { hasDangerTheme && + + } + + {this.state.validationMessage} +
); desc.describedBy = errorId; @@ -70,6 +83,10 @@ const asInput = (WrappedComponent, labelFirst = true) => { return desc; } + hasDangerTheme() { + return this.props.themes.includes('danger'); + } + handleBlur(event) { const val = event.target.value; @@ -95,10 +112,11 @@ const asInput = (WrappedComponent, labelFirst = true) => { { required: false, validator: undefined, className: [], + themes: [], }; return NewComponent;