Previously if the menu was opened and no there were no options to be displayed it was just showing a tiny empty menu, this commit adds an empty placeholder in order to make it clearer that the list is empty.
227 lines
7.5 KiB
TypeScript
227 lines
7.5 KiB
TypeScript
import React, { HTMLAttributes } from "react";
|
|
import { useMultipleSelection } from "downshift";
|
|
import classNames from "classnames";
|
|
|
|
import { Field } from ":/components/Forms/Field";
|
|
import { LabelledBox } from ":/components/Forms/LabelledBox";
|
|
import { Button } from ":/components/Button";
|
|
import { useCunningham } from ":/components/Provider";
|
|
import { Option, SelectProps } from ":/components/Forms/Select/mono";
|
|
import {
|
|
getOptionsFilter,
|
|
optionToValue,
|
|
} from ":/components/Forms/Select/mono-common";
|
|
|
|
/**
|
|
* This method returns a comparator that can be used to filter out options for multi select.
|
|
* For an option to be visible it must:
|
|
* - Match the input value in terms of search
|
|
* - Not be selected already
|
|
*
|
|
* @param selectedOptions
|
|
* @param inputValue
|
|
*/
|
|
export function getMultiOptionsFilter(
|
|
selectedOptions: Option[],
|
|
inputValue?: string,
|
|
) {
|
|
const optionsFilter = getOptionsFilter(inputValue);
|
|
return (option: Option) => {
|
|
return (
|
|
!selectedOptions.find(
|
|
(selectedOption) =>
|
|
optionToValue(selectedOption) === optionToValue(option),
|
|
) && optionsFilter(option)
|
|
);
|
|
};
|
|
}
|
|
|
|
export type SelectMultiProps = Omit<SelectProps, "onChange"> & {
|
|
onChange?: (event: { target: { value: string[] } }) => void;
|
|
};
|
|
|
|
export interface SubProps extends SelectMultiProps {
|
|
selectedItems: Option[];
|
|
onSelectedItemsChange: (selectedItems: Option[]) => void;
|
|
}
|
|
|
|
export const SelectMultiAux = ({
|
|
options,
|
|
labelAsPlaceholder,
|
|
selectedItems,
|
|
clearable = true,
|
|
disabled,
|
|
hideLabel,
|
|
name,
|
|
downshiftReturn,
|
|
useMultipleSelectionReturn,
|
|
children,
|
|
...props
|
|
}: SubProps & {
|
|
options: Option[];
|
|
labelAsPlaceholder: boolean;
|
|
selectedItems: Option[];
|
|
clearable?: boolean;
|
|
downshiftReturn: {
|
|
isOpen: boolean;
|
|
getLabelProps: any;
|
|
toggleButtonProps: any;
|
|
getMenuProps: any;
|
|
getItemProps: any;
|
|
highlightedIndex: number;
|
|
wrapperProps?: HTMLAttributes<HTMLDivElement>;
|
|
};
|
|
useMultipleSelectionReturn: ReturnType<typeof useMultipleSelection<Option>>;
|
|
}) => {
|
|
const { t } = useCunningham();
|
|
const labelProps = downshiftReturn.getLabelProps();
|
|
return (
|
|
<Field {...props}>
|
|
<div
|
|
className={classNames(
|
|
"c__select",
|
|
"c__select--multi",
|
|
"c__select--" + props.state,
|
|
{
|
|
"c__select--disabled": disabled,
|
|
"c__select--populated": selectedItems.length > 0,
|
|
},
|
|
)}
|
|
>
|
|
<div
|
|
className={classNames("c__select__wrapper", {
|
|
"c__select__wrapper--focus": downshiftReturn.isOpen && !disabled,
|
|
})}
|
|
{...downshiftReturn.wrapperProps}
|
|
>
|
|
{selectedItems.map((selectedItem, index) => (
|
|
<input
|
|
key={`${selectedItem.value}${index}`}
|
|
type="hidden"
|
|
name={name}
|
|
value={optionToValue(selectedItem)}
|
|
/>
|
|
))}
|
|
<LabelledBox
|
|
label={props.label}
|
|
labelAsPlaceholder={labelAsPlaceholder}
|
|
htmlFor={labelProps.htmlFor}
|
|
labelId={labelProps.id}
|
|
hideLabel={hideLabel}
|
|
disabled={disabled}
|
|
>
|
|
<div className="c__select__inner">
|
|
<div className="c__select__inner__actions">
|
|
{clearable && !disabled && selectedItems.length > 0 && (
|
|
<>
|
|
<Button
|
|
color="tertiary"
|
|
size="nano"
|
|
aria-label={t(
|
|
"components.forms.select.clear_all_button_aria_label",
|
|
)}
|
|
className="c__select__inner__actions__clear"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
props.onSelectedItemsChange([]);
|
|
}}
|
|
icon={<span className="material-icons">close</span>}
|
|
/>
|
|
<div className="c__select__inner__actions__separator" />
|
|
</>
|
|
)}
|
|
<Button
|
|
color="tertiary"
|
|
size="nano"
|
|
className="c__select__inner__actions__open"
|
|
icon={
|
|
<span
|
|
className={classNames("material-icons", {
|
|
opened: downshiftReturn.isOpen,
|
|
})}
|
|
>
|
|
arrow_drop_down
|
|
</span>
|
|
}
|
|
disabled={disabled}
|
|
{...downshiftReturn.toggleButtonProps}
|
|
/>
|
|
</div>
|
|
<div className="c__select__inner__value">
|
|
{selectedItems.map((selectedItemForRender, index) => {
|
|
return (
|
|
<div
|
|
className="c__select__inner__value__pill"
|
|
key={`${selectedItemForRender.value}${index}`}
|
|
{...useMultipleSelectionReturn.getSelectedItemProps({
|
|
selectedItem: selectedItemForRender,
|
|
index,
|
|
})}
|
|
>
|
|
<span>{selectedItemForRender.label}</span>
|
|
<Button
|
|
tabIndex={-1}
|
|
color="tertiary"
|
|
disabled={disabled}
|
|
size="small"
|
|
aria-label={t(
|
|
"components.forms.select.clear_button_aria_label",
|
|
)}
|
|
className="c__select__inner__value__pill__clear"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
useMultipleSelectionReturn.removeSelectedItem(
|
|
selectedItemForRender,
|
|
);
|
|
}}
|
|
icon={<span className="material-icons">close</span>}
|
|
/>
|
|
</div>
|
|
);
|
|
})}
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</LabelledBox>
|
|
</div>
|
|
<div
|
|
className={classNames("c__select__menu", {
|
|
"c__select__menu--opened": downshiftReturn.isOpen,
|
|
})}
|
|
{...downshiftReturn.getMenuProps()}
|
|
>
|
|
<ul>
|
|
{downshiftReturn.isOpen && (
|
|
<>
|
|
{options.map((option, index) => {
|
|
const isActive = index === downshiftReturn.highlightedIndex;
|
|
return (
|
|
<li
|
|
className={classNames("c__select__menu__item", {
|
|
"c__select__menu__item--highlight": isActive,
|
|
"c__select__menu__item--disabled": option.disabled,
|
|
})}
|
|
key={`${option.value}${index.toString()}`}
|
|
{...downshiftReturn.getItemProps({
|
|
item: option,
|
|
index,
|
|
})}
|
|
>
|
|
<span>{option.label}</span>
|
|
</li>
|
|
);
|
|
})}
|
|
{options.length === 0 && (
|
|
<li className="c__select__menu__item c__select__menu__empty-placeholder">
|
|
{t("components.forms.select.menu_empty_placeholder")}
|
|
</li>
|
|
)}
|
|
</>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</Field>
|
|
);
|
|
};
|