-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TextField and TextArea: add
error
prop (#2347)
## Summary: Adds an `error` prop to TextField and TextArea components so they can be put in an error state declaratively. This is useful for validation that happens after a form submission. Related to the LabeledField work: - By having an error prop, the LabeledField component will set the `error` prop to `true` if it is provided with an error message (will do this in a separate PR for LabeledField work) - The error prop is consistent with other fields such as the SingleSelect, MultiSelect, and Combobox components. I'll be updating SearchField in another PR (with other updates)! - These changes can be released separately from the LabeledField changes since they are incremental changes to existing components Issue: WB-1777 ## Test plan: ### TextField - Setting the `error` prop puts the component in an error state (`aria-invalid` is set to `true` and error styling is applied) (`?path=/story/packages-form-textfield--error`) - Validation continues to put the component in an error state if the value is not valid (`?path=/story/packages-form-textfield--error-from-validation`) - Required prop continues to put the component in an error state if a value is cleared (`?path=/story/packages-form-textfield--required`) ### TextArea - Setting the `error` prop puts the component in an error state (`aria-invalid` is set to `true` and error styling is applied) (`?path=/story/packages-form-textarea--error`) - Validation continues to put the component in an error state if the value is not valid (`?path=/story/packages-form-textarea--error-from-validation`) - Required prop continues to put the component in an error state if a value is cleared (`?path=/story/packages-form-textarea--required`) Author: beaesguerra Reviewers: beaesguerra, jandrade Required Reviewers: Approved By: jandrade Checks: ✅ Chromatic - Get results on regular PRs (ubuntu-latest, 20.x), ✅ Lint / Lint (ubuntu-latest, 20.x), ✅ Test / Test (ubuntu-latest, 20.x, 2/2), ✅ Test / Test (ubuntu-latest, 20.x, 1/2), ✅ Check build sizes (ubuntu-latest, 20.x), ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Chromatic - Build on regular PRs / chromatic (ubuntu-latest, 20.x), ⏭️ Chromatic - Skip on Release PR (changesets), ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 20.x), ✅ gerald, ⏭️ dependabot Pull Request URL: #2347
- Loading branch information
1 parent
fece121
commit cdcfe1b
Showing
10 changed files
with
399 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@khanacademy/wonder-blocks-form": minor | ||
--- | ||
|
||
- TextArea and TextField: Adds `error` prop so that the components can be put in an error state explicitly. This is useful for backend validation errors after a form has already been submitted. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ import {color, spacing} from "@khanacademy/wonder-blocks-tokens"; | |
import Button from "@khanacademy/wonder-blocks-button"; | ||
import {LabelSmall, LabelLarge} from "@khanacademy/wonder-blocks-typography"; | ||
import {Strut} from "@khanacademy/wonder-blocks-layout"; | ||
import {View} from "@khanacademy/wonder-blocks-core"; | ||
import {PropsFor, View} from "@khanacademy/wonder-blocks-core"; | ||
|
||
import TextAreaArgTypes from "./text-area.argtypes"; | ||
|
||
|
@@ -60,9 +60,9 @@ const styles = StyleSheet.create({ | |
}, | ||
}); | ||
|
||
const ControlledTextArea = (args: any) => { | ||
const ControlledTextArea = (args: PropsFor<typeof TextArea>) => { | ||
const [value, setValue] = React.useState(args.value || ""); | ||
const [error, setError] = React.useState<string | null>(null); | ||
const [error, setError] = React.useState<string | null | undefined>(null); | ||
|
||
const handleChange = (newValue: string) => { | ||
setValue(newValue); | ||
|
@@ -77,7 +77,11 @@ const ControlledTextArea = (args: any) => { | |
onValidate={setError} | ||
/> | ||
<Strut size={spacing.xxSmall_6} /> | ||
{error && <LabelSmall style={styles.error}>{error}</LabelSmall>} | ||
{(error || args.error) && ( | ||
<LabelSmall style={styles.error}> | ||
{error || "Error from error prop"} | ||
</LabelSmall> | ||
)} | ||
</View> | ||
); | ||
}; | ||
|
@@ -158,14 +162,42 @@ export const ReadOnly: StoryComponentType = { | |
}, | ||
}; | ||
|
||
/** | ||
* If the `error` prop is set to true, the TextArea will have error styling and | ||
* `aria-invalid` set to `true`. | ||
* | ||
* This is useful for scenarios where we want to show an error on a | ||
* specific field after a form is submitted (server validation). | ||
* | ||
* Note: The `required` and `validate` props can also put the TextArea in an | ||
* error state. | ||
*/ | ||
export const Error: StoryComponentType = { | ||
render: ControlledTextArea, | ||
args: { | ||
value: "With error", | ||
error: true, | ||
}, | ||
parameters: { | ||
chromatic: { | ||
// Disabling because this doesn't test anything visual. | ||
disableSnapshot: true, | ||
}, | ||
}, | ||
}; | ||
|
||
/** | ||
* If the textarea fails validation, `TextArea` will have error styling. | ||
* | ||
* This is useful for scenarios where we want to show errors while a | ||
* user is filling out a form (client validation). | ||
* | ||
* Note that we will internally set the correct `aria-invalid` attribute to the | ||
* `textarea` element: | ||
* - `aria-invalid="true"` if there is an error message. | ||
* - `aria-invalid="false"` if there is no error message. | ||
* - `aria-invalid="true"` if there is an error. | ||
* - `aria-invalid="false"` if there is no error. | ||
*/ | ||
export const Error: StoryComponentType = { | ||
export const ErrorFromValidation: StoryComponentType = { | ||
args: { | ||
value: "khan", | ||
validate(value: string) { | ||
|
@@ -176,6 +208,89 @@ export const Error: StoryComponentType = { | |
}, | ||
}, | ||
render: ControlledTextArea, | ||
parameters: { | ||
chromatic: { | ||
// Disabling because this doesn't test anything visual. | ||
disableSnapshot: true, | ||
}, | ||
}, | ||
}; | ||
|
||
/** | ||
* This example shows how the `error` and `validate` props can both be used to | ||
* put the field in an error state. This is useful for scenarios where we want | ||
* to show error while a user is filling out a form (client validation) | ||
* and after a form is submitted (server validation). | ||
* | ||
* In this example: | ||
* 1. It starts with an invalid email. The error message shown is the message returned | ||
* by the `validate` function prop | ||
* 2. Once the email is fixed to `[email protected]`, the validation error message | ||
* goes away since it is a valid email. | ||
* 3. When the Submit button is pressed, another error message is shown (this | ||
* simulates backend validation). | ||
* 4. When you enter any other email address, the error message is | ||
* cleared. | ||
*/ | ||
export const ErrorFromPropAndValidation = (args: PropsFor<typeof TextArea>) => { | ||
const [value, setValue] = React.useState(args.value || "test@test,com"); | ||
const [validationErrorMessage, setValidationErrorMessage] = React.useState< | ||
string | null | undefined | ||
>(null); | ||
const [backendErrorMessage, setBackendErrorMessage] = React.useState< | ||
string | null | undefined | ||
>(null); | ||
|
||
const handleChange = (newValue: string) => { | ||
setValue(newValue); | ||
// Clear the backend error message on change | ||
setBackendErrorMessage(null); | ||
}; | ||
|
||
const errorMessage = validationErrorMessage || backendErrorMessage; | ||
|
||
return ( | ||
<View> | ||
<TextArea | ||
{...args} | ||
value={value} | ||
onChange={handleChange} | ||
validate={(value: string) => { | ||
const emailRegex = /^[^@\s]+@[^@\s.]+\.[^@.\s]+$/; | ||
if (!emailRegex.test(value)) { | ||
return "Please enter a valid email"; | ||
} | ||
}} | ||
onValidate={setValidationErrorMessage} | ||
error={!!errorMessage} | ||
/> | ||
<Strut size={spacing.xxSmall_6} /> | ||
{errorMessage && ( | ||
<LabelSmall style={styles.error}>{errorMessage}</LabelSmall> | ||
)} | ||
<Strut size={spacing.xxSmall_6} /> | ||
<Button | ||
onClick={() => { | ||
if (value === "[email protected]") { | ||
setBackendErrorMessage( | ||
"This email is already being used, please try another email.", | ||
); | ||
} else { | ||
setBackendErrorMessage(null); | ||
} | ||
}} | ||
> | ||
Submit | ||
</Button> | ||
</View> | ||
); | ||
}; | ||
|
||
ErrorFromPropAndValidation.parameters = { | ||
chromatic: { | ||
// Disabling because this doesn't test anything visual. | ||
disableSnapshot: true, | ||
}, | ||
}; | ||
|
||
/** | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.