diff --git a/nx-dev/ui-animations/src/index.ts b/nx-dev/ui-animations/src/index.ts index 1979101c04141..eaa9db921885a 100644 --- a/nx-dev/ui-animations/src/index.ts +++ b/nx-dev/ui-animations/src/index.ts @@ -3,4 +3,5 @@ export * from './lib/blur-fade'; export * from './lib/fit-text'; export * from './lib/marquee'; export * from './lib/moving-border'; +export * from './lib/prefers-reduced-motion'; export * from './lib/shine-border'; diff --git a/nx-dev/ui-animations/src/lib/animate-value.tsx b/nx-dev/ui-animations/src/lib/animate-value.tsx index 2646e00c1c038..0422354d736c6 100644 --- a/nx-dev/ui-animations/src/lib/animate-value.tsx +++ b/nx-dev/ui-animations/src/lib/animate-value.tsx @@ -1,5 +1,6 @@ import { animate, useInView } from 'framer-motion'; import { useEffect, useRef, useState } from 'react'; +import { usePrefersReducedMotion } from './prefers-reduced-motion'; /** * Animates a value and renders it with a specified suffix. @@ -26,13 +27,14 @@ export function AnimateValue({ const ref = useRef(null); const [isComplete, setIsComplete] = useState(false); const isInView = useInView(ref); + const shouldReduceMotion = usePrefersReducedMotion(); useEffect(() => { if (!isInView) return; if (isComplete && once) return; animate(0, num, { - duration: 2.5, + duration: shouldReduceMotion ? 0 : 2.5, onUpdate(value) { if (!ref.current) return; diff --git a/nx-dev/ui-animations/src/lib/blur-fade.tsx b/nx-dev/ui-animations/src/lib/blur-fade.tsx index 43ea7b8be13f0..6781376884ea8 100644 --- a/nx-dev/ui-animations/src/lib/blur-fade.tsx +++ b/nx-dev/ui-animations/src/lib/blur-fade.tsx @@ -7,6 +7,7 @@ import { Variants, } from 'framer-motion'; import { ReactNode, useRef } from 'react'; +import { usePrefersReducedMotion } from './prefers-reduced-motion'; interface BlurFadeProps { children: ReactNode; @@ -62,6 +63,12 @@ export function BlurFade({ visible: { y: -yOffset, opacity: 1, filter: `blur(0px)` }, }; const combinedVariants = variant || defaultVariants; + + const shouldReduceMotion = usePrefersReducedMotion(); + if (shouldReduceMotion) { + return; + } + return ( { + const mediaQueryList = window.matchMedia(QUERY); + // Set the true initial value, now that we're on the client: + setPrefersReducedMotion(!window.matchMedia(QUERY).matches); + // Register our event listener + const listener = (event: any) => { + setPrefersReducedMotion(!event.matches); + }; + mediaQueryList.addEventListener('change', listener); + return () => { + mediaQueryList.removeEventListener('change', listener); + }; + }, []); + return prefersReducedMotion; +} diff --git a/nx-dev/ui-cloud/src/lib/agent-number-over-time.tsx b/nx-dev/ui-cloud/src/lib/agent-number-over-time.tsx index 39a7f5f1b32ca..9d940208a0717 100644 --- a/nx-dev/ui-cloud/src/lib/agent-number-over-time.tsx +++ b/nx-dev/ui-cloud/src/lib/agent-number-over-time.tsx @@ -1,4 +1,5 @@ 'use client'; +import { usePrefersReducedMotion } from '@nx/nx-dev/ui-animations'; import { SectionHeading } from '@nx/nx-dev/ui-common'; import { motion } from 'framer-motion'; import { useEffect, useState } from 'react'; @@ -48,6 +49,7 @@ export function AgentNumberOverTime(): JSX.Element { // Calculate the width of each item const itemWidthPercent = remainingPercent / agents.length; + const shouldReduceMotion = usePrefersReducedMotion(); const variants = { hidden: { opacity: 0, @@ -58,7 +60,7 @@ export function AgentNumberOverTime(): JSX.Element { visible: (i: number) => ({ opacity: 1, transition: { - delay: i || 0, + delay: shouldReduceMotion ? 0 : i || 0, }, }), }; @@ -67,8 +69,8 @@ export function AgentNumberOverTime(): JSX.Element { opacity: 1, y: 0, transition: { - delay: i * 0.035, - duration: 0.65, + delay: shouldReduceMotion ? 0 : i * 0.035, + duration: shouldReduceMotion ? 0 : 0.65, ease: 'easeOut', when: 'beforeChildren', staggerChildren: 0.3, diff --git a/nx-dev/ui-cloud/src/lib/automated-agents-management.tsx b/nx-dev/ui-cloud/src/lib/automated-agents-management.tsx index 649fec4e09a67..2e70bca836780 100644 --- a/nx-dev/ui-cloud/src/lib/automated-agents-management.tsx +++ b/nx-dev/ui-cloud/src/lib/automated-agents-management.tsx @@ -8,8 +8,10 @@ import { import { SectionHeading } from '@nx/nx-dev/ui-common'; import { motion } from 'framer-motion'; import { NxCloudIcon } from '@nx/nx-dev/ui-icons'; +import { usePrefersReducedMotion } from '@nx/nx-dev/ui-animations'; export function AutomatedAgentsManagement(): JSX.Element { + const shouldReduceMotion = usePrefersReducedMotion(); const variants = { hidden: { opacity: 0, @@ -20,7 +22,7 @@ export function AutomatedAgentsManagement(): JSX.Element { visible: (i: number) => ({ opacity: 1, transition: { - delay: i || 0, + delay: shouldReduceMotion ? 0 : i || 0, }, }), }; @@ -29,8 +31,8 @@ export function AutomatedAgentsManagement(): JSX.Element { opacity: 1, y: 0, transition: { - delay: i * 0.035, - duration: 0.65, + delay: shouldReduceMotion ? 0 : i * 0.035, + duration: shouldReduceMotion ? 0 : 0.65, ease: 'easeOut', when: 'beforeChildren', staggerChildren: 0.3, diff --git a/nx-dev/ui-cloud/src/lib/statistics.tsx b/nx-dev/ui-cloud/src/lib/statistics.tsx index 771fcf35bcde8..38d6990cb455b 100644 --- a/nx-dev/ui-cloud/src/lib/statistics.tsx +++ b/nx-dev/ui-cloud/src/lib/statistics.tsx @@ -2,6 +2,7 @@ import { motion } from 'framer-motion'; import { useEffect, useState } from 'react'; import { SectionHeading } from '@nx/nx-dev/ui-common'; +import { usePrefersReducedMotion } from '@nx/nx-dev/ui-animations'; /** * Calculate the total number of years worth of compute. @@ -71,6 +72,8 @@ const stats = [ ]; export function Statistics(): JSX.Element { + const shouldReduceMotion = usePrefersReducedMotion(); + const variants = { hidden: { opacity: 0, @@ -81,7 +84,7 @@ export function Statistics(): JSX.Element { visible: (i: number) => ({ opacity: 1, transition: { - delay: i || 0, + delay: shouldReduceMotion ? 0 : i || 0, }, }), }; @@ -90,8 +93,8 @@ export function Statistics(): JSX.Element { opacity: 1, y: 0, transition: { - delay: i * 0.25, - duration: 0.65, + delay: shouldReduceMotion ? 0 : i * 0.25, + duration: shouldReduceMotion ? 0 : 0.65, ease: 'easeOut', when: 'beforeChildren', staggerChildren: 0.3, diff --git a/nx-dev/ui-cloud/src/lib/understand-workspace.tsx b/nx-dev/ui-cloud/src/lib/understand-workspace.tsx index b56132a3f348f..ac672b62bd55a 100644 --- a/nx-dev/ui-cloud/src/lib/understand-workspace.tsx +++ b/nx-dev/ui-cloud/src/lib/understand-workspace.tsx @@ -14,6 +14,7 @@ import { BentoGrid, BentoGridItem } from './elements/bento-grid'; import { cx } from '@nx/nx-dev/ui-primitives'; import { animate, motion, useMotionValue, useTransform } from 'framer-motion'; import { useEffect } from 'react'; +import { usePrefersReducedMotion } from '@nx/nx-dev/ui-animations'; export function UnderstandWorkspace(): JSX.Element { return ( @@ -155,6 +156,7 @@ const Caching = () => { }; const FlakyTasks = () => { + const shouldReduceMotion = usePrefersReducedMotion(); const variants = { hidden: { opacity: 0, @@ -171,8 +173,8 @@ const FlakyTasks = () => { opacity: 1, x: 0, transition: { - delay: i * 0.2, - duration: 0.275, + delay: shouldReduceMotion ? 0 : i * 0.2, + duration: shouldReduceMotion ? 0 : 0.275, ease: 'easeOut', when: 'beforeChildren', staggerChildren: 0.3, @@ -439,6 +441,7 @@ const SplitE2eTests = () => { }; const TaskDistribution = () => { + const shouldReduceMotion = usePrefersReducedMotion(); const variants = { hidden: { opacity: 0, @@ -455,8 +458,8 @@ const TaskDistribution = () => { opacity: 1, x: 0, transition: { - delay: i * 0.2, - duration: 0.275, + delay: shouldReduceMotion ? 0 : i * 0.2, + duration: shouldReduceMotion ? 0 : 0.275, ease: 'easeOut', when: 'beforeChildren', staggerChildren: 0.3, @@ -745,6 +748,10 @@ export function Counter({ }) { const count = useMotionValue(0); const rounded = useTransform(count, Math.round); + const shouldReduceMotion = usePrefersReducedMotion(); + if (shouldReduceMotion) { + duration = 0; + } useEffect(() => { const animation = animate(count, value, {