import React from 'react'; import type { ViewStyle } from 'react-native'; import { resolveFieldState, resolveIndicatorSize, resolveSelectionControlColors, resolveSelectionControlNextChecked, } from '../../../../../internal/resolvers'; import { useControllableState } from '../../../../../internal/useControllableState'; import type { CheckboxProps } from '../../../../../types/checkbox'; import type { InteractionState } from '../../../../../types/pressable'; import type { SurfaceTheme } from '../../../../../types/theme'; import { View } from '../../../../layout/public'; import type { PressableProps } from '../../../../pressable/public'; import { Pressable } from '../../../../pressable/public'; import { useTheme } from '../../../../theme/runtime'; import { Text } from '../../../../typography/public'; /*** Renders a controlled or uncontrolled accessible checkbox. */ export function Checkbox({ accessibilityLabel, checked, children, color = 'primary', defaultChecked = false, disabled = false, invalid = false, onCheckedChange, readOnly = false, size = 'm', testID, ...buttonProps }: CheckboxProps) { const { theme } = useTheme(); const [isChecked, setChecked] = useControllableState({ value: checked, defaultValue: defaultChecked, onChange: onCheckedChange, }); return ( {children} ); } /*** Owns the checkbox interaction boundary around resolved selection content. */ function CheckboxControl({ accessibilityLabel, buttonProps, children, color, disabled, invalid, isChecked, readOnly, setChecked, size, testID, theme, }: CheckboxControlProps) { const nextChecked = resolveSelectionControlNextChecked({ checked: isChecked, disabled, kind: 'checkbox', readOnly, }); return ( setChecked(nextChecked)} testID={testID} > {(interactionState) => renderCheckboxContent({ children, color, disabled, interactionState, invalid, isChecked, readOnly, size, theme, }) } ); } /*** Renders the checkbox indicator and optional label for one interaction state. */ function renderCheckboxContent(input: CheckboxContentInput) { const { interactionState, theme } = input; const fieldState = resolveFieldState({ disabled: input.disabled, focused: interactionState.focused, invalid: input.invalid, readOnly: input.readOnly, }); const colors = resolveSelectionControlColors(theme, { checked: input.isChecked, fieldState, hovered: interactionState.hovered, pressed: interactionState.pressed, color: input.color, }); const indicatorSize = resolveIndicatorSize(input.size); const labelEmphasis = colors.labelColor === theme.semantics.content.muted ? 'muted' : 'default'; const indicatorColor = fieldState.invalid ? 'error' : input.color; return ( {input.isChecked ? ( ✓ ) : null} {input.children ? ( {input.children} ) : null} ); } /*** Resolves the horizontal selection-control row style. */ function resolveSelectionRowStyle(opacity: number | undefined): ViewStyle { return { alignItems: 'center', flexDirection: 'row', opacity }; } /*** Resolves checkbox indicator dimensions and interaction colors. */ function resolveCheckboxIndicatorStyle( backgroundColor: string, borderColor: string, size: number, ): ViewStyle { return { alignItems: 'center', backgroundColor, borderColor, borderWidth: 1.5, height: size, justifyContent: 'center', width: size, }; } interface CheckboxControlProps { accessibilityLabel: CheckboxProps['accessibilityLabel']; buttonProps: PressableProps; children: CheckboxProps['children']; color: NonNullable; disabled: boolean; invalid: boolean; isChecked: boolean; readOnly: boolean; setChecked: (checked: boolean) => void; size: NonNullable; testID: CheckboxProps['testID']; theme: SurfaceTheme; } interface CheckboxContentInput { children: CheckboxProps['children']; color: NonNullable; disabled: boolean; interactionState: InteractionState; invalid: boolean; isChecked: boolean; readOnly: boolean; size: NonNullable; theme: SurfaceTheme; }