import { forwardRef, useEffect, useId, useState } from 'react' import { Check, Minus } from 'lucide-react' import type { CheckboxProps } from './types' import { CheckboxSize } from './types' import { getCheckboxIconColor, getCheckboxTextProps, getCheckboxSubtextProps, getCheckboxLabelStyles, getSubtextId, mergeAriaDescribedBy, } from './checkboxUtils' import { StyledCheckboxRoot, StyledCheckboxIndicator } from './StyledCheckbox' import Block from '../Primitives/Block/Block' import PrimitiveText from '../Primitives/PrimitiveText/PrimitiveText' import { Tooltip } from '../Tooltip/Tooltip' import type { CheckboxTokensType } from './checkbox.token' import { useResponsiveTokens } from '../../hooks/useResponsiveTokens' import { FOUNDATION_THEME } from '../../tokens' import { useErrorShake } from '../common/useErrorShake' import { getErrorShakeStyle } from '../common/error.animations' import { addPxToValue, getTruncatedText } from '../../global-utils/GlobalUtils' export const Checkbox = forwardRef( ( { label, id, name, checked, defaultChecked = false, onCheckedChange, disabled = false, required = false, error = false, size = CheckboxSize.MEDIUM, children, subtext, slot, maxLength, ...rest }, ref ) => { const tokens = useResponsiveTokens('CHECKBOX') const generatedId = useId() const uniqueId = id || generatedId const shouldShake = useErrorShake(error) const isControlled = checked !== undefined const [uncontrolledChecked, setUncontrolledChecked] = useState< boolean | 'indeterminate' >(defaultChecked ?? false) const resolvedChecked = isControlled ? checked : uncontrolledChecked useEffect(() => { if (!isControlled) { setUncontrolledChecked(defaultChecked ?? false) } }, [defaultChecked, isControlled]) const handleCheckedChange = ( nextChecked: boolean | 'indeterminate' ) => { if (!isControlled) { setUncontrolledChecked(nextChecked) } onCheckedChange?.(nextChecked) } const labelMaxLength = maxLength?.label const subtextMaxLength = maxLength?.subtext const { 'aria-describedby': customAriaDescribedBy, ...restProps } = rest as { 'aria-describedby'?: string; [key: string]: unknown } const subtextId = getSubtextId(uniqueId, !!subtext) const ariaAttributes = { 'aria-required': required ? true : undefined, 'aria-invalid': error ? true : undefined, 'aria-describedby': mergeAriaDescribedBy( subtextId, customAriaDescribedBy ), } return ( {children && ( {slot && ( {slot} )} {subtext && ( {subtext} )} )} ) } ) const CheckboxIndicator: React.FC<{ checked: boolean | 'indeterminate' size: CheckboxSize tokens: CheckboxTokensType disabled: boolean }> = ({ checked, size, tokens, disabled }) => ( ) const CheckboxContent: React.FC<{ uniqueId: string disabled: boolean error: boolean required: boolean size: CheckboxSize children?: React.ReactNode tokens: CheckboxTokensType maxLength?: number }> = ({ uniqueId, disabled, error, required, size, children, tokens, maxLength, }) => { if (!children) return null const labelStyles = getCheckboxLabelStyles(disabled) const textProps = getCheckboxTextProps(tokens, size, disabled, error) const isStringChild = typeof children === 'string' || typeof children === 'number' const truncation = isStringChild ? getTruncatedText(String(children), maxLength) : null const content = ( ) return truncation?.isTruncated ? ( {content} ) : ( content ) } const CheckboxSubtext: React.FC<{ id?: string size: CheckboxSize disabled: boolean error: boolean tokens: CheckboxTokensType children: React.ReactNode maxLength?: number }> = ({ id, size, disabled, error, tokens, children, maxLength }) => { const subtextProps = getCheckboxSubtextProps(tokens, size, disabled, error) const isStringLike = typeof children === 'string' || typeof children === 'number' const truncation = isStringLike ? getTruncatedText(String(children), maxLength) : null const content = ( {truncation?.isTruncated ? truncation.truncatedValue : children} ) return truncation?.isTruncated ? ( {content} ) : ( content ) } Checkbox.displayName = 'Checkbox'