import { useCallback, useMemo, useRef } from "react"; import { ComboboxItem, ComboboxItemGroup, MultiSelect, MultiSelectProps, Select, SelectProps, } from "@mantine/core"; import { isNull, isUndefined } from "lodash"; import { LOG } from "@/utilities/console"; export type SelectorOption = Override< { value: T; label: string; }, ComboboxItem >; type SelectItemWithPayload = ComboboxItem & { 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 interface GroupedSelectorOptions { group: string; items: SelectorOption[]; } export type GroupedSelectorProps = Override< { options: ComboboxItemGroup[]; getkey?: (value: T) => string; }, Omit >; export function GroupedSelector({ options, ...select }: GroupedSelectorProps) { return ( ); } 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 | null) => { 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; buildOption?: (value: string) => T; }, Omit >; export function MultiSelector({ value, defaultValue, options, onChange, getkey = DefaultKeyBuilder, buildOption, ...select }: MultiSelectorProps) { const labelRef = useRef(getkey); labelRef.current = getkey; const buildRef = useRef(buildOption); buildRef.current = buildOption; 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); } else if (buildRef.current) { payloads.push(buildRef.current(value)); } } onChange?.(payloads); }, [data, onChange], ); return ( ); }