src/radio-v2/radiogroup.tsx (125 lines of code) (raw):
/*
Copyright (c) Uber Technologies, Inc.
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import * as React from 'react';
import { getOverrides } from '../helpers/overrides';
import { RadioGroupRoot as StyledRadioGroupRoot } from './styled-components';
import type { RadioGroupProps } from './types';
import { isFocusVisible } from '../utils/focusVisible';
import { ALIGN, LABEL_PLACEMENT } from './constants';
import { RadioGroupContext } from './radio-context';
import type { ChangeEvent } from 'react';
const StatelessRadioGroup: React.FC<RadioGroupProps> = (props) => {
const {
overrides = {},
name = '',
value = '',
disabled = false,
autoFocus = false,
labelPlacement = LABEL_PLACEMENT.right,
align = ALIGN.vertical,
error = false,
required = false,
onChange = () => {},
onMouseEnter = () => {},
onMouseLeave = () => {},
onFocus = () => {},
onBlur = () => {},
children,
id,
} = props;
const [isFocusVisibleState, setIsFocusVisibleState] = React.useState(false);
const [focusedRadioIndex, setFocusedRadioIndex] = React.useState(-1);
// Registration counter for child radios
const radioIndexRef = React.useRef(0);
// Reset the registration counter before each render
React.useLayoutEffect(() => {
radioIndexRef.current = 0;
});
const registerRadio = React.useCallback(() => {
const index = radioIndexRef.current;
radioIndexRef.current += 1;
return index;
}, []);
const handleFocus = React.useCallback(
(event: ChangeEvent<HTMLInputElement>, index: number) => {
if (isFocusVisible(event)) {
setIsFocusVisibleState(true);
}
setFocusedRadioIndex(index);
onFocus && onFocus(event);
},
[onFocus],
);
const handleBlur = React.useCallback(
(event: ChangeEvent<HTMLInputElement>, index: number) => {
if (isFocusVisibleState !== false) {
setIsFocusVisibleState(false);
}
setFocusedRadioIndex(-1);
onBlur && onBlur(event);
},
[isFocusVisibleState, onBlur],
);
const [RadioGroupRoot, radioGroupRootProps] = getOverrides(
overrides.RadioGroupRoot,
StyledRadioGroupRoot,
);
const contextValue = React.useMemo(
() => ({
name,
selectedValue: value,
disabled,
autoFocus,
error,
required,
align,
labelPlacement,
focusedIndex: focusedRadioIndex,
isFocusVisible: isFocusVisibleState,
onChange,
onMouseEnter,
onMouseLeave,
onFocus: handleFocus,
onBlur: handleBlur,
registerRadio,
}),
[
name,
value,
disabled,
autoFocus,
error,
required,
align,
labelPlacement,
focusedRadioIndex,
isFocusVisibleState,
onChange,
onMouseEnter,
onMouseLeave,
handleFocus,
handleBlur,
registerRadio,
],
);
return (
<RadioGroupContext.Provider value={contextValue}>
<RadioGroupRoot
id={id}
role='radiogroup'
aria-describedby={props['aria-describedby']}
aria-errormessage={props['aria-errormessage']}
aria-invalid={error || null}
aria-label={props['aria-label']}
aria-labelledby={props['aria-labelledby']}
aria-required={required || null}
$align={align}
$disabled={disabled}
$error={error}
$required={required}
$labelPlacement={labelPlacement}
{...radioGroupRootProps}
>
{children}
</RadioGroupRoot>
</RadioGroupContext.Provider>
);
};
StatelessRadioGroup.displayName = 'StatelessRadioGroup';
export default StatelessRadioGroup;