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,74 +10,92 @@ 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>
<div className="pull-left"> {this.props.description &&
<ControlLabel>
{hasError ?
<strong className="text-danger">{label}</strong> :
<span>{label}</span>
}
{' '}
{(validationErrors.length > 0) &&
<span> <span>
<OverlayTrigger <InfoTooltipWithTrigger
placement="right" label="descr"
overlay={ tooltip={this.props.description}
<Tooltip id={'error-tooltip'}> placement="top"
{validationErrors.join(' ')} />
</Tooltip>
}
>
<i className="fa fa-exclamation-circle text-danger" />
</OverlayTrigger>
{' '} {' '}
</span> </span>
} }
{description && {this.props.renderTrigger &&
<span> <span>
<InfoTooltipWithTrigger label={label} tooltip={description} /> <InfoTooltipWithTrigger
label="bolt"
tooltip={this.props.description}
placement="top"
icon="bolt"
/>
{' '} {' '}
</span> </span>
} }
{renderTrigger && </span>);
<span> }
<OverlayTrigger return null;
placement="right" }
overlay={ render() {
<Tooltip id={'rendertrigger-tooltip'}> const labelClass = (this.props.validationErrors.length > 0) ? 'text-danger' : '';
Takes effect on chart immediatly return (
</Tooltip> <div
} className="ControlHeader"
> >
<i className="fa fa-bolt text-muted" /> <div className="pull-left">
</OverlayTrigger> <ControlLabel>
{' '} {this.props.leftNode &&
<span>{this.props.leftNode}</span>
}
<span
onClick={this.props.onClick}
className={labelClass}
style={{ cursor: this.props.onClick ? 'pointer' : '' }}
>
{this.props.label}
</span> </span>
} {' '}
{leftNode && {(this.props.validationErrors.length > 0) &&
<span>{leftNode}</span> <span>
} <OverlayTrigger
</ControlLabel> placement="top"
</div> overlay={
{rightNode && <Tooltip id={'error-tooltip'}>
<div className="pull-right"> {this.props.validationErrors.join(' ')}
{rightNode} </Tooltip>
}
>
<i className="fa fa-exclamation-circle text-danger" />
</OverlayTrigger>
{' '}
</span>
}
{this.renderOptionalIcons()}
</ControlLabel>
</div> </div>
} {this.props.rightNode &&
<div className="clearfix" /> <div className="pull-right">
</div> {this.props.rightNode}
); </div>
}
<div className="clearfix" />
</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;
}