Merge pull request #201 from react-toolbox/ripple-decorator

Ripple decorator
old
Javi Velasco 2015-12-07 23:57:59 +01:00
commit fe5f0f8071
20 changed files with 239 additions and 271 deletions

View File

@ -251,24 +251,3 @@
transition-timing-function: $animation-curve-default; transition-timing-function: $animation-curve-default;
transition-duration: $duration; transition-duration: $duration;
} }
// The frames are this way to prevent a flicker in Safari
// See https://goo.gl/5luFDk
@mixin ripple-loading($name, $width, $height, $opacity: 0.3) {
@keyframes #{$name} {
0% {
opacity: $opacity;
transform: translate3d(-50%, -50%, 0) scale(0);
}
95% {
opacity: 0;
transform: translate3d(-50%, -50%, 0) scale(1);
}
100% {
opacity: 0;
transform: translate3d(-50%, -50%, 0) scale(0);
}
}
}

View File

@ -19,7 +19,6 @@ class Button extends React.Component {
mini: React.PropTypes.bool, mini: React.PropTypes.bool,
primary: React.PropTypes.bool, primary: React.PropTypes.bool,
raised: React.PropTypes.bool, raised: React.PropTypes.bool,
ripple: React.PropTypes.bool,
type: React.PropTypes.string type: React.PropTypes.string
}; };
@ -30,13 +29,7 @@ class Button extends React.Component {
floating: false, floating: false,
mini: false, mini: false,
primary: false, primary: false,
raised: false, raised: false
ripple: true
};
handleMouseDown = (event) => {
if (this.refs.ripple) this.refs.ripple.start(event);
if (this.props.onMouseDown) this.props.onMouseDown(event);
}; };
handleMouseUp = () => { handleMouseUp = () => {
@ -45,7 +38,7 @@ class Button extends React.Component {
render () { render () {
const { accent, children, className, flat, floating, href, icon, const { accent, children, className, flat, floating, href, icon,
inverse, label, mini, primary, raised, ripple, ...others} = this.props; inverse, label, mini, primary, raised, ...others} = this.props;
const element = href ? 'a' : 'button'; const element = href ? 'a' : 'button';
const level = primary ? 'primary' : accent ? 'accent' : 'neutral'; const level = primary ? 'primary' : accent ? 'accent' : 'neutral';
const shape = flat ? 'flat' : raised ? 'raised' : floating ? 'floating' : 'flat'; const shape = flat ? 'flat' : raised ? 'raised' : floating ? 'floating' : 'flat';
@ -61,13 +54,11 @@ class Button extends React.Component {
ref: 'button', ref: 'button',
className: classes, className: classes,
disabled: this.props.disabled, disabled: this.props.disabled,
onMouseDown: this.handleMouseDown,
onMouseUp: this.handleMouseUp, onMouseUp: this.handleMouseUp,
'data-react-toolbox': 'button' 'data-react-toolbox': 'button'
}; };
return React.createElement(element, props, return React.createElement(element, props,
ripple ? <Ripple ref='ripple' /> : null,
icon ? <FontIcon className={style.icon} value={icon}/> : null, icon ? <FontIcon className={style.icon} value={icon}/> : null,
label, label,
children children
@ -75,4 +66,4 @@ class Button extends React.Component {
} }
} }
export default Button; export default Ripple({centered: false})(Button);

View File

@ -4,7 +4,7 @@ import FontIcon from '../font_icon';
import Ripple from '../ripple'; import Ripple from '../ripple';
import style from './style'; import style from './style';
class Button extends React.Component { class IconButton extends React.Component {
static propTypes = { static propTypes = {
accent: React.PropTypes.bool, accent: React.PropTypes.bool,
children: React.PropTypes.node, children: React.PropTypes.node,
@ -14,20 +14,13 @@ class Button extends React.Component {
icon: React.PropTypes.string, icon: React.PropTypes.string,
inverse: React.PropTypes.bool, inverse: React.PropTypes.bool,
primary: React.PropTypes.bool, primary: React.PropTypes.bool,
ripple: React.PropTypes.bool,
type: React.PropTypes.string type: React.PropTypes.string
}; };
static defaultProps = { static defaultProps = {
accent: false, accent: false,
className: '', className: '',
primary: false, primary: false
ripple: true
};
handleMouseDown = (event) => {
if (this.refs.ripple) this.refs.ripple.start(event);
if (this.props.onMouseDown) this.props.onMouseDown(event);
}; };
handleMouseUp = () => { handleMouseUp = () => {
@ -35,7 +28,7 @@ class Button extends React.Component {
}; };
render () { render () {
const {accent, children, className, href, icon, inverse, primary, ripple, ...others} = this.props; const {accent, children, className, href, icon, inverse, primary, ...others} = this.props;
const element = href ? 'a' : 'button'; const element = href ? 'a' : 'button';
const level = primary ? 'primary' : accent ? 'accent' : 'neutral'; const level = primary ? 'primary' : accent ? 'accent' : 'neutral';
const classes = ClassNames([style.toggle, style[level]], {[style.inverse]: inverse}, className); const classes = ClassNames([style.toggle, style[level]], {[style.inverse]: inverse}, className);
@ -46,16 +39,15 @@ class Button extends React.Component {
ref: 'button', ref: 'button',
className: classes, className: classes,
disabled: this.props.disabled, disabled: this.props.disabled,
onMouseDown: this.handleMouseDown,
onMouseUp: this.handleMouseUp, onMouseUp: this.handleMouseUp,
'data-react-toolbox': 'button' 'data-react-toolbox': 'button'
}; };
return React.createElement(element, props, return React.createElement(element, props,
ripple ? <Ripple ref='ripple' centered /> : null, icon ? <FontIcon className={style.icon} value={icon}/> : null,
icon ? <FontIcon className={style.icon} value={icon}/> : children children
); );
} }
} }
export default Button; export default Ripple({centered: true})(IconButton);

View File

@ -0,0 +1,18 @@
import React from 'react';
import ClassNames from 'classnames';
import Ripple from '../ripple';
import style from './style';
const Check = ({checked, children, onMouseDown}) => {
const className = ClassNames(style.check, {
[style.checked]: checked
});
return <div data-role='checkbox' onMouseDown={onMouseDown} className={className}>{children}</div>;
};
export default Ripple({
className: style.ripple,
spread: 2.6,
centered: true
})(Check);

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import ClassNames from 'classnames'; import ClassNames from 'classnames';
import Ripple from '../ripple'; import Check from './Check';
import events from '../utils/events';
import style from './style'; import style from './style';
class Checkbox extends React.Component { class Checkbox extends React.Component {
@ -22,21 +21,10 @@ class Checkbox extends React.Component {
disabled: false disabled: false
}; };
handleClick = (event) => { handleToggle = (event) => {
events.pauseEvent(event); if (event.pageX !== 0 && event.pageY !== 0) this.blur();
if (!this.props.disabled && this.props.onChange) { if (!this.props.disabled && this.props.onChange) {
const value = !this.refs.input.checked; this.props.onChange(!this.props.checked, event);
this.props.onChange(value, event);
}
};
handleInputClick = (event) => {
events.pauseEvent(event);
};
handleMouseDown = (event) => {
if (!this.props.disabled) {
this.refs.ripple.start(event);
} }
}; };
@ -49,30 +37,22 @@ class Checkbox extends React.Component {
} }
render () { render () {
const fieldClassName = ClassNames(style.field, { const { onChange, ...others } = this.props;
const className = ClassNames(style.field, {
[style.disabled]: this.props.disabled [style.disabled]: this.props.disabled
}, this.props.className); }, this.props.className);
const checkboxClassName = ClassNames(style.check, {
[style.checked]: this.props.checked
});
return ( return (
<label <label data-react-toolbox='checkbox' className={className}>
data-react-toolbox='checkbox'
className={fieldClassName}
onClick={this.handleClick}
>
<input <input
ref='input' {...others}
{...this.props}
className={style.input} className={style.input}
onClick={this.handleInputClick} onClick={this.handleToggle}
readOnly
ref='input'
type='checkbox' type='checkbox'
/> />
<span data-role='checkbox' className={checkboxClassName} onMouseDown={this.handleMouseDown}> <Check checked={this.props.checked} disabled={this.props.disabled}/>
<Ripple ref='ripple' data-role='ripple' className={style.ripple} spread={3} centered />
</span>
{this.props.label ? <span data-role='label' className={style.text}>{this.props.label}</span> : null} {this.props.label ? <span data-role='label' className={style.text}>{this.props.label}</span> : null}
</label> </label>
); );

View File

@ -21,7 +21,6 @@ $calendar-primary-contrast-color: $calendar-primary-contrast !default;
$calendar-primary-hover-color: rgba($calendar-primary, 0.21) !default; $calendar-primary-hover-color: rgba($calendar-primary, 0.21) !default;
$calendar-arrows-color: $palette-grey-600 !default; $calendar-arrows-color: $palette-grey-600 !default;
$calendar-arrows-font-size: 2 * $unit; $calendar-arrows-font-size: 2 * $unit;
$calendar-arrows-ripple-duration: 450ms;
$calendar-year-font-size: 2.4; $calendar-year-font-size: 2.4;
$calendar-day-font-size: 1.3 * $unit; $calendar-day-font-size: 1.3 * $unit;
$calendar-day-disable-opacity: 0.25; $calendar-day-disable-opacity: 0.25;

View File

@ -91,8 +91,3 @@
.month { .month {
background-color: $calendar-primary-contrast-color; background-color: $calendar-primary-contrast-color;
} }
.ripple {
opacity: .5;
transition-duration: $calendar-arrows-ripple-duration;
}

View File

@ -9,6 +9,7 @@ class ListItem extends React.Component {
static propTypes = { static propTypes = {
avatar: React.PropTypes.string, avatar: React.PropTypes.string,
caption: React.PropTypes.string.isRequired, caption: React.PropTypes.string.isRequired,
children: React.PropTypes.any,
className: React.PropTypes.string, className: React.PropTypes.string,
disabled: React.PropTypes.bool, disabled: React.PropTypes.bool,
leftIcon: React.PropTypes.string, leftIcon: React.PropTypes.string,
@ -32,12 +33,6 @@ class ListItem extends React.Component {
} }
}; };
handleMouseDown = (event) => {
if (this.refs.ripple && !this.props.disabled) {
this.refs.ripple.start(event);
}
};
renderContent () { renderContent () {
const className = ClassNames(style.item, { const className = ClassNames(style.item, {
[style.withLegend]: this.props.legend, [style.withLegend]: this.props.legend,
@ -50,7 +45,6 @@ class ListItem extends React.Component {
{this.props.leftIcon ? <FontIcon className={`${style.icon} ${style.left}`} value={this.props.leftIcon} /> : null} {this.props.leftIcon ? <FontIcon className={`${style.icon} ${style.left}`} value={this.props.leftIcon} /> : null}
{this.props.avatar ? <img className={style.avatar} src={this.props.avatar} /> : null} {this.props.avatar ? <img className={style.avatar} src={this.props.avatar} /> : null}
<ListItemContent caption={this.props.caption} legend={this.props.legend} /> <ListItemContent caption={this.props.caption} legend={this.props.legend} />
{this.props.ripple ? <Ripple ref='ripple' className={style.ripple} spread={2} /> : null}
{this.props.rightIcon ? <FontIcon className={`${style.icon} ${style.right}`} value={this.props.rightIcon} /> : null} {this.props.rightIcon ? <FontIcon className={`${style.icon} ${style.right}`} value={this.props.rightIcon} /> : null}
</span> </span>
); );
@ -58,11 +52,15 @@ class ListItem extends React.Component {
render () { render () {
return ( return (
<li className={style['list-item']} onClick={this.handleClick} onMouseDown={this.handleMouseDown}> <li className={style.listItem} onClick={this.handleClick} onMouseDown={this.props.onMouseDown}>
{this.props.to ? <a href={this.props.to}>{this.renderContent()}</a> : this.renderContent()} {this.props.to ? <a href={this.props.to}>{this.renderContent()}</a> : this.renderContent()}
{this.props.children}
</li> </li>
); );
} }
} }
export default ListItem; export default Ripple({
className: style.ripple,
centered: false
})(ListItem);

View File

@ -32,11 +32,16 @@
.list + & { .list + & {
margin-top: - $list-vertical-padding; margin-top: - $list-vertical-padding;
} }
.list-item ~ & { .listItem ~ & {
margin: $list-vertical-padding 0; margin: $list-vertical-padding 0;
} }
} }
.listItem {
position: relative;
overflow: hidden;
}
.item { .item {
position: relative; position: relative;
display: flex; display: flex;
@ -79,7 +84,7 @@
} }
.ripple { .ripple {
opacity: .1; color: $color-text-secondary;
} }
.text { .text {

View File

@ -1,16 +1,17 @@
import React from 'react'; import React from 'react';
import FontIcon from '../font_icon'; import FontIcon from '../font_icon';
import ClassNames from 'classnames';
import Ripple from '../ripple'; import Ripple from '../ripple';
import style from './style.menu_item'; import style from './style.menu_item';
class MenuItem extends React.Component { class MenuItem extends React.Component {
static propTypes = { static propTypes = {
caption: React.PropTypes.string.isRequired, caption: React.PropTypes.string.isRequired,
children: React.PropTypes.any,
className: React.PropTypes.string, className: React.PropTypes.string,
disabled: React.PropTypes.bool, disabled: React.PropTypes.bool,
icon: React.PropTypes.string, icon: React.PropTypes.string,
onClick: React.PropTypes.func, onClick: React.PropTypes.func,
ripple: React.PropTypes.bool,
selected: React.PropTypes.bool, selected: React.PropTypes.bool,
shortcut: React.PropTypes.string shortcut: React.PropTypes.string
}; };
@ -18,7 +19,6 @@ class MenuItem extends React.Component {
static defaultProps = { static defaultProps = {
className: '', className: '',
disabled: false, disabled: false,
ripple: false,
selected: false selected: false
}; };
@ -28,32 +28,24 @@ class MenuItem extends React.Component {
} }
}; };
handleMouseDown = (event) => {
if (this.props.ripple && !this.props.disabled) {
this.refs.ripple.start(event);
}
};
render () { render () {
let className = style.root; const {icon, caption, children, shortcut, selected, disabled, ...others} = this.props;
if (this.props.selected) className += ` ${style.selected}`; const className = ClassNames(style.root, {
if (this.props.disabled) className += ` ${style.disabled}`; [style.selected]: selected,
if (this.props.className) className += ` ${this.props.className}`; [style.disabled]: disabled
}, this.props.className);
return ( return (
<li <li {...others} data-react-toolbox='menu-item' className={className} onClick={this.handleClick}>
data-react-toolbox='menu-item' {icon ? <FontIcon value={icon} className={style.icon}/> : null}
className={className} <span className={style.caption}>{caption}</span>
onClick={this.handleClick} {shortcut ? <small className={style.shortcut}>{shortcut}</small> : null}
onMouseDown={this.handleMouseDown} {children}
>
{this.props.icon ? <FontIcon value={this.props.icon} className={style.icon}/> : null}
<span className={style.caption}>{this.props.caption}</span>
{this.props.shortcut ? <small className={style.shortcut}>{this.props.shortcut}</small> : null}
{this.props.ripple ? <Ripple ref='ripple' className={style.ripple} spread={2.5} /> : null}
</li> </li>
); );
} }
} }
export default MenuItem; export default Ripple({
className: style.ripple
})(MenuItem);

View File

@ -38,5 +38,5 @@
} }
.ripple { .ripple {
opacity: .1; color: $color-text-secondary;
} }

View File

@ -0,0 +1,14 @@
import React from 'react';
import Ripple from '../ripple';
import style from './style';
const Radio = ({checked, children, onMouseDown}) => {
const className = style[checked ? 'radio-checked' : 'radio'];
return <div data-role='radio' onMouseDown={onMouseDown} className={className}>{children}</div>;
};
export default Ripple({
className: style.ripple,
spread: 2.6,
centered: true
})(Radio);

View File

@ -1,8 +1,7 @@
import React from 'react'; import React from 'react';
import ClassNames from 'classnames'; import ClassNames from 'classnames';
import Ripple from '../ripple'; import Radio from './Radio';
import style from './style'; import style from './style';
import events from '../utils/events';
class RadioButton extends React.Component { class RadioButton extends React.Component {
static propTypes = { static propTypes = {
@ -23,23 +22,10 @@ class RadioButton extends React.Component {
disabled: false disabled: false
}; };
handleChange = (event) => {
if (!this.props.checked && this.props.onChange) {
this.props.onChange(event, this);
}
};
handleClick = (event) => { handleClick = (event) => {
events.pauseEvent(event); const {checked, disabled, onChange} = this.props;
if (!this.props.disabled) this.handleChange(event); if (event.pageX !== 0 && event.pageY !== 0) this.blur();
}; if (!disabled && !checked && onChange) onChange(event, this);
handleInputClick = (event) => {
events.pauseEvent(event);
};
handleMouseDown = (event) => {
if (!this.props.disabled) this.refs.ripple.start(event);
}; };
blur () { blur () {
@ -51,22 +37,20 @@ class RadioButton extends React.Component {
} }
render () { render () {
const labelClassName = ClassNames(style[this.props.disabled ? 'disabled' : 'field'], this.props.className); const className = ClassNames(style[this.props.disabled ? 'disabled' : 'field'], this.props.className);
const radioClassName = style[this.props.checked ? 'radio-checked' : 'radio']; const { onChange, ...others } = this.props;
return ( return (
<label className={labelClassName} onClick={this.handleClick}> <label className={className}>
<input <input
{...this.props} {...others}
ref='input'
className={style.input} className={style.input}
onChange={this.handleChange} onClick={this.handleClick}
onClick={this.handleInputClick} readOnly
ref='input'
type='radio' type='radio'
/> />
<span role='radio' className={radioClassName} onMouseDown={this.handleMouseDown}> <Radio checked={this.props.checked} disabled={this.props.disabled}/>
<Ripple ref='ripple' role='ripple' className={style.ripple} spread={3} centered />
</span>
{this.props.label ? <span className={style.text}>{this.props.label}</span> : null} {this.props.label ? <span className={style.text}>{this.props.label}</span> : null}
</label> </label>
); );

View File

@ -28,15 +28,12 @@
margin: 0; margin: 0;
border: 0; border: 0;
opacity: 0; opacity: 0;
appearance: none; appearance: none;
&:focus:not(&:active) { &:focus ~ .radio {
+ .radio { box-shadow: 0 0 0 $unit $radio-focus-color;
box-shadow: 0 0 0 $unit $radio-focus-color; }
} &:focus ~ .radio-checked {
+ .radio-checked { box-shadow: 0 0 0 $unit $radio-checked-focus-color;
box-shadow: 0 0 0 $unit $radio-checked-focus-color;
}
} }
} }

View File

@ -1,84 +1,122 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import ClassNames from 'classnames'; import ClassNames from 'classnames';
import prefixer from '../utils/prefixer';
import style from './style'; import style from './style';
import prefixer from '../utils/prefixer';
class Ripple extends React.Component { const defaults = {
static propTypes = { centered: false,
centered: React.PropTypes.bool, className: '',
className: React.PropTypes.string, spread: 2
loading: React.PropTypes.bool, };
spread: React.PropTypes.number
};
static defaultProps = { const Ripple = (options = {}) => {
centered: false, const {
className: '', centered: defaultCentered,
loading: false, className: defaultClassName,
spread: 2 spread: defaultSpread
}; } = {...defaults, ...options};
state = { return ComposedComponent => {
active: false, return class RippledComponent extends React.Component {
left: null, static propTypes = {
restarting: false, children: React.PropTypes.any,
top: null, disabled: React.PropTypes.bool,
width: null ripple: React.PropTypes.bool,
}; rippleCentered: React.PropTypes.bool,
rippleClassName: React.PropTypes.string,
rippleSpread: React.PropTypes.number
};
handleEnd = () => { static defaultProps = {
document.removeEventListener(this.touch ? 'touchend' : 'mouseup', this.handleEnd); disabled: false,
this.setState({active: false}); ripple: true,
}; rippleCentered: defaultCentered,
rippleClassName: defaultClassName,
rippleSpread: defaultSpread
};
start = ({pageX, pageY}, touch = false) => { state = {
if (!this._isTouchRippleReceivingMouseEvent(touch)) { active: false,
this.touch = touch; left: null,
document.addEventListener(this.touch ? 'touchend' : 'mouseup', this.handleEnd); restarting: false,
const {top, left, width} = this._getDescriptor(pageX, pageY); top: null,
this.setState({active: false, restarting: true, top, left, width}, () => { width: null
this.refs.ripple.offsetWidth; //eslint-disable-line no-unused-expressions };
this.setState({active: true, restarting: false});
});
}
};
_isTouchRippleReceivingMouseEvent (touch) { handleEnd = () => {
return this.touch && !touch; document.removeEventListener(this.touch ? 'touchend' : 'mouseup', this.handleEnd);
} this.setState({active: false});
};
_getDescriptor (pageX, pageY) { start = ({pageX, pageY}, touch = false) => {
const {left, top, height, width} = ReactDOM.findDOMNode(this).getBoundingClientRect(); if (!this._isTouchRippleReceivingMouseEvent(touch)) {
return { this.touch = touch;
left: this.props.centered ? 0 : pageX - left - width / 2 - window.scrollX, document.addEventListener(this.touch ? 'touchend' : 'mouseup', this.handleEnd);
top: this.props.centered ? 0 : pageY - top - height / 2 - window.scrollY, const {top, left, width} = this._getDescriptor(pageX, pageY);
width: width * this.props.spread this.setState({active: false, restarting: true, top, left, width}, () => {
this.refs.ripple.offsetWidth; //eslint-disable-line no-unused-expressions
this.setState({active: true, restarting: false});
});
}
};
_isTouchRippleReceivingMouseEvent (touch) {
return this.touch && !touch;
}
_getDescriptor (pageX, pageY) {
const {left, top, height, width} = ReactDOM.findDOMNode(this).getBoundingClientRect();
const {rippleCentered: centered, rippleSpread: spread} = this.props;
return {
left: centered ? 0 : pageX - left - width / 2 - window.scrollX,
top: centered ? 0 : pageY - top - height / 2 - window.scrollY,
width: width * spread
};
}
handleMouseDown = (event) => {
if (!this.props.disabled) this.start(event);
if (this.props.onMouseDown) this.props.onMouseDown(event);
};
render () {
if (!this.props.ripple) {
return <ComposedComponent {...this.props} />;
} else {
const {
children,
ripple,
rippleClassName: className,
rippleCentered: centered,
rippleSpread: spread,
...other
} = this.props;
const rippleClassName = ClassNames(style.normal, {
[style.active]: this.state.active,
[style.restarting]: this.state.restarting
}, className);
const { left, top, width } = this.state;
const scale = this.state.restarting ? 0 : 1;
const rippleStyle = prefixer({
transform: `translate3d(${-width / 2 + left}px, ${-width / 2 + top}px, 0) scale(${scale})`
}, {width, height: width});
return (
<ComposedComponent {...other} onMouseDown={this.handleMouseDown}>
{children ? children : null}
<span data-react-toolbox='ripple' className={style.wrapper}>
<span ref='ripple' role='ripple' className={rippleClassName} style={rippleStyle} />
</span>
</ComposedComponent>
);
}
}
}; };
} };
};
render () {
const { left, top, width } = this.state;
const scale = this.state.restarting ? 0 : 1;
let rippleStyle = {width, height: width};
if (!this.props.loading) {
rippleStyle = prefixer({
transform: `translate3d(${-width / 2 + left}px, ${-width / 2 + top}px, 0) scale(${scale})`
}, rippleStyle);
}
const className = ClassNames(style[this.props.loading ? 'loading' : 'normal'], {
[style.active]: this.state.active,
[style.restarting]: this.state.restarting
}, this.props.className);
return (
<span data-react-toolbox='ripple' className={style.wrapper}>
<span ref='ripple' role='ripple' className={className} style={rippleStyle} />
</span>
);
}
}
export default Ripple; export default Ripple;

View File

@ -1,3 +1,3 @@
$ripple-duration: 1.2s; $ripple-duration: 800ms !default;
$ripple-final-opacity: .3; $ripple-final-opacity: .3 !default;
$ripple-size: 15 * $unit; $ripple-size: 15 * $unit !default;

View File

@ -38,15 +38,3 @@
transition-property: opacity, transform; transition-property: opacity, transform;
} }
} }
.loading {
@extend %ripple;
@include ripple-loading(ripple, $ripple-size, $ripple-size);
width: $ripple-size;
height: $ripple-size;
opacity: $ripple-final-opacity;
animation-name: ripple;
animation-duration: $ripple-duration;
animation-timing-function: $animation-curve-linear-out-slow-in;
animation-iteration-count: infinite;
}

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import Ripple from '../ripple'; import Thumb from './Thumb';
import style from './style'; import style from './style';
import events from '../utils/events';
class Switch extends React.Component { class Switch extends React.Component {
static propTypes = { static propTypes = {
@ -21,22 +20,13 @@ class Switch extends React.Component {
disabled: false disabled: false
}; };
handleChange = (event) => { handleToggle = (event) => {
events.pauseEvent(event); if (event.pageX !== 0 && event.pageY !== 0) this.blur();
if (this.props.onChange && !this.props.disabled) { if (!this.props.disabled && this.props.onChange) {
const value = !this.refs.input.checked; this.props.onChange(!this.props.checked, event);
this.props.onChange(value, event);
} }
}; };
handleInputClick = (event) => {
events.pauseEvent(event);
};
handleMouseDown = (event) => {
if (!this.props.disabled) this.refs.ripple.start(event);
};
blur () { blur () {
this.refs.input.blur(); this.refs.input.blur();
} }
@ -46,29 +36,24 @@ class Switch extends React.Component {
} }
render () { render () {
let labelClassName = style[this.props.disabled ? 'disabled' : 'field']; let className = style[this.props.disabled ? 'disabled' : 'field'];
const switchClassName = style[this.props.checked ? 'on' : 'off']; const switchClassName = style[this.props.checked ? 'on' : 'off'];
if (this.props.className) labelClassName += ` ${this.props.className}`; const { onChange, ...others } = this.props;
if (this.props.className) className += ` ${this.props.className}`;
return ( return (
<label <label data-react-toolbox='checkbox' className={className}>
data-react-toolbox='checkbox'
className={labelClassName}
onClick={this.handleChange}
>
<input <input
{...this.props} {...others}
ref='input'
checked={this.props.checked} checked={this.props.checked}
className={style.input} className={style.input}
onChange={this.handleChange} onClick={this.handleToggle}
onClick={this.handleInputClick} readOnly
ref='input'
type='checkbox' type='checkbox'
/> />
<span role='switch' className={switchClassName}> <span role='switch' className={switchClassName}>
<span role='thumb' className={style.thumb} onMouseDown={this.handleMouseDown}> <Thumb disabled={this.props.disabled} />
<Ripple ref='ripple' role='ripple' className={style.ripple} spread={2.4} centered />
</span>
</span> </span>
{this.props.label ? <span className={style.text}>{this.props.label}</span> : null} {this.props.label ? <span className={style.text}>{this.props.label}</span> : null}
</label> </label>

View File

@ -0,0 +1,13 @@
import React from 'react';
import Ripple from '../ripple';
import style from './style';
const Check = ({children, onMouseDown}) => (
<span role='thumb' className={style.thumb} onMouseDown={onMouseDown}>{children}</span>
);
export default Ripple({
className: style.ripple,
spread: 2.6,
centered: true
})(Check);

View File

@ -109,7 +109,7 @@
.text { .text {
color: $switch-disabled-text-color; color: $switch-disabled-text-color;
} }
.switch-on, .switch-off { .on, .off {
cursor: auto; cursor: auto;
background: $switch-disabled-track-color; background: $switch-disabled-track-color;
} }