Skip to content

Commit

Permalink
(PC-33528)[PRO] feat: add test to the new component
Browse files Browse the repository at this point in the history
  • Loading branch information
mleroy-pass committed Jan 13, 2025
1 parent 92a03f8 commit e1b3605
Show file tree
Hide file tree
Showing 8 changed files with 1,056 additions and 210 deletions.
29 changes: 0 additions & 29 deletions pro/src/ui-kit/MultiSelect/MultiSelect.spec.tsx

This file was deleted.

46 changes: 39 additions & 7 deletions pro/src/ui-kit/MultiSelect/MultiSelect.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,28 @@ export default {
}

const defaultOptions = [
{ id: '1', label: '78 - Yvelines' },
{ id: '2', label: '75 - Paris' },
{ id: '3', label: '44 - Nantes' },
{ id: '4', label: '76 - Rouen' },
{ id: '5', label: '77 - Seine et Marne' },
{ id: '1', label: '01 - Ain' },
{ id: '2', label: '02 - Aisne' },
{ id: '3', label: '03 - Allier' },
{ id: '4', label: '04 - Alpes-de-Haute-Provence' },
{ id: '5', label: '05 - Hautes-Alpes' },
{ id: '6', label: '06 - Alpes-Maritimes' },
{ id: '7', label: '07 - Ardèche' },
{ id: '8', label: '08 - Ardennes' },
{ id: '9', label: '09 - Ariège' },
{ id: '10', label: '10 - Aube' },
{ id: '11', label: '11 - Aude' },
{ id: '12', label: '12 - Aveyron' },
{ id: '13', label: '13 - Bouches-du-Rhône' },
{ id: '14', label: '14 - Calvados' },
{ id: '15', label: '15 - Cantal' },
]

const defaultProps = {
options: defaultOptions,
legend: 'Département',
label: 'Selectionner un département',
label:
'Sélectionner un département Sélectionner un département Sélectionner un département',
}

export const Default: StoryObj<typeof MultiSelect> = {
Expand All @@ -54,6 +65,27 @@ export const WithSearchInput: StoryObj<typeof MultiSelect> = {
searchExample: 'Ex : 44 - Nantes',
searchLabel: 'Rechercher des départements',
legend: 'Départements',
label: 'Selectionner des départements',
label: 'Sélectionner des départements',
},
}

export const WithSelectAllOption: StoryObj<typeof MultiSelect> = {
args: {
...defaultProps,
hasSelectAllOptions: true,
legend: 'Départements',
label: 'Sélectionner des départements',
},
}

export const WithSearchInputAndSelectAllOption: StoryObj<typeof MultiSelect> = {
args: {
...defaultProps,
hasSearch: true,
searchExample: 'Ex : 44 - Nantes',
searchLabel: 'Rechercher des départements',
hasSelectAllOptions: true,
legend: 'Départements',
label: 'Sélectionner des départements',
},
}
160 changes: 49 additions & 111 deletions pro/src/ui-kit/MultiSelect/MultiSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useEffect, useRef, useState } from 'react'
import { useEffect, useId, useRef, useState } from 'react'

import { SelectedValuesTags } from 'ui-kit/form/SelectAutoComplete/SelectedValuesTags/SelectedValuesTags'
import { useOnClickOrFocusOutside } from 'commons/hooks/useOnClickOrFocusOutside'

import { SelectedValuesTags } from '../SelectedValuesTags/SelectedValuesTags'

import styles from './MultiSelect.module.scss'
import { MultiSelectPanel } from './MultiSelectPanel'
Expand All @@ -16,17 +18,22 @@ type MultiSelectProps = {
defaultOptions?: Option[]
label: string
legend: string
hasSearch?: boolean
searchExample?: string
searchLabel?: string
hasSelectAllOptions?: boolean
disabled?: boolean
}
} & ( // If `hasSearch` is `true`, `searchExample` and `searchLabel` are required. // This part applies the condition
| { hasSearch: true; searchExample: string; searchLabel: string }
// If `hasSearch` is `false` or undefined, `searchExample` and `searchLabel` are optional.
| {
hasSearch?: false | undefined
searchExample?: never
searchLabel?: never
}
)

