2024-05-14 16:28:49 +02:00
|
|
|
import React, { HTMLAttributes, useRef } from "react";
|
2023-06-12 14:27:40 +02:00
|
|
|
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";
|
2023-10-06 16:55:30 +02:00
|
|
|
import { Option, SelectProps } from ":/components/Forms/Select";
|
2023-06-12 14:27:40 +02:00
|
|
|
import {
|
|
|
|
|
getOptionsFilter,
|
|
|
|
|
optionToValue,
|
|
|
|
|
} from ":/components/Forms/Select/mono-common";
|
2023-11-21 16:51:32 +01:00
|
|
|
import { SelectedItems } from ":/components/Forms/Select/multi-selected-items";
|
|
|
|
|
import { SelectMultiMenu } from ":/components/Forms/Select/multi-menu";
|
2023-06-12 14:27:40 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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[],
|
2025-08-22 10:14:28 +02:00
|
|
|
inputValue?: string
|
2023-06-12 14:27:40 +02:00
|
|
|
) {
|
|
|
|
|
const optionsFilter = getOptionsFilter(inputValue);
|
|
|
|
|
return (option: Option) => {
|
|
|
|
|
return (
|
|
|
|
|
!selectedOptions.find(
|
|
|
|
|
(selectedOption) =>
|
2025-08-22 10:14:28 +02:00
|
|
|
optionToValue(selectedOption) === optionToValue(option)
|
2023-06-12 14:27:40 +02:00
|
|
|
) && optionsFilter(option)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 16:50:20 +01:00
|
|
|
export type SubProps = Omit<SelectProps, "onChange"> & {
|
2023-06-12 14:27:40 +02:00
|
|
|
onChange?: (event: { target: { value: string[] } }) => void;
|
|
|
|
|
onSelectedItemsChange: (selectedItems: Option[]) => void;
|
2023-11-21 16:50:20 +01:00
|
|
|
selectedItems: Option[];
|
|
|
|
|
};
|
2023-06-12 14:27:40 +02:00
|
|
|
|
2023-11-21 16:51:32 +01:00
|
|
|
export interface SelectMultiAuxProps extends SubProps {
|
2023-06-12 14:27:40 +02:00
|
|
|
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>>;
|
2023-11-21 16:51:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const SelectMultiAux = ({ children, ...props }: SelectMultiAuxProps) => {
|
2023-06-12 14:27:40 +02:00
|
|
|
const { t } = useCunningham();
|
2023-11-21 16:51:32 +01:00
|
|
|
const labelProps = props.downshiftReturn.getLabelProps();
|
2024-05-14 16:28:49 +02:00
|
|
|
const ref = useRef<HTMLDivElement>(null);
|
2024-01-31 17:55:41 +01:00
|
|
|
|
|
|
|
|
// We need to remove onBlur from toggleButtonProps because it triggers a menu closing each time
|
|
|
|
|
// we tick a checkbox using the monoline style.
|
|
|
|
|
const { onBlur, ...toggleProps } = props.downshiftReturn.toggleButtonProps;
|
2023-06-12 14:27:40 +02:00
|
|
|
return (
|
2024-05-14 16:28:49 +02:00
|
|
|
<>
|
|
|
|
|
<Field {...props}>
|
2023-06-12 14:27:40 +02:00
|
|
|
<div
|
2024-05-14 16:28:49 +02:00
|
|
|
ref={ref}
|
|
|
|
|
className={classNames(
|
|
|
|
|
"c__select",
|
|
|
|
|
"c__select--multi",
|
|
|
|
|
"c__select--" + props.state,
|
|
|
|
|
"c__select--" + props.selectedItemsStyle,
|
|
|
|
|
{
|
|
|
|
|
"c__select--disabled": props.disabled,
|
|
|
|
|
"c__select--populated": props.selectedItems.length > 0,
|
|
|
|
|
"c__select--monoline": props.monoline,
|
|
|
|
|
"c__select--multiline": !props.monoline,
|
2025-08-22 10:14:28 +02:00
|
|
|
}
|
2024-05-14 16:28:49 +02:00
|
|
|
)}
|
2023-06-12 14:27:40 +02:00
|
|
|
>
|
2024-05-14 16:28:49 +02:00
|
|
|
<div
|
|
|
|
|
className={classNames("c__select__wrapper", {
|
|
|
|
|
"c__select__wrapper--focus":
|
|
|
|
|
props.downshiftReturn.isOpen && !props.disabled,
|
|
|
|
|
})}
|
|
|
|
|
{...props.downshiftReturn.wrapperProps}
|
|
|
|
|
{...toggleProps}
|
2023-06-12 14:27:40 +02:00
|
|
|
>
|
2024-05-14 16:28:49 +02:00
|
|
|
{props.selectedItems.map((selectedItem, index) => (
|
|
|
|
|
<input
|
|
|
|
|
key={`${optionToValue(selectedItem)}${index.toString()}`}
|
|
|
|
|
type="hidden"
|
|
|
|
|
name={props.name}
|
|
|
|
|
value={optionToValue(selectedItem)}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
<LabelledBox
|
|
|
|
|
label={props.label}
|
|
|
|
|
labelAsPlaceholder={props.labelAsPlaceholder}
|
|
|
|
|
htmlFor={labelProps.htmlFor}
|
|
|
|
|
labelId={labelProps.id}
|
|
|
|
|
hideLabel={props.hideLabel}
|
|
|
|
|
disabled={props.disabled}
|
|
|
|
|
>
|
|
|
|
|
<div className="c__select__inner">
|
|
|
|
|
<div className="c__select__inner__actions">
|
|
|
|
|
{props.clearable &&
|
|
|
|
|
!props.disabled &&
|
|
|
|
|
props.selectedItems.length > 0 && (
|
|
|
|
|
<>
|
|
|
|
|
<Button
|
2025-08-22 10:14:28 +02:00
|
|
|
color="tertiary"
|
|
|
|
|
variant="neutral"
|
2024-05-14 16:28:49 +02:00
|
|
|
size="nano"
|
|
|
|
|
aria-label={t(
|
2025-08-22 10:14:28 +02:00
|
|
|
"components.forms.select.clear_all_button_aria_label"
|
2024-05-14 16:28:49 +02:00
|
|
|
)}
|
|
|
|
|
className="c__select__inner__actions__clear"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
props.onSelectedItemsChange([]);
|
|
|
|
|
}}
|
|
|
|
|
icon={<span className="material-icons">close</span>}
|
|
|
|
|
type="button"
|
|
|
|
|
/>
|
|
|
|
|
<div className="c__select__inner__actions__separator" />
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
<Button
|
2025-08-22 10:14:28 +02:00
|
|
|
color="tertiary"
|
|
|
|
|
variant="neutral"
|
2024-05-14 16:28:49 +02:00
|
|
|
size="nano"
|
|
|
|
|
className="c__select__inner__actions__open"
|
|
|
|
|
icon={
|
|
|
|
|
<span
|
|
|
|
|
className={classNames("material-icons", {
|
|
|
|
|
opened: props.downshiftReturn.isOpen,
|
|
|
|
|
})}
|
|
|
|
|
>
|
|
|
|
|
arrow_drop_down
|
|
|
|
|
</span>
|
|
|
|
|
}
|
|
|
|
|
disabled={props.disabled}
|
|
|
|
|
type="button"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="c__select__inner__value">
|
|
|
|
|
<SelectedItems {...props} />
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
2023-06-12 14:27:40 +02:00
|
|
|
</div>
|
2024-05-14 16:28:49 +02:00
|
|
|
</LabelledBox>
|
|
|
|
|
</div>
|
2023-06-12 14:27:40 +02:00
|
|
|
</div>
|
2024-05-14 16:28:49 +02:00
|
|
|
</Field>
|
|
|
|
|
<SelectMultiMenu {...props} selectRef={ref} />
|
|
|
|
|
</>
|
2023-06-12 14:27:40 +02:00
|
|
|
);
|
|
|
|
|
};
|