Skip to content

Commit

Permalink
fix(Emptystate): accessibility violation (#6605)
Browse files Browse the repository at this point in the history
* fix(Empty state): hide decorative img from assistive technology

* fix(EmptyState): composable heading

* test(Datagrid): resolve test failure for datagrid  empty state

* fix(EmptyState): adding default to headingAs

* fix(EmptyState): giving defaults to headingAs in all variants
  • Loading branch information
anamikaanu96 authored Dec 30, 2024
1 parent 1bd724a commit cc8f1aa
Show file tree
Hide file tree
Showing 23 changed files with 150 additions and 26 deletions.
37 changes: 28 additions & 9 deletions packages/ibm-products/src/components/Datagrid/Datagrid.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1184,19 +1184,38 @@ describe(componentName, () => {
const { rerender } = render(<EmptyUsage data-testid={dataTestId} />);
screen.getAllByText('Empty State Title');
screen.getByText('Description test explaining why this card is empty.');
expect(screen.getByRole('img')).toHaveClass(
`${pkg.prefix}--empty-state__illustration-noData`
);

expect(
screen
.getAllByRole('img', { hidden: true })
.find((img) =>
img.classList.contains(
`${pkg.prefix}--empty-state__illustration-noData`
)
)
).toBeInTheDocument();

rerender(<EmptyUsage emptyStateType="error" />);
expect(screen.getByRole('img')).toHaveClass(
`${pkg.prefix}--empty-state__illustration-error`
);
expect(
screen
.getAllByRole('img', { hidden: true })
.find((img) =>
img.classList.contains(
`${pkg.prefix}--empty-state__illustration-error`
)
)
).toBeInTheDocument();

rerender(<EmptyUsage emptyStateType="notFound" />);
expect(screen.getByRole('img')).toHaveClass(
`${pkg.prefix}--empty-state__illustration-notFound`
);
expect(
screen
.getAllByRole('img', { hidden: true })
.find((img) =>
img.classList.contains(
`${pkg.prefix}--empty-state__illustration-notFound`
)
)
).toBeInTheDocument();

rerender(<EmptyUsage emptyStateType="12345" />);
expect(screen.queryByRole('img')).not.toBeInTheDocument();
Expand Down
20 changes: 16 additions & 4 deletions packages/ibm-products/src/components/EmptyStates/EmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

// Import portions of React that are needed.
import React, { ReactNode } from 'react';
import React, { ElementType, ReactNode } from 'react';
import { EmptyStateV2 } from '.';

// Other standard imports.
Expand All @@ -32,9 +32,10 @@ enum sizes {
}

// Default values for props
export const defaults: { position: string; size: sizes } = {
export const defaults: { position: string; size: sizes; headingAs: string } = {
position: 'top',
size: sizes.lg,
headingAs: 'h3',
};

export interface EmptyStateProps {
Expand Down Expand Up @@ -77,6 +78,11 @@ export interface EmptyStateProps {
href?: string;
};

/**
* Empty state headingAs allows you to customize the type of heading element
*/
headingAs?: (() => ReactNode) | string | ElementType;

/**
* Empty state size
*/
Expand Down Expand Up @@ -116,6 +122,7 @@ export let EmptyState = React.forwardRef<HTMLDivElement, EmptyStateProps>(
illustrationPosition = defaults.position,
link,
size = defaults.size,
headingAs = defaults.headingAs,
subtitle,
title,
...rest
Expand All @@ -140,12 +147,14 @@ export let EmptyState = React.forwardRef<HTMLDivElement, EmptyStateProps>(
`${blockClass}__illustration`,
`${blockClass}__illustration--${size}`,
])}
aria-hidden="true"
/>
)}
<EmptyStateContent
action={action}
link={link}
size={size}
headingAs={headingAs}
subtitle={subtitle}
title={title ?? ''}
/>
Expand Down Expand Up @@ -176,6 +185,11 @@ EmptyState.propTypes = {
*/
className: PropTypes.string,

/**
* Empty state headingAs allows you to customize the type of heading element
*/
headingAs: PropTypes.elementType,

/**
* Empty state illustration, specify the `src` for a provided illustration to be displayed. In the case of requiring a light and dark illustration of your own, simply pass the corresponding illustration based on the current theme of your application.
* For example: `illustration={appTheme === 'dark' ? darkIllustration : lightIllustration}`
Expand All @@ -194,7 +208,6 @@ EmptyState.propTypes = {
* Designates the position of the illustration relative to the content
*/
illustrationPosition: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),

/**
* Empty state link object
*/
Expand All @@ -204,7 +217,6 @@ EmptyState.propTypes = {
text: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
href: PropTypes.string,
}),

/**
* Empty state size
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,30 @@ import { pkg } from '../../settings';
import cx from 'classnames';

// Carbon and package components we use.
import { Button, Link } from '@carbon/react';
import { Button, Link, Section } from '@carbon/react';

// The block part of our conventional BEM class names (blockClass__E--M).
const blockClass = `${pkg.prefix}--empty-state`;
const componentName = 'EmptyStateContent';

export const EmptyStateContent = ({ action, link, size, subtitle, title }) => {
export const EmptyStateContent = ({
action,
link,
headingAs,
size,
subtitle,
title,
}) => {
return (
<div className={`${blockClass}__content`}>
<h3
<Section
as={headingAs}
className={cx(`${blockClass}__header`, {
[`${blockClass}__header--small`]: size === 'sm',
})}
>
{title}
</h3>
</Section>
{subtitle && (
<p
className={cx(`${blockClass}__subtitle`, {
Expand Down Expand Up @@ -79,6 +87,10 @@ EmptyStateContent.propTypes = {
onClick: Button.propTypes.onClick,
text: PropTypes.string,
}),
/**
* Empty state headingAs allows you to customize the type of heading element
*/
headingAs: PropTypes.elementType,
/**
* Empty state link object
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const EmptyStateIllustration = ({ kind, ...rest }) => {
const Illustration = getIllustration(kind);
return (
<Suspense>
<Illustration {...rest} />
<Illustration aria-hidden="true" {...rest} />
</Suspense>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default {
};

const emptyStateCommonProps = {
headingAs: 'h3',
title: 'Start by adding data assets',
subtitle: (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default {
};

const defaultStoryProps = {
headingAs: 'h3',
title: 'Empty state title',
subtitle: 'Description text explaining why this section is empty.',
illustrationDescription: 'Test alt text',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

// Import portions of React that are needed.
import React, { ReactNode } from 'react';
import React, { ElementType, ReactNode } from 'react';

// Other standard imports.
import PropTypes from 'prop-types';
Expand Down Expand Up @@ -68,6 +68,10 @@ export interface ErrorEmptyStateProps {
href?: string;
};

/**
* Empty state headingAs allows you to customize the type of heading element
*/
headingAs?: (() => ReactNode) | string | ElementType;
/**
* Empty state size
*/
Expand Down Expand Up @@ -102,6 +106,7 @@ export let ErrorEmptyState = React.forwardRef<
illustrationTheme,
link,
size = defaults.size,
headingAs = defaults.headingAs,
subtitle,
title,

Expand Down Expand Up @@ -134,6 +139,7 @@ export let ErrorEmptyState = React.forwardRef<
action={action}
link={link}
size={size}
headingAs={headingAs}
subtitle={subtitle}
title={title || ''}
/>
Expand Down Expand Up @@ -171,6 +177,11 @@ ErrorEmptyState.propTypes = {
*/
className: PropTypes.string,

/**
* Empty state headingAs allows you to customize the type of heading element
*/
headingAs: PropTypes.elementType,

/**
* The alt text for empty state svg images. If not provided , title will be used.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default {
};

const defaultStoryProps = {
headingAs: 'h3',
title: 'Empty state title',
subtitle: 'Description text explaining why this section is empty.',
illustrationDescription: 'Test alt text',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

// Import portions of React that are needed.
import React, { ReactNode } from 'react';
import React, { ElementType, ReactNode } from 'react';

// Other standard imports.
import PropTypes from 'prop-types';
Expand Down Expand Up @@ -67,6 +67,11 @@ export interface NoDataEmptyStateProps {
href?: string;
};

/**
* Empty state headingAs allows you to customize the type of heading element
*/
headingAs?: (() => ReactNode) | string | ElementType;

/**
* Empty state size
*/
Expand Down Expand Up @@ -101,6 +106,7 @@ export let NoDataEmptyState = React.forwardRef<
illustrationDescription,
link,
size = defaults.size,
headingAs = defaults.headingAs,
subtitle,
title,

Expand Down Expand Up @@ -133,6 +139,7 @@ export let NoDataEmptyState = React.forwardRef<
action={action}
link={link}
size={size}
headingAs={headingAs}
subtitle={subtitle}
title={title || ''}
/>
Expand Down Expand Up @@ -170,6 +177,11 @@ NoDataEmptyState.propTypes = {
*/
className: PropTypes.string,

/**
* Empty state headingAs allows you to customize the type of heading element
*/
headingAs: PropTypes.elementType,

/**
* The alt text for empty state svg images. If not provided , title will be used.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default {
};

const defaultStoryProps = {
headingAs: 'h3',
title: 'Empty state title',
subtitle: 'Description text explaining why this section is empty.',
illustrationDescription: 'Test alt text',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

// Import portions of React that are needed.
import React, { ReactNode } from 'react';
import React, { ElementType, ReactNode } from 'react';

// Other standard imports.
import PropTypes from 'prop-types';
Expand Down Expand Up @@ -68,6 +68,11 @@ export interface NoTagsEmptyStateProps {
href?: string;
};

/**
* Empty state headingAs allows you to customize the type of heading element
*/
headingAs?: (() => ReactNode) | string | ElementType;

/**
* Empty state size
*/
Expand Down Expand Up @@ -102,6 +107,7 @@ export let NoTagsEmptyState = React.forwardRef<
illustrationDescription,
link,
size = defaults.size,
headingAs = defaults.headingAs,
subtitle,
title,

Expand Down Expand Up @@ -134,6 +140,7 @@ export let NoTagsEmptyState = React.forwardRef<
action={action}
link={link}
size={size}
headingAs={headingAs}
subtitle={subtitle}
title={(title = '')}
/>
Expand Down Expand Up @@ -171,6 +178,11 @@ NoTagsEmptyState.propTypes = {
*/
className: PropTypes.string,

/**
* Empty state headingAs allows you to customize the type of heading element
*/
headingAs: PropTypes.elementType,

/**
* The alt text for empty state svg images. If not provided , title will be used.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default {
};

const defaultStoryProps = {
headingAs: 'h3',
title: 'Empty state title',
subtitle: 'Description text explaining why this section is empty.',
illustrationDescription: 'Test alt text',
Expand Down
Loading

0 comments on commit cc8f1aa

Please sign in to comment.