-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import React from 'react'; | ||
import cx from 'classnames'; | ||
|
||
/** | ||
* @module TextInput | ||
*/ | ||
class TextInput extends React.Component { | ||
|
||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
value: props.value || '', | ||
}; | ||
this.onChange = this.onChange.bind(this); | ||
} | ||
|
||
onChange(e) { | ||
this.setState({ value: e.target.value }); | ||
} | ||
|
||
render() { | ||
const { | ||
name, | ||
value, // eslint-disable-line no-unused-vars | ||
label, | ||
labelClassName, | ||
className, | ||
children, | ||
error, | ||
required, | ||
...other | ||
} = this.props; | ||
|
||
const classNames = cx( | ||
{ 'field--error': error }, | ||
className | ||
); | ||
|
||
const labelClassNames = cx( | ||
{ required }, | ||
labelClassName | ||
); | ||
|
||
return ( | ||
<div> | ||
<label className={labelClassNames} htmlFor={other.id}> | ||
{label} | ||
</label> | ||
|
||
<input type='text' | ||
name={name} | ||
value={this.state.value} | ||
required={required} | ||
className={classNames} | ||
onChange={this.onChange} | ||
{...other} /> | ||
|
||
{ this.props.maxLength && <p className='text--caption align--right'>{this.state.value.length} / {this.props.maxLength}</p> } | ||
|
||
{ error && <p className='text--error'>{error}</p> } | ||
{children} | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
TextInput.propTypes = { | ||
id: React.PropTypes.string, | ||
name: React.PropTypes.string.isRequired, | ||
error: React.PropTypes.string, | ||
label: React.PropTypes.string, | ||
labelClassName: React.PropTypes.string, | ||
value: React.PropTypes.string, | ||
required: React.PropTypes.bool | ||
}; | ||
|
||
export default TextInput; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import React from 'react'; | ||
import TextInput from './TextInput'; | ||
import Button from './Button'; | ||
import { storiesOf } from '@kadira/storybook'; | ||
|
||
storiesOf('TextInput', module) | ||
.add('default', () => <TextInput | ||
label='Your name' | ||
id='fullname' | ||
name='name' | ||
placeholder='Not your email' />) | ||
.add('with value', () => <TextInput | ||
label='Your name' | ||
id='fullname' | ||
name='name' | ||
value='Phife Dawg' | ||
placeholder='Not your email' />) | ||
.add('error state', () => <TextInput | ||
label='Your name' | ||
id='fullname' | ||
name='name' | ||
error='Not so fast. You have an error.' | ||
placeholder='Not your email' />) | ||
.add('required', () => { | ||
return (<form> | ||
<TextInput | ||
label='Your name' | ||
id='fullname' | ||
name='name' | ||
required | ||
placeholder='Not your email' /> | ||
<Button | ||
contrast | ||
fullWidth> | ||
Submit | ||
</Button> | ||
</form>); | ||
}) | ||
.add('with char counter (maxLength)', () => { | ||
const rules = { | ||
maxLength: 10, | ||
pattern:'.{5,10}' | ||
}; | ||
return (<TextInput | ||
label='Your name' | ||
id='fullname' | ||
name='name' | ||
value='' | ||
placeholder='Not your email' | ||
{...rules} />); | ||
}) | ||
.add('has a pattern for min length', () => { | ||
const rules = { | ||
pattern:'.{5,10}' | ||
}; | ||
return (<form> | ||
<TextInput | ||
label='Your name' | ||
id='fullname' | ||
name='name' | ||
value='>5' | ||
placeholder='Not your email' | ||
{...rules} /> | ||
<Button | ||
contrast | ||
fullWidth> | ||
Submit | ||
</Button> | ||
</form>); | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import TestUtils from 'react-addons-test-utils'; | ||
import TextInput from './TextInput'; | ||
|
||
describe('TextInput', function() { | ||
|
||
const LABEL_TEXT = 'Super Hero', | ||
VALUE = 'Batman', | ||
NAME_ATTR = 'superhero', | ||
MAX_LEN = '20', | ||
ERROR_TEXT = 'Too wimpy.'; | ||
|
||
let textInput, | ||
textInputEl, | ||
inputEl; | ||
|
||
beforeEach(() => { | ||
const formAttrs = { | ||
id: NAME_ATTR, | ||
maxLength: MAX_LEN, | ||
error: ERROR_TEXT, | ||
required: 'required' | ||
}; | ||
textInput = TestUtils.renderIntoDocument(<TextInput | ||
name={NAME_ATTR} | ||
label={LABEL_TEXT} | ||
value={VALUE} | ||
{...formAttrs} />); | ||
|
||
textInputEl = ReactDOM.findDOMNode(textInput); | ||
inputEl = textInputEl.querySelector('input'); | ||
}); | ||
|
||
afterEach(() => { | ||
textInputEl = null; | ||
inputEl = null; | ||
}); | ||
|
||
it('exists', () => { | ||
expect(textInputEl).not.toBeNull(); | ||
}); | ||
|
||
it('should have a name attribute', () => { | ||
expect(inputEl.name).toEqual(NAME_ATTR); | ||
}); | ||
|
||
it('should have a value when one is specified', () => { | ||
expect(inputEl.value).toEqual(VALUE); | ||
}); | ||
|
||
it('should have a label when label is given', () => { | ||
const labelEl = textInputEl.querySelector('label'); | ||
expect(labelEl).not.toBeNull(); | ||
expect(labelEl.textContent).toEqual(LABEL_TEXT); | ||
}); | ||
|
||
it('should have a required attribute when specified', () => { | ||
expect(inputEl.attributes.required).not.toBeNull(); | ||
}); | ||
|
||
it('should specify attributes that are passed in', function() { | ||
expect(inputEl.getAttribute('maxLength')).toEqual(MAX_LEN); | ||
}); | ||
|
||
it('should have an error when one is specified', function() { | ||
const errorEl = textInputEl.querySelector('.text--error'); | ||
expect(errorEl).not.toBeNull(); | ||
expect(errorEl.textContent).toEqual(ERROR_TEXT); | ||
}); | ||
|
||
it('should specify attributes that are passed in', function() { | ||
expect(inputEl.getAttribute('maxLength')).toEqual(MAX_LEN); | ||
}); | ||
|
||
it('should set its value on input change', function() { | ||
const newValue = `${VALUE}r`; | ||
expect(inputEl.value).toEqual(VALUE); | ||
TestUtils.Simulate.change(inputEl, { target: { value: newValue } }); | ||
expect(inputEl.value).toEqual(newValue); | ||
}); | ||
|
||
it('should call onChange and setState with input change', function() { | ||
const newValue = `${VALUE}r`; | ||
const changeSpy = spyOn(TextInput.prototype, 'onChange').and.callThrough(); | ||
const stateSpy = spyOn(TextInput.prototype, 'setState').and.callThrough(); | ||
|
||
const boundTextInput = TestUtils.renderIntoDocument(<TextInput | ||
name={NAME_ATTR} | ||
label={LABEL_TEXT} | ||
value={VALUE} />); | ||
|
||
textInputEl = ReactDOM.findDOMNode(boundTextInput); | ||
inputEl = textInputEl.querySelector('input'); | ||
TestUtils.Simulate.change(inputEl, { target: { value: newValue } }); | ||
|
||
expect(changeSpy).toHaveBeenCalled(); | ||
expect(stateSpy).toHaveBeenCalledWith({ value: newValue }); | ||
}); | ||
}); |