@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
317 lines • 10.1 kB
JavaScript
var __rest = this && this.__rest || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
};
import React, { Component } from 'react';
import cls from 'classnames';
import PropTypes from 'prop-types';
import { cssClasses, strings } from '@douyinfe/semi-foundation/lib/es/progress/constants';
import getDataAttr from '@douyinfe/semi-foundation/lib/es/utils/getDataAttr';
import '@douyinfe/semi-foundation/lib/es/progress/progress.css';
import { Animation } from '@douyinfe/semi-animation';
import { generateColor } from '@douyinfe/semi-foundation/lib/es/progress/generates';
const prefixCls = cssClasses.PREFIX;
class Progress extends Component {
constructor(props) {
super(props);
this._mounted = true;
this._mounted = true;
this.state = {
percentNumber: this.props.percent // Specially used for animation of numbers
};
}
componentDidUpdate(prevProps) {
if (isNaN(this.props.percent) || isNaN(prevProps.percent)) {
throw new Error('[Semi Progress]:percent can not be NaN');
return;
}
if (prevProps.percent !== this.props.percent) {
if (!this.props.motion) {
this.setState({
percentNumber: this.props.percent
});
return;
}
if (this.animation && this.animation.destroy) {
this.animation.destroy();
}
this.animation = new Animation({
from: {
value: prevProps.percent
},
to: {
value: this.props.percent
}
}, {
// easing: 'cubic-bezier(0, .68, .3, 1)'
easing: 'linear',
duration: 300
});
this.animation.on('frame', props => {
// prevent setState while component is unmounted but this timer is called
if (this._mounted === false) {
return;
}
// let percentNumber = Number.isInteger(props.value) ? props.value : Math.floor(props.value * 100) / 100;
const percentNumber = parseInt(props.value);
this.setState({
percentNumber
});
});
this.animation.on('rest', () => {
// prevent setState while component is unmounted but this timer is called
if (this._mounted === false) {
return;
}
this.setState({
percentNumber: this.props.percent
});
});
this.animation.start();
}
}
componentWillUnmount() {
this.animation && this.animation.destroy();
this._mounted = false;
}
renderCircleProgress() {
const _a = this.props,
{
strokeLinecap,
style,
className,
strokeWidth,
format,
size,
stroke,
strokeGradient,
showInfo,
percent,
orbitStroke,
id
} = _a,
rest = __rest(_a, ["strokeLinecap", "style", "className", "strokeWidth", "format", "size", "stroke", "strokeGradient", "showInfo", "percent", "orbitStroke", "id"]);
const ariaLabel = this.props['aria-label'];
const ariaLabelledBy = this.props['aria-labelledby'];
const ariaValueText = this.props['aria-valuetext'];
const {
percentNumber
} = this.state;
const classNames = {
wrapper: cls(`${prefixCls}-circle`, className),
svg: cls(`${prefixCls}-circle-ring`),
circle: cls(`${prefixCls}-circle-ring-inner`),
track: cls(`${prefixCls}-circle-ring-track`)
};
const perc = this.calcPercent(percent);
const percNumber = this.calcPercent(percentNumber);
let width;
if (this.props.width) {
width = this.props.width;
} else {
size === strings.DEFAULT_SIZE ? width = 72 : width = 24;
}
// parse stroke & generate gradients
const _stroke = this.selectStroke(stroke, percent, strokeGradient);
// cx, cy is circle center
const cy = width / 2;
const cx = width / 2;
const radius = (width - strokeWidth) / 2; // radius
const circumference = radius * 2 * Math.PI;
const strokeDashoffset = (1 - perc / 100) * circumference; // Offset
const strokeDasharray = `${circumference} ${circumference}`;
const text = format(percNumber);
return /*#__PURE__*/React.createElement("div", Object.assign({
id: id,
className: classNames.wrapper,
style: style,
role: "progressbar",
"aria-valuemin": 0,
"aria-valuemax": 100,
"aria-valuenow": percNumber,
"aria-labelledby": ariaLabelledBy,
"aria-label": ariaLabel,
"aria-valuetext": ariaValueText
}, getDataAttr(rest)), /*#__PURE__*/React.createElement("svg", {
key: size,
className: classNames.svg,
height: width,
width: width,
"aria-hidden": true
}, /*#__PURE__*/React.createElement("circle", {
className: classNames.track,
strokeDashoffset: 0,
strokeWidth: strokeWidth,
strokeDasharray: strokeDasharray,
strokeLinecap: strokeLinecap,
fill: "transparent",
style: {
stroke: orbitStroke
},
r: radius,
cx: cx,
cy: cy,
"aria-hidden": true
}), /*#__PURE__*/React.createElement("circle", {
className: classNames.circle,
strokeDashoffset: strokeDashoffset,
strokeWidth: strokeWidth,
strokeDasharray: strokeDasharray,
strokeLinecap: strokeLinecap,
fill: "transparent",
style: {
stroke: _stroke
},
r: radius,
cx: cx,
cy: cy,
"aria-hidden": true
})), showInfo && size !== 'small' ? /*#__PURE__*/React.createElement("span", {
className: `${prefixCls}-circle-text`
}, text) : null);
}
calcPercent(percent) {
let perc;
if (percent > 100) {
perc = 100;
} else if (percent < 0) {
perc = 0;
} else {
perc = percent;
}
return perc;
}
selectStroke(stroke, percent, strokeGradient) {
if (typeof stroke === 'string') {
return stroke;
}
const color = generateColor(stroke, percent, strokeGradient);
if (typeof color !== 'undefined') {
return color;
}
return null;
}
renderLineProgress() {
const _a = this.props,
{
className,
style,
stroke,
strokeGradient,
direction,
format,
showInfo,
size,
percent,
orbitStroke,
id
} = _a,
rest = __rest(_a, ["className", "style", "stroke", "strokeGradient", "direction", "format", "showInfo", "size", "percent", "orbitStroke", "id"]);
const ariaLabel = this.props['aria-label'];
const ariaLabelledBy = this.props['aria-labelledby'];
const ariaValueText = this.props['aria-valuetext'];
const {
percentNumber
} = this.state;
const progressWrapperCls = cls(prefixCls, className, {
[`${prefixCls}-horizontal`]: direction === strings.DEFAULT_DIRECTION,
[`${prefixCls}-vertical`]: direction !== strings.DEFAULT_DIRECTION,
[`${prefixCls}-large`]: size === 'large'
});
const progressTrackCls = cls({
[`${prefixCls}-track`]: true
});
const innerCls = cls(`${prefixCls}-track-inner`);
const perc = this.calcPercent(percent);
const percNumber = this.calcPercent(percentNumber);
// parse stroke & generate gradients
const _stroke = this.selectStroke(stroke, percent, strokeGradient);
const innerStyle = {
background: _stroke
};
if (direction === strings.DEFAULT_DIRECTION) {
innerStyle.width = `${perc}%`;
} else {
innerStyle.height = `${perc}%`;
}
const text = format(percNumber);
return /*#__PURE__*/React.createElement("div", Object.assign({
id: id,
className: progressWrapperCls,
style: style,
role: "progressbar",
"aria-valuemin": 0,
"aria-valuemax": 100,
"aria-valuenow": perc,
"aria-labelledby": ariaLabelledBy,
"aria-label": ariaLabel,
"aria-valuetext": ariaValueText
}, getDataAttr(rest)), /*#__PURE__*/React.createElement("div", {
className: progressTrackCls,
style: orbitStroke ? {
backgroundColor: orbitStroke
} : {},
"aria-hidden": true
}, /*#__PURE__*/React.createElement("div", {
className: innerCls,
style: innerStyle,
"aria-hidden": true
})), showInfo ? /*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-line-text`
}, text) : null);
}
render() {
const {
type
} = this.props;
if (type === 'line') {
return this.renderLineProgress();
} else {
return this.renderCircleProgress();
}
}
}
Progress.propTypes = {
'aria-label': PropTypes.string,
'aria-labelledby': PropTypes.string,
'aria-valuetext': PropTypes.string,
className: PropTypes.string,
direction: PropTypes.oneOf(strings.directions),
format: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
id: PropTypes.string,
motion: PropTypes.oneOfType([PropTypes.bool, PropTypes.func, PropTypes.object]),
orbitStroke: PropTypes.string,
percent: PropTypes.number,
scale: PropTypes.number,
showInfo: PropTypes.bool,
size: PropTypes.oneOf(strings.sizes),
stroke: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.shape({
percent: PropTypes.number,
color: PropTypes.string
}))]),
strokeGradient: PropTypes.bool,
strokeLinecap: PropTypes.oneOf(strings.strokeLineCap),
strokeWidth: PropTypes.number,
style: PropTypes.object,
type: PropTypes.oneOf(strings.types),
width: PropTypes.number
};
Progress.defaultProps = {
className: '',
direction: strings.DEFAULT_DIRECTION,
format: text => `${text}%`,
motion: true,
percent: 0,
showInfo: false,
size: strings.DEFAULT_SIZE,
strokeGradient: false,
strokeLinecap: strings.DEFAULT_LINECAP,
strokeWidth: 4,
style: {},
type: strings.DEFAULT_TYPE
};
export default Progress;