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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- Added `EuiComboBoxOptionOption` prop to `EuiComboBox` props table ([#4563](https://github.com/elastic/eui/pull/4563))
- Allowed dynamically changing the `direction` prop on `EuiResizableContainer` ([#4557](https://github.com/elastic/eui/pull/4557))
- Exported `useIsWithinBreakpoints` hook ([#4557](https://github.com/elastic/eui/pull/4557))
- Added focus to `EuiForm` error `EuiCallout` ([#4497](https://github.com/elastic/eui/pull/4497))

**Bug fixes**

Expand Down
110 changes: 58 additions & 52 deletions src/components/call_out/call_out.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import React, { FunctionComponent, HTMLAttributes, ReactNode } from 'react';
import React, { forwardRef, Ref, HTMLAttributes, ReactNode } from 'react';

import classNames from 'classnames';

Expand Down Expand Up @@ -54,59 +54,65 @@ const sizeToClassNameMap: { [size in Size]: string } = {
m: '',
};

export const EuiCallOut: FunctionComponent<EuiCallOutProps> = ({
title,
color = 'primary',
size = 'm',
iconType,
children,
className,
heading,
...rest
}) => {
const classes = classNames(
'euiCallOut',
colorToClassNameMap[color],
sizeToClassNameMap[size],
className
);

let headerIcon;

if (iconType) {
headerIcon = (
<EuiIcon
className="euiCallOutHeader__icon"
type={iconType}
size="m"
aria-hidden="true"
/>
export const EuiCallOut = forwardRef<HTMLDivElement, EuiCallOutProps>(
(
{
title,
color = 'primary',
size = 'm',
iconType,
children,
className,
heading,
...rest
},
ref: Ref<HTMLDivElement>
) => {
const classes = classNames(
'euiCallOut',
colorToClassNameMap[color],
sizeToClassNameMap[size],
className
);
}

let optionalChildren;
if (children && size === 's') {
optionalChildren = <EuiText size="xs">{children}</EuiText>;
} else if (children) {
optionalChildren = <EuiText size="s">{children}</EuiText>;
}

const H: any = heading ? `${heading}` : 'span';
let header;

if (title) {
header = (
<div className="euiCallOutHeader">
{headerIcon}
<H className="euiCallOutHeader__title">{title}</H>
let headerIcon;

if (iconType) {
headerIcon = (
<EuiIcon
className="euiCallOutHeader__icon"
type={iconType}
size="m"
aria-hidden="true"
/>
);
}

let optionalChildren;
if (children && size === 's') {
optionalChildren = <EuiText size="xs">{children}</EuiText>;
} else if (children) {
optionalChildren = <EuiText size="s">{children}</EuiText>;
}

const H: any = heading ? `${heading}` : 'span';
let header;

if (title) {
header = (
<div className="euiCallOutHeader">
{headerIcon}
<H className="euiCallOutHeader__title">{title}</H>
</div>
);
}
return (
<div className={classes} ref={ref} {...rest}>
{header}

{optionalChildren}
</div>
);
}
return (
<div className={classes} {...rest}>
{header}

{optionalChildren}
</div>
);
};
);
EuiCallOut.displayName = 'EuiCallOut';
3 changes: 3 additions & 0 deletions src/components/form/__snapshots__/form.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ exports[`EuiForm renders with error callout when isInvalid is "true" 1`] = `
aria-live="assertive"
class="euiCallOut euiCallOut--danger euiForm__errors"
role="alert"
tabindex="-1"
>
<div
class="euiCallOutHeader"
Expand All @@ -50,6 +51,7 @@ exports[`EuiForm renders with error callout when isInvalid is "true" and has mul
aria-live="assertive"
class="euiCallOut euiCallOut--danger euiForm__errors"
role="alert"
tabindex="-1"
>
<div
class="euiCallOutHeader"
Expand Down Expand Up @@ -94,6 +96,7 @@ exports[`EuiForm renders with error callout when isInvalid is "true" and has one
aria-live="assertive"
class="euiCallOut euiCallOut--danger euiForm__errors"
role="alert"
tabindex="-1"
>
<div
class="euiCallOutHeader"
Expand Down
7 changes: 7 additions & 0 deletions src/components/form/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import React, {
ReactNode,
HTMLAttributes,
FormHTMLAttributes,
useCallback,
} from 'react';
import classNames from 'classnames';
import { EuiCallOut } from '../call_out';
Expand Down Expand Up @@ -54,6 +55,10 @@ export const EuiForm: FunctionComponent<EuiFormProps> = ({
invalidCallout = 'above',
...rest
}) => {
const handleFocus = useCallback((node) => {
node?.focus();
}, []);

const classes = classNames('euiForm', className);

let optionalErrors: JSX.Element | null = null;
Expand All @@ -80,6 +85,8 @@ export const EuiForm: FunctionComponent<EuiFormProps> = ({
default="Please address the highlighted errors.">
{(addressFormErrors: string) => (
<EuiCallOut
tabIndex={-1}
ref={handleFocus}
className="euiForm__errors"
title={addressFormErrors}
color="danger"
Expand Down