@spaced-out/ui-design-system
Version:
Sense UI components library
135 lines (121 loc) • 3.33 kB
Flow
// @flow strict
import * as React from 'react';
import type {GroupAlign} from '../../types/common';
import classify from '../../utils/classify';
import css from './RadioButton.module.css';
/**
* Note:
* Do not wrap Radio in a label. For simplicity's sake, Radio uses an
* internal label tag to handle click delegation to the hidden input, and
* nesting labels is a bad idea.
*/
type ClassNames = $ReadOnly<{wrapper?: string, label?: string, radio?: string}>;
export type RadioButtonProps = {
children?: React.Node,
value?: string,
disabled?: boolean,
focused?: boolean,
name?: string,
selectedValue?: string,
align?: GroupAlign,
classNames?: ClassNames,
error?: boolean,
onChange?: (newValue: string) => mixed,
tabIndex?: number,
ariaLabel?: string,
};
export const RadioButton: React$AbstractComponent<
RadioButtonProps,
HTMLInputElement,
> = React.forwardRef<RadioButtonProps, HTMLInputElement>(
(
{
children,
disabled = false,
value = '',
focused = false,
name = 'radio',
selectedValue,
onChange,
align = 'vertical',
classNames,
error = false,
tabIndex = 0,
ariaLabel = '',
...props
}: RadioButtonProps,
forwardRef,
): React.Node => {
const radioInput = React.createRef<HTMLInputElement>();
React.useImperativeHandle(forwardRef, () => radioInput.current);
const checked = selectedValue === value;
React.useEffect(() => {
if (radioInput.current && focused) {
radioInput.current.focus();
}
}, [focused]);
const onWrapClickHandler = () => {
radioInput.current?.click();
};
const onChangeHandler = (e) => {
if (!disabled) {
onChange && onChange(e?.target?.value);
}
};
const onKeyDownHandler = (e) => {
if (e.key === 'Enter') {
onChangeHandler(e);
}
};
return (
<div
className={classify(
css.container,
{
[css.horizontalRadioButton]: align === 'horizontal-fixed',
[css.fluidRadioButton]: align === 'horizontal-fluid',
[css.containerDisabled]: disabled,
},
classNames?.wrapper,
)}
onClick={onWrapClickHandler}
>
<input
type="radio"
checked={checked}
disabled={disabled}
className={classify(
css.radio,
{
[css.unselected]: !checked && !disabled && !error,
[css.selected]: checked && !disabled && !error,
[css.disabled]: disabled,
[css.error]: error && !disabled,
},
classNames?.radio,
)}
ref={radioInput}
onChange={onChangeHandler}
onKeyDown={onKeyDownHandler}
tabIndex={disabled ? -1 : tabIndex}
name={name}
value={value || ''}
aria-label={ariaLabel || children}
{...props}
/>
{React.Children.count(children) > 0 && (
<div
color="secondary"
className={classify(
css.radioLabel,
{[css.labelDisabled]: disabled},
classNames?.label,
)}
>
{children}
</div>
)}
</div>
);
},
);