Skip to content

Commit

Permalink
feat(Input): add onChange(e, props) (#846)
Browse files Browse the repository at this point in the history
* feat(Input): add onChange(e, props)

* test(Input): add onChange(e, props) test
  • Loading branch information
levithomason committed Nov 13, 2016
1 parent 87b8d49 commit e4afa8f
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 136 deletions.
284 changes: 150 additions & 134 deletions src/elements/Input/Input.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from 'lodash'
import React, { PropTypes } from 'react'
import React, { Component, PropTypes } from 'react'
import cx from 'classnames'

import {
Expand Down Expand Up @@ -42,91 +42,7 @@ export const htmlInputPropNames = [
'value',
]

/**
* An Input is a field used to elicit a response from a user
* @see Button
* @see Form
* @see Icon
* @see Label
*/
function Input(props) {
const {
action,
actionPosition,
children,
className,
disabled,
error,
focus,
fluid,
icon,
iconPosition,
inverted,
label,
labelPosition,
loading,
size,
type,
input,
transparent,
} = props

const classes = cx(
'ui',
size,
useValueAndKey(actionPosition, 'action') || useKeyOnly(action, 'action'),
useKeyOnly(disabled, 'disabled'),
useKeyOnly(error, 'error'),
useKeyOnly(focus, 'focus'),
useKeyOnly(fluid, 'fluid'),
useKeyOnly(inverted, 'inverted'),
useValueAndKey(labelPosition, 'labeled') || useKeyOnly(label, 'labeled'),
useKeyOnly(loading, 'loading'),
useKeyOnly(transparent, 'transparent'),
useValueAndKey(iconPosition, 'icon') || useKeyOnly(icon, 'icon'),
className,
'input',
)
const unhandled = getUnhandledProps(Input, props)

const rest = _.omit(unhandled, htmlInputPropNames)
const htmlInputProps = _.pick(props, htmlInputPropNames)
const ElementType = getElementType(Input, props)

if (children) {
return <ElementType {...rest} className={classes}>{children}</ElementType>
}

const actionElement = Button.create(action, elProps => ({
className: cx(
// all action components should have the button className
!_.includes(elProps.className, 'button') && 'button',
),
}))
const iconElement = Icon.create(icon)
const labelElement = Label.create(label, elProps => ({
className: cx(
// all label components should have the label className
!_.includes(elProps.className, 'label') && 'label',
// add 'left|right corner'
_.includes(labelPosition, 'corner') && labelPosition,
),
}))

return (
<ElementType {...rest} className={classes}>
{actionPosition === 'left' && actionElement}
{iconPosition === 'left' && iconElement}
{labelPosition !== 'right' && labelElement}
{createHTMLInput(input || type, htmlInputProps)}
{actionPosition !== 'left' && actionElement}
{iconPosition !== 'left' && iconElement}
{labelPosition === 'right' && labelElement}
</ElementType>
)
}

Input._meta = {
const _meta = {
name: 'Input',
type: META.TYPES.ELEMENT,
props: {
Expand All @@ -137,73 +53,173 @@ Input._meta = {
},
}

Input.defaultProps = {
type: 'text',
}
/**
* An Input is a field used to elicit a response from a user
* @see Button
* @see Form
* @see Icon
* @see Label
*/
class Input extends Component {
static propTypes = {
/** An element type to render as (string or function). */
as: customPropTypes.as,

/** An Input can be formatted to alert the user to an action they may perform */
action: PropTypes.oneOfType([
PropTypes.bool,
customPropTypes.itemShorthand,
]),

Input.propTypes = {
/** An element type to render as (string or function). */
as: customPropTypes.as,
/** An action can appear along side an Input on the left or right */
actionPosition: PropTypes.oneOf(_meta.props.actionPosition),

/** An Input can be formatted to alert the user to an action they may perform */
action: PropTypes.oneOfType([
PropTypes.bool,
customPropTypes.itemShorthand,
]),
/** Primary content. */
children: PropTypes.node,

/** An action can appear along side an Input on the left or right */
actionPosition: PropTypes.oneOf(Input._meta.props.actionPosition),
/** Additional classes. */
className: PropTypes.string,

/** Primary content. */
children: PropTypes.node,
/** An Input field can show that it is disabled */
disabled: PropTypes.bool,

/** Additional classes. */
className: PropTypes.string,
/** An Input field can show the data contains errors */
error: PropTypes.bool,

/** An Input field can show that it is disabled */
disabled: PropTypes.bool,
/** An Input field can show a user is currently interacting with it */
focus: PropTypes.bool,

/** An Input field can show the data contains errors */
error: PropTypes.bool,
/** Take on the size of it's container */
fluid: PropTypes.bool,

/** An Input field can show a user is currently interacting with it */
focus: PropTypes.bool,
/** Optional Icon to display inside the Input */
icon: PropTypes.oneOfType([
PropTypes.bool,
customPropTypes.itemShorthand,
]),

/** Take on the size of it's container */
fluid: PropTypes.bool,
/** An Icon can appear inside an Input on the left or right */
iconPosition: PropTypes.oneOf(_meta.props.iconPosition),

/** Optional Icon to display inside the Input */
icon: PropTypes.oneOfType([
PropTypes.bool,
customPropTypes.itemShorthand,
]),
/** Format to appear on dark backgrounds */
inverted: PropTypes.bool,

/** An Icon can appear inside an Input on the left or right */
iconPosition: PropTypes.oneOf(Input._meta.props.iconPosition),
/** Shorthand for creating the HTML Input */
input: customPropTypes.itemShorthand,

/** Format to appear on dark backgrounds */
inverted: PropTypes.bool,
/** Optional Label to display along side the Input */
label: customPropTypes.itemShorthand,

/** Shorthand for creating the HTML Input */
input: customPropTypes.itemShorthand,
/** A Label can appear outside an Input on the left or right */
labelPosition: PropTypes.oneOf(_meta.props.labelPosition),

/** Optional Label to display along side the Input */
label: customPropTypes.itemShorthand,
/** An Icon Input field can show that it is currently loading data */
loading: PropTypes.bool,

/** A Label can appear outside an Input on the left or right */
labelPosition: PropTypes.oneOf(Input._meta.props.labelPosition),
/** Called with (e, props) on change. */
onChange: PropTypes.func,

/** An Icon Input field can show that it is currently loading data */
loading: PropTypes.bool,
/** An Input can vary in size */
size: PropTypes.oneOf(_meta.props.size),

/** An Input can vary in size */
size: PropTypes.oneOf(Input._meta.props.size),
/** Transparent Input has no background */
transparent: PropTypes.bool,

/** Transparent Input has no background */
transparent: PropTypes.bool,
/** The HTML input type */
type: PropTypes.string,
}

/** The HTML input type */
type: PropTypes.string,
static defaultProps = {
type: 'text',
}

static _meta = _meta

handleChange = (e) => {
const { onChange } = this.props
if (onChange) onChange(e, this.props)
}

render() {
const {
action,
actionPosition,
children,
className,
disabled,
error,
focus,
fluid,
icon,
iconPosition,
inverted,
label,
labelPosition,
loading,
onChange,
size,
type,
input,
transparent,
} = this.props

const classes = cx(
'ui',
size,
useValueAndKey(actionPosition, 'action') || useKeyOnly(action, 'action'),
useKeyOnly(disabled, 'disabled'),
useKeyOnly(error, 'error'),
useKeyOnly(focus, 'focus'),
useKeyOnly(fluid, 'fluid'),
useKeyOnly(inverted, 'inverted'),
useValueAndKey(labelPosition, 'labeled') || useKeyOnly(label, 'labeled'),
useKeyOnly(loading, 'loading'),
useKeyOnly(transparent, 'transparent'),
useValueAndKey(iconPosition, 'icon') || useKeyOnly(icon, 'icon'),
className,
'input',
)
const unhandled = getUnhandledProps(Input, this.props)

const rest = _.omit(unhandled, htmlInputPropNames)

const htmlInputProps = _.pick(this.props, htmlInputPropNames)
if (onChange) htmlInputProps.onChange = this.handleChange

const ElementType = getElementType(Input, this.props)

if (children) {
return <ElementType {...rest} className={classes}>{children}</ElementType>
}

const actionElement = Button.create(action, elProps => ({
className: cx(
// all action components should have the button className
!_.includes(elProps.className, 'button') && 'button',
),
}))
const iconElement = Icon.create(icon)
const labelElement = Label.create(label, elProps => ({
className: cx(
// all label components should have the label className
!_.includes(elProps.className, 'label') && 'label',
// add 'left|right corner'
_.includes(labelPosition, 'corner') && labelPosition,
),
}))

return (
<ElementType {...rest} className={classes}>
{actionPosition === 'left' && actionElement}
{iconPosition === 'left' && iconElement}
{labelPosition !== 'right' && labelElement}
{createHTMLInput(input || type, htmlInputProps)}
{actionPosition !== 'left' && actionElement}
{iconPosition !== 'left' && iconElement}
{labelPosition === 'right' && labelElement}
</ElementType>
)
}
}

export default Input
28 changes: 26 additions & 2 deletions test/specs/elements/Input/Input-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import cx from 'classnames'
import _ from 'lodash'
import React from 'react'
import { sandbox } from 'test/utils'

import Input, { htmlInputPropNames } from 'src/elements/Input/Input'
import * as common from 'test/specs/commonTests'
Expand Down Expand Up @@ -56,10 +57,33 @@ describe('Input', () => {
describe('input props', () => {
htmlInputPropNames.forEach(propName => {
it(`passes \`${propName}\` to the <input>`, () => {
shallow(<Input {...{ [propName]: 'foo' }} />)
const propValue = propName === 'onChange' ? () => null : 'foo'
const wrapper = shallow(<Input {...{ [propName]: propValue }} />)

// account for overloading the onChange prop
const expectedValue = propName === 'onChange'
? wrapper.instance().handleChange
: propValue

wrapper
.find('input')
.should.have.prop(propName, 'foo')
.should.have.prop(propName, expectedValue)
})
})
})

describe('onChange', () => {
it('is called with (event, props) on change', () => {
const spy = sandbox.spy()
const event = { fake: 'event' }
const props = { 'data-foo': 'bar', onChange: spy }

const wrapper = shallow(<Input {...props} />)

wrapper.find('input').simulate('change', event)

spy.should.have.been.calledOnce()
spy.should.have.been.calledWithMatch(event, props)
})
})
})

0 comments on commit e4afa8f

Please sign in to comment.