Skip to content

Commit

Permalink
feat(ValidatedTextInput): add onBlur prop and refactor helpers to use…
Browse files Browse the repository at this point in the history
… options objects (#93)

Allows passing an extra onBlur handler to ValidatedTextInput and ValidatedPasswordInput,
and to facilitate it, refactors getFormGroupProps and getTextInputProps to take an object of
options including the existing greenWhenValid and the new onBlur (to avoid piling on unnamed params)

BREAKING CHANGE: Any direct calls to getTextInputProps or getFormGroupProps that were passing a
boolean for greenWhenValid will now need to instead pass an options object including greenWhenValid
as a property.
  • Loading branch information
mturley authored Feb 13, 2022
1 parent 34e6a32 commit a52a0b7
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 37 deletions.
13 changes: 10 additions & 3 deletions src/components/ValidatedTextInput/ValidatedPasswordInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import {
TextInputProps,
} from '@patternfly/react-core';
import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons';
import { IValidatedFormField, getFormGroupProps, getTextInputProps } from '../..';
import { IValidatedFormField, getFormGroupProps, getTextInputProps, TextFieldOptions } from '../..';

interface IValidatedPasswordInputProps
extends Pick<FormGroupProps, 'label' | 'fieldId' | 'isRequired'> {
/** A field returned from useFormField() or useFormState().fields. */
field: IValidatedFormField<string> | IValidatedFormField<string | undefined>;
/** Whether to show the green 'valid' style when the field has been validated. Defaults to false ('default' style) */
greenWhenValid?: boolean;
/** Extra callback to call onBlur in addition to setting the field isTouched in state */
onBlur?: () => void;
/** Any extra props for the PatternFly FormGroup */
formGroupProps?: Partial<FormGroupProps>;
/** Any extra props for the PatternFly TextInput */
Expand All @@ -27,25 +31,28 @@ export const ValidatedPasswordInput: React.FunctionComponent<IValidatedPasswordI
label,
fieldId,
isRequired,
greenWhenValid = false,
onBlur,
formGroupProps = {},
inputProps = {},
showPasswordAriaLabel = `Show ${label}`,
hidePasswordAriaLabel = `Hide ${label}`,
}: IValidatedPasswordInputProps) => {
const [isValueVisible, toggleValueVisible] = React.useReducer((isVisible) => !isVisible, false);
const options: TextFieldOptions = { greenWhenValid, onBlur };
return (
<FormGroup
label={label}
isRequired={isRequired}
fieldId={fieldId}
{...getFormGroupProps(field as IValidatedFormField<string | undefined>)}
{...getFormGroupProps(field as IValidatedFormField<string | undefined>, options)}
{...formGroupProps}
>
<InputGroup>
<TextInput
id={fieldId}
type={isValueVisible ? 'text' : 'password'}
{...getTextInputProps(field)}
{...getTextInputProps(field, options)}
{...(inputProps as Partial<TextInputProps>)}
/>
<Button
Expand Down
57 changes: 32 additions & 25 deletions src/components/ValidatedTextInput/ValidatedTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getFormGroupProps,
getTextAreaProps,
getTextInputProps,
TextFieldOptions,
} from '../../hooks/useFormState';

interface IValidatedTextInputProps
Expand All @@ -23,6 +24,8 @@ interface IValidatedTextInputProps
component?: typeof TextInput | typeof TextArea;
/** Whether to show the green 'valid' style when the field has been validated. Defaults to false ('default' style) */
greenWhenValid?: boolean;
/** Extra callback to call onBlur in addition to setting the field isTouched in state */
onBlur?: () => void;
/** Any extra props for the PatternFly FormGroup */
formGroupProps?: Partial<FormGroupProps>;
/** Any extra props for the PatternFly TextInput or TextArea */
Expand All @@ -37,30 +40,34 @@ export const ValidatedTextInput: React.FunctionComponent<IValidatedTextInputProp
isRequired,
type = 'text',
greenWhenValid = false,
onBlur,
formGroupProps = {},
inputProps = {},
}: IValidatedTextInputProps) => (
<FormGroup
label={label}
isRequired={isRequired}
fieldId={fieldId}
{...getFormGroupProps(field as IValidatedFormField<string | undefined>, greenWhenValid)}
{...formGroupProps}
>
{component === TextInput ? (
<TextInput
id={fieldId}
type={type}
{...getTextInputProps(field, greenWhenValid)}
{...(inputProps as Partial<TextInputProps>)}
/>
) : (
<TextArea
id={fieldId}
{...getTextAreaProps(field, greenWhenValid)}
{...(inputProps as Partial<TextAreaProps>)}
ref={null} // Necessary because of some weird TS issue with spreading Partial<TextAreaProps>['ref']
/>
)}
</FormGroup>
);
}: IValidatedTextInputProps) => {
const options: TextFieldOptions = { greenWhenValid, onBlur };
return (
<FormGroup
label={label}
isRequired={isRequired}
fieldId={fieldId}
{...getFormGroupProps(field as IValidatedFormField<string | undefined>, options)}
{...formGroupProps}
>
{component === TextInput ? (
<TextInput
id={fieldId}
type={type}
{...getTextInputProps(field, options)}
{...(inputProps as Partial<TextInputProps>)}
/>
) : (
<TextArea
id={fieldId}
{...getTextAreaProps(field, options)}
{...(inputProps as Partial<TextAreaProps>)}
ref={null} // Necessary because of some weird TS issue with spreading Partial<TextAreaProps>['ref']
/>
)}
</FormGroup>
);
};
30 changes: 21 additions & 9 deletions src/hooks/useFormState/useFormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,33 +219,45 @@ export const useFormState = <TFieldValues>(

// PatternFly-specific rendering helpers for FormGroup and TextInput components:

export interface FormGroupOptions {
greenWhenValid?: boolean;
}

export const getFormGroupProps = <T>(
field: Pick<IValidatedFormField<T>, 'isTouched' | 'isValid' | 'error' | 'shouldShowError'>,
greenWhenValid = false
options?: FormGroupOptions
): Pick<FormGroupProps, 'validated' | 'helperTextInvalid'> => {
const validStyle: FormGroupProps['validated'] = greenWhenValid ? 'success' : 'default';
const validStyle: FormGroupProps['validated'] = options?.greenWhenValid ? 'success' : 'default';
return {
validated: field.shouldShowError ? 'error' : field.isValid ? validStyle : 'default',
helperTextInvalid: field.error?.message,
};
};

export interface TextFieldOptions {
greenWhenValid?: boolean;
onBlur?: () => void;
}

export const getTextFieldProps = (
field: IValidatedFormField<string> | IValidatedFormField<string | undefined>,
greenWhenValid = false
options?: TextFieldOptions
): Pick<TextInputProps | TextAreaProps, 'value' | 'onChange' | 'onBlur' | 'validated'> => ({
value: field.value,
onChange: field.setValue,
onBlur: () => field.setIsTouched(true),
validated: getFormGroupProps(field, greenWhenValid).validated,
onBlur: () => {
field.setIsTouched(true);
options?.onBlur?.();
},
validated: getFormGroupProps(field, options).validated,
});

export const getTextInputProps = (
field: IValidatedFormField<string> | IValidatedFormField<string | undefined>,
greenWhenValid = false
): Partial<TextInputProps> => getTextFieldProps(field, greenWhenValid) as Partial<TextInputProps>;
options?: TextFieldOptions
): Partial<TextInputProps> => getTextFieldProps(field, options) as Partial<TextInputProps>;

export const getTextAreaProps = (
field: IValidatedFormField<string> | IValidatedFormField<string | undefined>,
greenWhenValid = false
): Partial<TextAreaProps> => getTextFieldProps(field, greenWhenValid) as Partial<TextAreaProps>;
options?: TextFieldOptions
): Partial<TextAreaProps> => getTextFieldProps(field, options) as Partial<TextAreaProps>;

0 comments on commit a52a0b7

Please sign in to comment.