debian-image-builder-frontend/src/Components/CreateImageWizard/ValidatedInput.tsx
Michal Gold d448db9a04 Wizard: Refactor Minimum Size input in File system step to use new component
This commit refactors the Minimun size field by replacing HookValidatedInput with the new ValidatedInputAndTextArea component.
It fixes a bug where the error icon remained visible after the user cleared the value in the field.
2025-03-26 10:24:42 -05:00

269 lines
6.2 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import {
HelperText,
HelperTextItem,
TextArea,
TextAreaProps,
TextInput,
TextInputProps,
} from '@patternfly/react-core';
import type { StepValidation } from './utilities/useValidation';
type ValidatedTextInputPropTypes = TextInputProps & {
dataTestId?: string;
ouiaId?: string;
ariaLabel: string | undefined;
helperText: string | undefined;
validator: (value: string | undefined) => boolean;
value: string;
placeholder?: string;
};
type HookValidatedInputPropTypes = TextInputProps &
TextAreaProps & {
dataTestId?: string | undefined;
ouiaId?: string;
ariaLabel: string | undefined;
value: string;
placeholder?: string;
stepValidation: StepValidation;
fieldName: string;
warning?: string;
inputType?: 'textInput' | 'textArea';
};
type ValidationInputProp = TextInputProps &
TextAreaProps & {
value: string;
placeholder: string;
stepValidation: StepValidation;
dataTestId?: string;
fieldName: string;
inputType?: 'textInput' | 'textArea';
ariaLabel: string;
onChange: (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
value: string
) => void;
isRequired?: boolean;
warning?: string;
};
type ErrorMessageProps = {
errorMessage: string;
};
type ValidationResult = 'default' | 'success' | 'error';
export const ValidatedInputAndTextArea = ({
value,
stepValidation,
fieldName,
placeholder,
dataTestId,
onChange,
ariaLabel,
inputType = 'textInput',
isRequired,
warning = undefined,
}: ValidationInputProp) => {
const errorMessage = stepValidation.errors[fieldName] || '';
const hasError = errorMessage !== '';
const [isPristine, setIsPristine] = useState(!value);
const validated = getValidationState(isPristine, errorMessage, isRequired);
const handleBlur = () => {
if (value) {
setIsPristine(false);
}
};
useEffect(() => {
if (!value) {
setIsPristine(true);
}
}, [value, setIsPristine]);
return (
<>
{inputType === 'textArea' ? (
<TextArea
value={value}
onChange={onChange}
validated={validated}
onBlur={handleBlur}
placeholder={placeholder}
aria-label={ariaLabel}
data-testid={dataTestId}
/>
) : (
<TextInput
value={value}
onChange={onChange}
validated={validated}
onBlur={handleBlur}
placeholder={placeholder}
aria-label={ariaLabel}
data-testid={dataTestId}
/>
)}
{warning !== undefined && warning !== '' && (
<HelperText>
<HelperTextItem variant="warning" hasIcon>
{warning}
</HelperTextItem>
</HelperText>
)}
{hasError && <ErrorMessage errorMessage={errorMessage} />}
</>
);
};
const getValidationState = (
isPristine: boolean,
errorMessage: string,
isRequired: boolean | undefined
): ValidationResult => {
const validated = isPristine
? 'default'
: (isRequired && errorMessage) || errorMessage
? 'error'
: 'success';
return validated;
};
export const ErrorMessage = ({ errorMessage }: ErrorMessageProps) => {
return (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{errorMessage}
</HelperTextItem>
</HelperText>
);
};
export const HookValidatedInput = ({
dataTestId,
ouiaId,
ariaLabel,
value,
isDisabled,
placeholder,
onChange,
stepValidation,
fieldName,
type = 'text',
inputType,
warning = undefined,
}: HookValidatedInputPropTypes) => {
const [isPristine, setIsPristine] = useState(!value ? true : false);
// Do not surface validation on pristine state components
// Allow step validation to be set on pristine state, when needed
const validated = isPristine
? 'default'
: stepValidation.errors[fieldName] === 'default'
? 'default'
: stepValidation.errors[fieldName]
? 'error'
: 'success';
const handleBlur = () => {
setIsPristine(false);
};
return (
<>
{inputType === 'textArea' ? (
<TextArea
value={value}
data-testid={dataTestId}
onChange={onChange!}
validated={validated}
aria-label={ariaLabel || ''}
onBlur={handleBlur}
placeholder={placeholder || ''}
isDisabled={isDisabled || false}
/>
) : (
<TextInput
value={value}
data-testid={dataTestId}
ouiaId={ouiaId || ''}
type={type}
onChange={onChange!}
validated={validated}
aria-label={ariaLabel || ''}
onBlur={handleBlur}
placeholder={placeholder || ''}
isDisabled={isDisabled || false}
/>
)}
{validated === 'error' && (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{stepValidation.errors[fieldName]}
</HelperTextItem>
</HelperText>
)}
{warning !== undefined && warning !== '' && (
<HelperText>
<HelperTextItem variant="warning" hasIcon>
{warning}
</HelperTextItem>
</HelperText>
)}
</>
);
};
export const ValidatedInput = ({
dataTestId,
ouiaId,
ariaLabel,
helperText,
validator,
value,
placeholder,
onChange,
}: ValidatedTextInputPropTypes) => {
const [isPristine, setIsPristine] = useState(!value ? true : false);
const handleBlur = () => {
setIsPristine(false);
};
const handleValidation = () => {
// Prevent premature validation during user's first entry
if (isPristine) {
return 'default';
}
return validator(value) ? 'success' : 'error';
};
return (
<>
<TextInput
value={value}
data-testid={dataTestId}
ouiaId={ouiaId || ''}
type="text"
onChange={onChange!}
validated={handleValidation()}
aria-label={ariaLabel || ''}
onBlur={handleBlur}
placeholder={placeholder || ''}
/>
{!isPristine && !validator(value) && (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{helperText}
</HelperTextItem>
</HelperText>
)}
</>
);
};