Skip to content

Commit

Permalink
refactor(Checkbox): remove inputType
Browse files Browse the repository at this point in the history
  • Loading branch information
levithomason committed Sep 1, 2016
1 parent d992395 commit 2503a44
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 112 deletions.
38 changes: 18 additions & 20 deletions src/addons/Radio/Radio.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,45 @@
import React, { PropTypes } from 'react'

import { getElementType, getUnhandledProps, META } from '../../lib'
import { getUnhandledProps, META } from '../../lib'
import { Checkbox } from '../../modules'

/**
* A Radio is sugar for <Checkbox type='radio' inputType='radio' />.
* A Radio is sugar for <Checkbox radio />.
* Useful for exclusive groups of sliders or toggles.
* @see Checkbox
* @see Form
*/
function Radio(props) {
const { inputType, type } = props
const { slider, toggle, type } = props
const rest = getUnhandledProps(Radio, props)
const ElementType = getElementType(Radio, props)
return <ElementType {...rest} type={type} inputType={inputType} />
// const ElementType = getElementType(Radio, props)
// radio, slider, toggle are exclusive
// use an undefined radio if slider or toggle are present
const radio = !(slider || toggle) || undefined

return <Checkbox {...rest} type={type} radio={radio} slider={slider} toggle={toggle} />
}

Radio._meta = {
name: 'Radio',
type: META.TYPES.ADDON,
props: {
type: Checkbox._meta.props.type,
},
}

Radio.propTypes = {
/** An element type to render as (string or function). */
as: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
]),
/** Format to emphasize the current selection state */
slider: Checkbox.propTypes.slider,

/** Format to show an on or off choice */
toggle: Checkbox.propTypes.toggle,

/** HTML input type, either checkbox or radio. */
inputType: Checkbox.propTypes.inputType,

/**
* Display as a checkbox, radio, slider, or toggle.
* The input type is `checkbox` for both slider and toggle types.
* You can set `inputType` separately to mix and match appearance and behavior.
*/
type: Checkbox.propTypes.type,
type: PropTypes.oneOf(Radio._meta.props.type),
}

Radio.defaultProps = {
as: Checkbox,
inputType: 'radio',
type: 'radio',
}

Expand Down
69 changes: 29 additions & 40 deletions src/modules/Checkbox/Checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import cx from 'classnames'

