103 lines
2.6 KiB
TypeScript
103 lines
2.6 KiB
TypeScript
import { type ReactNode, useId } from 'react';
|
|
|
|
import { tv } from 'tailwind-variants';
|
|
|
|
export interface RadioProps {
|
|
name: string;
|
|
value: string;
|
|
checked?: boolean;
|
|
defaultChecked?: boolean;
|
|
onChange?: (value: string) => void;
|
|
disabled?: boolean;
|
|
readOnly?: boolean;
|
|
children?: ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
const radioStyles = tv({
|
|
base: [
|
|
'shrink-0 w-4 h-4 min-w-4 min-h-4 box-border rounded-full',
|
|
'border-2 border-dabeeo-gray-be bg-white',
|
|
'flex items-center justify-center',
|
|
'transition-all duration-200',
|
|
'peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2 peer-focus-visible:ring-primary',
|
|
],
|
|
variants: {
|
|
isSelected: {
|
|
true: 'border-primary bg-primary',
|
|
},
|
|
isDisabled: {
|
|
true: 'border-dabeeo-gray-be bg-dabeeo-gray-eb',
|
|
},
|
|
isReadOnly: {
|
|
true: '',
|
|
},
|
|
},
|
|
compoundVariants: [
|
|
{
|
|
isSelected: true,
|
|
isDisabled: true,
|
|
className: 'border-dabeeo-gray-be bg-dabeeo-gray-eb',
|
|
},
|
|
],
|
|
});
|
|
|
|
const innerCircleStyles = tv({
|
|
base: 'w-2 h-2 rounded-full transition-all duration-200',
|
|
variants: {
|
|
isSelected: {
|
|
true: 'bg-white',
|
|
false: 'bg-transparent',
|
|
},
|
|
isDisabled: {
|
|
true: 'bg-dabeeo-gray-eb',
|
|
},
|
|
},
|
|
compoundVariants: [
|
|
{
|
|
isSelected: true,
|
|
isDisabled: true,
|
|
className: 'bg-dabeeo-gray-eb',
|
|
},
|
|
],
|
|
});
|
|
|
|
const labelStyles = tv({
|
|
base: 'text-dabeeo-black-22 text-sm font-medium inline-flex items-center',
|
|
variants: {
|
|
isDisabled: {
|
|
true: 'text-dabeeo-gray-be',
|
|
},
|
|
},
|
|
});
|
|
|
|
export const Radio = (props: RadioProps) => {
|
|
const { name, value, checked, defaultChecked, onChange, disabled, readOnly, children, className } = props;
|
|
const id = useId();
|
|
const isInteractive = !disabled && !readOnly;
|
|
|
|
return (
|
|
<label
|
|
htmlFor={id}
|
|
className={`inline-flex h-9 items-center gap-2.5 ${isInteractive ? 'cursor-pointer' : 'cursor-not-allowed'} ${className ?? ''}`}
|
|
>
|
|
<input
|
|
type="radio"
|
|
id={id}
|
|
name={name}
|
|
value={value}
|
|
checked={checked}
|
|
defaultChecked={defaultChecked}
|
|
onChange={(e) => isInteractive && onChange?.(e.target.value)}
|
|
disabled={disabled}
|
|
readOnly={readOnly}
|
|
className="sr-only peer"
|
|
/>
|
|
<span className={radioStyles({ isSelected: checked, isDisabled: disabled, isReadOnly: readOnly })}>
|
|
<span className={innerCircleStyles({ isSelected: checked, isDisabled: disabled })} />
|
|
</span>
|
|
{children && <span className={labelStyles({ isDisabled: disabled })}>{children}</span>}
|
|
</label>
|
|
);
|
|
};
|