-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #900 from Adslot/toggle-switch-component
feat: Switch component
- Loading branch information
Showing
9 changed files
with
346 additions
and
2 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,66 @@ | ||
import _ from 'lodash'; | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import './style.scss'; | ||
|
||
class Switch extends React.Component { | ||
state = { checked: this.props.defaultChecked || false }; | ||
|
||
handleChange = event => { | ||
const { onChange, checked } = this.props; | ||
const targetCheckedValue = _.get(event, 'target.checked'); | ||
|
||
if (_.isNil(checked)) { | ||
this.setState({ checked: targetCheckedValue }); | ||
} | ||
|
||
if (onChange) { | ||
onChange(targetCheckedValue); | ||
} | ||
}; | ||
|
||
render() { | ||
const { defaultChecked, checked, value, onChange, className, dts } = this.props; | ||
|
||
if (!_.isNil(checked) && !_.isNil(defaultChecked)) | ||
console.warn( | ||
'Failed prop type: Contains an input of type checkbox with both `checked` and `defaultChecked` props. Input elements must be either controlled or uncontrolled' | ||
); | ||
|
||
if (!_.isNil(checked) && _.isNil(onChange)) | ||
console.warn( | ||
'Failed prop type: You have provided a `checked` prop to Switch Component without an `onChange` handler. This will render a read-only field.' | ||
); | ||
|
||
const toggleInputChecked = !_.isNil(checked) ? checked : this.state.checked; | ||
return ( | ||
<label className="aui--switch-label"> | ||
<input | ||
type="checkbox" | ||
checked={toggleInputChecked} | ||
value={value} | ||
onChange={this.handleChange} | ||
className={className} | ||
dts={dts} | ||
/> | ||
<span className="aui--switch-slider round"></span> | ||
</label> | ||
); | ||
} | ||
} | ||
|
||
Switch.defaultProps = { | ||
value: '', | ||
dts: 'switch-component', | ||
}; | ||
|
||
Switch.propTypes = { | ||
defaultChecked: PropTypes.bool, | ||
checked: PropTypes.bool, | ||
value: PropTypes.string, | ||
onChange: PropTypes.func, | ||
dts: PropTypes.string, | ||
className: PropTypes.string, | ||
}; | ||
|
||
export default Switch; |
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,92 @@ | ||
import React from 'react'; | ||
import _ from 'lodash'; | ||
import sinon from 'sinon'; | ||
import { mount } from 'enzyme'; | ||
import Switch from 'adslot-ui/Switch'; | ||
|
||
describe('Switch', () => { | ||
let sandbox = null; | ||
|
||
before(() => { | ||
sandbox = sinon.createSandbox(); | ||
}); | ||
|
||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
|
||
it('should correctly render defaults', () => { | ||
const wrapper = mount(<Switch />); | ||
const inputElement = wrapper.find('input'); | ||
|
||
expect(inputElement).to.have.lengthOf(1); | ||
|
||
const inputElementProps = inputElement.props(); | ||
expect(inputElementProps.checked).to.equal(false); | ||
expect(inputElementProps.dts).to.equal('switch-component'); | ||
}); | ||
|
||
it('should correctly render controlled Switch', () => { | ||
const wrapper = mount(<Switch checked onChange={_.noop} />); | ||
const inputElement = wrapper.find('input'); | ||
|
||
expect(inputElement).to.have.lengthOf(1); | ||
expect(inputElement.props().checked).to.equal(true); | ||
}); | ||
|
||
it('should throw warning if checked is provided without onChange', () => { | ||
sandbox.stub(console, 'warn'); | ||
mount(<Switch checked />); | ||
|
||
expect( | ||
console.warn.calledWith( | ||
'Failed prop type: You have provided a `checked` prop to Switch Component without an `onChange` handler. This will render a read-only field.' | ||
) | ||
).to.equal(true); | ||
}); | ||
|
||
it('should throw warning if both defaultChecked and checked are provided', () => { | ||
sandbox.stub(console, 'warn'); | ||
mount(<Switch defaultChecked checked onChange={_.noop} />); | ||
|
||
expect( | ||
console.warn.calledWith( | ||
'Failed prop type: Contains an input of type checkbox with both `checked` and `defaultChecked` props. Input elements must be either controlled or uncontrolled' | ||
) | ||
).to.equal(true); | ||
}); | ||
|
||
it('should correctly call onChange for controlled Switch', () => { | ||
const onChangeSpy = sinon.spy(); | ||
|
||
const wrapper = mount(<Switch checked onChange={onChangeSpy} />); | ||
const inputElement = wrapper.find('input'); | ||
|
||
expect(inputElement).to.have.lengthOf(1); | ||
expect(inputElement.props().checked).to.equal(true); | ||
|
||
inputElement.simulate('change'); | ||
|
||
expect(onChangeSpy.calledOnce).to.equal(true); | ||
}); | ||
|
||
it('should correctly change switch checked for uncontrolled Switch', () => { | ||
const wrapper = mount(<Switch defaultChecked={false} />); | ||
|
||
const inputElement = wrapper.find('input'); | ||
expect(inputElement).to.have.lengthOf(1); | ||
expect(inputElement.props().checked).to.equal(false); | ||
|
||
inputElement.simulate('change', { target: { checked: true } }); | ||
wrapper.update(); | ||
expect(wrapper.find('input').props().checked).to.equal(true); | ||
}); | ||
|
||
it('should correctly apply className', () => { | ||
const wrapper = mount(<Switch className="some-class" />); | ||
|
||
const inputElement = wrapper.find('input'); | ||
expect(inputElement).to.have.lengthOf(1); | ||
expect(inputElement.props().className).to.equal('some-class'); | ||
}); | ||
}); |
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,60 @@ | ||
@import '~styles/color'; | ||
@import '~styles/variable'; | ||
|
||
$color-switch-background-off: $color-light-red; | ||
$color-switch-background-on: $color-light-green; | ||
$color-switch-circle-off: $color-dark-red; | ||
$color-switch-circle-on: $color-dark-green; | ||
|
||
.aui--switch-label { | ||
position: relative; | ||
display: inline-block; | ||
width: 40px; | ||
height: 20px; | ||
} | ||
|
||
.aui--switch-label input { | ||
opacity: 0; | ||
width: 0; | ||
height: 0; | ||
} | ||
|
||
.aui--switch-slider { | ||
position: absolute; | ||
cursor: pointer; | ||
top: 0; | ||
left: 0; | ||
right: 0; | ||
bottom: 0; | ||
background-color: $color-switch-background-off; | ||
transition: .4s; | ||
} | ||
|
||
.aui--switch-slider::before { | ||
position: absolute; | ||
content: ''; | ||
height: 12px; | ||
width: 12px; | ||
margin-left: 4px; | ||
margin-top: 4px; | ||
background-color: $color-switch-circle-off; | ||
transition: .4s; | ||
} | ||
|
||
input:checked + .aui--switch-slider { | ||
background-color: $color-light-green; | ||
} | ||
|
||
input:checked + .aui--switch-slider::before { | ||
background-color: $color-dark-green; | ||
transform: translateX(20px); | ||
} | ||
|
||
.aui--switch-slider.round { | ||
border-radius: 34px; | ||
} | ||
|
||
.aui--switch-slider.round::before { | ||
border-radius: 50%; | ||
} | ||
|
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
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
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
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
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,105 @@ | ||
import _ from 'lodash'; | ||
import React from 'react'; | ||
import Example from '../components/Example'; | ||
import { Switch } from '../../src'; | ||
|
||
class SwitchExample extends React.PureComponent { | ||
state = { | ||
isToggleOn: true, | ||
}; | ||
|
||
onChange = newValue => { | ||
this.setState({ isToggleOn: newValue }); | ||
}; | ||
|
||
render() { | ||
return ( | ||
<React.Fragment> | ||
<div> | ||
<div className="component-heading">Un-Controlled Switch without defaultChecked</div> | ||
<div className="component-container"> | ||
<Switch /> | ||
</div> | ||
</div> | ||
<div> | ||
<div className="component-heading">Un-Controlled Switch with defaultChecked</div> | ||
<div className="component-container"> | ||
<Switch defaultChecked={true} /> | ||
</div> | ||
</div> | ||
<div> | ||
<div className="component-heading">Un-Controlled Switch with defaultChecked with onChange</div> | ||
<div className="component-container"> | ||
<Switch defaultChecked={false} onChange={_.noop} /> | ||
</div> | ||
</div> | ||
<div> | ||
<div className="component-heading">Controlled Switch</div> | ||
<div className="component-container"> | ||
<Switch checked={this.state.isToggleOn} onChange={this.onChange} /> | ||
</div> | ||
</div> | ||
</React.Fragment> | ||
); | ||
} | ||
} | ||
|
||
const exampleProps = { | ||
componentName: 'Switch', | ||
exampleCodeSnippet: ` | ||
<Switch /> | ||
<Switch defaultValue={true} /> | ||
<Switch defaultChecked={true} onChange={(nextState) => func(nextState)} /> | ||
<Switch checked={true} onChange={(nextState) => func(nextState)} /> | ||
`, | ||
propTypeSectionArray: [ | ||
{ | ||
propTypes: [ | ||
{ | ||
propType: 'defaultChecked', | ||
type: 'boolean', | ||
defaultValue: null, | ||
note: 'switch value, if the value is un-controlled', | ||
}, | ||
{ | ||
propType: 'checked', | ||
type: 'boolean', | ||
defaultValue: null, | ||
note: 'switch value, if the value is controlled', | ||
}, | ||
{ | ||
propType: 'value', | ||
type: 'string', | ||
defaultValue: '', | ||
}, | ||
{ | ||
propType: 'onChange', | ||
type: 'func', | ||
defaultValue: null, | ||
note: ( | ||
<div> | ||
This function is called when value is changed <br /> | ||
<pre>const onChange = (nextState) => ...)</pre> | ||
</div> | ||
), | ||
}, | ||
{ | ||
propType: 'className', | ||
type: 'string', | ||
defaultValue: null, | ||
}, | ||
{ | ||
propType: 'dts', | ||
type: 'string', | ||
defaultValue: 'switch-component', | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
|
||
export default () => ( | ||
<Example {...exampleProps}> | ||
<SwitchExample /> | ||
</Example> | ||
); |
Oops, something went wrong.