import React, { forwardRef, Ref, useCallback, useMemo, useState } from "react"; import { CommonComponentProps, hexToRgba } from "./common"; import styled from "styled-components"; import Text, { TextType } from "./Text"; import { theme } from "constants/DefaultTheme"; import { FORM_VALIDATION_INVALID_EMAIL, ERROR_MESSAGE_NAME_EMPTY, } from "constants/messages"; import { isEmail } from "utils/formhelpers"; export type Validator = ( value: string, ) => { isValid: boolean; message: string; }; export function emailValidator(email: string) { const isValid = isEmail(email); return { isValid: isValid, message: !isValid ? FORM_VALIDATION_INVALID_EMAIL : "", }; } export function notEmptyValidator(value: string) { const isValid = !!value; return { isValid: isValid, message: !isValid ? ERROR_MESSAGE_NAME_EMPTY : "", }; } export type TextInputProps = CommonComponentProps & { placeholder?: string; fill?: boolean; defaultValue?: string; validator?: (value: string) => { isValid: boolean; message: string }; onChange?: (value: string) => void; }; type boxReturnType = { bgColor: string; color: string; borderColor: string; }; const boxStyles = (props: TextInputProps, isValid: boolean): boxReturnType => { let bgColor = theme.colors.blackShades[0]; let color = theme.colors.blackShades[9]; let borderColor = theme.colors.blackShades[0]; if (props.disabled) { bgColor = theme.colors.blackShades[2]; color = theme.colors.blackShades[6]; borderColor = theme.colors.blackShades[2]; } if (!isValid) { bgColor = hexToRgba(theme.colors.danger.main, 0.1); color = theme.colors.danger.main; borderColor = theme.colors.danger.main; } return { bgColor, color, borderColor }; }; const StyledInput = styled.input< TextInputProps & { inputStyle: boxReturnType; isValid: boolean } >` width: ${props => (props.fill ? "100%" : "260px")}; border-radius: 0; outline: 0; box-shadow: none; border: 1px solid ${props => props.inputStyle.borderColor}; padding: ${props => props.theme.spaces[4]}px ${props => props.theme.spaces[6]}px; background-color: ${props => props.inputStyle.bgColor}; color: ${props => props.inputStyle.color}; &::placeholder { color: ${props => props.theme.colors.blackShades[5]}; } &:focus { border: 1px solid ${props => props.isValid ? props.theme.colors.info.main : props.theme.colors.danger.main}; box-shadow: ${props => props.isValid ? "0px 0px 4px 4px rgba(203, 72, 16, 0.18)" : "0px 0px 4px 4px rgba(226, 44, 44, 0.18)"}; } &:disabled { cursor: not-allowed; } `; const InputWrapper = styled.div` display: flex; flex-direction: column; align-items: flex-start; position: relative; span { color: ${props => props.theme.colors.danger.main}; } `; const ErrorWrapper = styled.div` position absolute; bottom: -17px; `; const TextInput = forwardRef( (props: TextInputProps, ref: Ref) => { const initialValidation = () => { let validationObj = { isValid: true, message: "" }; if (props.defaultValue && props.validator) { validationObj = props.validator(props.defaultValue); } return validationObj; }; const [validation, setValidation] = useState<{ isValid: boolean; message: string; }>(initialValidation()); const inputStyle = useMemo(() => boxStyles(props, validation.isValid), [ props.disabled, validation, ]); const memoizedChangeHandler = useCallback( el => { const validation = props.validator && props.validator(el.target.value); if (validation) { props.validator && setValidation(validation); return ( validation.isValid && props.onChange && props.onChange(el.target.value) ); } else { return props.onChange && props.onChange(el.target.value); } }, [props], ); const ErrorMessage = ( {validation.message} ); return ( {ErrorMessage} ); }, ); TextInput.displayName = "TextInput"; export default TextInput;