All files / src/components/shared/form-input index.tsx

35.63% Statements 31/87
100% Branches 0/0
0% Functions 0/1
35.63% Lines 31/87

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 881x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                                                                  
'use client';
 
import { InputHTMLAttributes, useId } from 'react';
 
import { Hint, Input, Label } from '@/components/ui';
import { cn } from '@/lib/utils';
 
type InputPropsWithIcon = InputHTMLAttributes<HTMLInputElement> & {
  iconButton?: React.ReactNode;
};
 
interface FormInputProps {
  className?: string;
  labelName?: string;
  hintMessage?: string;
  availabilityHint?: string;
  availabilityButtonDisabled?: boolean;
  availabilityStatus?: AvailabilityState;
  required?: boolean;
  inputProps?: InputPropsWithIcon;
  onClick?: () => void;
}
 
type AvailabilityState =
  | { status: 'idle' }
  | { status: 'checking' }
  | { status: 'available' }
  | { status: 'unavailable' }
  | { status: 'error' };
 
export const FormInput = ({
  className,
  labelName,
  hintMessage,
  availabilityHint,
  availabilityStatus,
  required = true,
  inputProps = {},
}: FormInputProps) => {
  const { type, id, required: _, iconButton, ...restInputProps } = inputProps;

  const generatedId = useId();

  const inputId = id ?? generatedId;

  let tone: 'default' | 'error' | 'success' = 'default';

  if (hintMessage || availabilityStatus?.status === 'unavailable') {
    tone = 'error';
  }

  if (availabilityStatus?.status === 'available') {
    tone = 'success';
  }

  return (
    <div className={cn('flex w-full flex-col gap-1', className)}>
      <Label htmlFor={inputId} required={required}>
        {labelName}
      </Label>
      <Input
        aria-invalid={!!hintMessage}
        id={inputId}
        className={cn(
          'bg-mono-white focus:border-mint-500 h-14 rounded-2xl border border-gray-300',
          tone === 'error' && 'border-error-500',
          tone === 'success' && 'border-gray-300',
          availabilityStatus && 'pr-23',
        )}
        iconButton={iconButton}
        required={required}
        type={type}
        {...restInputProps}
      />
      {hintMessage && <Hint message={hintMessage} />}
      {availabilityHint && (
        <Hint
          className={cn(
            tone === 'error' && 'text-error-500',
            tone === 'success' && 'text-mint-600',
          )}
          message={availabilityHint}
        />
      )}
    </div>
  );
};