From 490cce5bbb789e8ea633960feeabcba1aa0dc9cd Mon Sep 17 00:00:00 2001 From: julie BRUNETTO Date: Mon, 8 Jul 2024 11:07:54 +0200 Subject: [PATCH 1/6] feat(a11y): utiliser le select sur les pages meilisearch --- .../MeilisearchCustomRefinementList.tsx | 89 +--- .../MeilisearchCustomRefinementList.test.tsx | 379 ++---------------- 2 files changed, 54 insertions(+), 414 deletions(-) diff --git a/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.tsx b/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.tsx index 1a2c5f778f..465d087825 100644 --- a/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.tsx +++ b/src/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.tsx @@ -2,6 +2,7 @@ import classNames from 'classnames'; import React, { FocusEvent, useEffect, + useMemo, useRef, useState, } from 'react'; @@ -13,6 +14,7 @@ import { handleKeyBoardInteraction, } from '~/client/components/keyboard/select.keyboard'; import { Checkbox } from '~/client/components/ui/Checkbox/Checkbox'; +import { OptionSelect, Select } from '~/client/components/ui/Form/Select/Select'; import { Icon } from '~/client/components/ui/Icon/Icon'; import { getCapitalizedItems } from '~/client/components/ui/Meilisearch/getCapitalizedItems'; import styles from '~/client/components/ui/Meilisearch/MeilisearchCustomRefinementList.module.scss'; @@ -26,87 +28,26 @@ export function MeilisearchCustomRefinementList(props: UseRefinementListProps & const { refine, items } = useRefinementList(props); const { label, className } = props; - const [isOptionsOpen, setIsOptionsOpen] = useState(false); - const buttonLabel = 'Sélectionnez vos choix'; - const labelledBy = useRef(uuidv4()); - const buttonRef = useRef(null); - const listBoxRef = useRef(null); + // TODO (BRUJ 08/07/2024): A supprimer lors du passage des options en composition + const optionsList: Array = items.map((item) => ({ + libellé: item.label, + valeur: item.value, + })); - useEffect(function setFocusOnOpen() { - if (isOptionsOpen && items.length > 0) { - const currentItem = listBoxRef.current; - const firstElement = currentItem?.getElementsByTagName('li')[0]; - firstElement?.focus(); - } - }, [isOptionsOpen, items.length]); + const valuesItemsSelected = useMemo(() => { + return items.filter((item) => item.isRefined) + .map((item) => item.value); + }, [items]); - function changeFocusToButtonElement(){ - buttonRef.current?.focus(); - } - - function handleKeyDown (event: React.KeyboardEvent){ - const currentItem = event.currentTarget; - const updateValues = () => { - const currentInput = currentItem.querySelector('input'); - if (currentInput === null) return; - refine(currentInput.value); - }; - handleKeyBoardInteraction(event, currentItem, updateValues); - - if (event.key === KeyBoard.ESCAPE) { - changeFocusToButtonElement(); - setIsOptionsOpen(false); - } - } - function onBlur(event: FocusEvent) { - if (!event.currentTarget.contains(event.relatedTarget)) { - setIsOptionsOpen(false); - } + function onOptionSelected(option: HTMLElement) { + const value = option.getAttribute('data-value') ?? ''; + value && refine(value); } - const renderOptionList = () => ( -
    - {items.length > 0 && items.map((item) => ( -
  • - refine(item.value)} - /> -
  • - ))} -
- ); - - - if (items.length === 0) return null; return (
- {label} -
- - {isOptionsOpen && renderOptionList()} -
+ onOptionSelected(option)} value={valuesItemsSelected}/> -
- ); -} diff --git a/src/client/components/ui/Meilisearch/tests/MeilisearchCustomRefinementList.test.tsx b/src/client/components/ui/Meilisearch/MeilisearchSelectMultiple/MeilisearchSelectMultiple.test.tsx similarity index 85% rename from src/client/components/ui/Meilisearch/tests/MeilisearchCustomRefinementList.test.tsx rename to src/client/components/ui/Meilisearch/MeilisearchSelectMultiple/MeilisearchSelectMultiple.test.tsx index 4623de167f..9fd8b039c2 100644 --- a/src/client/components/ui/Meilisearch/tests/MeilisearchCustomRefinementList.test.tsx +++ b/src/client/components/ui/Meilisearch/MeilisearchSelectMultiple/MeilisearchSelectMultiple.test.tsx @@ -7,7 +7,7 @@ import { render, screen } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; import React from 'react'; -import { MeilisearchCustomRefinementList } from '~/client/components/ui/Meilisearch/MeilisearchCustomRefinementList'; +import { MeilisearchSelectMultiple } from '~/client/components/ui/Meilisearch/MeilisearchSelectMultiple/MeilisearchSelectMultiple'; import { generateRefinementListItem, mockUseRefinementList, @@ -17,13 +17,13 @@ import { mockScrollIntoView } from '~/client/components/window.mock'; // eslint-disable-next-line @typescript-eslint/no-var-requires const spyed = jest.spyOn(require('react-instantsearch'), 'useRefinementList'); -describe('MeilisearchCustomRefinementList', () => { +describe('MeilisearchSelectMultiple', () => { beforeEach(() => { mockScrollIntoView(); }); it('je vois le select avec son label', () => { - render(); + render(); const select = screen.getByRole('combobox', { name: 'test' }); expect(select).toBeVisible(); @@ -40,7 +40,7 @@ describe('MeilisearchCustomRefinementList', () => { refine, })); - render(); + render(); await user.click(screen.getByRole('combobox', { name: 'test' })); await user.click(screen.getByRole('option', { name: 'audit' })); @@ -60,7 +60,7 @@ describe('MeilisearchCustomRefinementList', () => { refine, })); - render(); + render(); expect(screen.getByRole('option', { hidden: true, name: 'audit' })).toHaveAttribute('aria-selected', 'true'); expect(screen.getByRole('option', { hidden: true, name: 'dev' })).toHaveAttribute('aria-selected', 'true'); diff --git a/src/client/components/ui/Meilisearch/MeilisearchSelectMultiple/MeilisearchSelectMultiple.tsx b/src/client/components/ui/Meilisearch/MeilisearchSelectMultiple/MeilisearchSelectMultiple.tsx new file mode 100644 index 0000000000..1920ee7039 --- /dev/null +++ b/src/client/components/ui/Meilisearch/MeilisearchSelectMultiple/MeilisearchSelectMultiple.tsx @@ -0,0 +1,33 @@ +import React, { useMemo } from 'react'; +import { useRefinementList, UseRefinementListProps } from 'react-instantsearch'; + +import { OptionSelect, Select } from '../../Form/Select/Select'; + +// TODO (BRUJ 08/07/2024): A supprimer le Omit lors du passage des options en composition +type MeilisearchSelectMultipleProps = Omit, 'optionList'> + +export function MeilisearchSelectMultiple(props: UseRefinementListProps & MeilisearchSelectMultipleProps) { + const { refine, items } = useRefinementList(props); + const { label, className } = props; + + // TODO (BRUJ 08/07/2024): A supprimer lors du passage des options en composition + const optionsList: Array = items.map((item) => ({ + libellé: item.label, + valeur: item.value, + })); + + const valuesSelected = useMemo(() => { + return items.filter((item) => item.isRefined) + .map((item) => item.value); + }, [items]); + + + function onOptionSelected(option: HTMLElement) { + const value = option.getAttribute('data-value') ?? ''; + value && refine(value); + } + + return ( + +