Skip to content

Commit

Permalink
feat(a11y): retour du focus sur les listes déroulantes (#3111)
Browse files Browse the repository at this point in the history
  • Loading branch information
juliebrunetto83 authored Jun 27, 2024
1 parent cedc9c9 commit 110898f
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 31 deletions.
3 changes: 2 additions & 1 deletion src/client/components/ui/SeeMore/SeeMoreItemList.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
}

.itemList {
scroll-margin-top: 4rem;
display: flex;
gap: 2rem;
justify-content: center;
Expand All @@ -16,4 +17,4 @@
@include utilities-deprecated.media(medium) {
margin-bottom: 2rem;
}
}
}
25 changes: 23 additions & 2 deletions src/client/components/ui/SeeMore/SeeMoreItemList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { userEvent } from '@testing-library/user-event';
import React from 'react';

import SeeMoreItemList from '~/client/components/ui/SeeMore/SeeMoreItemList';
import { mockScrollBy } from '~/client/components/window.mock';
import { mockScrollIntoView } from '~/client/components/window.mock';

const itemList = [
React.createElement('h1'),
Expand All @@ -16,7 +16,7 @@ const itemList = [

describe('SeeMore component', () => {
beforeEach(() => {
mockScrollBy();
mockScrollIntoView();
});

describe('Lorsque le nombre d‘élements à afficher est supérieur à la taille totale des éléments', () => {
Expand Down Expand Up @@ -55,6 +55,15 @@ describe('SeeMore component', () => {
});

describe('Au clic sur le bouton dépliant', () => {
it('déplace le focus sur la liste d‘élément',async () => {
const user = userEvent.setup();
render(<SeeMoreItemList itemList={itemList} numberOfVisibleItems={2} seeMoreAriaLabel={'Voir plus'} seeLessAriaLabel={''} />);
const button = screen.getByRole('button', { name: 'Voir plus' });
await user.click(button);

expect(screen.getByRole('list')).toHaveFocus();
});

it('affiche l‘ensemble des éléments', async () => {
const user = userEvent.setup();
render(<SeeMoreItemList itemList={itemList} numberOfVisibleItems={2} seeMoreAriaLabel={''} seeLessAriaLabel={''} />);
Expand All @@ -68,6 +77,18 @@ describe('SeeMore component', () => {
});

describe('Au clic à nouveau sur le bouton dépliant', () => {
it('déplace le focus sur la liste', async () => {
const user = userEvent.setup();
render(<SeeMoreItemList itemList={itemList} numberOfVisibleItems={2} seeMoreAriaLabel={'Voir plus'} seeLessAriaLabel={'Voir moins'} />);
const buttonVoirPlus = screen.getByRole('button', { name: 'Voir plus' });
await user.click(buttonVoirPlus);

const buttonVoirMoins = screen.getByRole('button', { name: 'Voir moins' });
await user.click(buttonVoirMoins);

expect(screen.getByRole('list')).toHaveFocus();
});

it('n‘affiche que les premiers éléments visibles', async () => {
const user = userEvent.setup();
render(<SeeMoreItemList itemList={itemList} numberOfVisibleItems={2} seeMoreAriaLabel={''} seeLessAriaLabel={''} />);
Expand Down
52 changes: 25 additions & 27 deletions src/client/components/ui/SeeMore/SeeMoreItemList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function SeeMoreItemList(props: SeeMoreProps) {
...rest
} = props;
const ariaId = useId();
const divRef = useRef<HTMLDivElement>(null);
const listRef = useRef<HTMLUListElement>(null);
const [isOpen, setIsOpen] = useState(false);

const completeItemList = useMemo(() => itemList, [itemList]);
Expand All @@ -46,10 +46,9 @@ export default function SeeMoreItemList(props: SeeMoreProps) {
}, [isOpen, completeItemList, visibleItemList]);

const toggle = useCallback(() => {
if (isOpen && divRef.current) {
const divPosition = divRef.current.getBoundingClientRect();
window.scrollBy({ behavior: 'smooth', top: -divPosition.height });
}
listRef.current?.focus();
listRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });

setIsOpen(!isOpen);
}, [isOpen]);

Expand All @@ -62,30 +61,29 @@ export default function SeeMoreItemList(props: SeeMoreProps) {
return (
<>
{itemListToDisplay.length > 0 &&
<div
ref={divRef}
id={`section-${ariaId}`}
{...rest}
>
<ul className={styles.itemList}>
{itemListToDisplay?.map((element, index) =>
<li key={index}>{element}</li>,
)}
</ul>
</div>
<div
id={`section-${ariaId}`}
{...rest}
>
<ul className={styles.itemList} ref={listRef} tabIndex={-1}>
{itemListToDisplay?.map((element, index) =>
<li key={index}>{element}</li>,
)}
</ul>
</div>
}
{itemList.length > numberOfVisibleItems &&
<ButtonComponent className={classNames(styles.seeMoreButton, className)}
appearance={'quaternary'}
label={buttonLabel}
icon={isOpen ? <Icon name={'angle-up'}/> : <Icon name={'angle-down'}/>}
iconPosition={'right'}
onClick={toggle}
type="button"
aria-expanded={isOpen}
aria-controls={`section-${ariaId}`}
aria-label={buttonAriaLabel}>
</ButtonComponent>
<ButtonComponent className={classNames(styles.seeMoreButton, className)}
appearance={'quaternary'}
label={buttonLabel}
icon={isOpen ? <Icon name={'angle-up'}/> : <Icon name={'angle-down'}/>}
iconPosition={'right'}
onClick={toggle}
type="button"
aria-expanded={isOpen}
aria-controls={`section-${ariaId}`}
aria-label={buttonAriaLabel}>
</ButtonComponent>
}
</>
);
Expand Down
3 changes: 2 additions & 1 deletion src/pages/espace-jeune/index.page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { render, screen, within } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';

import { mockUseRouter } from '~/client/components/useRouter.mock';
import { mockSmallScreen } from '~/client/components/window.mock';
import { mockScrollIntoView, mockSmallScreen } from '~/client/components/window.mock';
import { DependenciesProvider } from '~/client/context/dependenciesContainer.context';
import { aManualAnalyticsService } from '~/client/services/analytics/analytics.service.fixture';
import EspaceJeunePage from '~/pages/espace-jeune/index.page';
Expand All @@ -18,6 +18,7 @@ describe('Page Espace Jeune', () => {
beforeEach(() => {
mockSmallScreen();
mockUseRouter({});
mockScrollIntoView();
});
afterEach(() => {
jest.clearAllMocks();
Expand Down

0 comments on commit 110898f

Please sign in to comment.