AirAchievementProgressBar.tsx 4.2 Ko
Télécharger le fichier
import { Progress } from '@base-ui/react/progress';
import { CSSProperties, FC, useEffect, useRef, useState } from 'react';
import { AchievementUtilities, LocalizeText } from '../../api';
import { NativeTextTree } from '../../common/native-text/NativeTextTree';
interface AirAchievementProgressBarProps {
progress: number;
maxProgress: number;
localizationKey?: string;
width: number;
scoreAtStartOfLevel?: number;
className?: string;
}
export const AirAchievementProgressBar: FC<AirAchievementProgressBarProps> = ({
progress,
maxProgress,
localizationKey,
width,
scoreAtStartOfLevel = 0,
className = ''
}) => {
const targetWidth = maxProgress > 0 ? Math.max(0, Math.round((width * progress) / maxProgress)) : 0;
const currentWidth = useRef(targetWidth);
const [animation, setAnimation] = useState({ width: targetWidth, opacity: 1, progress });
useEffect(() => {
const startWidth = currentWidth.current;
let previousTime = performance.now();
let frame = 0;
const update = (time: number) => {
const difference = targetWidth - currentWidth.current;
const step = Math.trunc(Math.max(1, ((time - previousTime) / 32) * Math.round(Math.sqrt(Math.abs(difference)))));
currentWidth.current = difference > 0 ? Math.min(targetWidth, currentWidth.current + step) : Math.max(targetWidth, currentWidth.current - step);
previousTime = time;
const isComplete = currentWidth.current === targetWidth;
setAnimation({
width: currentWidth.current,
opacity: isComplete ? 1 : 1 - (targetWidth - currentWidth.current) / (targetWidth - startWidth),
progress: isComplete ? progress : Math.round((currentWidth.current / width) * maxProgress)
});
if (!isComplete) frame = requestAnimationFrame(update);
};
frame = requestAnimationFrame(update);
return () => cancelAnimationFrame(frame);
}, [maxProgress, progress, targetWidth, width]);
const style = {
'--air-achievement-progress-left': `url(${AchievementUtilities.getAchievementImageUrl('ach_progressbar1')})`,
'--air-achievement-progress-track': `url(${AchievementUtilities.getAchievementImageUrl('ach_progressbar2')})`,
'--air-achievement-progress-right': `url(${AchievementUtilities.getAchievementImageUrl('ach_progressbar3')})`,
'--air-achievement-progress-fill': `url(${AchievementUtilities.getAchievementImageUrl('ach_progressbar4')})`,
'--air-achievement-progress-fill-cap': `url(${AchievementUtilities.getAchievementImageUrl('ach_progressbar5')})`,
width: width + 10
} as CSSProperties;
return (
<NativeTextTree>
<Progress.Root className={`air-achievement-progress ${className}`.trim()} max={maxProgress} min={0} style={style} value={progress}>
<span className="air-achievement-progress__left" aria-hidden="true" />
<span className="air-achievement-progress__track" style={{ width }} aria-hidden="true" />
<span className="air-achievement-progress__right" style={{ left: width + 4 }} aria-hidden="true" />
<span className="air-achievement-progress__fill-background" style={{ width: animation.width + 1 }} aria-hidden="true" />
<span className="air-achievement-progress__fill" style={{ width: animation.width, opacity: animation.opacity }} aria-hidden="true" />
<span className="air-achievement-progress__fill-cap" style={{ left: animation.width + 4 }} aria-hidden="true" />
<span className="air-achievement-progress__text" style={{ width }}>
{localizationKey
? LocalizeText(
localizationKey,
['progress', 'limit'],
[String(animation.progress + scoreAtStartOfLevel), String(maxProgress + scoreAtStartOfLevel)]
)
: `${animation.progress + scoreAtStartOfLevel}/${maxProgress + scoreAtStartOfLevel}`}
</span>
</Progress.Root>
</NativeTextTree>
);
};
