react-ui-icheck
Version:
iCheck customizable checkboxes and radios built with React.
298 lines (257 loc) • 6.72 kB
JSX
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
const iCheck = 'iCheck';
const iCheckHelper = `${iCheck}-helper`;
class Input extends Component {
constructor(props) {
super(props);
this.handleBlur = this.handleBlur.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleFocus = this.handleFocus.bind(this);
this.handleHelperClick = this.handleHelperClick.bind(this);
const { checked, hovered, indeterminate } = props;
this.state = {
active: false,
focused: false,
prevPropChecked: props.checked,
checked,
hovered,
indeterminate,
};
}
static getDerivedStateFromProps(props, state) {
if (state.prevPropChecked !== props.checked) {
return {
checked: props.checked,
prevPropChecked: props.checked,
};
}
if (props.hovered !== state.hovered) {
return {
hovered: props.hovered,
};
}
return null;
}
handleBlur(event) {
const { onBlur } = this.props;
this.setState({ focused: false });
if (onBlur) onBlur(event);
}
handleChange(event) {
const { onChange } = this.props;
const { checked } = event.target;
this.setState((state) => ({ checked: !state.checked }));
if (onChange) onChange(event, checked);
}
handleFocus(event) {
const { onFocus } = this.props;
this.setState({ focused: true });
if (onFocus) onFocus(event);
}
handleHelperClick(event) {
const { disabled, inputType, insert, label, onChange } = this.props;
const { checked } = this.state;
if ((!label && !insert) || disabled) return;
if (inputType.toString() === 'checkbox')
this.setState((state) => ({ checked: !state.checked }));
event.preventDefault();
event.stopPropagation();
if (onChange) {
// make sure <ins /> element is not target
event.target = this.checkbox; // eslint-disable-line no-param-reassign
onChange(event, checked);
}
}
render() {
const {
activeClassName,
aria,
checkboxClassName,
checkedClassName,
className,
disabled,
disabledClassName,
focusClassName,
hoverClassName,
id,
increaseArea,
indeterminateClassName,
inheritClassName,
inheritID,
inputType,
insert,
isMobile,
name,
radioClassName,
value,
hovered,
indeterminate,
...other
} = this.props;
const {
active,
checked,
focused,
hovered: stateHovered,
indeterminate: stateIndeterminate,
} = this.state;
const wrapProps = {
className: classNames({
[checkboxClassName]: inputType.toString() === 'checkbox',
[radioClassName]: inputType.toString() === 'radio',
[indeterminateClassName]: stateIndeterminate,
[checkedClassName]: checked,
[disabledClassName]: disabled,
[hoverClassName]: stateHovered,
[focusClassName]: focused,
[activeClassName]: active,
}),
};
if (inheritClassName)
wrapProps.className = classNames(wrapProps.className, className);
if (inheritID && id) wrapProps.id = `${iCheck}-${id}`;
if (aria) {
wrapProps.role = inputType;
// Set ARIA "labelledby"
wrapProps['aria-labelledby'] = wrapProps.id;
wrapProps['aria-checked'] = checked;
}
// Clickable area
let area = `${increaseArea}`.replace('%', '') || 0;
if (area < -50) area = -50;
// Layer styles
const offset = `${-area}%`;
const size = `${100 + area * 2}%`;
const layer = {
position: 'absolute',
top: offset,
left: offset,
display: 'block',
width: size,
height: size,
margin: 0,
padding: 0,
background: '#fff',
border: 0,
opacity: 0,
cursor: disabled ? 'default' : 'pointer',
};
// Layer addition
const helper = (
<ins
className={iCheckHelper}
role="presentation"
style={layer}
onClick={this.handleHelperClick}
/>
);
// Choose how to hide input
let hide;
if (isMobile) {
hide = {
position: 'absolute',
visibility: 'hidden',
};
} else if (area) {
hide = layer;
} else {
hide = {
opacity: 0,
position: 'absolute',
};
}
const inputElement = (
<input
{...other}
checked={checked}
disabled={disabled}
name={name}
ref={(c) => {
this.checkbox = c;
}}
style={hide}
type={inputType}
value={value}
onBlur={this.handleBlur}
onChange={this.handleChange}
onFocus={this.handleFocus}
/>
);
let insertElement = null;
if (insert) {
insertElement = React.isValidElement(insert) ? (
insert
) : (
// eslint-disable-next-line react/no-danger
<div dangerouslySetInnerHTML={{ __html: insert }} />
);
}
return (
<div {...wrapProps}>
{inputElement}
{insertElement}
{helper}
</div>
);
}
}
Input.propTypes = {
inputType: PropTypes.string.isRequired,
activeClassName: PropTypes.string,
aria: PropTypes.bool,
checkboxClassName: PropTypes.string,
checked: PropTypes.bool,
checkedClassName: PropTypes.string,
className: PropTypes.string,
disabled: PropTypes.bool,
disabledClassName: PropTypes.string,
focusClassName: PropTypes.string,
hoverClassName: PropTypes.string,
hovered: PropTypes.bool,
id: PropTypes.string,
increaseArea: PropTypes.string,
indeterminate: PropTypes.bool,
indeterminateClassName: PropTypes.string,
inheritClassName: PropTypes.bool,
inheritID: PropTypes.bool,
insert: PropTypes.node,
isMobile: PropTypes.bool,
label: PropTypes.string,
name: PropTypes.string,
radioClassName: PropTypes.string,
value: PropTypes.string,
onBlur: PropTypes.func,
onChange: PropTypes.func,
onFocus: PropTypes.func,
};
Input.defaultProps = {
activeClassName: null,
aria: false,
checkboxClassName: 'icheckbox',
checked: false,
checkedClassName: 'checked',
className: null,
disabled: false,
disabledClassName: 'disabled',
focusClassName: 'focus',
hoverClassName: 'hover',
hovered: false,
id: null,
increaseArea: '',
indeterminate: false,
indeterminateClassName: 'indeterminate',
inheritClassName: false,
inheritID: false,
insert: null,
isMobile: false,
label: null,
name: null,
radioClassName: 'iradio',
value: '',
onBlur: null,
onChange: null,
onFocus: null,
};
export default Input;