Skip to content
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

[EuiForm, EuiCallout] Added focus state to error callout #4497

Merged
merged 14 commits into from
Mar 2, 2021

Conversation

akashgp09
Copy link
Contributor

@akashgp09 akashgp09 commented Feb 6, 2021

Summary

This PR Fixes: #1854

Added ability to show error callout at the bottom of the form .
User can now pass below as an option to invalidCallout prop of EuiForm.

Focus state shifts to EuiCallout when there is any validation error in EuiForm.

Screenshot

< EuiForm isInvalid={showErrors} error={errors} invalidCallout="below" >

Checklist

  • Check against all themes for compatibility in both light and dark modes
  • Checked in mobile
  • Checked in Chrome, Edge, and Firefox
    - [ ] Props have proper autodocs and playground toggles
  • Added documentation
    - [ ] Checked Code Sandbox works for the any docs examples
    - [ ] Added or updated jest tests
    - [ ] Checked for breaking changes and labeled appropriately
  • Checked for accessibility including keyboard-only and screenreader modes
  • A changelog entry exists and is marked appropriately

@kibanamachine
Copy link

Since this is a community submitted pull request, a Jenkins build has not been kicked off automatically. Can an Elastic organization member please verify the contents of this patch and then kick off a build manually?

@chandlerprall chandlerprall self-requested a review February 8, 2021 18:40
Copy link
Contributor

@chandlerprall chandlerprall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making this change! Code changes look good overall, but have a couple small requests to improve documentation & clean up the component code a little.

Comment on lines 30 to 33
display the callout above or below by passing
<EuiCode>invalidCallout=&ldquo;above&ldquo;</EuiCode>(default) or
<EuiCode>invalidCallout=&ldquo;below&ldquo;</EuiCode> depending upon
your choice. Additionally you can also hide the callout by passing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
display the callout above or below by passing
<EuiCode>invalidCallout=&ldquo;above&ldquo;</EuiCode>(default) or
<EuiCode>invalidCallout=&ldquo;below&ldquo;</EuiCode> depending upon
your choice. Additionally you can also hide the callout by passing
display the callout above or below the form by passing
<EuiCode>invalidCallout=&ldquo;above&ldquo;</EuiCode>(default) or
<EuiCode>invalidCallout=&ldquo;below&ldquo;</EuiCode>.
Additionally, you can hide the callout by passing

these changes may need to be reformatted with different line lengths for prettier's linting

