'use client' import { useFocusRing } from '@react-aria/focus' import { useHover } from '@react-aria/interactions' import React, { useCallback, useMemo, useState, type ElementType, type KeyboardEvent as ReactKeyboardEvent, type MouseEvent as ReactMouseEvent, type Ref, } from 'react' import { useActivePress } from '../../hooks/use-active-press' import { useControllable } from '../../hooks/use-controllable' import { useDisposables } from '../../hooks/use-disposables' import { useEvent } from '../../hooks/use-event' import { useId } from '../../hooks/use-id' import { useDisabled } from '../../internal/disabled' import { FormFields } from '../../internal/form-fields' import { useProvidedId } from '../../internal/id' import type { Props } from '../../types' import { isDisabledReactIssue7711 } from '../../utils/bugs' import { attemptSubmit } from '../../utils/form' import { forwardRefWithAs, mergeProps, render, type HasDisplayName, type RefProp, } from '../../utils/render' import { useDescribedBy } from '../description/description' import { Keys } from '../keyboard' import { useLabelledBy } from '../label/label' let DEFAULT_CHECKBOX_TAG = 'span' as const type CheckboxRenderPropArg = { checked: boolean changing: boolean focus: boolean active: boolean hover: boolean autofocus: boolean disabled: boolean indeterminate: boolean } type CheckboxPropsWeControl = | 'aria-checked' | 'aria-describedby' | 'aria-disabled' | 'aria-labelledby' | 'role' | 'tabIndex' export type CheckboxProps< TTag extends ElementType = typeof DEFAULT_CHECKBOX_TAG, TType = string, > = Props< TTag, CheckboxRenderPropArg, CheckboxPropsWeControl, { value?: TType disabled?: boolean indeterminate?: boolean checked?: boolean defaultChecked?: boolean autoFocus?: boolean form?: string name?: string onChange?: (checked: boolean) => void } > function CheckboxFn( props: CheckboxProps, ref: Ref ) { let internalId = useId() let providedId = useProvidedId() let providedDisabled = useDisabled() let { id = providedId || `headlessui-checkbox-${internalId}`, disabled = providedDisabled || false, autoFocus = false, checked: controlledChecked, defaultChecked = false, onChange: controlledOnChange, name, value, form, indeterminate = false, ...theirProps } = props let [checked, onChange] = useControllable(controlledChecked, controlledOnChange, defaultChecked) let labelledBy = useLabelledBy() let describedBy = useDescribedBy() let d = useDisposables() let [changing, setChanging] = useState(false) let toggle = useEvent(() => { setChanging(true) onChange?.(!checked) d.nextFrame(() => { setChanging(false) }) }) let handleClick = useEvent((event: ReactMouseEvent) => { if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault() event.preventDefault() toggle() }) let handleKeyUp = useEvent((event: ReactKeyboardEvent) => { if (event.key === Keys.Space) { event.preventDefault() toggle() } else if (event.key === Keys.Enter) { attemptSubmit(event.currentTarget) } }) // This is needed so that we can "cancel" the click event when we use the `Enter` key on a button. let handleKeyPress = useEvent((event: ReactKeyboardEvent) => event.preventDefault()) let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus }) let { isHovered: hover, hoverProps } = useHover({ isDisabled: disabled }) let { pressed: active, pressProps } = useActivePress({ disabled }) let ourProps = mergeProps( { ref, id, role: 'checkbox', 'aria-checked': indeterminate ? 'mixed' : checked ? 'true' : 'false', 'aria-labelledby': labelledBy, 'aria-describedby': describedBy, 'aria-disabled': disabled ? true : undefined, indeterminate: indeterminate ? 'true' : undefined, tabIndex: 0, onKeyUp: disabled ? undefined : handleKeyUp, onKeyPress: disabled ? undefined : handleKeyPress, onClick: disabled ? undefined : handleClick, }, focusProps, hoverProps, pressProps ) let slot = useMemo(() => { return { checked, disabled, hover, focus, active, indeterminate, changing, autofocus: autoFocus, } satisfies CheckboxRenderPropArg }, [checked, indeterminate, disabled, hover, focus, active, changing, autoFocus]) let reset = useCallback(() => { return onChange?.(defaultChecked) }, [onChange /* Explicitly ignoring `defaultChecked` */]) return ( <> {name != null && ( )} {render({ ourProps, theirProps, slot, defaultTag: DEFAULT_CHECKBOX_TAG, name: 'Checkbox', })} ) } // --- export interface _internal_ComponentCheckbox extends HasDisplayName { ( props: CheckboxProps & RefProp ): JSX.Element } export let Checkbox = forwardRefWithAs(CheckboxFn) as _internal_ComponentCheckbox