fix(multiselect): allow to have options with the same label

This commit is contained in:
Paul Chavard 2022-05-10 12:57:47 +02:00
parent fd9f480083
commit 83afe1ad8c

View file

@ -4,10 +4,7 @@ import React, {
useRef, useRef,
useContext, useContext,
createContext, createContext,
useEffect,
useId, useId,
useLayoutEffect,
MutableRefObject,
ReactNode, ReactNode,
ChangeEventHandler, ChangeEventHandler,
KeyboardEventHandler KeyboardEventHandler
@ -28,7 +25,6 @@ import invariant from 'tiny-invariant';
import { useDeferredSubmit, useHiddenField } from './shared/hooks'; import { useDeferredSubmit, useHiddenField } from './shared/hooks';
const Context = createContext<{ const Context = createContext<{
selectionsRef: MutableRefObject<string[]>;
onRemove: (value: string) => void; onRemove: (value: string) => void;
} | null>(null); } | null>(null);
@ -38,16 +34,6 @@ function isOptions(options: string[] | Option[]): options is Option[] {
return Array.isArray(options[0]); return Array.isArray(options[0]);
} }
const optionValueByLabel = (
values: string[],
options: Option[],
label: string
): string => {
const maybeOption: Option | undefined = values.includes(label)
? [label, label]
: options.find(([optionLabel]) => optionLabel == label);
return maybeOption ? maybeOption[1] : '';
};
const optionLabelByValue = ( const optionLabelByValue = (
values: string[], values: string[],
options: Option[], options: Option[],
@ -141,7 +127,7 @@ export default function ComboMultiple({
const onSelect = (value: string) => { const onSelect = (value: string) => {
const maybeValue = [...extraOptions, ...optionsWithLabels].find( const maybeValue = [...extraOptions, ...optionsWithLabels].find(
([val]) => val == value ([, val]) => val == value
); );
const selectedValue = maybeValue && maybeValue[1]; const selectedValue = maybeValue && maybeValue[1];
if (selectedValue) { if (selectedValue) {
@ -167,8 +153,7 @@ export default function ComboMultiple({
hidePopover(); hidePopover();
}; };
const onRemove = (label: string) => { const onRemove = (optionValue: string) => {
const optionValue = optionValueByLabel(newValues, optionsWithLabels, label);
if (optionValue) { if (optionValue) {
saveSelection((selections) => saveSelection((selections) =>
selections.filter((value) => value != optionValue) selections.filter((value) => value != optionValue)
@ -242,13 +227,11 @@ export default function ComboMultiple({
{selections.map((selection) => ( {selections.map((selection) => (
<ComboboxToken <ComboboxToken
key={selection} key={selection}
value={selection}
describedby={removedLabelledby} describedby={removedLabelledby}
value={optionLabelByValue( >
newValues, {optionLabelByValue(newValues, optionsWithLabels, selection)}
optionsWithLabels, </ComboboxToken>
selection
)}
/>
))} ))}
</ul> </ul>
<ComboboxInput <ComboboxInput
@ -287,11 +270,15 @@ export default function ComboMultiple({
</button> </button>
</li> </li>
)} )}
{results.map(([label], index) => { {results.map(([label, value], index) => {
if (label.startsWith('--')) { if (label.startsWith('--')) {
return <ComboboxSeparator key={index} value={label} />; return <ComboboxSeparator key={index} value={label} />;
} }
return <ComboboxOption key={index} value={label} />; return (
<ComboboxOption key={index} value={value}>
{label}
</ComboboxOption>
);
})} })}
</ComboboxList> </ComboboxList>
</ComboboxPopover> </ComboboxPopover>
@ -307,22 +294,8 @@ function ComboboxTokenLabel({
onRemove: (value: string) => void; onRemove: (value: string) => void;
children: ReactNode; children: ReactNode;
}) { }) {
const selectionsRef = useRef<string[]>([]);
useLayoutEffect(() => {
selectionsRef.current = [];
return () => {
selectionsRef.current = [];
};
}, []);
return ( return (
<Context.Provider <Context.Provider value={{ onRemove }}>
value={{
onRemove,
selectionsRef
}}
>
<div data-reach-combobox-token-label>{children}</div> <div data-reach-combobox-token-label>{children}</div>
</Context.Provider> </Context.Provider>
); );
@ -339,17 +312,16 @@ function ComboboxSeparator({ value }: { value: string }) {
function ComboboxToken({ function ComboboxToken({
value, value,
describedby, describedby,
children,
...props ...props
}: { }: {
value: string; value: string;
describedby: string; describedby: string;
children: ReactNode;
}) { }) {
const context = useContext(Context); const context = useContext(Context);
invariant(context, 'invalid context'); invariant(context, 'invalid context');
const { selectionsRef, onRemove } = context; const { onRemove } = context;
useEffect(() => {
selectionsRef.current.push(value);
});
return ( return (
<li data-reach-combobox-token {...props}> <li data-reach-combobox-token {...props}>
@ -366,7 +338,7 @@ function ComboboxToken({
aria-describedby={describedby} aria-describedby={describedby}
> >
<XIcon className="icon-size mr-1" aria-hidden="true" /> <XIcon className="icon-size mr-1" aria-hidden="true" />
{value} {children}
</button> </button>
</li> </li>
); );