-
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
247 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,70 @@ | ||
import React from 'react'; | ||
import cx from 'classnames'; | ||
|
||
/** | ||
* @module TextField | ||
*/ | ||
class TextField extends React.Component { | ||
|
||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
value: props.value, | ||
error: props.error | ||
}; | ||
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, | ||
...other | ||
} = this.props; | ||
|
||
const classNames = cx( | ||
{ 'field--error': this.state.error }, | ||
className | ||
); | ||
|
||
const labelClassNames = cx( | ||
{ required : other && other.required }, | ||
labelClassName | ||
); | ||
|
||
return ( | ||
<div> | ||
<label className={labelClassNames} htmlFor={other.id}> | ||
{label} | ||
</label> | ||
|
||
<input type='text' | ||
name={name} | ||
value={this.state.value} | ||
className={classNames} | ||
onChange={this.onChange} | ||
{...other} /> | ||
|
||
{ this.props.maxLength && <p className='text--caption align--right'>{this.state.value.length} / {this.props.maxLength}</p> } | ||
|
||
{ this.state.error && <p className='text--error'>{error}</p> } | ||
{children} | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
TextField.propTypes = { | ||
name: React.PropTypes.string.isRequired | ||
}; | ||
|
||
export default TextField; |
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 TextField from './TextField'; | ||
import Button from './Button'; | ||
import { storiesOf } from '@kadira/storybook'; | ||
|
||
storiesOf('TextField', module) | ||
.add('default', () => <TextField | ||
label='Your name' | ||
id='fullname' | ||
name='name' | ||
value='' | ||
placeholder='Not your email' />) | ||
.add('with value', () => <TextField | ||
label='Your name' | ||
id='fullname' | ||
name='name' | ||
value='Phife Dawg' | ||
placeholder='Not your email' />) | ||
.add('error state', () => <TextField | ||
label='Your name' | ||
id='fullname' | ||
name='name' | ||
value='' | ||
error='Not so fast. You have an error.' | ||
placeholder='Not your email' />) | ||
.add('required', () => { | ||
const rules = { | ||
required: 'required' | ||
}; | ||
return (<form> | ||
<TextField | ||
label='Your name' | ||
id='fullname' | ||
name='name' | ||
value='' | ||
{...rules} | ||
placeholder='Not your email' /> | ||
<Button | ||
contrast | ||
fullWidth> | ||
Submit | ||
</Button> | ||
</form>); | ||
}) | ||
.add('with char counter (maxLength)', () => { | ||
const rules = { | ||
maxLength: 10, | ||
pattern:'.{5,10}' | ||
}; | ||
return (<TextField | ||
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> | ||
<TextField | ||
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 TextField from './TextField'; | ||
|
||
describe('TextField', function() { | ||
|
||
const LABEL_TEXT = 'Super Hero', | ||
VALUE = 'Batman', | ||
NAME_ATTR = 'superhero', | ||
MAX_LEN = '20', | ||
ERROR_TEXT = 'Too wimpy.'; | ||
|
||
let textField, | ||
textFieldEl, | ||
inputEl; | ||
|
||
beforeEach(() => { | ||
const formAttrs = { | ||
id: NAME_ATTR, | ||
maxLength: MAX_LEN, | ||
error: ERROR_TEXT, | ||
required: 'required' | ||
}; | ||
textField = TestUtils.renderIntoDocument(<TextField | ||
name={NAME_ATTR} | ||
label={LABEL_TEXT} | ||
value={VALUE} | ||
{...formAttrs} />); | ||
|
||
textFieldEl = ReactDOM.findDOMNode(textField); | ||
inputEl = textFieldEl.querySelector('input'); | ||
}); | ||
|
||
afterEach(() => { | ||
textFieldEl = null; | ||
inputEl = null; | ||
}); | ||
|
||
it('exists', () => { | ||
expect(textFieldEl).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 = textFieldEl.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 = textFieldEl.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(TextField.prototype, 'onChange').and.callThrough(); | ||
const stateSpy = spyOn(TextField.prototype, 'setState').and.callThrough(); | ||
|
||
const boundTextField = TestUtils.renderIntoDocument(<TextField | ||
name={NAME_ATTR} | ||
label={LABEL_TEXT} | ||
value={VALUE} />); | ||
|
||
textFieldEl = ReactDOM.findDOMNode(boundTextField); | ||
inputEl = textFieldEl.querySelector('input'); | ||
TestUtils.Simulate.change(inputEl, { target: { value: newValue } }); | ||
|
||
expect(changeSpy).toHaveBeenCalled(); | ||
expect(stateSpy).toHaveBeenCalledWith({ value: newValue }); | ||
}); | ||
}); |