import React, { forwardRef, useMemo } from 'react'; import { Pressable, View } from 'react-native'; import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated'; import { switchStyles } from './Switch.styles'; import Text from '../Text'; import type { SwitchProps } from './types'; import { getNativeSelectionAccessibilityProps } from '../utils/accessibility'; import type { IdealystElement } from '../utils/refTypes'; const Switch = forwardRef(({ checked = false, onChange, disabled = false, error, helperText, label, labelPosition = 'right', intent = 'primary', size = 'md', // Spacing variants from FormInputStyleProps margin, marginVertical, marginHorizontal, style, testID, id, // Accessibility props accessibilityLabel, accessibilityHint, accessibilityDisabled, accessibilityHidden, accessibilityRole, accessibilityLabelledBy, accessibilityDescribedBy, accessibilityChecked, }, ref) => { // Derive hasError from error prop const hasError = Boolean(error); // Determine if we need a wrapper (when error or helperText is present) const needsWrapper = Boolean(error) || Boolean(helperText); const showFooter = Boolean(error) || Boolean(helperText); switchStyles.useVariants({ size, disabled, checked, hasError, intent, labelPosition, margin, marginVertical, marginHorizontal, }); // Static style references (variants resolved by useVariants above) const switchContainerStyle = switchStyles.switchContainer as any; const switchTrackStyle = switchStyles.switchTrack as any; const containerStyle = switchStyles.container as any; const labelStyle = switchStyles.label as any; const wrapperStyle = switchStyles.wrapper as any; const helperTextStyle = switchStyles.helperText as any; const progress = useSharedValue(checked ? 1 : 0); React.useEffect(() => { progress.value = withSpring(checked ? 1 : 0, { damping: 40, stiffness: 200, }); }, [checked, progress]); const handlePress = () => { if (!disabled && onChange) { onChange(!checked); } }; // Generate native accessibility props const nativeA11yProps = useMemo(() => { const computedLabel = accessibilityLabel ?? label; const computedChecked = accessibilityChecked ?? checked; return getNativeSelectionAccessibilityProps({ accessibilityLabel: computedLabel, accessibilityHint, accessibilityDisabled: accessibilityDisabled ?? disabled, accessibilityHidden, accessibilityRole: accessibilityRole ?? 'switch', accessibilityLabelledBy, accessibilityDescribedBy, accessibilityChecked: computedChecked, }); }, [ accessibilityLabel, label, accessibilityHint, accessibilityDisabled, disabled, accessibilityHidden, accessibilityRole, accessibilityLabelledBy, accessibilityDescribedBy, accessibilityChecked, checked, ]); const getThumbDistance = () => { if (size === 'sm') return 16; if (size === 'lg') return 24; return 20; }; // Native-specific thumb styles const getThumbSize = () => { if (size === 'sm') return 16; if (size === 'lg') return 24; return 20; }; const getTrackHeight = () => { if (size === 'sm') return 20; if (size === 'lg') return 28; return 24; }; const thumbSize = getThumbSize(); const thumbDistance = getThumbDistance(); const trackHeight = getTrackHeight(); const thumbTop = (trackHeight - thumbSize) / 2; const thumbAnimatedStyle = useAnimatedStyle(() => { return { transform: [ { translateX: progress.value * thumbDistance + 2, }, ], }; }); const switchElement = ( ); // The switch + label row const switchWithLabel = label ? ( {labelPosition === 'left' && ( {label} )} {switchElement} {labelPosition === 'right' && ( {label} )} ) : switchElement; // If wrapper needed for error/helperText if (needsWrapper) { return ( {switchWithLabel} {showFooter && ( {error && {error}} {!error && helperText && {helperText}} )} ); } return switchWithLabel; }); Switch.displayName = 'Switch'; export default Switch;