118 lines
2.9 KiB
TypeScript
118 lines
2.9 KiB
TypeScript
import clsx from "clsx";
|
|
import { ChangeEvent, FC, HTMLAttributes, useRef, useState } from "react";
|
|
|
|
import styles from "./styles.module.css";
|
|
|
|
export interface RadioOption {
|
|
label: string;
|
|
value: string | number;
|
|
description?: string;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export interface RadioGroupProps
|
|
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
name: string;
|
|
initialValue?: string | number;
|
|
title: string;
|
|
options: RadioOption[];
|
|
description?: string;
|
|
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
|
onChangeConfirm?: (
|
|
e: ChangeEvent<HTMLInputElement>,
|
|
newValue: string | number,
|
|
oldValue: string | number,
|
|
accept: () => void,
|
|
reject: () => void
|
|
) => void;
|
|
disabled?: boolean;
|
|
themed?: boolean;
|
|
}
|
|
|
|
export const RadioGroup: FC<RadioGroupProps> = ({
|
|
className,
|
|
name,
|
|
initialValue,
|
|
title,
|
|
options,
|
|
description,
|
|
disabled,
|
|
onChange,
|
|
onChangeConfirm,
|
|
themed,
|
|
...props
|
|
}) => {
|
|
const radioGroupRef = useRef<HTMLDivElement>(null!);
|
|
const [selectedValue, setSelectedValue] = useState(
|
|
initialValue || options[0]?.value
|
|
);
|
|
|
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
const newValue =
|
|
typeof selectedValue === "number"
|
|
? Number(e.target.value)
|
|
: e.target.value;
|
|
|
|
if (onChangeConfirm) {
|
|
onChangeConfirm(
|
|
e,
|
|
newValue,
|
|
selectedValue,
|
|
() => {
|
|
if (onChange) onChange(e);
|
|
setSelectedValue(newValue);
|
|
},
|
|
() => {
|
|
// noop
|
|
}
|
|
);
|
|
} else {
|
|
if (onChange) onChange(e);
|
|
setSelectedValue(newValue);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={clsx([styles.radioGroup, themed && styles.themed, className])}
|
|
ref={radioGroupRef}
|
|
{...props}
|
|
>
|
|
<legend>{title}</legend>
|
|
{description && <p className={styles.subtitle}>{description}</p>}
|
|
<div className={styles.options}>
|
|
{options?.map((option) => {
|
|
const id = `${name}_${option.value}`;
|
|
const checked = String(selectedValue) === String(option.value);
|
|
|
|
return (
|
|
<div
|
|
className={styles.option}
|
|
key={id}
|
|
data-testid="radio-option-container"
|
|
>
|
|
<input
|
|
type="radio"
|
|
name={name}
|
|
id={id}
|
|
onChange={handleChange}
|
|
value={option.value}
|
|
aria-checked={checked}
|
|
disabled={disabled || option.disabled}
|
|
/>
|
|
<div className={styles.content}>
|
|
<label className={styles.label} htmlFor={id}>
|
|
{option.label}
|
|
</label>
|
|
{option.description && (
|
|
<p className={styles.description}>{option.description}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|