fix(ui): align gauge animation with validation
Harness Validate / harness-validate (push) Has started running
Create Release Tag / create-tag (push) Has started running
Release Version PR / version-packages (push) Has started running

This commit is contained in:
2026-03-25 20:00:09 +08:00
parent f049736c8a
commit f24da6987b
3 changed files with 20 additions and 15 deletions
@@ -116,6 +116,10 @@ describe("Gauge", () => {
const indicator = gauge.querySelector('[data-slot="indicator"]');
const value = gauge.querySelector('[data-slot="value"]');
act(() => {
vi.advanceTimersByTime(16);
});
expect(gauge).toHaveAttribute("data-animating", "");
expect(indicator).toHaveAttribute("stroke-dasharray", "0 100");
expect(value).toHaveTextContent("0%");
+15 -14
View File
@@ -374,6 +374,7 @@ export const Gauge = forwardRef<HTMLDivElement, GaugeProps>(function Gauge(
useEffect(() => {
const targetPercentage = percentage ?? 0;
const targetValue = computedAriaValueNow ?? null;
let frame = 0;
if (
disableMotion ||
@@ -383,17 +384,21 @@ export const Gauge = forwardRef<HTMLDivElement, GaugeProps>(function Gauge(
previousPercentageRef.current === targetPercentage &&
previousValueRef.current === targetValue)
) {
setDisplayPercentage(targetPercentage);
setDisplayValue(targetValue);
setIsAnimating(false);
previousPercentageRef.current = targetPercentage;
previousValueRef.current = targetValue;
frame = window.requestAnimationFrame(() => {
setDisplayPercentage(targetPercentage);
setDisplayValue(targetValue);
setIsAnimating(false);
previousPercentageRef.current = targetPercentage;
previousValueRef.current = targetValue;
if (targetValue != null) {
hasAnimatedRef.current = true;
}
if (targetValue != null) {
hasAnimatedRef.current = true;
}
});
return;
return () => {
window.cancelAnimationFrame(frame);
};
}
const fromPercentage = hasAnimatedRef.current ? previousPercentageRef.current : 0;
@@ -401,13 +406,8 @@ export const Gauge = forwardRef<HTMLDivElement, GaugeProps>(function Gauge(
? previousValueRef.current ?? resolvedMin
: resolvedMin;
const duration = hasAnimatedRef.current ? UPDATE_SWEEP_DURATION : INITIAL_SWEEP_DURATION;
let frame = 0;
let startTime: number | null = null;
setDisplayPercentage(fromPercentage);
setDisplayValue(fromValue);
setIsAnimating(true);
const tick = (timestamp: number) => {
if (startTime == null) {
startTime = timestamp;
@@ -416,6 +416,7 @@ export const Gauge = forwardRef<HTMLDivElement, GaugeProps>(function Gauge(
const progress = Math.min((timestamp - startTime) / duration, 1);
const easedProgress = easeOutCubic(progress);
setIsAnimating(progress < 1);
setDisplayPercentage(fromPercentage + (targetPercentage - fromPercentage) * easedProgress);
setDisplayValue(fromValue + (targetValue - fromValue) * easedProgress);
+1 -1
View File
@@ -110,7 +110,7 @@ describe("Progress", () => {
const { rerender } = render(<Progress aria-label="Reduced sync status" value={null} />);
let progressbar = screen.getByRole("progressbar", { name: "Reduced sync status" });
let indicator = progressbar.querySelector('[data-slot="indicator"]');
const indicator = progressbar.querySelector('[data-slot="indicator"]');
expect(progressbar).toHaveAttribute("data-reduced-motion", "");
expect(indicator).toHaveAttribute("data-reduced-motion", "");