Better looking checkboxes (#3345)

Also showing icon only on hover on control headers
This commit is contained in:
Maxime Beauchemin 2017-08-21 13:47:50 -07:00 committed by GitHub
parent e79adbbc5f
commit 254645773c
7 changed files with 100 additions and 63 deletions

View File

@ -9,15 +9,19 @@ const propTypes = {
icon: PropTypes.string, icon: PropTypes.string,
className: PropTypes.string, className: PropTypes.string,
onClick: PropTypes.func, onClick: PropTypes.func,
placement: PropTypes.string,
}; };
const defaultProps = { const defaultProps = {
icon: 'question-circle-o', icon: 'info-circle',
className: 'text-muted',
placement: 'right',
}; };
export default function InfoTooltipWithTrigger({ label, tooltip, icon, className, onClick }) { export default function InfoTooltipWithTrigger({
label, tooltip, icon, className, onClick, placement }) {
return ( return (
<OverlayTrigger <OverlayTrigger
placement="right" placement={placement}
overlay={<Tooltip id={`${slugify(label)}-tooltip`}>{tooltip}</Tooltip>} overlay={<Tooltip id={`${slugify(label)}-tooltip`}>{tooltip}</Tooltip>}
> >
<i <i

View File

@ -56,6 +56,7 @@ const defaultProps = {
export default class Control extends React.PureComponent { export default class Control extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { hovered: false };
this.validate = this.validate.bind(this); this.validate = this.validate.bind(this);
this.onChange = this.onChange.bind(this); this.onChange = this.onChange.bind(this);
} }
@ -65,6 +66,9 @@ export default class Control extends React.PureComponent {
onChange(value, errors) { onChange(value, errors) {
this.validateAndSetValue(value, errors); this.validateAndSetValue(value, errors);
} }
setHover(hovered) {
this.setState({ hovered });
}
validateAndSetValue(value, errors) { validateAndSetValue(value, errors) {
let validationErrors = this.props.validationErrors; let validationErrors = this.props.validationErrors;
let currentErrors = this.validate(value); let currentErrors = this.validate(value);
@ -96,9 +100,14 @@ export default class Control extends React.PureComponent {
const ControlType = controlMap[this.props.type]; const ControlType = controlMap[this.props.type];
const divStyle = this.props.hidden ? { display: 'none' } : null; const divStyle = this.props.hidden ? { display: 'none' } : null;
return ( return (
<div style={divStyle}> <div
style={divStyle}
onMouseEnter={this.setHover.bind(this, true)}
onMouseLeave={this.setHover.bind(this, false)}
>
<ControlType <ControlType
onChange={this.onChange} onChange={this.onChange}
hovered={this.state.hovered}
{...this.props} {...this.props}
/> />
</div> </div>

View File

@ -10,32 +10,72 @@ const propTypes = {
renderTrigger: PropTypes.bool, renderTrigger: PropTypes.bool,
rightNode: PropTypes.node, rightNode: PropTypes.node,
leftNode: PropTypes.node, leftNode: PropTypes.node,
onClick: PropTypes.func,
hovered: PropTypes.bool,
}; };
const defaultProps = { const defaultProps = {
validationErrors: [], validationErrors: [],
renderTrigger: false, renderTrigger: false,
hovered: false,
}; };
export default function ControlHeader({ export default class ControlHeader extends React.Component {
label, description, validationErrors, renderTrigger, leftNode, rightNode }) { renderOptionalIcons() {
const hasError = (validationErrors.length > 0); if (this.props.hovered) {
return ( return (
<div> <span>
{this.props.description &&
<span>
<InfoTooltipWithTrigger
label="descr"
tooltip={this.props.description}
placement="top"
/>
{' '}
</span>
}
{this.props.renderTrigger &&
<span>
<InfoTooltipWithTrigger
label="bolt"
tooltip={this.props.description}
placement="top"
icon="bolt"
/>
{' '}
</span>
}
</span>);
}
return null;
}
render() {
const labelClass = (this.props.validationErrors.length > 0) ? 'text-danger' : '';
return (
<div
className="ControlHeader"
>
<div className="pull-left"> <div className="pull-left">
<ControlLabel> <ControlLabel>
{hasError ? {this.props.leftNode &&
<strong className="text-danger">{label}</strong> : <span>{this.props.leftNode}</span>
<span>{label}</span>
} }
<span
onClick={this.props.onClick}
className={labelClass}
style={{ cursor: this.props.onClick ? 'pointer' : '' }}
>
{this.props.label}
</span>
{' '} {' '}
{(validationErrors.length > 0) && {(this.props.validationErrors.length > 0) &&
<span> <span>
<OverlayTrigger <OverlayTrigger
placement="right" placement="top"
overlay={ overlay={
<Tooltip id={'error-tooltip'}> <Tooltip id={'error-tooltip'}>
{validationErrors.join(' ')} {this.props.validationErrors.join(' ')}
</Tooltip> </Tooltip>
} }
> >
@ -44,40 +84,18 @@ export default function ControlHeader({
{' '} {' '}
</span> </span>
} }
{description && {this.renderOptionalIcons()}
<span>
<InfoTooltipWithTrigger label={label} tooltip={description} />
{' '}
</span>
}
{renderTrigger &&
<span>
<OverlayTrigger
placement="right"
overlay={
<Tooltip id={'rendertrigger-tooltip'}>
Takes effect on chart immediatly
</Tooltip>
}
>
<i className="fa fa-bolt text-muted" />
</OverlayTrigger>
{' '}
</span>
}
{leftNode &&
<span>{leftNode}</span>
}
</ControlLabel> </ControlLabel>
</div> </div>
{rightNode && {this.props.rightNode &&
<div className="pull-right"> <div className="pull-right">
{rightNode} {this.props.rightNode}
</div> </div>
} }
<div className="clearfix" /> <div className="clearfix" />
</div> </div>
); );
}
} }
ControlHeader.propTypes = propTypes; ControlHeader.propTypes = propTypes;

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Checkbox } from 'react-bootstrap';
import ControlHeader from '../ControlHeader'; import ControlHeader from '../ControlHeader';
const propTypes = { const propTypes = {
@ -24,11 +23,16 @@ export default class CheckboxControl extends React.Component {
return ( return (
<ControlHeader <ControlHeader
{...this.props} {...this.props}
onClick={this.onToggle.bind(this)}
leftNode={ leftNode={
<Checkbox <span>
checked={this.props.value} <i
onChange={this.onToggle.bind(this)} className={`fa fa-check ${this.props.value ? 'text-primary' : 'text-transparent'}`}
onClick={this.onToggle.bind(this)}
style={{ border: '1px solid #aaa', borderRadius: '2px', cursor: 'pointer' }}
/> />
&nbsp;&nbsp;
</span>
} }
/> />
); );

View File

@ -66,7 +66,7 @@
"react-addons-css-transition-group": "^15.6.0", "react-addons-css-transition-group": "^15.6.0",
"react-addons-shallow-compare": "^15.4.2", "react-addons-shallow-compare": "^15.4.2",
"react-alert": "^1.0.14", "react-alert": "^1.0.14",
"react-bootstrap": "^0.31.0", "react-bootstrap": "^0.31.2",
"react-bootstrap-table": "^3.1.7", "react-bootstrap-table": "^3.1.7",
"react-dom": "^15.5.1", "react-dom": "^15.5.1",
"react-gravatar": "^2.6.1", "react-gravatar": "^2.6.1",

View File

@ -1,6 +1,5 @@
/* eslint-disable no-unused-expressions */ /* eslint-disable no-unused-expressions */
import React from 'react'; import React from 'react';
import { Checkbox } from 'react-bootstrap';
import sinon from 'sinon'; import sinon from 'sinon';
import { expect } from 'chai'; import { expect } from 'chai';
import { describe, it, beforeEach } from 'mocha'; import { describe, it, beforeEach } from 'mocha';
@ -28,6 +27,6 @@ describe('CheckboxControl', () => {
expect(controlHeader).to.have.lengthOf(1); expect(controlHeader).to.have.lengthOf(1);
const headerWrapper = controlHeader.shallow(); const headerWrapper = controlHeader.shallow();
expect(headerWrapper.find(Checkbox)).to.have.length(1); expect(headerWrapper.find('i.fa-check')).to.have.length(1);
}); });
}); });

View File

@ -329,3 +329,6 @@ iframe {
border: none; border: none;
width: 100%; width: 100%;
} }
.text-transparent {
color: transparent;
}