Skip to content

Commit 578257c

Browse files
committed
feat(form): added a new useTextField hook to validate the TextField and TextArea values
1 parent 4dfd50a commit 578257c

File tree

5 files changed

+598
-0
lines changed

5 files changed

+598
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ReactNode } from "react";
2+
3+
/**
4+
* A function that can be used to dynamically get an error icon based on the
5+
* current visible error.
6+
*
7+
* @param errorMessage The current error message or an empty string
8+
* @param error Boolean if the `TextField` or `TextArea` are considered to be in
9+
* an errored state
10+
* @param errorIcon The current `errorIcon` that was provided to the
11+
* `useTextField` hook.
12+
* @return An icon to render or falsey to render nothing.
13+
*/
14+
export type GetErrorIcon = (
15+
errorMessage: string,
16+
error: boolean,
17+
errorIcon: ReactNode
18+
) => ReactNode;
19+
20+
/**
21+
* The default implementation for showing an error icon in `TextField` and
22+
* `TextArea` components that will only display when the error flag is enabled.
23+
*/
24+
export const defaultGetErrorIcon: GetErrorIcon = (_message, error, errorIcon) =>
25+
error && errorIcon;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { InputHTMLAttributes } from "react";
2+
3+
export type TextConstraints = Pick<
4+
InputHTMLAttributes<HTMLInputElement>,
5+
"pattern" | "required" | "minLength" | "maxLength"
6+
>;
7+
8+
/**
9+
* Since the default validation messages can be verbose, this type is used to
10+
* configure when/how to display the native browser messages when the validation
11+
* state changes during the `change` event phase. The validation message will
12+
* always be shown on blur.
13+
*
14+
* When this is:
15+
*
16+
* - `true` -> always show the browser message when it exists
17+
* - `false` -> never show the browser message
18+
* - `"recommended"` -> only shows the browser message if it is not one of the
19+
* `RECOMMENDED_STATE_KEYS` validation errors
20+
* - `keyof ValidityState` -> only shows the browser message if it is not the
21+
* specific validation error
22+
* - `(keyof ValidityState)[]` -> only shows the browser message if it is not
23+
* the specific validation errors
24+
*/
25+
export type ChangeValidationBehavior =
26+
| boolean
27+
| "recommended"
28+
| keyof ValidityState
29+
| readonly (keyof ValidityState)[];
30+
31+
export interface ErrorMessageOptions extends TextConstraints {
32+
/**
33+
* The current input or textarea's validity state.
34+
*/
35+
validity: ValidityState;
36+
37+
/**
38+
* The browser defined validation message based on the validity state. This
39+
* will be the empty string when there are no errors.
40+
*/
41+
validationMessage: string;
42+
43+
/**
44+
* The current `TextField` or `TextArea` value.
45+
*/
46+
value: string;
47+
48+
/**
49+
* Boolean if this is triggered from a blur event instead of a change event.
50+
*/
51+
isBlurEvent: boolean;
52+
53+
/**
54+
* The change event validation behavior that is specified in the hook.
55+
*/
56+
validateOnChange: ChangeValidationBehavior;
57+
}
58+
59+
/**
60+
* A function to get a custom error message for specific errors. This is really
61+
* useful when using the `pattern` attribute to give additional information or
62+
* changing the native "language translated" error message.
63+
*
64+
* @param options An object containing metadata that can be used to create an
65+
* error message for your `TextField` or `TextArea`.
66+
* @return An error message to display or an empty string.
67+
*/
68+
export type GetErrorMessage = (options: ErrorMessageOptions) => string;
69+
70+
/** @internal */
71+
const RECOMMENDED_STATE_KEYS: readonly (keyof ValidityState)[] = [
72+
"valueMissing",
73+
"tooShort",
74+
"tooLong",
75+
"badInput",
76+
];
77+
78+
/**
79+
* The default implementation for getting an error message for the `TextField`
80+
* or `TextArea` components that:
81+
*
82+
* - prevents the browser `minLength` and `tooLong` error text from appearing
83+
* during change events since the message is extremely verbose
84+
* - prevents the `valueMissing` and `badInput` error text from appearing during
85+
* change events since it's better to wait for the blur event.
86+
*
87+
* The above behavior is also configured by the {@link ChangeValidationBehavior}.
88+
*/
89+
export const defaultGetErrorMessage: GetErrorMessage = ({
90+
isBlurEvent,
91+
validity,
92+
validationMessage,
93+
validateOnChange,
94+
}) => {
95+
if (isBlurEvent || !validationMessage) {
96+
return validationMessage;
97+
}
98+
99+
if (!validateOnChange) {
100+
return "";
101+
}
102+
103+
let keys = RECOMMENDED_STATE_KEYS;
104+
if (
105+
typeof validateOnChange === "string" &&
106+
validateOnChange !== "recommended"
107+
) {
108+
keys = [validateOnChange];
109+
} else if (Array.isArray(validateOnChange)) {
110+
keys = validateOnChange;
111+
}
112+
113+
if (
114+
Object.entries(validity).some(
115+
([key, value]) => value && !keys.includes(key as keyof ValidityState)
116+
)
117+
) {
118+
return validationMessage;
119+
}
120+
121+
return "";
122+
};

packages/form/src/text-field/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ export * from "./TextFieldContainer";
33
export * from "./TextFieldAddon";
44
export * from "./Password";
55
export * from "./TextArea";
6+
67
export * from "./FormMessage";
8+
9+
export * from "./isErrored";
10+
export * from "./getErrorIcon";
11+
export * from "./getErrorMessage";
12+
export * from "./useTextField";
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { ErrorMessageOptions } from "./getErrorMessage";
2+
3+
export interface IsErroredOptions extends ErrorMessageOptions {
4+
/**
5+
* The current error message or an empty string.
6+
*/
7+
errorMessage: string;
8+
}
9+
10+
/**
11+
* A function that is used to determine if a `TextField` or `TextArea` is in an
12+
* errored state.
13+
*
14+
* @param options All the current options that can be used to determine the
15+
* error state.
16+
* @return True if the component is considered to be in an errored state.
17+
*/
18+
export type IsErrored = (options: IsErroredOptions) => boolean;
19+
20+
/**
21+
* The default implementation for checking if a `TextField` or `TextArea` is
22+
* errored by returning `true` if the `errorMessage` string is truthy or the
23+
* value is not within the `minLength` and `maxLength` constraints when they
24+
* exist.
25+
*/
26+
export const defaultIsErrored: IsErrored = ({
27+
value,
28+
errorMessage,
29+
minLength,
30+
maxLength,
31+
}) =>
32+
!!errorMessage ||
33+
(typeof minLength === "number" && value.length < minLength) ||
34+
(typeof maxLength === "number" && value.length > maxLength);

0 commit comments

Comments
 (0)