(cherry picked from commit a9072ac460f971d3da737de6446153d8cbf1e1c2) Closes #10404pull/10406/head
parent
032e8aa920
commit
f0019d622a
@ -1,138 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import styles from './CircularProgressBar.css';
|
||||
|
||||
class CircularProgressBar extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
progress: 0
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._progressStep();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const progress = this.props.progress;
|
||||
|
||||
if (prevProps.progress !== progress) {
|
||||
this._cancelProgressStep();
|
||||
this._progressStep();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._cancelProgressStep();
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
_progressStep() {
|
||||
this.requestAnimationFrame = window.requestAnimationFrame(() => {
|
||||
this.setState({
|
||||
progress: this.state.progress + 1
|
||||
}, () => {
|
||||
if (this.state.progress < this.props.progress) {
|
||||
this._progressStep();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_cancelProgressStep() {
|
||||
if (this.requestAnimationFrame) {
|
||||
window.cancelAnimationFrame(this.requestAnimationFrame);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
containerClassName,
|
||||
size,
|
||||
strokeWidth,
|
||||
strokeColor,
|
||||
showProgressText
|
||||
} = this.props;
|
||||
|
||||
const progress = this.state.progress;
|
||||
|
||||
const center = size / 2;
|
||||
const radius = center - strokeWidth;
|
||||
const circumference = Math.PI * (radius * 2);
|
||||
const sizeInPixels = `${size}px`;
|
||||
const strokeDashoffset = ((100 - progress) / 100) * circumference;
|
||||
const progressText = `${Math.round(progress)}%`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={containerClassName}
|
||||
style={{
|
||||
width: sizeInPixels,
|
||||
height: sizeInPixels,
|
||||
lineHeight: sizeInPixels
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
className={className}
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
>
|
||||
<circle
|
||||
fill="transparent"
|
||||
r={radius}
|
||||
cx={center}
|
||||
cy={center}
|
||||
strokeDasharray={circumference}
|
||||
style={{
|
||||
stroke: strokeColor,
|
||||
strokeWidth,
|
||||
strokeDashoffset
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{
|
||||
showProgressText &&
|
||||
<div className={styles.circularProgressBarText}>
|
||||
{progressText}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CircularProgressBar.propTypes = {
|
||||
className: PropTypes.string,
|
||||
containerClassName: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
progress: PropTypes.number.isRequired,
|
||||
strokeWidth: PropTypes.number,
|
||||
strokeColor: PropTypes.string,
|
||||
showProgressText: PropTypes.bool
|
||||
};
|
||||
|
||||
CircularProgressBar.defaultProps = {
|
||||
className: styles.circularProgressBar,
|
||||
containerClassName: styles.circularProgressBarContainer,
|
||||
size: 60,
|
||||
strokeWidth: 5,
|
||||
strokeColor: '#ffc230',
|
||||
showProgressText: false
|
||||
};
|
||||
|
||||
export default CircularProgressBar;
|
@ -0,0 +1,99 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import styles from './CircularProgressBar.css';
|
||||
|
||||
interface CircularProgressBarProps {
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
size?: number;
|
||||
progress: number;
|
||||
strokeWidth?: number;
|
||||
strokeColor?: string;
|
||||
showProgressText?: boolean;
|
||||
}
|
||||
|
||||
function CircularProgressBar({
|
||||
className = styles.circularProgressBar,
|
||||
containerClassName = styles.circularProgressBarContainer,
|
||||
size = 60,
|
||||
strokeWidth = 5,
|
||||
strokeColor = '#ffc230',
|
||||
showProgressText = false,
|
||||
progress,
|
||||
}: CircularProgressBarProps) {
|
||||
const [currentProgress, setCurrentProgress] = useState(0);
|
||||
const raf = React.useRef<number>(0);
|
||||
const center = size / 2;
|
||||
const radius = center - strokeWidth;
|
||||
const circumference = Math.PI * (radius * 2);
|
||||
const sizeInPixels = `${size}px`;
|
||||
const strokeDashoffset = ((100 - currentProgress) / 100) * circumference;
|
||||
const progressText = `${Math.round(currentProgress)}%`;
|
||||
|
||||
const handleAnimation = useCallback(
|
||||
(p: number) => {
|
||||
setCurrentProgress((prevProgress) => {
|
||||
if (prevProgress < p) {
|
||||
return prevProgress + Math.min(1, p - prevProgress);
|
||||
}
|
||||
|
||||
return prevProgress;
|
||||
});
|
||||
},
|
||||
[setCurrentProgress]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (progress > currentProgress) {
|
||||
cancelAnimationFrame(raf.current);
|
||||
|
||||
raf.current = requestAnimationFrame(() => handleAnimation(progress));
|
||||
}
|
||||
}, [progress, currentProgress, handleAnimation]);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
return () => cancelAnimationFrame(raf.current);
|
||||
},
|
||||
// We only want to run this effect once
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={containerClassName}
|
||||
style={{
|
||||
width: sizeInPixels,
|
||||
height: sizeInPixels,
|
||||
lineHeight: sizeInPixels,
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
className={className}
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
>
|
||||
<circle
|
||||
fill="transparent"
|
||||
r={radius}
|
||||
cx={center}
|
||||
cy={center}
|
||||
strokeDasharray={circumference}
|
||||
style={{
|
||||
stroke: strokeColor,
|
||||
strokeWidth,
|
||||
strokeDashoffset,
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{showProgressText && (
|
||||
<div className={styles.circularProgressBarText}>{progressText}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CircularProgressBar;
|
@ -1,114 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { ColorImpairedConsumer } from 'App/ColorImpairedContext';
|
||||
import { kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './ProgressBar.css';
|
||||
|
||||
function ProgressBar(props) {
|
||||
const {
|
||||
className,
|
||||
containerClassName,
|
||||
title,
|
||||
progress,
|
||||
precision,
|
||||
showText,
|
||||
text,
|
||||
kind,
|
||||
size,
|
||||
width
|
||||
} = props;
|
||||
|
||||
const progressPercent = `${progress.toFixed(precision)}%`;
|
||||
const progressText = text || progressPercent;
|
||||
const actualWidth = width ? `${width}px` : '100%';
|
||||
|
||||
return (
|
||||
<ColorImpairedConsumer>
|
||||
{(enableColorImpairedMode) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
containerClassName,
|
||||
styles[size]
|
||||
)}
|
||||
title={title}
|
||||
style={{ width: actualWidth }}
|
||||
>
|
||||
{
|
||||
showText && width ?
|
||||
<div
|
||||
className={classNames(styles.backTextContainer, styles[kind])}
|
||||
style={{ width: actualWidth }}
|
||||
>
|
||||
<div className={styles.backText}>
|
||||
<div>
|
||||
{progressText}
|
||||
</div>
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
className,
|
||||
styles[kind],
|
||||
enableColorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
role="meter"
|
||||
aria-label={translate('ProgressBarProgress', { progress: progress.toFixed(0) })}
|
||||
aria-valuenow={progress.toFixed(0)}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
style={{ width: progressPercent }}
|
||||
/>
|
||||
|
||||
{
|
||||
showText ?
|
||||
<div
|
||||
className={classNames(styles.frontTextContainer, styles[kind])}
|
||||
style={{ width: progressPercent }}
|
||||
>
|
||||
<div
|
||||
className={styles.frontText}
|
||||
style={{ width: actualWidth }}
|
||||
>
|
||||
<div>
|
||||
{progressText}
|
||||
</div>
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</ColorImpairedConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
ProgressBar.propTypes = {
|
||||
className: PropTypes.string,
|
||||
containerClassName: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
progress: PropTypes.number.isRequired,
|
||||
precision: PropTypes.number.isRequired,
|
||||
showText: PropTypes.bool.isRequired,
|
||||
text: PropTypes.string,
|
||||
kind: PropTypes.oneOf(kinds.all).isRequired,
|
||||
size: PropTypes.oneOf(sizes.all).isRequired,
|
||||
width: PropTypes.number
|
||||
};
|
||||
|
||||
ProgressBar.defaultProps = {
|
||||
className: styles.progressBar,
|
||||
containerClassName: styles.container,
|
||||
precision: 1,
|
||||
showText: false,
|
||||
kind: kinds.PRIMARY,
|
||||
size: sizes.MEDIUM
|
||||
};
|
||||
|
||||
export default ProgressBar;
|
@ -0,0 +1,94 @@
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { ColorImpairedConsumer } from 'App/ColorImpairedContext';
|
||||
import { Kind } from 'Helpers/Props/kinds';
|
||||
import { Size } from 'Helpers/Props/sizes';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './ProgressBar.css';
|
||||
|
||||
interface ProgressBarProps {
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
title?: string;
|
||||
progress: number;
|
||||
precision?: number;
|
||||
showText?: boolean;
|
||||
text?: string;
|
||||
kind?: Extract<Kind, keyof typeof styles>;
|
||||
size?: Extract<Size, keyof typeof styles>;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
function ProgressBar({
|
||||
className = styles.progressBar,
|
||||
containerClassName = styles.container,
|
||||
title,
|
||||
progress,
|
||||
precision = 1,
|
||||
showText = false,
|
||||
text,
|
||||
kind = 'primary',
|
||||
size = 'medium',
|
||||
width,
|
||||
}: ProgressBarProps) {
|
||||
const progressPercent = `${progress.toFixed(precision)}%`;
|
||||
const progressText = text || progressPercent;
|
||||
const actualWidth = width ? `${width}px` : '100%';
|
||||
|
||||
return (
|
||||
<ColorImpairedConsumer>
|
||||
{(enableColorImpairedMode) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames(containerClassName, styles[size])}
|
||||
title={title}
|
||||
style={{ width: actualWidth }}
|
||||
>
|
||||
{showText && width ? (
|
||||
<div
|
||||
className={classNames(styles.backTextContainer, styles[kind])}
|
||||
style={{ width: actualWidth }}
|
||||
>
|
||||
<div className={styles.backText}>
|
||||
<div>{progressText}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
className,
|
||||
styles[kind],
|
||||
enableColorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
role="meter"
|
||||
aria-label={translate('ProgressBarProgress', {
|
||||
progress: progress.toFixed(0),
|
||||
})}
|
||||
aria-valuenow={Math.floor(progress)}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
style={{ width: progressPercent }}
|
||||
/>
|
||||
|
||||
{showText ? (
|
||||
<div
|
||||
className={classNames(styles.frontTextContainer, styles[kind])}
|
||||
style={{ width: progressPercent }}
|
||||
>
|
||||
<div
|
||||
className={styles.frontText}
|
||||
style={{ width: actualWidth }}
|
||||
>
|
||||
<div>{progressText}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</ColorImpairedConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProgressBar;
|
Loading…
Reference in new issue