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;