d2-ui
Version:
350 lines (297 loc) • 9.69 kB
JSX
import React from 'react';
import ReactDOM from 'react-dom';
import StylePropable from './mixins/style-propable';
import autoPrefix from './styles/auto-prefix';
import Transitions from './styles/transitions';
import Paper from './paper';
import getMuiTheme from './styles/getMuiTheme';
const VIEWBOX_SIZE = 32;
const RefreshIndicator = React.createClass({
propTypes: {
/**
* Override the theme's color of the indicator while it's status is
* "ready" and it's percentage is less than 100.
*/
color: React.PropTypes.string,
/**
* The absolute left position of the indicator in pixels.
*/
left: React.PropTypes.number.isRequired,
/**
* Override the theme's color of the indicator while
* it's status is "loading" or when it's percentage is 100.
*/
loadingColor: React.PropTypes.string,
/**
* The confirmation progress to fetch data. Max value is 100.
*/
percentage: React.PropTypes.number,
/**
* Size in pixels.
*/
size: React.PropTypes.number,
/**
* The display status of the indicator. If the status is
* "ready", the indicator will display the ready state
* arrow. If the status is "loading", it will display
* the loading progress indicator. If the status is "hide",
* the indicator will be hidden.
*/
status: React.PropTypes.oneOf(['ready', 'loading', 'hide']),
/**
* Override the inline-styles of the root element.
*/
style: React.PropTypes.object,
/**
* The absolute top position of the indicator in pixels.
*/
top: React.PropTypes.number.isRequired,
},
contextTypes: {
muiTheme: React.PropTypes.object,
},
//for passing default theme context to children
childContextTypes: {
muiTheme: React.PropTypes.object,
},
mixins: [
StylePropable,
],
getDefaultProps() {
return {
percentage: 0,
size: 40,
status: 'hide',
};
},
getInitialState() {
return {
muiTheme: this.context.muiTheme || getMuiTheme(),
};
},
getChildContext() {
return {
muiTheme: this.state.muiTheme,
};
},
componentDidMount() {
this.componentDidUpdate();
},
//to update theme inside state whenever a new theme is passed down
//from the parent / owner using context
componentWillReceiveProps(nextProps, nextContext) {
let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme;
this.setState({muiTheme: newMuiTheme});
},
componentDidUpdate() {
this._scalePath(ReactDOM.findDOMNode(this.refs.path), 0);
this._rotateWrapper(ReactDOM.findDOMNode(this.refs.wrapper));
},
componentWillUnmount() {
clearTimeout(this.scalePathTimer);
clearTimeout(this.rotateWrapperTimer);
clearTimeout(this.rotateWrapperSecondTimer);
},
scalePathTimer: undefined,
rotateWrapperTimer: undefined,
rotateWrapperSecondTimer: undefined,
_renderChildren() {
const paperSize = this._getPaperSize();
let childrenCmp = null;
if (this.props.status !== 'ready') {
const circleStyle = this._getCircleStyle(paperSize);
childrenCmp = (
<div ref="wrapper" style={this.prepareStyles({
transition: Transitions.create('transform', '20s', null, 'linear'),
width: '100%',
height: '100%',
})}
>
<svg style={{
width: paperSize,
height: paperSize,
}}
viewBox={`0 0 ${VIEWBOX_SIZE} ${VIEWBOX_SIZE}`}
>
<circle ref="path"
style={this.prepareStyles(circleStyle.style, {
transition: Transitions.create('all', '1.5s', null, 'ease-in-out'),
})}
{...circleStyle.attr}
/>
</svg>
</div>
);
} else {
const circleStyle = this._getCircleStyle(paperSize);
const polygonStyle = this._getPolygonStyle(paperSize);
childrenCmp = (
<svg style={{
width: paperSize,
height: paperSize,
}}
viewBox={`0 0 ${VIEWBOX_SIZE} ${VIEWBOX_SIZE}`}
>
<circle
style={this.prepareStyles(circleStyle.style)}
{...circleStyle.attr}
>
</circle>
<polygon
style={this.prepareStyles(polygonStyle.style)}
{...polygonStyle.attr}
/>
</svg>
);
}
return childrenCmp;
},
_getTheme() {
return this.state.muiTheme.refreshIndicator;
},
_getPaddingSize() {
const padding = this.props.size * 0.1;
return padding;
},
_getPaperSize() {
return this.props.size - this._getPaddingSize() * 2;
},
_getCircleAttr() {
return {
radiu: VIEWBOX_SIZE / 2 - 5,
originX: VIEWBOX_SIZE / 2,
originY: VIEWBOX_SIZE / 2,
strokeWidth: 3,
};
},
_getArcDeg() {
const p = this.props.percentage / 100;
const beginDeg = p * 120;
const endDeg = p * 410;
return [beginDeg, endDeg];
},
_getFactor() {
const p = this.props.percentage / 100;
const p1 = Math.min(1, p / 0.4);
return p1;
},
_getRootStyle() {
const padding = this._getPaddingSize();
return {
position: 'absolute',
zIndex: 2,
width: this.props.size,
height: this.props.size,
padding: padding,
top: -10000,
left: -10000,
transform: `translate3d(${10000 + this.props.left}px, ${10000 + this.props.top}px, 0)`,
opacity: this.props.status === 'hide' ? 0 : 1,
transition: this.props.status === 'hide' ? Transitions.create('all', '.3s', 'ease-out') : 'none',
};
},
_getCircleStyle() {
const isLoading = this.props.status === 'loading';
const p1 = isLoading ? 1 : this._getFactor();
const circle = this._getCircleAttr();
const perimeter = Math.PI * 2 * circle.radiu;
const [beginDeg, endDeg] = this._getArcDeg();
const arcLen = (endDeg - beginDeg) * perimeter / 360;
const dashOffset = -beginDeg * perimeter / 360;
const theme = this._getTheme();
return {
style: {
strokeDasharray: arcLen + ', ' + (perimeter - arcLen),
strokeDashoffset: dashOffset,
stroke: (isLoading || this.props.percentage === 100) ?
(this.props.loadingColor || theme.loadingStrokeColor) :
(this.props.color || theme.strokeColor),
strokeLinecap: 'round',
opacity: p1,
strokeWidth: circle.strokeWidth * p1,
fill: 'none',
},
attr: {
cx: circle.originX,
cy: circle.originY,
r: circle.radiu,
},
};
},
_getPolygonStyle() {
const p1 = this._getFactor();
const circle = this._getCircleAttr();
const triangleCx = circle.originX + circle.radiu;
const triangleCy = circle.originY;
const dx = (circle.strokeWidth * 7 / 4) * p1;
const trianglePath = (triangleCx - dx) + ',' + triangleCy + ' ' + (triangleCx + dx) + ',' +
triangleCy + ' ' + triangleCx + ',' + (triangleCy + dx);
const [, endDeg] = this._getArcDeg();
const theme = this._getTheme();
return {
style: {
fill: this.props.percentage === 100 ?
(this.props.loadingColor || theme.loadingStrokeColor) :
(this.props.color || theme.strokeColor),
transform: `rotate(${endDeg}deg)`,
transformOrigin: `${circle.originX}px ${circle.originY}px`,
opacity: p1,
},
attr: {
points: trianglePath,
},
};
},
_scalePath(path, step) {
if (this.props.status !== 'loading') return;
const currStep = (step || 0) % 3;
const circle = this._getCircleAttr();
const perimeter = Math.PI * 2 * circle.radiu;
const arcLen = perimeter * 0.64;
let strokeDasharray;
let strokeDashoffset;
let transitionDuration;
if (currStep === 0) {
strokeDasharray = '1, 200';
strokeDashoffset = 0;
transitionDuration = '0ms';
} else if (currStep === 1) {
strokeDasharray = arcLen + ', 200';
strokeDashoffset = -15;
transitionDuration = '750ms';
} else {
strokeDasharray = arcLen + ',200';
strokeDashoffset = -(perimeter - 1);
transitionDuration = '850ms';
}
autoPrefix.set(path.style, 'strokeDasharray', strokeDasharray, this.state.muiTheme);
autoPrefix.set(path.style, 'strokeDashoffset', strokeDashoffset, this.state.muiTheme);
autoPrefix.set(path.style, 'transitionDuration', transitionDuration, this.state.muiTheme);
this.scalePathTimer = setTimeout(() => this._scalePath(path, currStep + 1), currStep ? 750 : 250);
},
_rotateWrapper(wrapper) {
if (this.props.status !== 'loading') return;
autoPrefix.set(wrapper.style, 'transform', null, this.state.muiTheme);
autoPrefix.set(wrapper.style, 'transform', 'rotate(0deg)', this.state.muiTheme);
autoPrefix.set(wrapper.style, 'transitionDuration', '0ms', this.state.muiTheme);
this.rotateWrapperSecondTimer = setTimeout(() => {
autoPrefix.set(wrapper.style, 'transform', 'rotate(1800deg)', this.state.muiTheme);
autoPrefix.set(wrapper.style, 'transitionDuration', '10s', this.state.muiTheme);
autoPrefix.set(wrapper.style, 'transitionTimingFunction', 'linear', this.state.muiTheme);
}, 50);
this.rotateWrapperTimer = setTimeout(() => this._rotateWrapper(wrapper), 10050);
},
render() {
const rootStyle = this._getRootStyle();
return (
<Paper
circle={true}
style={this.mergeStyles(rootStyle, this.props.style)}
ref="indicatorCt"
>
{this._renderChildren()}
</Paper>
);
},
});
export default RefreshIndicator;