d2-ui
Version:
460 lines (399 loc) • 12.3 kB
JSX
import React from 'react';
import ReactDOM from 'react-dom';
import KeyCode from './utils/key-code';
import StylePropable from './mixins/style-propable';
import Transitions from './styles/transitions';
import UniqueId from './utils/unique-id';
import WindowListenable from './mixins/window-listenable';
import ClearFix from './clearfix';
import FocusRipple from './ripples/focus-ripple';
import TouchRipple from './ripples/touch-ripple';
import Paper from './paper';
import getMuiTheme from './styles/getMuiTheme';
import warning from 'warning';
const EnhancedSwitch = React.createClass({
propTypes: {
checked: React.PropTypes.bool,
/**
* The css class name of the root element.
*/
className: React.PropTypes.string,
defaultSwitched: React.PropTypes.bool,
disableFocusRipple: React.PropTypes.bool,
disableTouchRipple: React.PropTypes.bool,
disabled: React.PropTypes.bool,
iconStyle: React.PropTypes.object,
id: React.PropTypes.string,
inputType: React.PropTypes.string.isRequired,
label: React.PropTypes.node,
labelPosition: React.PropTypes.oneOf(['left', 'right']),
labelStyle: React.PropTypes.object,
name: React.PropTypes.string,
onBlur: React.PropTypes.func,
onFocus: React.PropTypes.func,
onMouseDown: React.PropTypes.func,
onMouseLeave: React.PropTypes.func,
onMouseUp: React.PropTypes.func,
onParentShouldUpdate: React.PropTypes.func.isRequired,
onSwitch: React.PropTypes.func,
onTouchEnd: React.PropTypes.func,
onTouchStart: React.PropTypes.func,
required: React.PropTypes.bool,
rippleColor: React.PropTypes.string,
rippleStyle: React.PropTypes.object,
/**
* Override the inline-styles of the root element.
*/
style: React.PropTypes.object,
switchElement: React.PropTypes.element.isRequired,
switched: React.PropTypes.bool.isRequired,
thumbStyle: React.PropTypes.object,
trackStyle: React.PropTypes.object,
value: React.PropTypes.string,
},
contextTypes: {
muiTheme: React.PropTypes.object,
},
//for passing default theme context to children
childContextTypes: {
muiTheme: React.PropTypes.object,
},
mixins: [
WindowListenable,
StylePropable,
],
getInitialState() {
return {
isKeyboardFocused: false,
parentWidth: 100,
muiTheme: this.context.muiTheme || getMuiTheme(),
};
},
getChildContext() {
return {
muiTheme: this.state.muiTheme,
};
},
componentDidMount() {
let inputNode = ReactDOM.findDOMNode(this.refs.checkbox);
if (!this.props.switched || inputNode.checked !== this.props.switched) {
this.props.onParentShouldUpdate(inputNode.checked);
}
window.addEventListener('resize', this._handleResize);
this._handleResize();
},
componentWillReceiveProps(nextProps, nextContext) {
let hasCheckedLinkProp = nextProps.hasOwnProperty('checkedLink');
let hasCheckedProp = nextProps.hasOwnProperty('checked');
let hasToggledProp = nextProps.hasOwnProperty('toggled');
let hasNewDefaultProp =
(nextProps.hasOwnProperty('defaultSwitched') &&
(nextProps.defaultSwitched !== this.props.defaultSwitched));
let newState = {};
newState.muiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme;
if (hasCheckedProp) {
newState.switched = nextProps.checked;
} else if (hasToggledProp) {
newState.switched = nextProps.toggled;
} else if (hasCheckedLinkProp) {
newState.switched = nextProps.checkedLink.value;
} else if (hasNewDefaultProp) {
newState.switched = nextProps.defaultSwitched;
}
if (newState.switched !== undefined && (newState.switched !== this.props.switched)) {
this.props.onParentShouldUpdate(newState.switched);
}
this.setState(newState);
},
componentWillUnmount() {
window.removeEventListener('resize', this._handleResize);
},
windowListeners: {
keydown: '_handleWindowKeydown',
keyup: '_handleWindowKeyup',
},
getEvenWidth() {
return (
parseInt(window
.getComputedStyle(ReactDOM.findDOMNode(this.refs.root))
.getPropertyValue('width'), 10)
);
},
getTheme() {
return this.state.muiTheme.rawTheme.palette;
},
getStyles() {
let spacing = this.state.muiTheme.rawTheme.spacing;
let switchWidth = 60 - spacing.desktopGutterLess;
let labelWidth = 'calc(100% - 60px)';
let styles = {
root: {
position: 'relative',
cursor: this.props.disabled ? 'default' : 'pointer',
overflow: 'visible',
display: 'table',
height: 'auto',
width: '100%',
},
input: {
position: 'absolute',
cursor: this.props.disabled ? 'default' : 'pointer',
pointerEvents: 'all',
opacity: 0,
width: '100%',
height: '100%',
zIndex: 2,
left: 0,
boxSizing: 'border-box',
padding: 0,
margin: 0,
},
controls: {
width: '100%',
height: '100%',
},
label: {
float: 'left',
position: 'relative',
display: 'block',
width: labelWidth,
lineHeight: '24px',
color: this.getTheme().textColor,
fontFamily: this.state.muiTheme.rawTheme.fontFamily,
},
wrap: {
transition: Transitions.easeOut(),
float: 'left',
position: 'relative',
display: 'block',
width: switchWidth,
marginRight: (this.props.labelPosition === 'right') ?
spacing.desktopGutterLess : 0,
marginLeft: (this.props.labelPosition === 'left') ?
spacing.desktopGutterLess : 0,
},
ripple: {
height: '200%',
width: '200%',
top: -12,
left: -12,
},
};
return styles;
},
isSwitched() {
return ReactDOM.findDOMNode(this.refs.checkbox).checked;
},
// no callback here because there is no event
setSwitched(newSwitchedValue) {
if (!this.props.hasOwnProperty('checked') || this.props.checked === false) {
this.props.onParentShouldUpdate(newSwitchedValue);
ReactDOM.findDOMNode(this.refs.checkbox).checked = newSwitchedValue;
} else {
warning(false, 'Cannot call set method while checked is defined as a property.');
}
},
getValue() {
return ReactDOM.findDOMNode(this.refs.checkbox).value;
},
isKeyboardFocused() {
return this.state.isKeyboardFocused;
},
_handleChange(e) {
this._tabPressed = false;
this.setState({
isKeyboardFocused: false,
});
let isInputChecked = ReactDOM.findDOMNode(this.refs.checkbox).checked;
if (!this.props.hasOwnProperty('checked')) {
this.props.onParentShouldUpdate(isInputChecked);
}
if (this.props.onSwitch) {
this.props.onSwitch(e, isInputChecked);
}
},
// Checkbox inputs only use SPACE to change their state. Using ENTER will
// update the ui but not the input.
_handleWindowKeydown(e) {
if (e.keyCode === KeyCode.TAB) {
this._tabPressed = true;
}
if (e.keyCode === KeyCode.SPACE && this.state.isKeyboardFocused) {
this._handleChange(e);
}
},
_handleWindowKeyup(e) {
if (e.keyCode === KeyCode.SPACE && this.state.isKeyboardFocused) {
this._handleChange(e);
}
},
/**
* Because both the ripples and the checkbox input cannot share pointer
* events, the checkbox input takes control of pointer events and calls
* ripple animations manually.
*/
_handleMouseDown(e) {
//only listen to left clicks
if (e.button === 0) {
this.refs.touchRipple.start(e);
}
},
_handleMouseUp() {
this.refs.touchRipple.end();
},
_handleMouseLeave() {
this.refs.touchRipple.end();
},
_handleTouchStart(e) {
this.refs.touchRipple.start(e);
},
_handleTouchEnd() {
this.refs.touchRipple.end();
},
_handleBlur(e) {
this.setState({
isKeyboardFocused: false,
});
if (this.props.onBlur) {
this.props.onBlur(e);
}
},
_handleFocus(e) {
//setTimeout is needed becuase the focus event fires first
//Wait so that we can capture if this was a keyboard focus
//or touch focus
setTimeout(() => {
if (this._tabPressed) {
this.setState({
isKeyboardFocused: true,
});
}
}, 150);
if (this.props.onFocus) {
this.props.onFocus(e);
}
},
_handleResize() {
this.setState({parentWidth: this.getEvenWidth()});
},
render() {
let {
name,
value,
label,
onSwitch,
defaultSwitched,
onBlur,
onFocus,
onMouseUp,
onMouseDown,
onMouseLeave,
onTouchStart,
onTouchEnd,
disableTouchRipple,
disableFocusRipple,
className,
...other,
} = this.props;
let styles = this.getStyles();
let wrapStyles = this.mergeStyles(styles.wrap, this.props.iconStyle);
let rippleStyle = this.mergeStyles(styles.ripple, this.props.rippleStyle);
let rippleColor = this.props.hasOwnProperty('rippleColor') ? this.props.rippleColor :
this.getTheme().primary1Color;
if (this.props.thumbStyle) {
wrapStyles.marginLeft /= 2;
wrapStyles.marginRight /= 2;
}
let inputId = this.props.id || UniqueId.generate();
let labelStyle = this.mergeStyles(styles.label, this.props.labelStyle);
let labelElement = this.props.label ? (
<label style={this.prepareStyles(labelStyle)} htmlFor={inputId}>
{this.props.label}
</label>
) : null;
const inputProps = {
ref: 'checkbox',
type: this.props.inputType,
style: this.prepareStyles(styles.input),
name: this.props.name,
value: this.props.value,
defaultChecked: this.props.defaultSwitched,
onBlur: this._handleBlur,
onFocus: this._handleFocus,
};
let hideTouchRipple = this.props.disabled || disableTouchRipple;
if (!hideTouchRipple) {
inputProps.onMouseUp = this._handleMouseUp;
inputProps.onMouseDown = this._handleMouseDown;
inputProps.onMouseLeave = this._handleMouseLeave;
inputProps.onTouchStart = this._handleTouchStart;
inputProps.onTouchEnd = this._handleTouchEnd;
}
if (!this.props.hasOwnProperty('checkedLink')) {
inputProps.onChange = this._handleChange;
}
let inputElement = (
<input
{...other}
{...inputProps}
/>
);
let touchRipple = (
<TouchRipple
ref="touchRipple"
key="touchRipple"
style={rippleStyle}
color={rippleColor}
muiTheme={this.state.muiTheme}
centerRipple={true}
/>
);
let focusRipple = (
<FocusRipple
key="focusRipple"
innerStyle={rippleStyle}
color={rippleColor}
muiTheme={this.state.muiTheme}
show={this.state.isKeyboardFocused}
/>
);
let ripples = [
hideTouchRipple ? null : touchRipple,
this.props.disabled || disableFocusRipple ? null : focusRipple,
];
// If toggle component (indicated by whether the style includes thumb) manually lay out
// elements in order to nest ripple elements
let switchElement = !this.props.thumbStyle ? (
<div style={this.prepareStyles(wrapStyles)}>
{this.props.switchElement}
{ripples}
</div>
) : (
<div style={this.prepareStyles(wrapStyles)}>
<div style={this.prepareStyles(this.props.trackStyle)}/>
<Paper style={this.props.thumbStyle} zDepth={1} circle={true}> {ripples} </Paper>
</div>
);
let labelPositionExist = this.props.labelPosition;
// Position is left if not defined or invalid.
let elementsInOrder = (labelPositionExist &&
(this.props.labelPosition.toUpperCase() === 'RIGHT')) ? (
<ClearFix style={styles.controls}>
{switchElement}
{labelElement}
</ClearFix>
) : (
<ClearFix style={styles.controls}>
{labelElement}
{switchElement}
</ClearFix>
);
return (
<div ref="root" className={className} style={this.prepareStyles(styles.root, this.props.style)}>
{inputElement}
{elementsInOrder}
</div>
);
},
});
export default EnhancedSwitch;