export const MultiSelect = ({
options,
defaultOptions,
hasSearch,
defaultOptions = [],
hasSearch = false,
searchExample,
searchLabel,
label,
Expand All @@ -35,87 +42,71 @@ export const MultiSelect = ({
disabled,
}: MultiSelectProps): JSX.Element => {
const [isOpen, setIsOpen] = useState(false)
const [selectedItems, setSelectedItems] = useState<Option[]>(defaultOptions)
const isSelectAllChecked = selectedItems.length === options.length

const containerRef = useRef<HTMLFieldSetElement>(null)
const [selectedItems, setSelectedItems] = useState<Option[]>(
defaultOptions ?? []
)
const id = useId()

const handleSelectOrRemoveItem = (item: Option | 'all' | undefined) => {
if (item === 'all') {
setSelectedItems(options)
} else if (item === undefined) {
setSelectedItems([])
} else {
setSelectedItems((prev) =>
prev.some((prevItem) => prevItem.id === item.id)
? prev.filter((prevItem) => prevItem.id !== item.id)
: [...prev, item]
)
}
const toggleDropdown = () => setIsOpen((prev) => !prev)

const handleSelectItem = (item: Option) => {
const updatedSelectedItems = selectedItems.some((i) => i.id === item.id)
? selectedItems.filter((i) => i.id !== item.id)
: [...selectedItems, item]

setSelectedItems(updatedSelectedItems)
}

const handleRemoveItem = (itemId: string) => {
setSelectedItems((prev) => prev.filter((item) => item.id !== itemId))
const handleSelectAll = () => {
const updatedItems = isSelectAllChecked ? [] : options
setSelectedItems(updatedItems)
}

const toggleDropdown = () => setIsOpen(!isOpen)
const handleRemoveTag = (itemId: string) => {
const updatedItems = selectedItems.filter((item) => item.id !== itemId)
setSelectedItems(updatedItems)
}

const handleKeyDown = (event: React.KeyboardEvent) => {
event.preventDefault()
if (event.key === 'Enter' || event.key === ' ') {
toggleDropdown()
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setIsOpen(false)
}
}

useEffect(() => {
const handleWindowClick = (event: MouseEvent) => {
if (
containerRef.current &&
!containerRef.current.contains(event.target as Node)
) {
setIsOpen(false)
}
}

const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setIsOpen(false)
}
}

window.addEventListener('click', handleWindowClick)
window.addEventListener('keydown', handleKeyDown)
document.addEventListener('keydown', handleKeyDown)

return () => {
window.removeEventListener('click', handleWindowClick)
window.removeEventListener('keydown', handleKeyDown)
document.removeEventListener('keydown', handleKeyDown)
}
}, [])

useOnClickOrFocusOutside(containerRef, () => setIsOpen(false))

return (
<fieldset
className={styles['container']}
style={{ position: 'relative' }}
ref={containerRef}
>
<fieldset className={styles.container} ref={containerRef}>
<MultiSelectTrigger
id={id}
legend={legend}
label={label}
isOpen={isOpen}
toggleDropdown={toggleDropdown}
handleKeyDown={handleKeyDown}
selectedCount={selectedItems.length}
disabled={disabled}
/>

{isOpen && (
<MultiSelectPanel
id={id}
label={label}
options={options.map((option) => ({
...option,
checked: selectedItems.some((item) => item.id === option.id),
}))}
onOptionSelect={handleSelectOrRemoveItem}
onOptionSelect={handleSelectItem}
onSelectAll={handleSelectAll}
isAllChecked={isSelectAllChecked}
hasSearch={hasSearch}
searchExample={searchExample}
searchLabel={searchLabel}
Expand All @@ -124,9 +115,9 @@ export const MultiSelect = ({
)}

<SelectedValuesTags
disabled={disabled}
disabled={false}
selectedOptions={selectedItems.map((item) => item.id)}
removeOption={handleRemoveItem}
removeOption={handleRemoveTag}
fieldName="tags"
optionsLabelById={selectedItems.reduce(
(acc, item) => ({ ...acc, [item.id]: item.label }),
Expand All @@ -136,56 +127,3 @@ export const MultiSelect = ({
</fieldset>
)
}

{
/* <fieldset>
<legend><button aria-controls="control-id" aria-expanded=...>Label du bouton</button></legend>
<div id="control-id">
<label class="visually-hidden" for="id-input">Rechercher des ...</label>
<svg> // icon visuelle
<input type="search" id="id-input"/>
<ul>
<li><input /> <label>...</label></li>
<li></li>
<li></li>
</ul>
</div>
</fieldset>
<div
role="listbox"
tabindex="0"
id="listbox1"
onclick="return listItemClick(event);"
onkeydown="return listItemKeyEvent(event);"
onkeypress="return listItemKeyEvent(event);"
onfocus="this.className='focus';"
onblur="this.className='blur';"
aria-activedescendant="listbox1-1">
<div role="option" id="listbox1-1" class="selected">Vert</div>
<div role="option" id="listbox1-2">Orange</div>
<div role="option" id="listbox1-3">Rouge</div>
<div role="option" id="listbox1-4">Bleu</div>
<div role="option" id="listbox1-5">Violet</div>
<div role="option" id="listbox1-6">Pervenche</div>
</div>
*/
}

{
/* <div className={styles.container} role="listbox" aria-label="Liste des départements">
{departments.map(department => (
<label key={department.id} className={styles.item}>
<div className={styles.checkbox}>
<input
type="checkbox"
checked={department.checked}
onChange={() => handleCheckboxChange(department.id)}
className={styles.input}
/>
<span className={styles.checkmark} aria-hidden="true" />
</div>
<span className={styles.label}>{department.name}</span>
</label>
))}
</div> */
}
Loading

0 comments on commit e1b3605

Please sign in to comment.