import React, { FC, useRef, useState, FormEvent, FocusEvent, MutableRefObject } from 'react' import { formatClassList, joinStrings } from '../../utils' type FormFieldProps = { id?: string, invalidMessage: string, label: string, name?: string, optional: boolean, setValid(valid: boolean): void, setValue(value: string): void, type?: string, validator( elem: HTMLInputElement | HTMLTextAreaElement | HTMLDivElement | null ): boolean, [other:string]: unknown } const LABEL: string = ` block mt-4 mb-1 leading-tight text-base text-bscs-gray-600 text-left tracking-wider ` const INPUT: string = ` block focus:bg-bscs-gray-100 duration-300 ease-in-out focus min-h-2 outline-none p-2 rounded md:rounded-md shadow-inner text-base focus:text-bscs-gray-800 transform transition w-full ` const UNTOUCHED: string = ` bg-bscs-gray-200 ` const INVALID: string = ` ${INPUT} bg-bscs-red-100 ` const VALID: string = ` ${INPUT} bg-bscs-green-100 ` const INVALID_MESSAGE: string = ` leading-tight mb-3 text-base text-bscs-red-800 text-left tracking-wider transition-all duration-300 ease-in-out -z-1 ` const handleValidation = ( e: FormEvent | FocusEvent, optional: boolean, elem: MutableRefObject, setValid: (valid: boolean) => void, setValue: (value: string) => void, validator: ( elem: HTMLInputElement | HTMLTextAreaElement | HTMLDivElement | null ) => boolean, setShowInvalidMessage: (show: boolean) => void, touched: boolean, showInvalidMessage: boolean ): boolean => { e.preventDefault() if (!elem || !elem.current) { return false } setValue(elem.current.value) const valid: boolean = validator(elem.current) if ((!optional && valid) || (optional && (!elem.current.value || valid)) ) { setValid(true) elem.current.classList.value = formatClassList(VALID) if (touched && showInvalidMessage) { setShowInvalidMessage(false) } return true } setValid(false) elem.current.classList.value = formatClassList(INVALID) if (touched && !showInvalidMessage) { setShowInvalidMessage(true) } return false } const FormField:FC = ({ id, invalidMessage, label, name, optional, setValid, setValue, type='text', validator, ...other }: FormFieldProps) => { const ref = useRef(null) const [showInvalidMessage, setShowInvalidMessage] = useState(false) const [touched, setTouched] = useState(false) const labelClassList: string = formatClassList(LABEL) const inputClassList: string = formatClassList(joinStrings(' ', INPUT, UNTOUCHED)) const invalidMessageClassList: string = formatClassList(INVALID_MESSAGE) const maxLength: number | undefined = type === 'text' ? 50 : undefined const minLength: number | undefined = type === 'text' ? 1 : undefined const Tag = type === 'textarea' ? 'textarea' : 'input' return (
ref.current = instance} className={inputClassList} onBlur={(e: FocusEvent) => { const valid = handleValidation( e, optional, ref, setValid, setValue, validator, setShowInvalidMessage, true, showInvalidMessage ) if (!touched && !valid) { setTouched(true) } if ( touched && (valid || (e.target as HTMLInputElement).value === "") ) { setTouched(false) } }} onInput={(e: FormEvent) => { handleValidation( e, optional, ref, setValid, setValue, validator, setShowInvalidMessage, touched, showInvalidMessage ) }} id={id} type={type} name={name} minLength={minLength} maxLength={maxLength} style={{ maxWidth: '100%' }} />
{invalidMessage}
) } export default FormField