-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TextField and TextArea: add error
prop
#2347
Open
beaesguerra
wants to merge
12
commits into
main
Choose a base branch
from
form-error-states
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
b5c1e1b
TextArea: rename internal error state to errorMessage for clarity
beaesguerra 98017f6
TextArea: add `error` prop so it can be put in an error state declart…
beaesguerra b99f434
TextField: rename internal error state to errorMessage for clarity
beaesguerra 59fbbd2
TextField: add `error` prop so it can be put in an error state declar…
beaesguerra 6382c67
Add changeset
beaesguerra 8b977af
TextArea stories: Add story with error prop and validate prop. Update…
beaesguerra c5dfedf
TextField stories: Add story with error prop and validate prop. Updat…
beaesguerra 40aa75f
TextArea: Add more docs for error/validate props
beaesguerra 42b1818
TextField: Add more docs for error/validate props
beaesguerra 78779d5
fix linting
beaesguerra 78d91a7
Merge branch 'main' into form-error-states
beaesguerra 3240bef
update error stories so it shows a different error message when the e…
beaesguerra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,36 @@ 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, | ||
}, | ||
}; | ||
|
||
/** | ||
* 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) { | ||
|
@@ -178,6 +204,83 @@ export const Error: StoryComponentType = { | |
render: ControlledTextArea, | ||
}; | ||
|
||
/** | ||
* 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, | ||
}, | ||
}; | ||
|
||
/** | ||
* A required field will have error styling if the field is left blank. To | ||
* observe this, type something into the field, backspace all the way, | ||
|
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we can explicitly put the component in an error state, the variants stories can properly show the error styling in all cases now! Previously, we weren't able to show an empty field with an error state since validation wouldn't be triggered yet in that case.