UNPKG

@ysmood/material-ui

Version:

Material Design UI components built with React

374 lines (335 loc) 11.7 kB
var React = require('react'); var StylePropable = require('./mixins/style-propable'); var Draggable = require('react-draggable2'); var Transitions = require('./styles/transitions.js'); var FocusRipple = require('./ripples/focus-ripple'); var Slider = React.createClass({ mixins: [StylePropable], contextTypes: { muiTheme: React.PropTypes.object }, propTypes: { required: React.PropTypes.bool, disabled: React.PropTypes.bool, min: React.PropTypes.number, max: React.PropTypes.number, step: React.PropTypes.number, error: React.PropTypes.string, description: React.PropTypes.string, name: React.PropTypes.string.isRequired, onChange: React.PropTypes.func, onFocus: React.PropTypes.func, onBlur: React.PropTypes.func, onDragStart: React.PropTypes.func, onDragStop: React.PropTypes.func }, getDefaultProps: function() { return { required: true, disabled: false, defaultValue: 0, min: 0, max: 1, dragging: false }; }, getInitialState: function() { var value = this.props.value; if (value == null) value = this.props.defaultValue; var percent = (value - this.props.min) / (this.props.max - this.props.min); if (isNaN(percent)) percent = 0; return { value: value, percent: percent, focused: false, active: false, hovered: false } }, componentWillReceiveProps: function(nextProps) { if (nextProps.value != null) { this.setValue(nextProps.value); } }, getTheme: function() { return this.context.muiTheme.component.slider; }, getStyles: function() { var size = this.getTheme().handleSize + this.getTheme().trackSize; var gutter = (this.getTheme().handleSizeDisabled + this.getTheme().trackSize) / 2; var fillGutter = this.getTheme().handleSizeDisabled - this.getTheme().trackSize; var styles = { root: { touchCallout: 'none', userSelect: 'none', cursor: 'default', height: this.getTheme().handleSizeActive, position: 'relative', marginTop: 24, marginBottom: 48 }, track: { position: 'absolute', top: (this.getTheme().handleSizeActive - this.getTheme(). trackSize) / 2, left: 0, width: '100%', height: this.getTheme().trackSize }, filledAndRemaining: { position: 'absolute', top: 0, height: '100%', transition: Transitions.easeOut(null, 'margin'), }, percentZeroRemaining: { left: 1, marginLeft: gutter }, handle: { boxSizing: 'border-box', position: 'absolute', cursor: 'pointer', pointerEvents: 'inherit', top: ((this.getTheme().handleSizeActive - this.getTheme().trackSize) / 2) + 'px', left: '0%', zIndex: 1, margin: (this.getTheme().trackSize / 2) + 'px 0 0 0', width: this.getTheme().handleSize, height: this.getTheme().handleSize, backgroundColor: this.getTheme().selectionColor, backgroundClip: 'padding-box', border: '0px solid transparent', borderRadius: '50%', transform: 'translate(-50%, -50%)', transition: Transitions.easeOut('450ms', 'border') + ',' + Transitions.easeOut('450ms', 'width') + ',' + Transitions.easeOut('450ms', 'height'), overflow: 'visible' }, handleWhenDisabled: { boxSizing: 'content-box', cursor: 'not-allowed', backgroundColor: this.getTheme().trackColor, width: this.getTheme().handleSizeDisabled, height: this.getTheme().handleSizeDisabled, border: '2px solid white' }, handleWhenPercentZero: { border: this.getTheme().trackSize + 'px solid ' + this.getTheme().trackColor, backgroundColor: this.getTheme().handleFillColor, boxShadow: 'none' }, handleWhenActive: { borderColor: this.getTheme().trackColorSelected, width: this.getTheme().handleSizeActive, height: this.getTheme().handleSizeActive, transition: Transitions.easeOut('450ms', 'backgroundColor') + ',' + Transitions.easeOut('450ms', 'width') + ',' + Transitions.easeOut('450ms', 'height') }, ripples: { height: '300%', width: '300%', top: '-12px', left: '-12px' }, handleWhenDisabledAndZero: { width: (size / 2) + 'px', height: (size /2) + 'px' }, handleWhenPercentZeroAndHovered: { border: this.getTheme().trackSize + 'px solid ' + this.getTheme().handleColorZero, width: size + 'px', height: size + 'px' }, }; styles.filled = this.mergeAndPrefix(styles.filledAndRemaining, { left: 0, backgroundColor: (this.props.disabled) ? this.getTheme().trackColor : this.getTheme().selectionColor, marginRight: fillGutter, width: (this.state.percent * 100) + (this.props.disabled ? -1 : 0) + '%' }); styles.remaining = this.mergeAndPrefix(styles.filledAndRemaining, { right: 0, backgroundColor: this.getTheme().trackColor, marginLeft: fillGutter, width: ((1 - this.state.percent) * 100) + (this.props.disabled ? -1 : 0) + '%' }); styles.percentZeroRemaining.width = styles.remaining.width - styles.percentZeroRemaining.left; return styles; }, render: function() { var percent = this.state.percent; if (percent > 1) percent = 1; else if (percent < 0) percent = 0; var gutter = (this.getTheme().handleSizeDisabled + this.getTheme().trackSize) / 2; var fillGutter = this.getTheme().handleSizeDisabled - this.getTheme().trackSize; var styles = this.getStyles(); var sliderStyles = this.mergeAndPrefix(styles.root, this.props.style); var trackStyles = styles.track; var filledStyles = styles.filled; var remainingStyles = this.mergeAndPrefix( styles.remaining, percent === 0 && styles.percentZeroRemaining ); var handleStyles = percent === 0 ? this.mergeAndPrefix( styles.handle, styles.handleWhenPercentZero, this.state.active && styles.handleWhenActive, this.state.focused && {outline: 'none'}, this.state.hovered && styles.handleWhenPercentZeroAndHovered, this.props.disabled && styles.handleWhenDisabledAndZero ) : this.mergeAndPrefix( styles.handle, this.state.active && styles.handleWhenActive, this.state.focused && {outline: 'none'}, this.props.disabled && styles.handleWhenDisabled ); var rippleStyle = {height: '12px', width: '12px'}; if ((this.state.hovered || this.state.focused) && !this.props.disabled) { remainingStyles.backgroundColor = this.getTheme().trackColorSelected; } if (percent === 0) filledStyles.marginRight = gutter; if (this.state.percent === 0 && this.state.active) remainingStyles.marginLeft = fillGutter; var rippleShowCondition = (this.state.hovered || this.state.focused) && !this.state.active && this.state.percent !== 0; var rippleColor = this.state.percent === 0 ? this.getTheme().handleColorZero : this.getTheme().rippleColor; var focusRipple; if (!this.props.disabled && !this.props.disableFocusRipple) { focusRipple = ( <FocusRipple ref="focusRipple" key="focusRipple" style={rippleStyle} innerStyle={styles.ripples} show={rippleShowCondition} color={rippleColor}/> ); } return ( <div style={this.props.style}> <span className="mui-input-highlight"></span> <span className="mui-input-bar"></span> <span className="mui-input-description">{this.props.description}</span> <span className="mui-input-error">{this.props.error}</span> <div style={sliderStyles} onTouchTap={this._onClick} onFocus={this._onFocus} onBlur={this._onBlur} onMouseOver={this._onMouseOver} onMouseOut={this._onMouseOut} onMouseUp={this._onMouseUp} > <div ref="track" style={trackStyles}> <div style={filledStyles}></div> <div style={remainingStyles}></div> <Draggable axis="x" bound="point" cancel={this.props.disabled ? '*' : null} start={{x: (percent * 100) + '%'}} onStart={this._onDragStart} onStop={this._onDragStop} onDrag={this._onDragUpdate} onMouseDown={this._onMouseDown}> <div style={handleStyles} tabIndex={0}> {focusRipple} </div> </Draggable> </div> </div> <input ref="input" type="hidden" name={this.props.name} value={this.state.value} required={this.props.required} min={this.props.min} max={this.props.max} step={this.props.step} /> </div> ); }, getValue: function() { return this.state.value; }, setValue: function(i) { // calculate percentage var percent = (i - this.props.min) / (this.props.max - this.props.min); if (isNaN(percent)) percent = 0; // update state this.setState({ value: i, percent: percent }); }, getPercent: function() { return this.state.percent; }, setPercent: function (percent) { var value = this._percentToValue(percent); this.setState({value: value, percent: percent}); }, clearValue: function() { this.setValue(0); }, _onClick: function (e) { this._tabPressed = false; // let draggable handle the slider if (this.state.dragging || this.props.disabled) return; var node = React.findDOMNode(this.refs.track); var boundingClientRect = node.getBoundingClientRect(); var offset = e.clientX - boundingClientRect.left; this._updateWithChangeEvent(e, offset / node.clientWidth); }, _onFocus: function (e) { this.setState({focused: true}); if (this.props.onFocus) this.props.onFocus(e); }, _onBlur: function (e) { this.setState({focused: false, active: false}); if (this.props.onBlur) this.props.onBlur(e); }, _onMouseOver: function () { this.setState({hovered: true}); }, _onMouseOut: function () { this.setState({hovered: false}); }, _onMouseUp: function () { if (!this.props.disabled) this.setState({active: false}); }, _onMouseDown: function () { if (!this.props.disabled) this.setState({active: true}); }, _onDragStart: function(e, ui) { this.setState({ dragging: true, active: true }); if (this.props.onDragStart) this.props.onDragStart(e, ui); }, _onDragStop: function(e, ui) { this.setState({ dragging: false, active: false }); if (this.props.onDragStop) this.props.onDragStop(e, ui); }, _onDragUpdate: function(e, ui) { if (!this.state.dragging) return; if (!this.props.disabled) this._dragX(e, ui.position.left); }, _dragX: function(e, pos) { var max = React.findDOMNode(this.refs.track).clientWidth; if (pos < 0) pos = 0; else if (pos > max) pos = max; this._updateWithChangeEvent(e, pos / max); }, _updateWithChangeEvent: function(e, percent) { if (this.state.percent === percent) return; this.setPercent(percent); var value = this._percentToValue(percent); if (this.props.onChange) this.props.onChange(e, value); }, _percentToValue: function(percent) { return percent * (this.props.max - this.props.min) + this.props.min; } }); module.exports = Slider;