Skip to content

Instantly share code, notes, and snippets.

@darotar
Created June 21, 2018 06:02
Show Gist options
  • Save darotar/5bf1da74980b4ea4d9e627a7a4f06bbe to your computer and use it in GitHub Desktop.
Save darotar/5bf1da74980b4ea4d9e627a7a4f06bbe to your computer and use it in GitHub Desktop.
RadioGroup enterprise reusable component
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;
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;
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;
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;
}
};
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);
});
});
@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