diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx index de9b8914..b09bc96b 100644 --- a/src/components/bottomSheet/BottomSheet.tsx +++ b/src/components/bottomSheet/BottomSheet.tsx @@ -22,8 +22,9 @@ import Animated, { useWorkletCallback, type WithSpringConfig, type WithTimingConfig, + useReducedMotion, + ReduceMotion, } from 'react-native-reanimated'; -// import BottomSheetDebugView from '../bottomSheetDebugView'; import { ANIMATION_SOURCE, ANIMATION_STATE, @@ -56,6 +57,7 @@ import { import BottomSheetBackdropContainer from '../bottomSheetBackdropContainer'; import BottomSheetBackgroundContainer from '../bottomSheetBackgroundContainer'; import BottomSheetContainer from '../bottomSheetContainer'; +// import BottomSheetDebugView from '../bottomSheetDebugView'; import BottomSheetDraggableView from '../bottomSheetDraggableView'; import BottomSheetFooterContainer from '../bottomSheetFooterContainer/BottomSheetFooterContainer'; import BottomSheetGestureHandlersProvider from '../bottomSheetGestureHandlersProvider'; @@ -106,6 +108,7 @@ const BottomSheetComponent = forwardRef( enablePanDownToClose = DEFAULT_ENABLE_PAN_DOWN_TO_CLOSE, enableDynamicSizing = DEFAULT_DYNAMIC_SIZING, overDragResistanceFactor = DEFAULT_OVER_DRAG_RESISTANCE_FACTOR, + overrideReduceMotion: _providedOverrideReduceMotion, // styles style: _providedStyle, @@ -318,6 +321,13 @@ const BottomSheetComponent = forwardRef( shouldHandleKeyboardEvents, } = useKeyboard(); const animatedKeyboardHeightInContainer = useSharedValue(0); + const userReduceMotionSetting = useReducedMotion(); + const reduceMotion = useMemo(() => { + return !_providedOverrideReduceMotion || + _providedOverrideReduceMotion === ReduceMotion.System + ? userReduceMotionSetting + : _providedOverrideReduceMotion === ReduceMotion.Always; + }, [userReduceMotionSetting, _providedOverrideReduceMotion]); //#endregion //#region state/dynamic variables @@ -640,9 +650,8 @@ const BottomSheetComponent = forwardRef( isAnimatedOnMount.value = true; } - isForcedClosing.value = false; - // reset values + isForcedClosing.value = false; animatedAnimationSource.value = ANIMATION_SOURCE.NONE; animatedAnimationState.value = ANIMATION_STATE.STOPPED; animatedNextPosition.value = INITIAL_VALUE; @@ -717,10 +726,15 @@ const BottomSheetComponent = forwardRef( point: position, configs: configs || _providedAnimationConfigs, velocity, + overrideReduceMotion: _providedOverrideReduceMotion, onComplete: animateToPositionCompleted, }); }, - [handleOnAnimate, _providedAnimationConfigs] + [ + handleOnAnimate, + _providedAnimationConfigs, + _providedOverrideReduceMotion, + ] ); /** * Set to position without animation. @@ -963,6 +977,16 @@ const BottomSheetComponent = forwardRef( animatedAnimationState.value !== ANIMATION_STATE.RUNNING && animatedCurrentIndex.value === -1 ) { + /** + * early exit if reduce motion is enabled and index is out of sync with position. + */ + if ( + reduceMotion && + animatedSnapPoints.value[animatedIndex.value] !== + animatedPosition.value + ) { + return; + } setToPosition(animatedClosedPosition.value); return; } @@ -987,7 +1011,7 @@ const BottomSheetComponent = forwardRef( animationConfigs ); }, - [getEvaluatedPosition, animateToPosition, setToPosition] + [getEvaluatedPosition, animateToPosition, setToPosition, reduceMotion] ); //#endregion @@ -1469,12 +1493,14 @@ const BottomSheetComponent = forwardRef( height: animate({ point: animatedContentHeightMax.value, configs: _providedAnimationConfigs, + overrideReduceMotion: _providedOverrideReduceMotion, }), }; }, [ enableDynamicSizing, animatedContentHeight.value, animatedContentHeightMax.value, + _providedOverrideReduceMotion, _providedAnimationConfigs, ]); const contentContainerStyle = useMemo( @@ -1771,6 +1797,19 @@ const BottomSheetComponent = forwardRef( return; } + /** + * exit the method if the animated index is out of sync with the + * animated position. this happened when the user enable reduce + * motion setting only. + */ + if ( + reduceMotion && + _animatedIndex === animatedCurrentIndex.value && + animatedSnapPoints.value[_animatedIndex] !== _animatedPosition + ) { + return; + } + /** * if the index is not equal to the current index, * than the sheet position had changed and we trigger @@ -1811,7 +1850,7 @@ const BottomSheetComponent = forwardRef( runOnJS(_providedOnClose)(); } }, - [handleOnChange, _providedOnClose] + [reduceMotion, handleOnChange, _providedOnClose] ); /** diff --git a/src/components/bottomSheet/types.d.ts b/src/components/bottomSheet/types.d.ts index ddeb3914..254f0155 100644 --- a/src/components/bottomSheet/types.d.ts +++ b/src/components/bottomSheet/types.d.ts @@ -3,6 +3,7 @@ import type { Insets, StyleProp, ViewStyle } from 'react-native'; import type { PanGesture } from 'react-native-gesture-handler'; import type { AnimateStyle, + ReduceMotion, SharedValue, WithSpringConfig, WithTimingConfig, @@ -94,6 +95,16 @@ export interface BottomSheetProps * @default true */ animateOnMount?: boolean; + /** + * To override the user reduce motion setting. + * - `ReduceMotion.System`: if the `Reduce motion` accessibility setting is enabled on the device, disable the animation. + * - `ReduceMotion.Always`: disable the animation, even if `Reduce motion` accessibility setting is not enabled. + * - `ReduceMotion.Never`: enable the animation, even if `Reduce motion` accessibility setting is enabled. + * @type ReduceMotion + * @see https://docs.swmansion.com/react-native-reanimated/docs/guides/accessibility + * @default ReduceMotion.System + */ + overrideReduceMotion?: ReduceMotion; //#endregion //#region layout diff --git a/src/utilities/animate.ts b/src/utilities/animate.ts index e7ca4850..b1f6fb43 100644 --- a/src/utilities/animate.ts +++ b/src/utilities/animate.ts @@ -1,6 +1,6 @@ import { type AnimationCallback, - ReduceMotion, + type ReduceMotion, type WithSpringConfig, type WithTimingConfig, withSpring, @@ -12,6 +12,7 @@ interface AnimateParams { point: number; velocity?: number; configs?: WithSpringConfig | WithTimingConfig; + overrideReduceMotion?: ReduceMotion; onComplete?: AnimationCallback; } @@ -19,6 +20,7 @@ export const animate = ({ point, configs, velocity = 0, + overrideReduceMotion, onComplete, }: AnimateParams) => { 'worklet'; @@ -30,7 +32,11 @@ export const animate = ({ // Users might have an accessibility setting to reduce motion turned on. // This prevents the animation from running when presenting the sheet, which results in // the bottom sheet not even appearing so we need to override it to ensure the animation runs. - configs.reduceMotion = ReduceMotion.Never; + // configs.reduceMotion = ReduceMotion.Never; + + if (overrideReduceMotion) { + configs.reduceMotion = overrideReduceMotion; + } // detect animation type const type = diff --git a/website/docs/props.md b/website/docs/props.md index c700d11a..209bd258 100644 --- a/website/docs/props.md +++ b/website/docs/props.md @@ -109,6 +109,18 @@ This will initially mount the sheet closed and when it's mounted and calculated | ------- | ------- | -------- | | boolean | true | NO | +### overrideReduceMotion + +To override the user reduce motion accessibility setting, [read more](https://docs.swmansion.com/react-native-reanimated/docs/guides/accessibility). + +- `ReduceMotion.System`: if the `Reduce motion` accessibility setting is enabled on the device, disable the animation. +- `ReduceMotion.Always`: disable the animation, even if `Reduce motion` accessibility setting is not enabled. +- `ReduceMotion.Never`: enable the animation, even if `Reduce motion` accessibility setting is enabled. + +| type | default | required | +| ------- | ------- | -------- | +| ReduceMotion | ReduceMotion.System | NO | + ## Styles ### style