import { forwardRef, memo, PropsWithoutRef, useEffect, useMemo, useRef } from 'react'; import { Animated, StyleSheet, View } from 'react-native'; import type { ViewProps } from 'react-native'; import setColor from 'color'; import { TouchableRipple, type TouchableRippleProps } from '../TouchableRipple'; import { resolveStateVariant } from '../../utils'; import { StateLayer } from '../StateLayer'; import { ANIMATION_DURATION, radioButtonStyles } from './utils'; import { useActionState } from '../../hooks'; import { tokenStylesParser } from '../../utils/tokenStylesParser'; export type Props = Omit & { /** * Status of radio button. */ status?: 'checked' | 'unchecked'; /** * Whether radio is disabled. */ disabled?: boolean; /** * Custom color for unchecked radio. */ uncheckedColor?: string; /** * Custom color for radio. */ color?: string; /** * testID to be used on tests. */ testID?: string; /** * passed from RadioButton component */ checked: boolean; onPress: (() => void) | undefined; /** * props for the stateLayer */ stateLayerProps?: PropsWithoutRef; }; const BORDER_WIDTH = 2; const RadioButtonAndroid = ( { disabled = false, status, testID, color: colorProp, uncheckedColor: uncheckedColorProp, style, checked, onPress, stateLayerProps = {}, ...rest }: Props, ref: any, ) => { const { actionsRef, hovered } = useActionState({ ref, actionsToListen: ['hover'] }); const { current: borderAnim } = useRef(new Animated.Value(BORDER_WIDTH)); const { current: radioAnim } = useRef(new Animated.Value(1)); const isFirstRendering = useRef(true); const state = resolveStateVariant({ disabled, checkedAndHovered: checked && hovered, checked, hovered, }); radioButtonStyles.useVariants({ state: state as any, }); const { containerStyles, rippleColor, radioStyles, dotStyles, dotContainerStyles, stateLayerStyle, } = useMemo(() => { const _color = tokenStylesParser.getColor(checked ? colorProp : uncheckedColorProp); let _rippleColor: string | undefined; try { _rippleColor = setColor(_color).alpha(0.32).rgb().string(); } catch (e) { _rippleColor = undefined; } return { containerStyles: [radioButtonStyles.container, radioButtonStyles.root, style], rippleColor: _rippleColor, radioStyles: [ radioButtonStyles.radio, { borderWidth: borderAnim, }, tokenStylesParser.getColor(checked ? colorProp : uncheckedColorProp, 'borderColor'), ], dotContainerStyles: [StyleSheet.absoluteFill, radioButtonStyles.radioContainer], dotStyles: [ radioButtonStyles.dot, { transform: [{ scale: radioAnim }], }, tokenStylesParser.getColor( checked ? colorProp : uncheckedColorProp, 'backgroundColor', ), ], stateLayerStyle: [radioButtonStyles.stateLayer, stateLayerProps?.style], }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [ borderAnim, checked, colorProp, radioAnim, stateLayerProps?.style, uncheckedColorProp, style, state, ]); useEffect(() => { // Do not run animation on very first rendering if (isFirstRendering.current) { isFirstRendering.current = false; return; } if (status === 'checked') { radioAnim.setValue(1.2); Animated.timing(radioAnim, { toValue: 1, duration: ANIMATION_DURATION, useNativeDriver: true, }).start(); } else { borderAnim.setValue(10); Animated.timing(borderAnim, { toValue: BORDER_WIDTH, duration: ANIMATION_DURATION, useNativeDriver: false, }).start(); } }, [status, borderAnim, radioAnim]); return ( <> {checked ? ( ) : null} ); }; RadioButtonAndroid.displayName = 'RadioButton_Android'; export default memo(forwardRef(RadioButtonAndroid));