2022-05-31 15:49:04 +00:00
|
|
|
import { LOG } from "@/utilities/console";
|
|
|
|
import {
|
|
|
|
MultiSelect,
|
|
|
|
MultiSelectProps,
|
|
|
|
Select,
|
|
|
|
SelectItem,
|
|
|
|
SelectProps,
|
|
|
|
} from "@mantine/core";
|
|
|
|
import { isNull, isUndefined } from "lodash";
|
|
|
|
import { useCallback, useMemo, useRef } from "react";
|
2022-03-16 06:26:15 +00:00
|
|
|
|
2022-05-31 15:49:04 +00:00
|
|
|
export type SelectorOption<T> = Override<
|
|
|
|
{
|
|
|
|
value: T;
|
|
|
|
label: string;
|
|
|
|
},
|
|
|
|
SelectItem
|
2022-03-16 06:26:15 +00:00
|
|
|
>;
|
|
|
|
|
2022-05-31 15:49:04 +00:00
|
|
|
type SelectItemWithPayload<T> = SelectItem & {
|
|
|
|
payload: T;
|
|
|
|
};
|
|
|
|
|
|
|
|
function DefaultKeyBuilder<T>(value: T) {
|
|
|
|
if (typeof value === "string") {
|
|
|
|
return value;
|
|
|
|
} else if (typeof value === "number") {
|
|
|
|
return value.toString();
|
|
|
|
} else {
|
|
|
|
LOG("error", "Unknown value type", value);
|
|
|
|
throw new Error(
|
|
|
|
`Invalid type (${typeof value}) in the SelectorOption, please provide a label builder`
|
|
|
|
);
|
|
|
|
}
|
2021-03-25 14:22:43 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 15:49:04 +00:00
|
|
|
export type SelectorProps<T> = Override<
|
|
|
|
{
|
|
|
|
value?: T | null;
|
|
|
|
defaultValue?: T | null;
|
|
|
|
options: SelectorOption<T>[];
|
|
|
|
onChange?: (value: T | null) => void;
|
|
|
|
getkey?: (value: T) => string;
|
|
|
|
},
|
|
|
|
Omit<SelectProps, "data">
|
|
|
|
>;
|
|
|
|
|
|
|
|
export function Selector<T>({
|
|
|
|
value,
|
|
|
|
defaultValue,
|
|
|
|
options,
|
|
|
|
onChange,
|
|
|
|
getkey = DefaultKeyBuilder,
|
|
|
|
...select
|
|
|
|
}: SelectorProps<T>) {
|
|
|
|
const keyRef = useRef(getkey);
|
|
|
|
keyRef.current = getkey;
|
|
|
|
|
|
|
|
const data = useMemo(
|
|
|
|
() =>
|
|
|
|
options.map<SelectItemWithPayload<T>>(({ value, label, ...option }) => ({
|
|
|
|
label,
|
|
|
|
value: keyRef.current(value),
|
|
|
|
payload: value,
|
|
|
|
...option,
|
|
|
|
})),
|
|
|
|
[keyRef, options]
|
|
|
|
);
|
2022-03-16 06:26:15 +00:00
|
|
|
|
2022-05-31 15:49:04 +00:00
|
|
|
const wrappedValue = useMemo(() => {
|
|
|
|
if (isNull(value) || isUndefined(value)) {
|
|
|
|
return value;
|
|
|
|
} else {
|
|
|
|
return keyRef.current(value);
|
|
|
|
}
|
|
|
|
}, [keyRef, value]);
|
|
|
|
|
|
|
|
const wrappedDefaultValue = useMemo(() => {
|
|
|
|
if (isNull(defaultValue) || isUndefined(defaultValue)) {
|
|
|
|
return defaultValue;
|
|
|
|
} else {
|
|
|
|
return keyRef.current(defaultValue);
|
|
|
|
}
|
|
|
|
}, [defaultValue, keyRef]);
|
|
|
|
|
|
|
|
const wrappedOnChange = useCallback(
|
|
|
|
(value: string) => {
|
|
|
|
const payload = data.find((v) => v.value === value)?.payload ?? null;
|
|
|
|
onChange?.(payload);
|
2021-03-25 14:22:43 +00:00
|
|
|
},
|
2022-05-31 15:49:04 +00:00
|
|
|
[data, onChange]
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Select
|
2023-06-10 13:10:20 +00:00
|
|
|
withinPortal={true}
|
2022-05-31 15:49:04 +00:00
|
|
|
data={data}
|
|
|
|
defaultValue={wrappedDefaultValue}
|
|
|
|
value={wrappedValue}
|
|
|
|
onChange={wrappedOnChange}
|
|
|
|
{...select}
|
|
|
|
></Select>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export type MultiSelectorProps<T> = Override<
|
|
|
|
{
|
|
|
|
value?: readonly T[];
|
|
|
|
defaultValue?: readonly T[];
|
|
|
|
options: readonly SelectorOption<T>[];
|
|
|
|
onChange?: (value: T[]) => void;
|
|
|
|
getkey?: (value: T) => string;
|
2022-10-11 15:49:52 +00:00
|
|
|
buildOption?: (value: string) => T;
|
2022-05-31 15:49:04 +00:00
|
|
|
},
|
|
|
|
Omit<MultiSelectProps, "data">
|
|
|
|
>;
|
|
|
|
|
|
|
|
export function MultiSelector<T>({
|
|
|
|
value,
|
|
|
|
defaultValue,
|
|
|
|
options,
|
|
|
|
onChange,
|
|
|
|
getkey = DefaultKeyBuilder,
|
2022-10-11 15:49:52 +00:00
|
|
|
buildOption,
|
2022-05-31 15:49:04 +00:00
|
|
|
...select
|
|
|
|
}: MultiSelectorProps<T>) {
|
|
|
|
const labelRef = useRef(getkey);
|
|
|
|
labelRef.current = getkey;
|
|
|
|
|
2022-10-11 15:49:52 +00:00
|
|
|
const buildRef = useRef(buildOption);
|
|
|
|
buildRef.current = buildOption;
|
|
|
|
|
2022-05-31 15:49:04 +00:00
|
|
|
const data = useMemo(
|
|
|
|
() =>
|
|
|
|
options.map<SelectItemWithPayload<T>>(({ value, ...option }) => ({
|
|
|
|
value: labelRef.current(value),
|
|
|
|
payload: value,
|
|
|
|
...option,
|
|
|
|
})),
|
2021-03-25 14:22:43 +00:00
|
|
|
[options]
|
|
|
|
);
|
|
|
|
|
2022-05-31 15:49:04 +00:00
|
|
|
const wrappedValue = useMemo(
|
|
|
|
() => value && value.map(labelRef.current),
|
|
|
|
[value]
|
|
|
|
);
|
|
|
|
const wrappedDefaultValue = useMemo(
|
|
|
|
() => defaultValue && defaultValue.map(labelRef.current),
|
|
|
|
[defaultValue]
|
|
|
|
);
|
|
|
|
|
|
|
|
const wrappedOnChange = useCallback(
|
|
|
|
(values: string[]) => {
|
|
|
|
const payloads: T[] = [];
|
|
|
|
for (const value of values) {
|
|
|
|
const payload = data.find((v) => v.value === value)?.payload;
|
|
|
|
if (payload) {
|
|
|
|
payloads.push(payload);
|
2022-10-11 15:49:52 +00:00
|
|
|
} else if (buildRef.current) {
|
|
|
|
payloads.push(buildRef.current(value));
|
2021-03-25 14:22:43 +00:00
|
|
|
}
|
|
|
|
}
|
2022-05-31 15:49:04 +00:00
|
|
|
onChange?.(payloads);
|
2021-03-25 14:22:43 +00:00
|
|
|
},
|
2022-05-31 15:49:04 +00:00
|
|
|
[data, onChange]
|
2021-11-03 02:33:24 +00:00
|
|
|
);
|
2021-03-25 14:22:43 +00:00
|
|
|
|
|
|
|
return (
|
2022-05-31 15:49:04 +00:00
|
|
|
<MultiSelect
|
2022-10-11 15:49:52 +00:00
|
|
|
{...select}
|
2022-05-31 15:49:04 +00:00
|
|
|
value={wrappedValue}
|
|
|
|
defaultValue={wrappedDefaultValue}
|
|
|
|
onChange={wrappedOnChange}
|
|
|
|
data={data}
|
|
|
|
></MultiSelect>
|
2021-03-25 14:22:43 +00:00
|
|
|
);
|
|
|
|
}
|