Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog/7924-reduce-motion-animated-counts.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type: Changed
description: Dashboard animated counts and chart animations now respect the "Reduce Motion" user preference.
pr: 7924
labels: []
1 change: 0 additions & 1 deletion clients/admin-ui/src/features/common/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export * from "../pagination/usePagination";
export * from "./useAPIHelper";
export * from "./useConnectionLogo";
export * from "./usePrefersReducedMotion";
export * from "./useSearch";
export * from "./useSorting";
8 changes: 7 additions & 1 deletion clients/admin-ui/src/home/useCountUp.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { usePrefersReducedMotion } from "fidesui";
import { useEffect, useRef, useState } from "react";

function easeOut(t: number): number {
Expand All @@ -8,8 +9,13 @@ export const useCountUp = (target: number, duration = 800): number => {
const [value, setValue] = useState(0);
const rafRef = useRef<number | undefined>(undefined);
const startRef = useRef<number | undefined>(undefined);
const reduceMotion = usePrefersReducedMotion();

useEffect(() => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clients/admin-ui/src/home/useCountUp.ts:15

return setValue(target) is misleading here. setValue is a React state setter that returns void. In a useEffect callback, the only meaningful return value is a cleanup function — returning the result of a side-effect call looks like it's returning a cleanup, but it's actually returning undefined.

Use the idiomatic form instead:

if (reduceMotion) {
  setValue(target);
  return;
}

This makes the intent explicit: set state, then exit early.

if (reduceMotion) {
return setValue(target);
}

if (target === 0) {
setValue(0);
return undefined;
Expand Down Expand Up @@ -37,7 +43,7 @@ export const useCountUp = (target: number, duration = 800): number => {
cancelAnimationFrame(rafRef.current);
}
};
}, [target, duration]);
}, [reduceMotion, target, duration]);

return value;
};
2 changes: 1 addition & 1 deletion clients/admin-ui/src/pages/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Input,
Typography,
useMessage,
usePrefersReducedMotion,
} from "fidesui";
import { motion } from "motion/react";
import type { NextPage } from "next";
Expand All @@ -27,7 +28,6 @@ import {
} from "~/features/auth";
import { passwordRules as strongPasswordRules } from "~/features/common/form/validation";
import { getErrorMessage } from "~/features/common/helpers";
import { usePrefersReducedMotion } from "~/features/common/hooks";
import { RouterLink } from "~/features/common/nav/RouterLink";
import { useGetAllOpenIDProvidersSimpleQuery } from "~/features/openid-authentication/openprovider.slice";
import { RTKErrorResult } from "~/types/errors/api";
Expand Down
11 changes: 8 additions & 3 deletions clients/fidesui/src/components/charts/chart-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { format, isValid } from "date-fns";
import type { RefObject } from "react";
import { useEffect, useState } from "react";

import { usePrefersReducedMotion } from "../../hooks";
import { MAX_INTERVAL_HOURS } from "./chart-constants";

export const HOUR_MS = 3_600_000;
Expand Down Expand Up @@ -34,8 +35,11 @@ export const useContainerSize = (

export const useChartAnimation = (animationDuration: number): boolean => {
const [animationActive, setAnimationActive] = useState(true);
const reduceMotion = usePrefersReducedMotion();

useEffect(() => {
if (animationDuration <= 0) {
if (reduceMotion || animationDuration <= 0) {
setAnimationActive(false);
return undefined;
}
const startTime = performance.now();
Expand All @@ -49,8 +53,9 @@ export const useChartAnimation = (animationDuration: number): boolean => {
};
animationId = requestAnimationFrame(tick);
return () => cancelAnimationFrame(animationId);
}, [animationDuration]);
return animationActive;
}, [animationDuration, reduceMotion]);

return reduceMotion ? false : animationActive;
};

/** Format a timestamp for chart axes (verbose=false) or tooltips (verbose=true). */
Expand Down
1 change: 1 addition & 0 deletions clients/fidesui/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type { UseFormModalOptions } from "./useFormModal";
export { useFormModal } from "./useFormModal";
export { usePrefersReducedMotion } from "./usePrefersReducedMotion";
export type { ThemeMode, ThemeModeProviderProps } from "./useThemeMode";
export { ThemeModeProvider, useThemeMode } from "./useThemeMode";
7 changes: 6 additions & 1 deletion clients/fidesui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,4 +451,9 @@ export type {
ThemeModeProviderProps,
UseFormModalOptions,
} from "./hooks";
export { ThemeModeProvider, useFormModal, useThemeMode } from "./hooks";
export {
ThemeModeProvider,
useFormModal,
usePrefersReducedMotion,
useThemeMode,
} from "./hooks";
Loading