Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ReducedMotionConfig component #6164

Merged
merged 16 commits into from
Jul 15, 2024
16 changes: 15 additions & 1 deletion apps/common-app/src/examples/ReducedMotionExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Animated, {
withSpring,
withSequence,
ReduceMotion,
ReducedMotionConfig,
} from 'react-native-reanimated';
import {
Gesture,
Expand Down Expand Up @@ -126,18 +127,31 @@ const EXAMPLES = [

export default function ReducedMotionExample() {
const [currentExample, setCurrentExample] = useState(0);

const [reduceMotion, setReduceMotion] = useState(ReduceMotion.Never);
const { component, exampleList } = EXAMPLES[currentExample];

function handleReduceMotionModeChange() {
setReduceMotion(
reduceMotion === ReduceMotion.System
? ReduceMotion.Never
: ReduceMotion.System
);
}

return (
<View style={styles.container}>
<Button
onPress={handleReduceMotionModeChange}
title={`Overwrite reduce motion: ${reduceMotion}`}
/>
{EXAMPLES.map((example, i) => (
<Button
key={i}
onPress={() => setCurrentExample(i)}
title={example.title}
/>
))}
<ReducedMotionConfig mode={reduceMotion} />
{component}
<View key={currentExample}>
{exampleList.map((example, i) => {
Expand Down
9 changes: 0 additions & 9 deletions packages/react-native-reanimated/src/PlatformChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,3 @@ export function isWindowAvailable() {
// @ts-ignore Fallback if `window` is undefined.
return typeof window !== 'undefined';
}

export function isReducedMotion() {
return isWeb()
? isWindowAvailable()
? // @ts-ignore Fallback if `window` is undefined.
!window.matchMedia('(prefers-reduced-motion: no-preference)').matches
: false
: !!(global as localGlobal)._REANIMATED_IS_REDUCED_MOTION;
}
25 changes: 25 additions & 0 deletions packages/react-native-reanimated/src/ReducedMotion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';
import { isWeb, isWindowAvailable } from './PlatformChecker';
import { makeMutable } from './mutables';

type localGlobal = typeof global & Record<string, unknown>;

export function isReducedMotionEnabledInSystem() {
return isWeb()
? isWindowAvailable()
? // @ts-ignore Fallback if `window` is undefined.
!window.matchMedia('(prefers-reduced-motion: no-preference)').matches
: false
: !!(global as localGlobal)._REANIMATED_IS_REDUCED_MOTION;
}

const IS_REDUCED_MOTION_ENABLED_IN_SYSTEM = isReducedMotionEnabledInSystem();

export const ReducedMotionManager = {
jsValue: IS_REDUCED_MOTION_ENABLED_IN_SYSTEM,
uiValue: makeMutable(IS_REDUCED_MOTION_ENABLED_IN_SYSTEM),
setEnabled(value: boolean) {
ReducedMotionManager.jsValue = value;
ReducedMotionManager.uiValue.value = value;
},
};
9 changes: 5 additions & 4 deletions packages/react-native-reanimated/src/animation/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ import {
subtractMatrices,
getRotationMatrix,
} from './transformationMatrix/matrixUtils';
import { isReducedMotion, shouldBeUseWeb } from '../PlatformChecker';
import { shouldBeUseWeb } from '../PlatformChecker';
import type { EasingFunction, EasingFunctionFactory } from '../Easing';
import { ReducedMotionManager } from '../ReducedMotion';

let IN_STYLE_UPDATER = false;
const IS_REDUCED_MOTION = isReducedMotion();
const SHOULD_BE_USE_WEB = shouldBeUseWeb();

if (__DEV__ && IS_REDUCED_MOTION) {
if (__DEV__ && ReducedMotionManager.jsValue) {
console.warn(
`[Reanimated] Reduced motion setting is enabled on this device. This warning is visible only in the development mode. Some animations will be disabled by default. You can override the behavior for individual animations, see https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#reduced-motion-setting-is-enabled-on-this-device.`
);
Expand Down Expand Up @@ -108,10 +108,11 @@ export function recognizePrefixSuffix(
* Returns whether the motion should be reduced for a specified config.
* By default returns the system setting.
*/
const isReduceMotionOnUI = ReducedMotionManager.uiValue;
export function getReduceMotionFromConfig(config?: ReduceMotion) {
'worklet';
return !config || config === ReduceMotion.System
? IS_REDUCED_MOTION
? isReduceMotionOnUI.value
: config === ReduceMotion.Always;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';
import { useEffect } from 'react';
import { ReduceMotion } from '../commonTypes';
import {
ReducedMotionManager,
isReducedMotionEnabledInSystem,
} from '../ReducedMotion';

/**
* A component that lets you overwrite default reduce motion behavior globally in your application.
*
* @param mode - Determines default reduce motion behavior globally in your application. Configured with {@link ReduceMotion} enum.
* @see https://docs.swmansion.com/react-native-reanimated/docs/components/ReduceMotionConfig
*/
export function ReducedMotionConfig({ mode }: { mode: ReduceMotion }) {
useEffect(() => {
if (!__DEV__) {
return;
}
console.warn(
`[Reanimated] Reduced motion setting is overwritten with mode '${mode}'.`
);
}, []);

useEffect(() => {
const wasEnabled = ReducedMotionManager.jsValue;
switch (mode) {
case ReduceMotion.System:
ReducedMotionManager.setEnabled(isReducedMotionEnabledInSystem());
break;
case ReduceMotion.Always:
ReducedMotionManager.setEnabled(true);
break;
case ReduceMotion.Never:
ReducedMotionManager.setEnabled(false);
break;
}
return () => {
ReducedMotionManager.setEnabled(wasEnabled);
};
}, [mode]);

return null;
}
6 changes: 3 additions & 3 deletions packages/react-native-reanimated/src/hook/useReducedMotion.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';
import { isReducedMotion } from '../PlatformChecker';
import { isReducedMotionEnabledInSystem } from '../ReducedMotion';

const IS_REDUCED_MOTION = isReducedMotion();
const IS_REDUCED_MOTION_ENABLED_IN_SYSTEM = isReducedMotionEnabledInSystem();

/**
* Lets you query the reduced motion system setting.
Expand All @@ -12,5 +12,5 @@ const IS_REDUCED_MOTION = isReducedMotion();
* @see https://docs.swmansion.com/react-native-reanimated/docs/device/useReducedMotion
*/
export function useReducedMotion() {
return IS_REDUCED_MOTION;
return IS_REDUCED_MOTION_ENABLED_IN_SYSTEM;
}
1 change: 1 addition & 0 deletions packages/react-native-reanimated/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ export {
export { LayoutAnimationConfig } from './component/LayoutAnimationConfig';
export { PerformanceMonitor } from './component/PerformanceMonitor';
export type { PerformanceMonitorProps } from './component/PerformanceMonitor';
export { ReducedMotionConfig } from './component/ReducedMotionConfig';
export type {
Adaptable,
AdaptTransforms,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import { scheduleAnimationCleanup } from './domUtils';
import { _updatePropsJS } from '../../js-reanimated';
import type { ReanimatedHTMLElement } from '../../js-reanimated';
import { ReduceMotion } from '../../commonTypes';
import { isReducedMotion } from '../../PlatformChecker';
import { LayoutAnimationType } from '../animationBuilder/commonTypes';
import type { ReanimatedSnapshot, ScrollOffsets } from './componentStyle';
import { setElementPosition, snapshots } from './componentStyle';
import { Keyframe } from '../animationBuilder';
import { ReducedMotionManager } from '../../ReducedMotion';

function getEasingFromConfig(config: CustomConfig): string {
const easingName =
Expand Down Expand Up @@ -51,7 +51,7 @@ function getDelayFromConfig(config: CustomConfig): number {

export function getReducedMotionFromConfig(config: CustomConfig) {
if (!config.reduceMotionV) {
return isReducedMotion();
return ReducedMotionManager.jsValue;
}

switch (config.reduceMotionV) {
Expand All @@ -60,7 +60,7 @@ export function getReducedMotionFromConfig(config: CustomConfig) {
case ReduceMotion.Always:
return true;
default:
return isReducedMotion();
return ReducedMotionManager.jsValue;
}
}

Expand Down