* fix: make completion modal a little more accessible * remove: unneeded aria attribute * fix: update snapshots
124 lines
3.6 KiB
TypeScript
124 lines
3.6 KiB
TypeScript
import BezierEasing from 'bezier-easing';
|
|
import React, { PureComponent } from 'react';
|
|
import { TFunction, withTranslation } from 'react-i18next';
|
|
import GreenPass from '../../../assets/icons/green-pass';
|
|
|
|
interface CompletionModalBodyProps {
|
|
block: string;
|
|
completedPercent: number;
|
|
superBlock: string;
|
|
t: TFunction;
|
|
}
|
|
|
|
interface CompletionModalBodyState {
|
|
// This type was driving me nuts - seems like `NodeJS.Timeout | null;` should work
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
progressInterval: number | null;
|
|
shownPercent: number;
|
|
}
|
|
|
|
export class CompletionModalBody extends PureComponent<
|
|
CompletionModalBodyProps,
|
|
CompletionModalBodyState
|
|
> {
|
|
static displayName: string;
|
|
constructor(props: CompletionModalBodyProps) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
progressInterval: null,
|
|
shownPercent: 0
|
|
};
|
|
|
|
this.animateProgressBar = this.animateProgressBar.bind(this);
|
|
}
|
|
|
|
animateProgressBar(completedPercent: number): void {
|
|
const easing = BezierEasing(0.2, 0.5, 0.4, 1);
|
|
|
|
if (completedPercent > 100) completedPercent = 100;
|
|
if (completedPercent < 0) completedPercent = 0;
|
|
|
|
const transitionLength = completedPercent * 10 + 750;
|
|
const intervalLength = 10;
|
|
const intervalsToFinish = transitionLength / intervalLength;
|
|
const amountPerInterval = completedPercent / intervalsToFinish;
|
|
let percent = 0;
|
|
|
|
const myInterval = window.setInterval(() => {
|
|
percent += amountPerInterval;
|
|
|
|
if (percent > completedPercent) percent = completedPercent;
|
|
|
|
this.setState({
|
|
shownPercent: Math.round(
|
|
completedPercent * easing(percent / completedPercent)
|
|
)
|
|
});
|
|
|
|
if (percent >= completedPercent) clearInterval(myInterval);
|
|
}, intervalLength);
|
|
|
|
this.setState({
|
|
progressInterval: myInterval
|
|
});
|
|
}
|
|
|
|
componentWillUnmount(): void {
|
|
if (this.state.progressInterval !== null)
|
|
clearInterval(this.state.progressInterval);
|
|
}
|
|
|
|
render(): JSX.Element {
|
|
const { block, completedPercent, superBlock, t } = this.props;
|
|
const blockTitle = t(`intro:${superBlock}.blocks.${block}.title`);
|
|
|
|
return (
|
|
<>
|
|
<div className='completion-challenge-details'>
|
|
<GreenPass
|
|
className='completion-success-icon'
|
|
data-testid='fcc-completion-success-icon'
|
|
onAnimationEnd={() => {
|
|
setTimeout(() => {
|
|
this.animateProgressBar(completedPercent);
|
|
}, 50);
|
|
}}
|
|
/>
|
|
</div>
|
|
<div className='completion-block-details'>
|
|
<div className='completion-block-name'>{blockTitle}</div>
|
|
<div
|
|
className='progress-bar-wrap'
|
|
aria-label={t('learn.percent-complete', {
|
|
percent: completedPercent
|
|
})}
|
|
>
|
|
<div className='progress-bar-background' aria-hidden='true'>
|
|
{t('learn.percent-complete', {
|
|
percent: this.state.shownPercent
|
|
})}
|
|
</div>
|
|
<div
|
|
aria-hidden='true'
|
|
className='progress-bar-percent'
|
|
data-testid='fcc-progress-bar-percent'
|
|
style={{ width: `${this.state.shownPercent}%` }}
|
|
>
|
|
<div className='progress-bar-foreground'>
|
|
{t('learn.percent-complete', {
|
|
percent: this.state.shownPercent
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
|
|
CompletionModalBody.displayName = 'CompletionModalBody';
|
|
|
|
export default withTranslation()(CompletionModalBody);
|