/** * FormCheckbox Component * * A reusable checkbox component that handles: * - Single checkbox * - Checkbox groups * - Label * - Controller (react-hook-form) * - Error handling * * This component reduces code duplication for checkbox fields across forms. */ import React from 'react'; import { Control, Controller, FieldError, FieldPath, FieldValues } from 'react-hook-form'; import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; import { cn } from '@/lib/utils'; // ============================================================================= // TYPES // ============================================================================= export interface CheckboxOption { value: string | number; label: string; disabled?: boolean; } export interface FormCheckboxProps< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath > { // Form control control: Control; name: TName; // Field configuration label?: string; required?: boolean; // Checkbox configuration single?: boolean; // Single checkbox vs checkbox group options?: CheckboxOption[]; // For checkbox groups // Styling className?: string; labelClassName?: string; checkboxClassName?: 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: CheckboxOption, checked: boolean, onChange: (checked: boolean) => void) => React.ReactElement; } // ============================================================================= // COMPONENT // ============================================================================= export function FormCheckbox< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath >({ control, name, label, required = false, single = false, options = [], className = '', labelClassName = '', checkboxClassName = '', errorClassName = '', groupClassName = '', layout = 'horizontal', columns = 1, validation, disabled = false, size = 'md', renderOption, }: FormCheckboxProps) { // ============================================================================= // HELPER FUNCTIONS // ============================================================================= const getCheckboxSize = (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-4'; }; // ============================================================================= // RENDER FUNCTIONS // ============================================================================= const renderSingleCheckbox = (field: any, fieldState: { error?: FieldError }) => (
); const renderCheckboxGroup = (field: any, fieldState: { error?: FieldError }) => { const selectedValues = field.value || []; const handleOptionChange = (optionValue: string | number, checked: boolean) => { const newValues = checked ? [...selectedValues, optionValue] : selectedValues.filter((value: any) => value !== optionValue); field.onChange(newValues); }; const renderDefaultOption = (option: CheckboxOption) => { const checked = selectedValues.includes(option.value); if (renderOption) { return renderOption(option, checked, (checked) => handleOptionChange(option.value, checked)); } return (
handleOptionChange(option.value, checked as boolean)} disabled={disabled || option.disabled} className={getCheckboxSize(size)} />
); }; return (
{options.map(renderDefaultOption)}
); }; // ============================================================================= // MAIN RENDER // ============================================================================= return (
{/* Group Label */} {label && !single && ( )} {/* Controller */} (
{single ? renderSingleCheckbox(field, fieldState) : renderCheckboxGroup(field, fieldState) } {/* Error Message */} {fieldState.error && (

{fieldState.error.message}

)}
)} />
); } export default FormCheckbox;