/** * FormRadio Component * * A reusable radio button component that handles: * - Radio button groups * - Label * - Controller (react-hook-form) * - Error handling * * This component reduces code duplication for radio button fields across forms. */ import React from 'react'; import { Control, Controller, FieldError, FieldPath, FieldValues } from 'react-hook-form'; import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { cn } from '@/lib/utils'; // ============================================================================= // TYPES // ============================================================================= export interface RadioOption { value: string | number; label: string; disabled?: boolean; } export interface FormRadioProps< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath > { // Form control control: Control; name: TName; // Field configuration label?: string; required?: boolean; // Radio configuration options: RadioOption[]; // Styling className?: string; labelClassName?: string; radioClassName?: string; errorClassName?: string; groupClassName?: string; // Layout layout?: 'horizontal' | 'vertical'; columns?: number; // For grid layout // Validation validation?: { required?: boolean | string; }; // Additional props disabled?: boolean; size?: 'sm' | 'md' | 'lg'; // Custom render functions renderOption?: (option: RadioOption, checked: boolean, onChange: (value: string) => void) => React.ReactElement; } // ============================================================================= // COMPONENT // ============================================================================= export function FormRadio< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath >({ control, name, label, required = false, options, className = '', labelClassName = '', radioClassName = '', errorClassName = '', groupClassName = '', layout = 'horizontal', columns = 1, validation, disabled = false, size = 'md', renderOption, }: FormRadioProps) { // ============================================================================= // HELPER FUNCTIONS // ============================================================================= const getRadioSize = (size: 'sm' | 'md' | 'lg') => { switch (size) { case 'sm': return 'h-4 w-4'; case 'lg': return 'h-6 w-6'; default: return 'h-5 w-5'; } }; const getGroupLayout = () => { if (layout === 'vertical') { return 'flex flex-col gap-2'; } if (columns > 1) { return `grid grid-cols-${columns} gap-2`; } return 'flex flex-wrap gap-3'; }; // ============================================================================= // RENDER FUNCTIONS // ============================================================================= const renderDefaultOption = (option: RadioOption, checked: boolean, onChange: (value: string) => void) => { if (renderOption) { return renderOption(option, checked, onChange); } return (
); }; // ============================================================================= // MAIN RENDER // ============================================================================= return (
{/* Group Label */} {label && ( )} {/* Controller */} (
{options.map((option) => renderDefaultOption( option, field.value === String(option.value), field.onChange ) )} {/* Error Message */} {fieldState.error && (

{fieldState.error.message}

)}
)} />
); } export default FormRadio;