/** * FormDatePicker Component * * A reusable date picker component that handles: * - Single date selection * - Date range selection * - Label * - Controller (react-hook-form) * - Error handling * * This component reduces code duplication for date picker fields across forms. */ import React from 'react'; import { Control, Controller, FieldError, FieldPath, FieldValues } from 'react-hook-form'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { Calendar } from '@/components/ui/calendar'; import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover'; import { CalendarIcon } from 'lucide-react'; import { format } from 'date-fns'; import { DateRange } from 'react-day-picker'; import { cn } from '@/lib/utils'; // ============================================================================= // TYPES // ============================================================================= export interface FormDatePickerProps< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath > { // Form control control: Control; name: TName; // Field configuration label?: string; placeholder?: string; required?: boolean; // Date picker configuration mode?: 'single' | 'range'; numberOfMonths?: number; // Styling className?: string; labelClassName?: string; buttonClassName?: string; errorClassName?: string; // Validation validation?: { required?: boolean | string; }; // Additional props disabled?: boolean; size?: 'sm' | 'md' | 'lg'; // Date formatting dateFormat?: string; // Custom render functions renderTrigger?: (props: { field: any; fieldState: { error?: FieldError }; selectedDate: Date | DateRange | undefined; placeholder: string; }) => React.ReactElement; } // ============================================================================= // COMPONENT // ============================================================================= export function FormDatePicker< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath >({ control, name, label, placeholder = 'Pick a date', required = false, mode = 'single', numberOfMonths = 1, className = '', labelClassName = '', buttonClassName = '', errorClassName = '', validation, disabled = false, size = 'md', dateFormat = 'LLL dd, y', renderTrigger, }: FormDatePickerProps) { // ============================================================================= // HELPER FUNCTIONS // ============================================================================= const getButtonSize = (size: 'sm' | 'md' | 'lg') => { switch (size) { case 'sm': return 'h-8 text-sm'; case 'lg': return 'h-12 text-base'; default: return 'h-10 text-sm'; } }; const formatDate = (date: Date | DateRange | undefined): string => { if (!date) return ''; if (mode === 'range' && 'from' in date) { if (date.from) { if (date.to) { return `${format(date.from, dateFormat)} - ${format(date.to, dateFormat)}`; } return format(date.from, dateFormat); } } else if (date instanceof Date) { return format(date, dateFormat); } return ''; }; // ============================================================================= // RENDER FUNCTIONS // ============================================================================= const renderDefaultTrigger = ( field: any, fieldState: { error?: FieldError }, selectedDate: Date | DateRange | undefined, placeholder: string ) => ( ); // ============================================================================= // MAIN RENDER // ============================================================================= return (
{/* Label */} {label && ( )} {/* Controller */} { const selectedDate = field.value; return (
{renderTrigger ? renderTrigger({ field, fieldState, selectedDate, placeholder }) : renderDefaultTrigger(field, fieldState, selectedDate, placeholder) } {/* Error Message */} {fieldState.error && (

{fieldState.error.message}

)}
); }} />
); } export default FormDatePicker;