import React from 'react'; import { cn } from '@/lib/utils'; import FormLabel from './FormLabel'; import FormError from './FormError'; /** * FormRadio Component * Radio button group with custom styling and keyboard navigation */ export interface RadioOption { value: string; label: string; disabled?: boolean; description?: string; } export interface FormRadioProps { label?: string; error?: string | string[]; helpText?: string; required?: boolean; options: RadioOption[]; value?: string; onChange?: (value: string) => void; containerClassName?: string; radioClassName?: string; disabled?: boolean; id?: string; name?: string; layout?: 'vertical' | 'horizontal'; } export const FormRadio: React.FC = ({ label, error, helpText, required = false, options, value, onChange, containerClassName, radioClassName, disabled = false, id, name, layout = 'vertical', }) => { const hasError = !!error; const showError = hasError; const handleChange = (e: React.ChangeEvent) => { if (onChange) { onChange(e.target.value); } }; const inputId = id || (label ? label.toLowerCase().replace(/\s+/g, '-') : undefined); const groupName = name || inputId; const baseRadioClasses = cn( 'w-4 h-4 border rounded-full transition-all duration-200', 'focus:outline-none focus:ring-2 focus:ring-primary', { 'border-neutral-dark bg-neutral-light': !hasError, 'border-danger': hasError, 'opacity-60 cursor-not-allowed': disabled, 'cursor-pointer': !disabled, }, radioClassName ); const selectedIndicatorClasses = cn( 'w-2.5 h-2.5 rounded-full bg-primary transition-all duration-200', { 'scale-0': false, 'scale-100': true, } ); const containerClasses = cn( 'flex flex-col gap-2', { 'gap-3': layout === 'vertical', 'gap-4 flex-row flex-wrap': layout === 'horizontal', }, containerClassName ); const optionWrapperClasses = cn( 'flex items-start gap-2', { 'opacity-60': disabled, } ); const labelClasses = cn( 'text-sm font-medium leading-none cursor-pointer', { 'text-text-primary': !hasError, 'text-danger': hasError, } ); const descriptionClasses = 'text-xs text-text-secondary mt-0.5'; return (
{label && ( {label} )}
{options.map((option) => { const optionId = `${inputId}-${option.value}`; const isChecked = value === option.value; return (
{isChecked && (
)}
{option.description && (

{option.description}

)}
); })}
{helpText && (

{helpText}

)} {showError && ( )}
); }; FormRadio.displayName = 'FormRadio'; export default FormRadio;