Skip to content

Commit

Permalink
fix(accessibility): use alert and aria-busy for loader
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinWeb committed Sep 8, 2023
1 parent 6cdea0a commit 9766a55
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 21 deletions.
33 changes: 24 additions & 9 deletions packages/loader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,29 @@ import '@axa-fr/react-toolkit-loader/dist/af-spinner.css';

### Use

The `text` attribute is optionnal. If you pass it to the component it will be displayed under the loader.
If not, the message will be calculated based on the mode you're using.

| mode | Associated message |
| ------ | ------------------------------------------------------- |
| get | Chargement en cours |
| post | Sauvegarde en cours |
| delete | Suppression en cours |
| update | Mise à jour en cours |
| error | Une erreur est survenue lors du chargement du composant |

```javascript
const LoaderDefault = () => (
<Loader mode="get" text="Chargement en cours">
<div>
<h1>Title Child</h1>
<span>Here your child component</span>
</div>
</Loader>
);
export default LoaderDefault;
<Loader mode="get" text="Chargement en cours">
<div>
<h1>Title Child</h1>
<span>Here your child component</span>
</div>
</Loader>
...
<Loader mode="update">
<div>
<h1>Title Child</h1>
<span>Here your child component</span>
</div>
</Loader>
```
13 changes: 9 additions & 4 deletions packages/loader/src/Loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const texts: { [index: string]: string } = {
type LoaderProps = {
className?: string;
mode: string;
text: string;
text?: string;
children: React.ReactNode;
classModifier?: string;
};
Expand All @@ -32,15 +32,20 @@ const Loader = ({
);
const message = text || texts[mode];
const isLoaderVisible = mode !== LoaderModes.none;
const isLoaderErrored = mode === LoaderModes.error;
const isLoaderInError = mode === LoaderModes.error;

return (
<div className={componentClassName}>
{children}
{isLoaderVisible && (
<div className={`${componentClassName} af-loader-on`}>
<div className="af-spinner">
{!isLoaderErrored && <div className="af-spinner__animation" />}
<div
className="af-spinner"
role="alert"
aria-live="assertive"
aria-busy={!isLoaderInError}
aria-label={message}>
{!isLoaderInError && <div className="af-spinner__animation" />}
<div className="af-spinner__caption">{message}</div>
</div>
</div>
Expand Down
48 changes: 40 additions & 8 deletions packages/loader/src/__tests__/Loader.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import LoaderModes from '../LoaderModes';

describe('<Loader>', () => {
it.each`
mode | message | isMessageVisible | isSpinnerVisible
${LoaderModes.get} | ${''} | ${true} | ${true}
${LoaderModes.none} | ${'Chargement en cours'} | ${false} | ${false}
${LoaderModes.error} | ${''} | ${true} | ${false}
mode | message | isMessageVisible | isSpinnerVisible
${LoaderModes.get} | ${'Chargement des informations en cours'} | ${true} | ${true}
${LoaderModes.none} | ${'Chargement en cours'} | ${false} | ${false}
${LoaderModes.error} | ${'Erreur de chargement'} | ${true} | ${false}
`(
'renders Loader with mode : $mode and message : $message and isMessageVisible is $isMessageVisible and isSpinnerVisible is $isSpinnerVisible',
({ mode, message, isMessageVisible, isSpinnerVisible }) => {
Expand All @@ -23,11 +23,15 @@ describe('<Loader>', () => {
const messageLoader = document.getElementsByClassName(
'af-spinner__caption'
);
isMessageVisible
? expect(messageLoader.length).toBe(1)
: expect(messageLoader.length).toBe(0);
if (isMessageVisible) {
expect(messageLoader.length).toBe(1);
expect(queryByText(message)).toBeInTheDocument();
} else {
expect(messageLoader.length).toBe(0);
expect(queryByText(message)).not.toBeInTheDocument();
}

expect(queryByText('Title Child')).not.toBeNull();
expect(queryByText('Title Child')).toBeInTheDocument();

const animation = document.getElementsByClassName(
'af-spinner__animation'
Expand All @@ -37,4 +41,32 @@ describe('<Loader>', () => {
: expect(animation.length).toBe(0);
}
);

it.each`
mode | isElementBusy | message
${LoaderModes.get} | ${true} | ${'Chargement en cours'}
${LoaderModes.post} | ${true} | ${'Sauvegarde en cours'}
${LoaderModes.delete} | ${true} | ${'Suppression en cours'}
${LoaderModes.update} | ${true} | ${'Mise à jour en cours'}
${LoaderModes.error} | ${false} | ${'Une erreur est survenue lors du chargement du composant'}
${LoaderModes.none} | ${false} | ${''}
`(
'renders Loader with mode : $mode, aria-busy should be set to $isElementBusy',
({ mode, isElementBusy, message }) => {
const { queryByRole, queryByText } = render(
<Loader mode={mode}>
<div>
<h1>Title Child</h1>
<span>Here your child component</span>
</div>
</Loader>
);
const loader = queryByRole('alert');

if (loader !== null) {
expect(queryByText(message)).toBeInTheDocument();
expect(loader.getAttribute('aria-busy')).toBe(isElementBusy.toString());
}
}
);
});

0 comments on commit 9766a55

Please sign in to comment.