@@ -73,7 +73,7 @@ export const EuiForm: FunctionComponent<EuiFormProps> = ({

let optionalErrorAlert;

if (isInvalid && invalidCallout === 'above') {
if (isInvalid && (invalidCallout === 'above' || invalidCallout === 'below')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's invert this to check for !none vs. above || below

Comment on lines 96 to 110
if (invalidCallout === 'above') {
return (
<Element className={classes} {...(rest as HTMLAttributes<HTMLElement>)}>
{optionalErrorAlert}
{children}
</Element>
);
} else {
return (
<Element className={classes} {...(rest as HTMLAttributes<HTMLElement>)}>
{children}
{optionalErrorAlert}
</Element>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a little cleaner to avoid the wrapping if and conditionally render optionalErrorAlert instead,

Suggested change
if (invalidCallout === 'above') {
return (
<Element className={classes} {...(rest as HTMLAttributes<HTMLElement>)}>
{optionalErrorAlert}
{children}
</Element>
);
} else {
return (
<Element className={classes} {...(rest as HTMLAttributes<HTMLElement>)}>
{children}
{optionalErrorAlert}
</Element>
);
}
if (invalidCallout === 'above') {
return (
<Element className={classes} {...(rest as HTMLAttributes<HTMLElement>)}>
{optionalErrorAlert}
{children}
</Element>
);
} else {
return (
<Element className={classes} {...(rest as HTMLAttributes<HTMLElement>)}>
{invalidCallout === 'above' && optionalErrorAlert}
{children}
{invalidCallout === 'below' && optionalErrorAlert}
</Element>

{button}
<EuiSpacer />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this example interactive to show the two positions, I've tested by adding the following at line 36 of validation.js and adding EuiButtonGroup to the list of component imports at the top of the file

 const [calloutPosition, setCalloutPosition] = useState('above');

  return (
    <Fragment>
      <EuiButtonGroup
        legend="sets callout position in the form"
        options={[
          { id: 'above', label: 'Show callout above form' },
          { id: 'below', label: 'Show callout below form' },
        ]}
        idSelected={calloutPosition}
        onChange={setCalloutPosition}
      />

      <EuiSpacer />

      <EuiForm
          ...

form

@chandlerprall
Copy link
Contributor

@akashgp09 thanks for the additional changes! We just had a discussion around this & what was asked for in #1854 and are going to think about the request more and what the best implementation is. For transparency, these thoughts include:

  • shift focus to the validation errors when the box appears, better for screen reader support & can scroll it into view
  • in addition to above/below, is there a version of the component that returns the validation errors element, allowing it to be placed anywhere in the form
  • pattern familiarity & recognition - does this or related changes cause confusion for users who are familiar with the existing pattern of always displaying errors at the top

@cchaos
Copy link
Contributor

cchaos commented Feb 10, 2021

@akashgp09 If you feel up to experimenting on this one, I think the most ideal UX would be to:

  • Always have the form error at the top (don't actually support 'bottom')
  • When the user hits the submit button and there are errors, the user's focus moves to the EuiCallOut that is holding the errors. This accordion PR Shift focus to accordion content when expanded #4442 handled a very similar experience.
  • 🤞 That by shifting the user's focus, the window should auto scroll (or put that focused item within the visible viewport), but if not, implement some sort of smooth scrollTo so that the callout is then visible.

@akashgp09
Copy link
Contributor Author

thank you for the suggestions, will make the changes soon :-)

@akashgp09 akashgp09 marked this pull request as draft February 14, 2021 16:20
@akashgp09
Copy link
Contributor Author

Added focus to Error

Before

Needed to scroll up to see the error

After

Auto scroll to the Error

@akashgp09 akashgp09 changed the title [EuiForm] Added ability to show error callout at the bottom of the form [EuiForm] Added focus to error callout Feb 18, 2021
@chandlerprall
Copy link
Contributor

running CI to deploy this for testing: jenkins test this

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_4497/

@cchaos
Copy link
Contributor

cchaos commented Feb 19, 2021

The auto-scroll works pretty well, but my guess is that the focused element has to include the title of the callout as well. So I'd lean towards putting the focus on the entire EuiCallOut instead of just the bullet list.
Screen Shot 2021-02-19 at 10 19 32 AM

@akashgp09
Copy link
Contributor Author

akashgp09 commented Feb 19, 2021

will wrapping the EuiCallout component within a div work? this way it will cover the title of the callout as well

 <div
            tabIndex={-1}
            ref={(node) => {
              node && node.focus();
            }}>
            <EuiCallOut
              className="euiForm__errors"
              title={addressFormErrors}
              color="danger"
              role="alert"
              aria-live="assertive">
              {optionalErrors}
            </EuiCallOut>
</div>

@akashgp09
Copy link
Contributor Author

Or should i implement forwardRef to EuiCallout component to pass ref

@cchaos
Copy link
Contributor

cchaos commented Feb 19, 2021

I would go the forwardRef route as it's most likely a common pattern to pulling attention to the EuiCallOut.

@@ -84,6 +80,10 @@ export const EuiForm: FunctionComponent<EuiFormProps> = ({
default="Please address the highlighted errors.">
{(addressFormErrors: string) => (
<EuiCallOut
tabIndex={-1}
ref={(node) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting. Because it creates the new ref function every render pass, it will re-focus the element every time and not only first mount. I imagine this will be aggravating in cases when the user tries to edit an invalid field, but we'll test what happens in practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we need to focus the element only once, for the first mount ?
We can accomplish this by setting focus to the Element inside useEffect hook 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested this by adding a setState to track the value of the example's textarea, and not only does the screen scroll back to the error box, but it takes focus off the textarea so I had to click back into it for each character. Either a useEffect, or moving the function passed to ref into a useCallback, should help clear that up.

form_validation_test

@chandlerprall
Copy link
Contributor

jenkins test this

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_4497/

@akashgp09
Copy link
Contributor Author

seems to be working fine, now the focus stays on the textarea while editing.

ezgif-2-f32f52b01b1f

Copy link
Contributor

@chandlerprall chandlerprall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small syntax request & changelog update needs correction, but the code change & functionality looks good to me.

CHANGELOG.md Outdated Show resolved Hide resolved
src/components/form/form.tsx Outdated Show resolved Hide resolved
@akashgp09 akashgp09 changed the title [EuiForm] Added focus to error callout [EuiForm, EuiCallout] Added focus state to error callout Feb 24, 2021
@akashgp09
Copy link
Contributor Author

@chandlerprall i have made the changes as suggested. Please let me know if there are any changes required.

CHANGELOG.md Outdated Show resolved Hide resolved
@cchaos
Copy link
Contributor

cchaos commented Mar 2, 2021

Jenkins, test this

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_4497/

@cchaos
Copy link
Contributor

cchaos commented Mar 2, 2021

Jenkins, test this

Copy link
Contributor

@cchaos cchaos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked this in Chrome, FF, Safari and Chrome mobile. I even checked how it would behave if the form existed in an element with overflow. They all seem to behave well. 🎉

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_4497/

Copy link
Contributor

@chandlerprall chandlerprall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes LGTM; pulled & tested scenarios locally

@cchaos
Copy link
Contributor

cchaos commented Mar 2, 2021

Jenkins, test this

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_4497/

@cchaos cchaos merged commit e4e6970 into elastic:master Mar 2, 2021
@akashgp09
Copy link
Contributor Author

akashgp09 commented Mar 2, 2021

@chandlerprall @cchaos thank you for your continuous feedbacks and guidance on this one. Finally it's done ^_^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[EuiForm] Add ability to show error callout in the bottom of the form
4 participants