Untitled

mail@pastecode.io avatar
unknown
plain_text
18 days ago
3.8 kB
2
Indexable
Never
import React, { useEffect } from 'react';
import { Input } from './input';
import { useForm, Control, Controller, FieldErrors, useWatch } from 'react-hook-form';
import { differenceInYears, parse, isValid } from 'date-fns';

interface DateOfBirthProps {
  control: Control<any>;
  setAge: React.Dispatch<React.SetStateAction<number | null>>;
  errors: FieldErrors;
}

export const AgeLabel: React.FC<{ age: number | null; year: string }> = ({ age, year }) => {
  if (age === null || year.length !== 4) return null;
  return (
    <span className="text-gray-500">
      <span> · {age} year</span>
      <span>{age > 1 ? 's' : ''}</span>
      <span> old</span>
    </span>
  );
};

const DateOfBirth: React.FC<DateOfBirthProps> = ({ control, setAge, errors }) => {
  const { clearErrors, setError } = useForm();

  const inputs = [
    { name: 'bday-day', placeholder: 'DD', maxLength: 2, label: 'Day' },
    { name: 'bday-month', placeholder: 'MM', maxLength: 2, label: 'Month' },
    { name: 'bday-year', placeholder: 'YYYY', maxLength: 4, label: 'Year' },
  ];

  const { 'bday-day': day, 'bday-month': month, 'bday-year': year } = useWatch({ control });

  useEffect(() => {
    if (day && month && year) {
      const date = parse(`${year}-${month}-${day}`, 'yyyy-MM-dd', new Date());
      if (isValid(date)) {
        const age = differenceInYears(new Date(), date);
        if (age < 0) {
          setError('dob', { type: 'manual', message: 'Date of birth cannot be in the future' });
          setAge(null);
        } else {
          clearErrors('dob');
          setAge(age);
        }
      } else {
        setError('dob', { type: 'manual', message: 'Invalid date' });
        setAge(null);
      }
    } else {
      clearErrors('dob');
      setAge(null);
    }
  }, [day, month, year, setAge, setError, clearErrors]);

  return (
    <div className="grid grid-cols-3">
      {inputs.map(({ name, placeholder, maxLength, label }, idx) => (
        <Controller
          key={name}
          name={name}
          control={control}
          render={({ field }) => (
            <div>
              <label htmlFor={name} className="sr-only">
                {label}
              </label>
              <Input
                {...field}
                id={name}
                placeholder={placeholder}
                maxLength={maxLength}
                type="text"
                inputMode="numeric"
                aria-label={label}
                aria-invalid={errors[name] ? 'true' : 'false'}
                aria-describedby={errors[name] ? `${name}-error` : undefined}
                className={`relative w-full 
                  ${errors[name] ? '!border-danger-500 hover:!border-danger-500 focus:!border-danger-500 focus:!ring-danger-500/20' : ''}
                  ${idx > 0 && errors[inputs[idx - 1].name] ? 'border-l-danger-500' : ''}
                  ${idx < 2 && errors[inputs[idx + 1].name] ? 'border-r-danger-500' : ''}
                  tabular-nums placeholder:text-neutral-400 
                  ${
                    idx === 0
                      ? '!rounded-r-none rounded-l-md'
                      : idx === 2
                        ? '!rounded-l-none rounded-r-md'
                        : '!rounded-none'
                  }
                  ${idx === 1 ? '-ml-px' : idx === 2 ? 'ml-[-2px] w-[calc(100%+2px)]' : ''}
                  hover:z-10 focus:z-20`}
              />
              {errors[name] && (
                <span
                  id={`${name}-error`}
                  className="sr-only text-sm text-danger-500"
                  aria-live="assertive"
                >
                  {errors[name]?.message as string}
                </span>
              )}
            </div>
          )}
        />
      ))}
    </div>
  );
};

export default DateOfBirth;
Leave a Comment