import { forwardRef, useCallback, useMemo } from 'react'; import { useIsOnSurface } from '../../helpers/external/hooks'; import { AnimationSettingsProvider, FormFieldProvider, } from '../../helpers/internal/contexts'; import { childrenToString } from '../../helpers/internal/utils'; import * as RadioGroupPrimitives from '../../primitives/radio-group'; import Label from '../label/label'; import Radio from '../radio/radio'; import { useRadioGroupRootAnimation } from './radio-group.animation'; import { DISPLAY_NAME } from './radio-group.constants'; import { RadioGroupItemProvider } from './radio-group.context'; import { radioGroupClassNames } from './radio-group.styles'; import type { RadioGroupItemProps, RadioGroupItemRenderProps, RadioGroupProps, } from './radio-group.types'; const useRadioGroup = RadioGroupPrimitives.useRadioGroupContext; // -------------------------------------------------- const RadioGroupRoot = forwardRef< RadioGroupPrimitives.RootRef, RadioGroupProps >((props, ref) => { const { className, isDisabled = false, isInvalid = false, animation, ...restProps } = props; const rootClassName = radioGroupClassNames.root({ className, }); const { isAllAnimationsDisabled } = useRadioGroupRootAnimation({ animation, }); const animationSettingsContextValue = useMemo( () => ({ isAllAnimationsDisabled, }), [isAllAnimationsDisabled] ); const formFieldContextValue = useMemo( () => ({ isDisabled, isInvalid, isRequired: false, hasFieldPadding: false, }), [isDisabled, isInvalid] ); return ( ); }); // -------------------------------------------------- const RadioGroupItem = forwardRef< RadioGroupPrimitives.ItemRef, RadioGroupItemProps >((props, ref) => { const { children, value, isDisabled, isInvalid, variant, className, ...restProps } = props; const stringifiedChildren = typeof children === 'function' ? null : childrenToString(children as React.ReactNode); const { value: groupValue, onValueChange: groupOnValueChange, isInvalid: groupIsInvalid, isDisabled: groupIsDisabled, variant: groupVariant, } = useRadioGroup(); const isSelected = groupValue === value; const isDisabledValue = isDisabled ?? groupIsDisabled ?? false; const isInvalidValue = isInvalid ?? groupIsInvalid ?? false; /** Selects this item in the group (radio behavior: always selects, never deselects) */ const handleSelectedChange = useCallback(() => { groupOnValueChange(value); }, [groupOnValueChange, value]); const isOnSurfaceAutoDetected = useIsOnSurface(); const finalVariant = variant !== undefined ? variant : groupVariant !== undefined ? groupVariant : isOnSurfaceAutoDetected ? 'secondary' : 'primary'; const itemClassName = radioGroupClassNames.item({ className, }); const renderProps: RadioGroupItemRenderProps = { isSelected, isDisabled: isDisabledValue, isInvalid: isInvalidValue, }; const content = stringifiedChildren ? ( <> ) : typeof children === 'function' ? ( children(renderProps) ) : ( children ); const contextValue = useMemo( () => ({ isSelected, isDisabled: isDisabledValue, isInvalid: isInvalidValue, variant: finalVariant, onSelectedChange: handleSelectedChange, }), [ isSelected, isDisabledValue, isInvalidValue, finalVariant, handleSelectedChange, ] ); const formFieldContextValue = useMemo( () => ({ isDisabled: isDisabledValue, isInvalid: isInvalidValue, isRequired: false, hasFieldPadding: false, }), [isDisabledValue, isInvalidValue] ); return ( {content} ); }); RadioGroupRoot.displayName = DISPLAY_NAME.RADIO_GROUP_ROOT; RadioGroupItem.displayName = DISPLAY_NAME.RADIO_GROUP_ITEM; /** * Compound RadioGroup component with sub-components. * * @component RadioGroup - Container that manages the selection state of RadioGroupItem components. * Supports both horizontal and vertical orientations. * * @component RadioGroup.Item - Individual radio option within a RadioGroup. Must be used inside * RadioGroup. Handles selection state and renders a default `` indicator if text children * are provided. Supports render function children to access state. * * Use the `Radio` component (and its sub-components `Radio.Indicator`, `Radio.IndicatorThumb`) * inside RadioGroup.Item for custom indicator rendering. The Radio component automatically detects * the RadioGroupItem context and derives selection state from it. * * Props flow from RadioGroup to RadioGroupItem to Radio via context (variant, value, isSelected). * RadioGroup manages the overall selection state and orientation. * * @see Full documentation: https://heroui.com/docs/native/components/radio-group */ const CompoundRadioGroup = Object.assign(RadioGroupRoot, { /** Individual radio option within a RadioGroup */ Item: RadioGroupItem, }); export default CompoundRadioGroup; export { useRadioGroup };