debian-image-builder-frontend/src/Components/CreateImageWizardV2/ValidatedTextInput.tsx
Ondrej Ezr 709ae39d23 WizardV2: Validate steps through redux state
Store validation status in redux state.
This is bit complex on the redux side, but pretty simple on the components.
It allows for reuse of the validation state instead of revalidating wherever needed.
2024-04-18 10:01:06 +02:00

155 lines
3.5 KiB
TypeScript

import React, { useState } from 'react';
import {
HelperText,
HelperTextItem,
TextInput,
TextInputProps,
} from '@patternfly/react-core';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import {
setStepInputValidation,
selectInputValidation,
} from '../../store/wizardSlice';
interface ValidatedTextInputPropTypes extends TextInputProps {
dataTestId?: string | undefined;
ouiaId?: string;
ariaLabel: string | undefined;
helperText: string | undefined;
validator: (value: string | undefined) => boolean;
value: string;
placeholder?: string;
}
interface StateValidatedTextInputPropTypes extends TextInputProps {
dataTestId?: string | undefined;
ouiaId?: string;
stepId: string;
inputId: string;
ariaLabel: string | undefined;
helperText: string | undefined;
validator: (value: string | undefined) => boolean;
value: string;
placeholder?: string;
}
export const StateValidatedInput = ({
dataTestId,
ouiaId,
stepId,
inputId,
ariaLabel,
helperText,
validator,
value,
placeholder,
onChange,
}: StateValidatedTextInputPropTypes) => {
const dispatch = useAppDispatch();
const validatedState = useAppSelector(selectInputValidation(stepId, inputId));
const [isPristine, setIsPristine] = useState(!value ? true : false);
// Do not surface validation on pristine state components
const validated = isPristine ? 'default' : validatedState;
const handleBlur = () => {
setIsPristine(false);
const isValid = validator(value);
dispatch(
setStepInputValidation({
stepId,
inputId,
isValid,
errorText: isValid ? helperText : undefined,
})
);
};
const wrappedOnChange = (
evt: React.FormEvent<HTMLInputElement>,
newVal: string
) => {
if (onChange) onChange(evt, newVal);
const isValid = validator(newVal);
dispatch(
setStepInputValidation({
stepId,
inputId,
isValid,
errorText: isValid ? helperText : undefined,
})
);
};
return (
<>
<TextInput
value={value}
data-testid={dataTestId}
ouiaId={ouiaId}
type="text"
onChange={wrappedOnChange}
validated={validated}
aria-label={ariaLabel}
onBlur={handleBlur}
placeholder={placeholder}
/>
{validated === 'error' && (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{helperText}
</HelperTextItem>
</HelperText>
)}
</>
);
};
export const ValidatedTextInput = ({
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>
)}
</>
);
};