From 5c6daa2fa61f4ae9316070e0b0b31ed65e901b58 Mon Sep 17 00:00:00 2001 From: Tyler Hallada Date: Fri, 6 Apr 2018 10:09:41 -0400 Subject: [PATCH] feat(asinput): add inputGroupAppend/Prepend props (#208) If either prop is provided, the input will be placed in an `input-group` div with the given addons alongside in `input-group-prepend` and/or `input-group-append` divs. --- .../__snapshots__/Storyshots.test.js.snap | 2181 +++++++++++------ src/InputText/InputText.stories.jsx | 73 + src/asInput/README.md | 6 + src/asInput/asInput.scss | 1 + src/asInput/asInput.test.jsx | 63 + src/asInput/index.jsx | 51 +- 6 files changed, 1621 insertions(+), 754 deletions(-) diff --git a/.storybook/__snapshots__/Storyshots.test.js.snap b/.storybook/__snapshots__/Storyshots.test.js.snap index e795dbd4f3..85c53c106a 100644 --- a/.storybook/__snapshots__/Storyshots.test.js.snap +++ b/.storybook/__snapshots__/Storyshots.test.js.snap @@ -1551,6 +1551,56 @@ exports[`Storyshots CheckBox basic usage 1`] = ` className="css-d52hbj" /> + + + inputGroupPrepend + + + + + + - + + + - + + + + + + inputGroupAppend + + + + + + - + + + - + + + @@ -2492,6 +2542,56 @@ exports[`Storyshots CheckBox call a function 1`] = ` className="css-d52hbj" /> + + + inputGroupPrepend + + + + + + - + + + - + + + + + + inputGroupAppend + + + + + + - + + + - + + + @@ -3707,6 +3807,56 @@ exports[`Storyshots CheckBox default checked 1`] = ` className="css-d52hbj" /> + + + inputGroupPrepend + + + + + + - + + + - + + + + + + inputGroupAppend + + + + + + - + + + - + + + @@ -4628,6 +4778,56 @@ exports[`Storyshots CheckBox disabled 1`] = ` className="css-d52hbj" /> + + + inputGroupPrepend + + + + + + - + + + - + + + + + + inputGroupAppend + + + + + + - + + + - + + + @@ -9054,810 +9254,1313 @@ exports[`Storyshots InputSelect with validation 1`] = ` `; exports[`Storyshots InputText different textual input types 1`] = ` -
-
- - - -
+ } +>
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
+ +
+ + + +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
- + `; exports[`Storyshots InputText displayed inline 1`] = `
- - -
- -
-
-`; - -exports[`Storyshots InputText focus test 1`] = ` -
-
- - -
- + + +
+ +
`; -exports[`Storyshots InputText label as element 1`] = ` +exports[`Storyshots InputText focus test 1`] = `
- - +
+ +
+ + +
+ +
+
+
+
+ +`; + +exports[`Storyshots InputText label as element 1`] = ` +
- +
+ + +
+ +
+
`; exports[`Storyshots InputText minimal usage 1`] = `
- -
- +
+ + +
+ +
+
`; exports[`Storyshots InputText validation 1`] = `
- -
- +
+ + +
+ +
+ + The unique name that identifies you throughout the site. + +
- - The unique name that identifies you throughout the site. -
`; exports[`Storyshots InputText validation with danger theme 1`] = `
- - + + +
+ +
+ + The unique name that identifies you throughout the site. + +
+ + +`; + +exports[`Storyshots InputText with input group addons 1`] = ` +
+ } +>
- +
+
+ +
+
+
+ @ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+ @example.com +
+
+
+
+ +
+
+
+ +
+
+
+ $ +
+
+ +
+
+ .00 +
+
+
+
+ +
+
+
+ +
+
+ +
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+ + + Checkmark + +
+
+
+
+
+ +
+
+
- - The unique name that identifies you throughout the site. -
`; diff --git a/src/InputText/InputText.stories.jsx b/src/InputText/InputText.stories.jsx index 4ac88665cd..de29fa6656 100644 --- a/src/InputText/InputText.stories.jsx +++ b/src/InputText/InputText.stories.jsx @@ -1,7 +1,11 @@ /* eslint-disable import/no-extraneous-dependencies */ import React from 'react'; import { storiesOf } from '@storybook/react'; +import centered from '@storybook/addon-centered'; +import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css'; +import Button from '../Button'; +import Icon from '../Icon'; import InputText from './index'; import StatusAlert from '../StatusAlert'; @@ -40,6 +44,7 @@ class FocusInputWrapper extends React.Component { storiesOf('InputText', module) + .addDecorator(centered) .add('minimal usage', () => ( + )) + .add('with input group addons', () => ( +
+ + {'@'} +
+ )} + /> + + {'@example.com'} +
+ )} + /> + + {'$'} +
+ )} + inputGroupAppend={( +
+ {'.00'} +
+ )} + /> + + )} + /> + + +
+ )} + /> + )); diff --git a/src/asInput/README.md b/src/asInput/README.md index eed2f7d16b..3a3a68d7e9 100644 --- a/src/asInput/README.md +++ b/src/asInput/README.md @@ -22,6 +22,12 @@ Handles all necessary props that are related to Input typed components. ### `inline` (boolean; optional) `inline` specifies if the form-group will be displayed inline (label and input elements on the same line). The default is false. +### `inputGroupAppend` (element, optional) +`inputGroupAppend` specifies the element to display inline to the right of the input. See [the Bootstrap docs](https://getbootstrap.com/docs/4.0/components/input-group/) for more info on input groups. The default is `undefined`. + +### `inputGroupPrepend` (element, optional) +`inputGroupPrepend` specifies the element to display inline to the left of the input. See [the Bootstrap docs](https://getbootstrap.com/docs/4.0/components/input-group/) for more info on input groups. The default is `undefined`. + ### `name` (string; required) `name` specifies the value for the name property within the component. diff --git a/src/asInput/asInput.scss b/src/asInput/asInput.scss index da7b43a61e..52a36c323b 100644 --- a/src/asInput/asInput.scss +++ b/src/asInput/asInput.scss @@ -1,6 +1,7 @@ @import "~bootstrap/scss/_forms"; @import "~bootstrap/scss/mixins/_forms"; @import "~bootstrap/scss/utilities/_screenreaders.scss"; +@import '~bootstrap/scss/input-group'; .fa-icon-spacing { padding: 0px 5px 0px 0px; diff --git a/src/asInput/asInput.test.jsx b/src/asInput/asInput.test.jsx index bd389ca21a..e6dc38d519 100644 --- a/src/asInput/asInput.test.jsx +++ b/src/asInput/asInput.test.jsx @@ -337,5 +337,68 @@ describe('asInput()', () => { const input = wrapper.find('.form-group'); expect(input.hasClass('form-inline')).toEqual(true); }); + + describe('input group addons', () => { + it('does not create an input-group div if no input group addons are given', () => { + const wrapper = mount(); + const input = wrapper.find('.form-group'); + expect(input.find('.input-group').exists()).toEqual(false); + }); + + it('displays inputGroupPrepend', () => { + const props = { + ...baseProps, + inputGroupPrepend: ( +
+ {'$'} +
+ ), + }; + const wrapper = mount(); + const input = wrapper.find('.form-group'); + expect(input.find('.input-group').exists()).toEqual(true); + expect(input.find('.input-group-prepend').exists()).toEqual(true); + expect(input.find('.input-group-prepend').text()).toEqual('$'); + }); + + it('displays inputGroupAppend', () => { + const props = { + ...baseProps, + inputGroupAppend: ( +
+ {'.00'} +
+ ), + }; + const wrapper = mount(); + const input = wrapper.find('.form-group'); + expect(input.find('.input-group').exists()).toEqual(true); + expect(input.find('.input-group-append').exists()).toEqual(true); + expect(input.find('.input-group-append').text()).toEqual('.00'); + }); + + it('displays both inputGroupPrepend and inputGroupAppend', () => { + const props = { + ...baseProps, + inputGroupPrepend: ( +
+ {'$'} +
+ ), + inputGroupAppend: ( +
+ {'.00'} +
+ ), + }; + const wrapper = mount(); + const input = wrapper.find('.form-group'); + expect(input.find('.input-group').exists()).toEqual(true); + expect(input.find('.input-group-prepend').exists()).toEqual(true); + expect(input.find('.input-group-prepend').text()).toEqual('$'); + expect(input.find('.input-group-append').exists()).toEqual(true); + expect(input.find('.input-group-append').text()).toEqual('.00'); + }); + }); }); }); diff --git a/src/asInput/index.jsx b/src/asInput/index.jsx index 2cf7216587..59e58f0245 100644 --- a/src/asInput/index.jsx +++ b/src/asInput/index.jsx @@ -30,6 +30,8 @@ export const inputProps = { className: PropTypes.arrayOf(PropTypes.string), themes: PropTypes.arrayOf(PropTypes.string), inline: PropTypes.bool, + inputGroupPrepend: PropTypes.element, + inputGroupAppend: PropTypes.element, }; export const defaultProps = { @@ -47,6 +49,8 @@ export const defaultProps = { className: [], themes: [], inline: false, + inputGroupPrepend: undefined, + inputGroupAppend: undefined, }; const asInput = (WrappedComponent, inputType = undefined, labelFirst = true) => { @@ -55,6 +59,7 @@ const asInput = (WrappedComponent, inputType = undefined, labelFirst = true) => super(props); this.handleChange = this.handleChange.bind(this); this.handleBlur = this.handleBlur.bind(this); + this.renderInput = this.renderInput.bind(this); const id = this.props.id ? this.props.id : newId('asInput'); const isValid = this.props.validator ? true : this.props.isValid; @@ -201,6 +206,26 @@ const asInput = (WrappedComponent, inputType = undefined, labelFirst = true) => ); } + renderInput(describedBy) { + return ( + + ); + } + render() { const { description, error, describedBy } = this.getDescriptions(); return ( @@ -211,21 +236,17 @@ const asInput = (WrappedComponent, inputType = undefined, labelFirst = true) => })]} > {labelFirst && this.getLabel()} - + {this.props.inputGroupPrepend || this.props.inputGroupAppend ? ( +
+
+ {this.props.inputGroupPrepend} +
+ {this.renderInput(describedBy)} +
+ {this.props.inputGroupAppend} +
+
+ ) : this.renderInput(describedBy)} {!labelFirst && this.getLabel()} {error} {description}