import { useRef, useEffect, useMemo, memo, forwardRef, useCallback, PropsWithoutRef } from 'react'; import { Animated, Platform, View, type ViewProps } from 'react-native'; import setColor from 'color'; import { StyleSheet } from 'react-native-unistyles'; import type { CheckBoxBaseProps, States } from './types'; import { resolveStateVariant } from '../../utils'; import { TouchableRipple } from '../TouchableRipple'; import { Icon } from '../Icon'; import { StateLayer } from '../StateLayer'; import { styles, iconSizeMap } from './utils'; import { useActionState } from '../../hooks'; import { tokenStylesParser } from '../../utils/tokenStylesParser'; export type Props = Omit & { value: boolean; /** * props for the stateLayer */ stateLayerProps?: PropsWithoutRef; }; const CheckboxAndroid = ( { value: checked, indeterminate, disabled = false, size = 'md', onChange: onChangeProp, testID, style, color: colorProp, uncheckedColor: uncheckedColorProp, stateLayerProps = {}, ...rest }: Props, ref: any, ) => { const { current: scaleAnim } = useRef(new Animated.Value(1)); const isFirstRendering = useRef(true); const { actionsRef, hovered } = useActionState({ ref, actionsToListen: ['hover'] }); const state = resolveStateVariant({ disabled, checkedAndHovered: checked && !indeterminate && hovered, checked: checked && !indeterminate, hovered, }); styles.useVariants({ variant: 'android', state: state as States, size, }); // const componentStyles = useComponentStyles('Checkbox', style, { // variant: 'android', // state: resolveStateVariant({ // disabled, // checkedAndHovered: checked && !indeterminate && hovered, // checked: checked && !indeterminate, // hovered, // }), // size, // }); const borderWidth = scaleAnim.interpolate({ inputRange: [0.8, 1], outputRange: [7, 0], }); const { iconSize, rippleColor, scale, animationDuration, rippleContainerStyles, filledContainerStyles, animatedContainerStyles, animatedFillStyles, stateLayerStyle, iconStyle, } = useMemo(() => { // const { // color: checkedColor, // uncheckedColor, // animationScale: _scale, // animationDuration: _animationDuration, // iconSize: _iconSize, // padding, // width, // height, // borderRadius, // ...checkboxStyles // // @ts-ignore // } = styles.root; const _color = tokenStylesParser.getColor(checked ? colorProp : uncheckedColorProp); return { iconStyle: [styles.icon, _color], iconSize: iconSizeMap[size], // TODO - fix this on web rippleColor: Platform.OS === 'web' ? undefined : setColor(_color).fade(0.32).rgb().string(), checkboxStyle: [styles.root, style], scale: 1, animationDuration: 100, rippleContainerStyles: [styles.root], animatedContainerStyles: { transform: [{ scale: scaleAnim }] }, filledContainerStyles: [StyleSheet.absoluteFill, styles.fillContainer], // for toggle animation // This needs to be computed because it's opinionated animation animatedFillStyles: [ styles.animatedFill, // 4 because padding - border(which is 1px each side) tokenStylesParser.getColor(checked ? colorProp : uncheckedColorProp, 'borderColor'), { borderWidth }, ], stateLayerStyle: [styles.stateLayer, stateLayerProps?.style], }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [ borderWidth, checked, colorProp, scaleAnim, stateLayerProps?.style, style, uncheckedColorProp, state, size, ]); useEffect(() => { // Do not run animation on very first rendering if (isFirstRendering.current) { isFirstRendering.current = false; return; } Animated.sequence([ Animated.timing(scaleAnim, { toValue: 0.85, duration: checked ? animationDuration * scale : 0, useNativeDriver: false, }), Animated.timing(scaleAnim, { toValue: 1, duration: checked ? animationDuration * scale : animationDuration * scale * 1.75, useNativeDriver: false, }), ]).start(); }, [checked, scaleAnim, scale, animationDuration]); const onChange = useCallback(() => { onChangeProp?.(!checked); }, [checked, onChangeProp]); const icon = indeterminate ? 'minus-box' : checked ? 'checkbox-marked' : 'checkbox-blank-outline'; const accessibilityState = useMemo(() => ({ disabled, checked }), [checked, disabled]); return ( <> ); }; // const styles = StyleSheet.create({ // fillContainer: { // alignItems: 'center', // justifyContent: 'center', // }, // }); export default memo(forwardRef(CheckboxAndroid));