11import * as React from 'react' ;
2- import { Animated , StyleSheet , View } from 'react-native' ;
2+ import { StyleSheet , View } from 'react-native' ;
3+
4+ import Animated , {
5+ Easing ,
6+ ReduceMotion ,
7+ useAnimatedStyle ,
8+ useSharedValue ,
9+ withTiming ,
10+ } from 'react-native-reanimated' ;
311
412import { RadioButtonContext } from './RadioButtonGroup' ;
513import { RadioButtonTokens } from './tokens' ;
614import { getSelectionControlColor , handlePress , isChecked } from './utils' ;
715import { useInternalTheme } from '../../core/theming' ;
16+ import { useReduceMotion } from '../../theme/accessibility/ReduceMotionContext' ;
817import type { $RemoveChildren , ThemeProp } from '../../types' ;
918import TouchableRipple from '../TouchableRipple/TouchableRipple' ;
1019
@@ -104,17 +113,30 @@ const RadioButton = ({
104113 value,
105114 } ) === 'checked' ;
106115
116+ const reduceMotion = useReduceMotion ( ) ;
117+ const reanimatedReduceMotion = reduceMotion
118+ ? ReduceMotion . Always
119+ : ReduceMotion . Never ;
120+
107121 // Single selection animation path: the dot scales in (with a slight
108122 // overshoot) when the radio becomes checked. The ring outline stays a
109123 // constant width. Keyed on `checked` (not `status`) so radios driven by a
110124 // `RadioButton.Group` animate too.
111- const { current : radioAnim } = React . useRef < Animated . Value > (
112- new Animated . Value ( 1 )
113- ) ;
114-
125+ const dotScale = useSharedValue ( 1 ) ;
115126 const isFirstRendering = React . useRef < boolean > ( true ) ;
116127
117- const { scale } = theme . animation ;
128+ const dotTimingConfig = React . useMemo (
129+ ( ) => ( {
130+ duration : theme . motion . duration . short3 ,
131+ easing : Easing . bezier ( ...theme . motion . easing . standard ) ,
132+ reduceMotion : reanimatedReduceMotion ,
133+ } ) ,
134+ [
135+ theme . motion . duration . short3 ,
136+ theme . motion . easing . standard ,
137+ reanimatedReduceMotion ,
138+ ]
139+ ) ;
118140
119141 React . useEffect ( ( ) => {
120142 // Do not run animation on very first rendering
@@ -124,15 +146,15 @@ const RadioButton = ({
124146 }
125147
126148 if ( checked ) {
127- radioAnim . setValue ( 1.2 ) ;
128-
129- Animated . timing ( radioAnim , {
130- toValue : 1 ,
131- duration : 150 * scale ,
132- useNativeDriver : true ,
133- } ) . start ( ) ;
149+ // Jump to the overshoot value, then settle back to 1.
150+ dotScale . value = 1.2 ;
151+ dotScale . value = withTiming ( 1 , dotTimingConfig ) ;
134152 }
135- } , [ checked , radioAnim , scale ] ) ;
153+ } , [ checked , dotScale , dotTimingConfig ] ) ;
154+
155+ const dotAnimatedStyle = useAnimatedStyle ( ( ) => ( {
156+ transform : [ { scale : dotScale . value } ] ,
157+ } ) ) ;
136158
137159 const { selectionControlColor, selectionControlOpacity } =
138160 getSelectionControlColor ( {
@@ -188,10 +210,8 @@ const RadioButton = ({
188210 < Animated . View
189211 style = { [
190212 styles . dot ,
191- {
192- backgroundColor : selectionControlColor ,
193- transform : [ { scale : radioAnim } ] ,
194- } ,
213+ { backgroundColor : selectionControlColor } ,
214+ dotAnimatedStyle ,
195215 ] }
196216 />
197217 </ View >
0 commit comments