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"; export type SelectorOption = Override< { value: T; label: string; }, SelectItem >; type SelectItemWithPayload = SelectItem & { payload: T; }; function DefaultKeyBuilder(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` ); } } export type SelectorProps = Override< { value?: T | null; defaultValue?: T | null; options: SelectorOption[]; onChange?: (value: T | null) => void; getkey?: (value: T) => string; }, Omit >; export function Selector({ value, defaultValue, options, onChange, getkey = DefaultKeyBuilder, ...select }: SelectorProps) { const keyRef = useRef(getkey); keyRef.current = getkey; const data = useMemo( () => options.map>(({ value, label, ...option }) => ({ label, value: keyRef.current(value), payload: value, ...option, })), [keyRef, options] ); 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); }, [data, onChange] ); return ( ); } export type MultiSelectorProps = Override< { value?: readonly T[]; defaultValue?: readonly T[]; options: readonly SelectorOption[]; onChange?: (value: T[]) => void; getkey?: (value: T) => string; }, Omit >; export function MultiSelector({ value, defaultValue, options, onChange, getkey = DefaultKeyBuilder, ...select }: MultiSelectorProps) { const labelRef = useRef(getkey); labelRef.current = getkey; const data = useMemo( () => options.map>(({ value, ...option }) => ({ value: labelRef.current(value), payload: value, ...option, })), [options] ); 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); } } onChange?.(payloads); }, [data, onChange] ); return ( ); }