import React, { forwardRef, Ref, useCallback, useMemo, useState } from "react"; import { Classes, CommonComponentProps, hexToRgba } from "./common"; import styled, { withTheme } from "styled-components"; import Text, { TextType } from "./Text"; import { ERROR_MESSAGE_NAME_EMPTY, createMessage, FORM_VALIDATION_INVALID_EMAIL, } from "constants/messages"; import { isEmail } from "utils/formhelpers"; import { AsyncControllableInput } from "@blueprintjs/core/lib/esnext/components/forms/asyncControllableInput"; export type Validator = ( value: string, ) => { isValid: boolean; message: string; }; export function emailValidator(email: string) { let isValid = true; if (email) { isValid = isEmail(email); } return { isValid: isValid, message: !isValid ? createMessage(FORM_VALIDATION_INVALID_EMAIL) : "", }; } export function notEmptyValidator(value: string) { const isValid = !!value; return { isValid: isValid, message: !isValid ? createMessage(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; readOnly?: boolean; dataType?: string; theme?: any; }; type boxReturnType = { bgColor: string; color: string; borderColor: string; }; const boxStyles = ( props: TextInputProps, isValid: boolean, theme: any, ): boxReturnType => { let bgColor = theme.colors.textInput.normal.bg; let color = theme.colors.textInput.normal.text; let borderColor = theme.colors.textInput.normal.border; if (props.disabled) { bgColor = theme.colors.textInput.disable.bg; color = theme.colors.textInput.disable.text; borderColor = theme.colors.textInput.disable.border; } if (props.readOnly) { bgColor = theme.colors.textInput.readOnly.bg; color = theme.colors.textInput.readOnly.text; borderColor = theme.colors.textInput.readOnly.border; } 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((props) => { return props.asyncControl ? ( ) : ( ); })` width: ${(props) => (props.fill ? "100%" : "320px")}; border-radius: 0; outline: 0; box-shadow: none; border: 1px solid ${(props) => props.inputStyle.borderColor}; padding: 0px ${(props) => props.theme.spaces[6]}px; height: 38px; background-color: ${(props) => props.inputStyle.bgColor}; color: ${(props) => props.inputStyle.color}; &:-internal-autofill-selected, &:-webkit-autofill, &:-webkit-autofill:hover, &:-webkit-autofill:focus { -webkit-box-shadow: 0 0 0 30px ${(props) => props.inputStyle.bgColor} inset !important; -webkit-text-fill-color: ${(props) => props.inputStyle.color} !important; } &::placeholder { color: ${(props) => props.theme.colors.textInput.placeholder}; } &:disabled { cursor: not-allowed; } ${(props) => !props.readOnly ? ` &:focus { border: 1px solid ${ props.isValid ? props.theme.colors.info.main : props.theme.colors.danger.main }; box-shadow: ${ props.isValid ? "0px 0px 4px 4px rgba(203, 72, 16, 0.18)" : "0px 0px 4px 4px rgba(226, 44, 44, 0.18)" }; } ` : null}; `; const InputWrapper = styled.div` display: flex; flex-direction: column; align-items: flex-start; position: relative; .${Classes.TEXT} { 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.theme), [props, validation.isValid, props.theme], ); const memoizedChangeHandler = useCallback( (el) => { const inputValue = el.target.value.trim(); const validation = props.validator && props.validator(inputValue); if (validation) { props.validator && setValidation(validation); return ( validation.isValid && props.onChange && props.onChange(inputValue) ); } else { return props.onChange && props.onChange(inputValue); } }, [props], ); const ErrorMessage = ( {validation.message} ); return ( {ErrorMessage} ); }, ); TextInput.displayName = "TextInput"; export default withTheme(TextInput); export type InputType = "text" | "password" | "number" | "email" | "tel";