Files
cunningham/packages/react/src/components/Forms/Select/multi-simple.tsx

130 lines
4.4 KiB
TypeScript

import React, { useImperativeHandle, useRef } from "react";
import { useMultipleSelection, useSelect } from "downshift";
import {
getMultiOptionsFilter,
SelectMultiAux,
SubProps,
} from ":/components/Forms/Select/multi-common";
import {
optionsEqual,
optionToString,
} from ":/components/Forms/Select/mono-common";
import { Option } from ":/components/Forms/Select/index";
export const SelectMultiSimple = ({ ref, ...props }: SubProps) => {
const isSelected = (option: Option) =>
!!props.selectedItems.find((selectedItem) =>
optionsEqual(selectedItem, option),
);
const options = React.useMemo(() => {
if (props.monoline) {
return props.options.map((option) => ({
...option,
highlighted: isSelected(option),
}));
}
return props.options.filter(getMultiOptionsFilter(props.selectedItems, ""));
}, [props.selectedItems]);
const useMultipleSelectionReturn = useMultipleSelection({
selectedItems: props.selectedItems,
onStateChange({ selectedItems: newSelectedItems, type }) {
switch (type) {
case useMultipleSelection.stateChangeTypes.SelectedItemKeyDownBackspace:
case useMultipleSelection.stateChangeTypes.SelectedItemKeyDownDelete:
case useMultipleSelection.stateChangeTypes.DropdownKeyDownBackspace:
case useMultipleSelection.stateChangeTypes.FunctionRemoveSelectedItem:
props.onSelectedItemsChange(newSelectedItems ?? []);
break;
default:
break;
}
},
});
const downshiftReturn = useSelect({
items: options,
itemToString: optionToString,
selectedItem: null, // Important, without this we are not able to re-select the last removed option.
defaultHighlightedIndex: 0, // after selection, highlight the first item.
stateReducer: (state, actionAndChanges) => {
const { changes, type } = actionAndChanges;
switch (type) {
case useSelect.stateChangeTypes.ToggleButtonKeyDownEnter:
case useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton:
case useSelect.stateChangeTypes.ItemClick:
return {
...changes,
isOpen: true, // keep the menu open after selection.
highlightedIndex: state.highlightedIndex, // avoid automatic scroll up on click.
};
}
return changes;
},
onStateChange: ({ type, selectedItem: newSelectedItem }) => {
switch (type) {
case useSelect.stateChangeTypes.ToggleButtonKeyDownEnter:
case useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton:
case useSelect.stateChangeTypes.ItemClick:
if (!newSelectedItem) {
break;
}
if (isSelected(newSelectedItem)) {
// Remove the item if it is already selected.
props.onSelectedItemsChange(
props.selectedItems.filter(
(selectedItem) => !optionsEqual(selectedItem, newSelectedItem),
),
);
} else {
props.onSelectedItemsChange([
...props.selectedItems,
newSelectedItem,
]);
}
break;
default:
break;
}
},
isItemDisabled: (item) => !!item.disabled,
});
const toggleRef = useRef<HTMLElement>(null);
useImperativeHandle(ref, () => ({
blur: () => {
downshiftReturn.closeMenu();
toggleRef.current?.blur();
},
}));
return (
<SelectMultiAux
{...props}
options={options}
labelAsPlaceholder={props.selectedItems.length === 0}
selectedItems={props.selectedItems}
selectedItemsStyle={props.monoline ? "text" : "pills"}
menuOptionsStyle={props.monoline ? "checkbox" : "plain"}
downshiftReturn={{
...downshiftReturn,
toggleButtonProps: downshiftReturn.getToggleButtonProps({
...useMultipleSelectionReturn.getDropdownProps({
preventKeyAction: downshiftReturn.isOpen,
ref: toggleRef,
}),
disabled: props.disabled,
onClick: (e: React.MouseEvent): void => {
// As the wrapper also has an onClick handler, we need to stop the event propagation here on it will toggle
// twice the menu opening which will ... do nothing :).
e.stopPropagation();
},
}),
}}
useMultipleSelectionReturn={useMultipleSelectionReturn}
/>
);
};