import {
AutoControlledComponent as Component,
customPropTypes,
getElementType,
getUnhandledProps,
META,
Expand All @@ -12,27 +13,13 @@ import {

const debug = makeDebugger('checkbox')

// maps checkbox types to input types
const typeMap = {
checkbox: 'checkbox',
radio: 'radio',
slider: 'checkbox',
toggle: 'checkbox',
}

const _meta = {
name: 'Checkbox',
type: META.TYPES.MODULE,
props: {
inputType: [
'checkbox',
'radio',
],
type: [
'checkbox',
'radio',
'slider',
'toggle',
],
},
}
Expand All @@ -59,6 +46,24 @@ export default class Checkbox extends Component {
/** The initial value of checked. */
defaultChecked: PropTypes.bool,

/** Format to emphasize the current selection state */
slider: customPropTypes.every([
PropTypes.bool,
customPropTypes.disallow(['radio', 'toggle']),
]),

/** Format as a radio element. This means it is an exclusive option.*/
radio: customPropTypes.every([
PropTypes.bool,
customPropTypes.disallow(['slider', 'toggle']),
]),

/** Format to show an on or off choice */
toggle: customPropTypes.every([
PropTypes.bool,
customPropTypes.disallow(['radio', 'slider']),
]),

/** A checkbox can appear disabled and be unable to change states */
disabled: PropTypes.bool,

Expand All @@ -69,7 +74,7 @@ export default class Checkbox extends Component {
label: PropTypes.string,

/** HTML input type, either checkbox or radio. */
inputType: PropTypes.oneOf(_meta.props.inputType),
type: PropTypes.oneOf(_meta.props.type),

/** The HTML input name. */
name: PropTypes.string,
Expand All @@ -83,13 +88,6 @@ export default class Checkbox extends Component {
/** A checkbox can be read-only and unable to change states */
readOnly: PropTypes.bool,

/**
* Display as a checkbox, radio, slider, or toggle.
* The input type is `checkbox` for both slider and toggle types.
* You can set `inputType` separately to mix and match appearance and behavior.
*/
type: PropTypes.oneOf(_meta.props.type),

/** The HTML input value. */
value: PropTypes.string,
}
Expand All @@ -106,16 +104,11 @@ export default class Checkbox extends Component {

state = {}

get inputType() {
const { inputType, type } = this.props
return inputType || typeMap[type]
}

canToggle = () => {
const { disabled, readOnly } = this.props
const { disabled, radio, readOnly } = this.props
const { checked } = this.state

return !(disabled || readOnly || this.inputType === 'radio' && checked)
return !disabled && !readOnly && !(radio && checked)
}

handleClick = (e) => {
Expand All @@ -135,30 +128,26 @@ export default class Checkbox extends Component {
}

render() {
const { className, label, name, type, value } = this.props
const { className, label, name, radio, slider, toggle, type, value } = this.props
const { checked } = this.state
const classes = cx(
'ui',
// don't add duplicate "checkbox" classes, but add any other type
type !== 'checkbox' && type,
useKeyOnly(checked, 'checked'),
// auto apply fitted class to compact white space when there is no label
// http://semantic-ui.com/modules/checkbox.html#fitted
useKeyOnly(!label, 'fitted'),
useKeyOnly(checked, 'checked'),
useKeyOnly(radio, 'radio'),
useKeyOnly(slider, 'slider'),
useKeyOnly(toggle, 'toggle'),
'checkbox',
className
)
const rest = getUnhandledProps(Checkbox, this.props)
const ElementType = getElementType(Checkbox, this.props)
return (
<ElementType
{...rest}
className={classes}
onClick={this.handleClick}
onChange={this.handleClick}
>
<ElementType {...rest} className={classes} onClick={this.handleClick} onChange={this.handleClick}>
<input
type={this.inputType}
type={type}
name={name}
checked={checked}
className='hidden'
Expand Down
13 changes: 11 additions & 2 deletions test/specs/addons/Radio/Radio-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ describe('Radio', () => {
const wrapper = shallow(<Radio />)
wrapper.type().should.equal(Checkbox)

wrapper.should.have.prop('type', 'radio')
wrapper.should.have.prop('inputType', 'radio')
wrapper.should.have.prop('radio', true)
})

it('is not a radio when slider', () => {
shallow(<Radio slider />)
.should.not.have.prop('radio')
})

it('is not a radio when toggle', () => {
shallow(<Radio toggle />)
.should.not.have.prop('radio')
})
})
73 changes: 23 additions & 50 deletions test/specs/modules/Checkbox/Checkbox-test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import _ from 'lodash'
import React from 'react'

import Checkbox from 'src/modules/Checkbox/Checkbox'
Expand All @@ -8,6 +7,9 @@ import { sandbox } from 'test/utils'
describe('Checkbox', () => {
common.isConformant(Checkbox)
common.hasUIClassName(Checkbox)
common.propKeyOnlyToClassName(Checkbox, 'checked')
common.propKeyOnlyToClassName(Checkbox, 'slider')
common.propKeyOnlyToClassName(Checkbox, 'toggle')

describe('defaultChecked', () => {
it('sets the initial checked state', () => {
Expand All @@ -18,6 +20,23 @@ describe('Checkbox', () => {
})
})

describe('checking', () => {
it('can be checked and unchecked', () => {
const wrapper = shallow(<Checkbox />)

wrapper.find('input').should.not.be.checked()
wrapper.simulate('click').find('input').should.be.checked()
wrapper.simulate('click').find('input').should.not.be.checked()
})
it('can be checked but not unchecked when radio', () => {
const wrapper = shallow(<Checkbox radio />)

wrapper.find('input').should.not.be.checked()
wrapper.simulate('click').find('input').should.be.checked()
wrapper.simulate('click').find('input').should.be.checked()
})
})

describe('disabled', () => {
it('cannot be checked', () => {
shallow(<Checkbox disabled />)
Expand All @@ -33,67 +52,21 @@ describe('Checkbox', () => {
})
})

describe('inputType', () => {
it('overrides type', () => {
// for every type, override with each inputType
_.each(Checkbox._meta.props.type, (type) => {
_.each(Checkbox._meta.props.inputType, (inputType) => {
shallow(<Checkbox type={type} inputType={inputType} />)
.find('input')
.should.have.prop('type', inputType)
})
})
})
})

describe('type', () => {
it('renders an input of type checkbox when not set', () => {
shallow(<Checkbox />)
.find('input')
.should.have.prop('type', 'checkbox')
})
it('renders an input of type checkbox when set to slider', () => {
shallow(<Checkbox type='slider' />)
.find('input')
.should.have.prop('type', 'checkbox')
})
it('renders an input of type checkbox when set to toggle', () => {
shallow(<Checkbox type='toggle' />)
it('sets the input type ', () => {
shallow(<Checkbox type='checkbox' />)
.find('input')
.should.have.prop('type', 'checkbox')
})
it('renders an input of type radio when set to radio', () => {

shallow(<Checkbox type='radio' />)
.find('input')
.should.have.prop('type', 'radio')
})
it('it adds the prop value to className, except checkbox', () => {
const types = _.without(Checkbox._meta.props.type, 'checkbox')
_.each(types, (type) => {
shallow(<Checkbox type={type} />)
.should.have.className(type)
})
})
it('does not add duplicate checkbox classes when set to "checkbox"', () => {
shallow(<Checkbox type='checkbox' />)
.prop('className')
.match(/checkbox/g)
.should.have.length(1)
})
it('can be checked but not unchecked when radio', () => {
const wrapper = shallow(<Checkbox type='radio' />)

wrapper.find('input').should.not.be.checked()
wrapper.simulate('click').find('input').should.be.checked()
wrapper.simulate('click').find('input').should.be.checked()
})
it('can be checked and unchecked when checkbox', () => {
const wrapper = shallow(<Checkbox type='checkbox' />)

wrapper.find('input').should.not.be.checked()
wrapper.simulate('click').find('input').should.be.checked()
wrapper.simulate('click').find('input').should.not.be.checked()
})
})

describe('onClick', () => {
Expand Down

0 comments on commit 2503a44

Please sign in to comment.