Created
June 21, 2018 06:02
-
-
Save darotar/5bf1da74980b4ea4d9e627a7a4f06bbe to your computer and use it in GitHub Desktop.
RadioGroup enterprise reusable component
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
import React from 'react'; | |
import classnames from 'classnames/bind'; | |
import PropTypes from 'prop-types'; | |
import style from './style.less'; | |
const cn = classnames.bind(style); | |
class ElementsGroup extends React.Component { | |
getStyle = (i) => { | |
const marginType = this.props.type === 'horizontal' ? 'marginLeft' : 'marginTop'; | |
return { [marginType]: i === 0 ? 0 : this.props.margin }; | |
} | |
getClassName = () => { | |
return cn( | |
this.props.className, | |
'elementsGroup', { | |
'elementsGroup--vertical': this.props.type === 'vertical' | |
} | |
); | |
} | |
mapChildren = () => { | |
return React.Children.map(this.props.children, (child, i) => { | |
return React.createElement('div', { style: this.getStyle(i) }, child); | |
}); | |
} | |
render() { | |
return <div className={this.getClassName()}>{this.mapChildren()}</div>; | |
} | |
} | |
ElementsGroup.defaultProps = { | |
margin: 20, | |
type: 'horizontal' | |
}; | |
ElementsGroup.propTypes = { | |
margin: PropTypes.number, | |
type: PropTypes.string, | |
className: PropTypes.string | |
}; | |
export default ElementsGroup; |
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
import React from 'react'; | |
import PropTypes from 'prop-types'; | |
import classnames from 'classnames'; | |
import colorString from 'color-string'; | |
import style from './style.less'; | |
import loader from './imgs/loader.m.svg'; | |
class Loader extends React.Component { | |
getFilter = () => { | |
const matrix = colorString.get.rgb(this.props.color).map(color => { | |
return Math.round((color * 100) / 255) / 100; | |
}); | |
return <defs xmlns="http://www.w3.org/2000/svg"> | |
<filter id={this.filterId()}> | |
<feColorMatrix | |
colorInterpolationFilters="sRGB" | |
type="matrix" | |
values={`${matrix[0]} 0 0 0 0 0 ${matrix[1]} 0 0 0 0 0 ${matrix[2]} 0 0 0 0 0 1 0 `} /> | |
</filter> | |
</defs>; | |
} | |
getClassNames = () => { | |
return classnames( | |
'qa-loader', | |
this.props.className, | |
style.loader | |
); | |
} | |
filterId = () => { | |
return `loaderColor${colorString.get.rgb(this.props.color).join('')}`; | |
} | |
render() { | |
return ( | |
<svg className={this.getClassNames()}> | |
{this.getFilter()} | |
<g filter={`url(#${this.filterId()})`}> | |
<use xlinkHref={`#${loader.id}`} /> | |
</g> | |
</svg> | |
); | |
} | |
} | |
Loader.defaultProps = { | |
color: '#209AC4' | |
}; | |
Loader.propTypes = { | |
className: PropTypes.string, | |
color: PropTypes.string | |
}; | |
export default Loader; |
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
import React, { Component } from 'react'; | |
import classnames from 'classnames/bind'; | |
import propTypes from 'prop-types'; | |
import _ from 'underscore'; | |
import style from './style.less'; | |
import Loader from '../Loader'; | |
const cn = classnames.bind(style); | |
class Radio extends Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
focus: false | |
}; | |
this.id = _.uniqueId('radio_'); | |
} | |
onChange = (e) => { | |
const { onChange } = this.props; | |
return onChange ? onChange(e): null; | |
} | |
onFocus = () => { | |
this.setState({ | |
focus: true | |
}); | |
} | |
onClick = () => { | |
this.setState({ | |
focus: false | |
}); | |
} | |
onBlur = () => { | |
this.setState({ | |
focus: false | |
}); | |
} | |
getClassNames = () => { | |
const { loading, checked, disabled } = this.props; | |
return cn({ | |
radio: true, | |
'radio--loading': loading, | |
'radio--checked': checked, | |
'radio--disabled': disabled, | |
'radio--focus': this.state.focus | |
}); | |
} | |
getRadio = () => { | |
if (this.props.loading) { | |
return <Loader className={style.radio__loader} />; | |
} | |
return <span className={style.radio__inner} />; | |
} | |
isDisabledNative = () => { | |
return this.props.disabled || this.props.loading; | |
} | |
render() { | |
return ( | |
<label htmlFor={this.id} className={this.getClassNames()}> | |
<span className={style.radio__input}> | |
{ this.getRadio() } | |
<input | |
id={this.id} | |
type="radio" | |
className={style.radio__original} | |
checked={this.props.checked} | |
disabled={this.isDisabledNative()} | |
onChange={this.onChange} | |
onFocus={this.onFocus} | |
onClick={this.onClick} | |
onBlur={this.onBlur} | |
/> | |
</span> | |
<span | |
className={style.radio__label} | |
> | |
{ this.props.children } | |
</span> | |
</label> | |
); | |
} | |
} | |
Radio.propTypes = { | |
onChange: propTypes.func, | |
disabled: propTypes.bool, | |
checked: propTypes.bool, | |
loading: propTypes.bool | |
}; | |
Radio.defaultProps = { | |
checked: false, | |
disabled: false, | |
loading: false | |
}; | |
export default Radio; |
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
import React, { Component } from 'react'; | |
import propTypes from 'prop-types'; | |
import Radio from './Radio'; | |
import ElementsGroup from '../../components/ElementsGroup'; | |
export default class RadioGroup extends Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
value: this.props.value | |
}; | |
} | |
componentWillReceiveProps(nextProps) { | |
this.setState({ | |
value: nextProps.value | |
}); | |
} | |
onChange = ({ value }) => { | |
this.setState({ | |
value | |
}, () => this.props.onChange({ value })); | |
} | |
render() { | |
const { data, loading } = this.props; | |
return ( | |
<div> | |
<ElementsGroup margin={10}> | |
{ data.map(item => { | |
return <Radio | |
checked={this.state.value === item.value} | |
key={item.value} | |
onChange={() => this.onChange({ value: item.value })} | |
disabled={item.disabled} | |
loading={loading} | |
>{ item.text }</Radio>; | |
}) | |
} | |
</ElementsGroup> | |
</div> | |
); | |
} | |
} | |
RadioGroup.propTypes = { | |
value: propTypes.node, | |
onChange: propTypes.func.isRequired, | |
loading: propTypes.bool, | |
data(props, propName, componentName) { | |
if (!props.data.length) { | |
return new Error(`Invalid prop '${propName}' supplied to '${componentName}'. Validation failed.`); | |
} | |
return null; | |
} | |
}; |
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
import React from 'react'; | |
import expect from 'expect'; | |
import { shallow } from 'enzyme'; | |
import RadioGroup from './RadioGroup'; | |
describe('radioGroup component test', () => { | |
it('is exist', () => { | |
expect(shallow(<RadioGroup | |
value="male" | |
onChange={value => console.log(value)} | |
data={[ | |
{ value: 'male', text: 'Male' }, | |
{ value: 'female', text: 'Female' } | |
]} | |
/>).length).toEqual(1); | |
}); | |
}); |
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
@size: 22px; | |
@checkedColor: #3da1c5; | |
@borderColor: #d1dbe5; | |
@disabledColor: #eef1f6; | |
@disabledInner: #bfcbd9; | |
@hoverColor: #3791b1; | |
@activeColor: #31819e; | |
@borderActive: #1d8ab0; | |
@borderHover: #209ac4; | |
@textDisabled: @disabledInner; | |
.common() { | |
position: relative; | |
display: inline-flex; | |
align-items: flex-start; | |
} | |
.radio { | |
color: #1f2d3d; | |
cursor: pointer; | |
.common; | |
&__input { | |
outline: 0; | |
line-height: 16px; | |
align-items: center; | |
.common; | |
&:hover { | |
.radio__inner { | |
border-color: @borderHover; | |
} | |
} | |
&:active { | |
.radio__inner { | |
border-color: @activeColor; | |
} | |
} | |
.radio__inner { | |
border: 1px solid @borderColor; | |
width: @size; | |
height: @size; | |
border-radius: 50%; | |
box-sizing: border-box; | |
background-color: #fff; | |
transition: background 175ms, box-shadow 175ms, border 175ms, color 175ms; | |
.common; | |
&:after { | |
width: 6px; | |
height: 6px; | |
border-radius: 50%; | |
background-color: #fff; | |
content: ''; | |
position: absolute; | |
left: 50%; | |
top: 50%; | |
transform: translate(-50%,-50%) scale(1); | |
} | |
} | |
.radio__original { | |
opacity: 0; | |
outline: 0; | |
position: absolute; | |
z-index: -1; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
margin: 0; | |
} | |
} | |
&__label { | |
padding-left: 10px; | |
padding-top: 3px; | |
color: #333333; | |
font-size: 15px; | |
line-height: 18px; | |
font-family: Roboto; | |
} | |
&__loader { | |
width: @size; | |
height: @size; | |
} | |
&--loading { | |
cursor: default; | |
} | |
&--checked { | |
&:hover { | |
.radio__inner { | |
background: @hoverColor; | |
border-color: @hoverColor; | |
} | |
} | |
&:active { | |
.radio__inner { | |
background: @activeColor; | |
border-color: @activeColor; | |
} | |
} | |
.radio__inner { | |
border-color: @checkedColor; | |
background: @checkedColor; | |
} | |
} | |
&--focus { | |
.radio__inner { | |
box-shadow: 0 0 10px 0 @checkedColor; | |
} | |
} | |
&--disabled { | |
cursor: not-allowed; | |
&.radio--checked { | |
.radio__inner:after { | |
background-color: @disabledInner !important; | |
} | |
} | |
.radio__inner { | |
cursor: not-allowed; | |
background: @disabledColor !important; | |
border-color: @borderColor !important; | |
&:hover { | |
border-color: @borderColor !important; | |
} | |
&:after { | |
background-color: @disabledColor !important; | |
} | |
} | |
& + .radio__label { | |
cursor: default; | |
color: @textDisabled; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment