Skip to content

Commit

Permalink
feat: [Select] Multi
Browse files Browse the repository at this point in the history
  • Loading branch information
meissadia committed Nov 16, 2023
1 parent 44670e6 commit 3014e0a
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 0 deletions.
89 changes: 89 additions & 0 deletions src/components/Select/SelectMulti.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Multiselect } from '@cfpb/cfpb-forms';
import { useEffect, useRef, useState } from 'react';
import { noOp } from '~/src/utils/noOp';
import type { SelectProperties } from './Select';
import { SelectTag } from './SelectTag';
import { buildOptions } from './selectUtils';

const MAX_SELECTIONS = 5;

export const SelectMulti = ({
id,
options,
label,
onChange = noOp,
maxSelections = MAX_SELECTIONS,
...properties
}: SelectProperties): JSX.Element => {
const [selectElement, setSelectElement] = useState(null);
const [selectedIndicies, setSelectedIndicies] = useState([]);
const inputReference = useRef(null);

// Initialize and configure DS Multiselect
useEffect(() => {
const ms = new Multiselect(inputReference.current);
const newSelect = ms.init({ maxSelections, renderTags: false });

const onUpdate = (): void => {
const modelSelected = newSelect.getModel().getSelectedIndices();
setSelectedIndicies([...modelSelected]);
};

const EVT_SELECT = 'selectionsupdated';
newSelect.addEventListener(EVT_SELECT, onUpdate);

setSelectElement(newSelect);

return () => newSelect.removeEventListener(EVT_SELECT, onUpdate);
}, [maxSelections]);

// Notify parent on change of selected options
useEffect(() => {
// Map our simplified tracking state to actual Option objects
const selectedValues = selectedIndicies.map(index => ({
...options[index],
selected: true
}));

onChange(selectedValues);
}, [selectedIndicies, onChange, options]);

return (
<div
className='m-form-field m-form-field__select'
id={`multi-wrapper-${id}`}
>
<label className='a-label a-label__heading' htmlFor={id}>
{label}
</label>
<ul className='o-multiselect_choices selected'>
{selectedIndicies.map(index => {
const option = options[index];
const labelId = `${id}-${option.value}`;

return (
<SelectTag
key={option.value}
{...option}
onClick={selectElement.selectionClickHandler}
onKeyDown={selectElement.selectionKeyDownHandler}
htmlFor={labelId}
data-testid={`tag-${labelId}`}
/>
);
})}
</ul>
<select
id={id}
data-testid={id}
ref={inputReference}
multiple
placeholder={`Select up to ${maxSelections}`}
data-open
{...properties}
>
{buildOptions(options)}
</select>
</div>
);
};
28 changes: 28 additions & 0 deletions src/components/Select/SelectTag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { KeyboardEvent, MouseEvent } from 'react';
import { Icon } from '~/src/index';

interface TagProperties {
value: string;
label: string;
onClick: (event: MouseEvent<HTMLButtonElement>) => void;
onKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void;
htmlFor: string;
'data-testid'?: string;
}

export const SelectTag = ({
value,
label,
onClick,
onKeyDown,
htmlFor,
...properties
}: TagProperties): JSX.Element => (
<li key={value} data-testid={properties['data-testid']}>
<button onClick={onClick} onKeyDown={onKeyDown} type='button'>
<label htmlFor={htmlFor}>
{label} <Icon name='error' />
</label>
</button>
</li>
);

0 comments on commit 3014e0a

Please sign in to comment.