import { useState, useEffect, forwardRef, useMemo, memo } from 'react'; import { View, Text, Pressable } from 'react-native'; import MaterialDesignIcons from '@react-native-vector-icons/material-design-icons'; import { useUnistyles } from 'react-native-unistyles'; import type { Intent, Theme } from '@idealyst/theme'; import { CheckboxProps } from './types'; import { checkboxStyles } from './Checkbox.styles'; import { getNativeSelectionAccessibilityProps } from '../utils/accessibility'; import type { IdealystElement } from '../utils/refTypes'; /** * Isolated checkmark icon component. * Uses the resolved checkmark style as the single source of truth for sizing, * reducing rerenders on the parent Checkbox component. */ const CheckmarkIcon = memo(({ indeterminate, checked, intent }: { indeterminate: boolean; checked: boolean; intent: Intent }) => { const { theme }: { theme: Theme } = useUnistyles(); const checkmarkStyle = (checkboxStyles.checkmark as any)({ checked }); const iconSize = (typeof checkmarkStyle?.width === 'number' ? checkmarkStyle.width : 14); const iconColor = theme.intents[intent].contrast; return ( ); }); CheckmarkIcon.displayName = 'CheckmarkIcon'; const Checkbox = forwardRef(({ checked = false, indeterminate = false, disabled = false, onChange, size = 'md', intent = 'primary', variant = 'default', label, children, // Spacing variants from FormInputStyleProps margin, marginVertical, marginHorizontal, style, testID, required: _required = false, error, helperText, id, // Accessibility props accessibilityLabel, accessibilityHint, accessibilityDisabled, accessibilityHidden, accessibilityRole, accessibilityLabelledBy, accessibilityDescribedBy, accessibilityChecked, }, ref) => { const [internalChecked, setInternalChecked] = useState(checked); useEffect(() => { setInternalChecked(checked); }, [checked]); const handlePress = () => { if (disabled) return; const newChecked = !internalChecked; setInternalChecked(newChecked); onChange?.(newChecked); }; // Generate native accessibility props const nativeA11yProps = useMemo(() => { const labelContent = children || label; const computedLabel = accessibilityLabel ?? (typeof labelContent === 'string' ? labelContent : undefined); const computedChecked = accessibilityChecked ?? (indeterminate ? 'mixed' : internalChecked); return getNativeSelectionAccessibilityProps({ accessibilityLabel: computedLabel, accessibilityHint: accessibilityHint ?? (error || helperText), accessibilityDisabled: accessibilityDisabled ?? disabled, accessibilityHidden, accessibilityRole: accessibilityRole ?? 'checkbox', accessibilityLabelledBy, accessibilityDescribedBy, accessibilityChecked: computedChecked, }); }, [ accessibilityLabel, children, label, accessibilityHint, error, helperText, accessibilityDisabled, disabled, accessibilityHidden, accessibilityRole, accessibilityLabelledBy, accessibilityDescribedBy, accessibilityChecked, indeterminate, internalChecked, ]); // Apply variants checkboxStyles.useVariants({ size, intent, type: variant as any, checked: internalChecked, disabled, visible: internalChecked || indeterminate, error: !!error, margin, marginVertical, marginHorizontal, }); const labelContent = children || label; const displayHelperText = error || helperText; // Get dynamic styles - call as functions for theme reactivity const wrapperStyle = (checkboxStyles.wrapper as any)({}); const containerStyle = (checkboxStyles.container as any)({}); const checkboxStyle = (checkboxStyles.checkbox as any)({ intent, checked: internalChecked, disabled, type: variant }); const labelStyle = (checkboxStyles.label as any)({ disabled }); const helperTextStyle = (checkboxStyles.helperText as any)({ error: !!error }); return ( {(internalChecked || indeterminate) && ( )} {labelContent && ( {labelContent} )} {displayHelperText && ( {error || helperText} )} ); }); Checkbox.displayName = 'Checkbox'; export default Checkbox;