Skip to content

Commit

Permalink
feat(nx-dev): honor prefers-reduced-motion (#27541)
Browse files Browse the repository at this point in the history
Disables animations when browser is set to `prefers-reduced-motion`

Fixes #27114

(cherry picked from commit d6c3b24)
  • Loading branch information
isaacplmann authored and FrozenPandaz committed Aug 21, 2024
1 parent d078a9b commit e0b3cfc
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 14 deletions.
1 change: 1 addition & 0 deletions nx-dev/ui-animations/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
4 changes: 3 additions & 1 deletion nx-dev/ui-animations/src/lib/animate-value.tsx
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -26,13 +27,14 @@ export function AnimateValue({
const ref = useRef<HTMLSpanElement | null>(null);
const [isComplete, setIsComplete] = useState<boolean>(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;

Expand Down
7 changes: 7 additions & 0 deletions nx-dev/ui-animations/src/lib/blur-fade.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Variants,
} from 'framer-motion';
import { ReactNode, useRef } from 'react';
import { usePrefersReducedMotion } from './prefers-reduced-motion';

interface BlurFadeProps {
children: ReactNode;
Expand Down Expand Up @@ -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 (
<AnimatePresence>
<motion.div
Expand Down
4 changes: 4 additions & 0 deletions nx-dev/ui-animations/src/lib/marquee.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { cx } from '@nx/nx-dev/ui-primitives';
import { usePrefersReducedMotion } from './prefers-reduced-motion';

interface MarqueeProps {
className?: string;
Expand Down Expand Up @@ -33,6 +34,8 @@ export function Marquee({
repeat = 4,
...props
}: MarqueeProps) {
const shouldReduceMotion = usePrefersReducedMotion();

return (
<div
{...props}
Expand All @@ -53,6 +56,7 @@ export function Marquee({
className={cx('flex shrink-0 justify-around [gap:var(--gap)]', {
'animate-marquee flex-row': !vertical,
'animate-marquee-vertical flex-col': vertical,
'[animation-play-state:paused]': shouldReduceMotion,
'group-hover:[animation-play-state:paused]': pauseOnHover,
'[animation-direction:reverse]': reverse,
})}
Expand Down
6 changes: 6 additions & 0 deletions nx-dev/ui-animations/src/lib/moving-border.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useTransform,
} from 'framer-motion';
import { ReactNode, useRef } from 'react';
import { usePrefersReducedMotion } from './prefers-reduced-motion';

/**
* Creates a moving border effect around the specified component by animating a rectangular SVG element.
Expand Down Expand Up @@ -54,6 +55,11 @@ export function MovingBorder({

const transform = useMotionTemplate`translateX(${x}px) translateY(${y}px) translateX(-50%) translateY(-50%)`;

const shouldReduceMotion = usePrefersReducedMotion();
if (shouldReduceMotion) {
return;
}

return (
<>
<svg
Expand Down
23 changes: 23 additions & 0 deletions nx-dev/ui-animations/src/lib/prefers-reduced-motion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useState, useEffect } from 'react';

const QUERY = '(prefers-reduced-motion: no-preference)';

export function usePrefersReducedMotion(): boolean {
// Default to no-animations, since we don't know what the
// user's preference is on the server.
const [prefersReducedMotion, setPrefersReducedMotion] = useState(true);
useEffect(() => {
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;
}
8 changes: 5 additions & 3 deletions nx-dev/ui-cloud/src/lib/agent-number-over-time.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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,
Expand All @@ -58,7 +60,7 @@ export function AgentNumberOverTime(): JSX.Element {
visible: (i: number) => ({
opacity: 1,
transition: {
delay: i || 0,
delay: shouldReduceMotion ? 0 : i || 0,
},
}),
};
Expand All @@ -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,
Expand Down
8 changes: 5 additions & 3 deletions nx-dev/ui-cloud/src/lib/automated-agents-management.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,7 +22,7 @@ export function AutomatedAgentsManagement(): JSX.Element {
visible: (i: number) => ({
opacity: 1,
transition: {
delay: i || 0,
delay: shouldReduceMotion ? 0 : i || 0,
},
}),
};
Expand All @@ -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,
Expand Down
9 changes: 6 additions & 3 deletions nx-dev/ui-cloud/src/lib/statistics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -71,6 +72,8 @@ const stats = [
];

export function Statistics(): JSX.Element {
const shouldReduceMotion = usePrefersReducedMotion();

const variants = {
hidden: {
opacity: 0,
Expand All @@ -81,7 +84,7 @@ export function Statistics(): JSX.Element {
visible: (i: number) => ({
opacity: 1,
transition: {
delay: i || 0,
delay: shouldReduceMotion ? 0 : i || 0,
},
}),
};
Expand All @@ -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,
Expand Down
15 changes: 11 additions & 4 deletions nx-dev/ui-cloud/src/lib/understand-workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -155,6 +156,7 @@ const Caching = () => {
};

const FlakyTasks = () => {
const shouldReduceMotion = usePrefersReducedMotion();
const variants = {
hidden: {
opacity: 0,
Expand All @@ -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,
Expand Down Expand Up @@ -439,6 +441,7 @@ const SplitE2eTests = () => {
};

const TaskDistribution = () => {
const shouldReduceMotion = usePrefersReducedMotion();
const variants = {
hidden: {
opacity: 0,
Expand All @@ -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,
Expand Down Expand Up @@ -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, {
Expand Down

0 comments on commit e0b3cfc

Please sign in to comment.