import { createContext, useContext, useMemo, type InputHTMLAttributes, type ReactNode, type Ref, } from 'react'; import { cx } from '../utils/cx'; import type { Size } from '../utils/types'; import { describedBy, useFieldIds } from './Field'; import { Icon } from './Icon'; interface ToggleShellProps { label?: ReactNode; description?: ReactNode; error?: ReactNode; size?: Exclude; /** Puts the control after the label, e.g. in settings rows. */ labelPosition?: 'end' | 'start'; } function ToggleRow({ ids, className, size, labelPosition, disabled, control, label, description, error, }: { ids: ReturnType; className?: string; size: 'sm' | 'md'; labelPosition: 'end' | 'start'; disabled?: boolean; control: ReactNode; label?: ReactNode; description?: ReactNode; error?: ReactNode; }) { return (
{/* The label wraps only the control and its name so the accessible name never absorbs the description text. */} {description && (

{description}

)} {error && (

{error}

)}
); } export interface CheckboxProps extends Omit, 'size' | 'type'>, ToggleShellProps { /** Renders the mixed state; pairs with `aria-checked="mixed"`. */ indeterminate?: boolean; ref?: Ref; } export function Checkbox({ label, description, error, size = 'md', labelPosition = 'end', indeterminate = false, className, id, disabled, 'aria-describedby': ariaDescribedBy, ...rest }: CheckboxProps) { const ids = useFieldIds(id); return ( } /> ); } export interface RadioProps extends Omit, 'size' | 'type'>, ToggleShellProps { ref?: Ref; } const RadioGroupContext = createContext<{ name?: string } | null>(null); export function Radio({ label, description, error, size = 'md', labelPosition = 'end', className, id, name, disabled, 'aria-describedby': ariaDescribedBy, ...rest }: RadioProps) { const ids = useFieldIds(id); const group = useContext(RadioGroupContext); return (