2022-03-16 06:26:15 +00:00
|
|
|
import clsx from "clsx";
|
|
|
|
import { FocusEvent, useCallback, useMemo, useRef } from "react";
|
|
|
|
import Select, { GroupBase, OnChangeValue } from "react-select";
|
2021-11-03 02:33:24 +00:00
|
|
|
import { SelectComponents } from "react-select/dist/declarations/src/components";
|
2022-03-16 06:26:15 +00:00
|
|
|
|
|
|
|
export type SelectorOption<T> = {
|
|
|
|
label: string;
|
|
|
|
value: T;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type SelectorComponents<T, M extends boolean> = SelectComponents<
|
|
|
|
SelectorOption<T>,
|
|
|
|
M,
|
|
|
|
GroupBase<SelectorOption<T>>
|
|
|
|
>;
|
|
|
|
|
|
|
|
export type SelectorValueType<T, M extends boolean> = M extends true
|
|
|
|
? ReadonlyArray<T>
|
|
|
|
: Nullable<T>;
|
2021-03-25 14:22:43 +00:00
|
|
|
|
|
|
|
export interface SelectorProps<T, M extends boolean> {
|
|
|
|
className?: string;
|
|
|
|
placeholder?: string;
|
|
|
|
options: readonly SelectorOption<T>[];
|
|
|
|
disabled?: boolean;
|
|
|
|
clearable?: boolean;
|
|
|
|
loading?: boolean;
|
|
|
|
multiple?: M;
|
|
|
|
onChange?: (k: SelectorValueType<T, M>) => void;
|
2022-03-16 06:26:15 +00:00
|
|
|
onFocus?: (e: FocusEvent<HTMLElement>) => void;
|
2021-03-25 14:22:43 +00:00
|
|
|
label?: (item: T) => string;
|
|
|
|
defaultValue?: SelectorValueType<T, M>;
|
|
|
|
value?: SelectorValueType<T, M>;
|
2022-03-16 06:26:15 +00:00
|
|
|
components?: Partial<
|
|
|
|
SelectComponents<SelectorOption<T>, M, GroupBase<SelectorOption<T>>>
|
|
|
|
>;
|
2021-03-25 14:22:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function Selector<T = string, M extends boolean = false>(
|
|
|
|
props: SelectorProps<T, M>
|
|
|
|
) {
|
|
|
|
const {
|
|
|
|
className,
|
|
|
|
placeholder,
|
|
|
|
label,
|
|
|
|
disabled,
|
|
|
|
clearable,
|
|
|
|
loading,
|
|
|
|
options,
|
|
|
|
multiple,
|
|
|
|
onChange,
|
2021-08-14 12:59:08 +00:00
|
|
|
onFocus,
|
2021-03-25 14:22:43 +00:00
|
|
|
defaultValue,
|
2021-04-21 15:07:51 +00:00
|
|
|
components,
|
2021-03-25 14:22:43 +00:00
|
|
|
value,
|
|
|
|
} = props;
|
|
|
|
|
2022-03-16 06:26:15 +00:00
|
|
|
const labelRef = useRef(label);
|
|
|
|
|
|
|
|
const getName = useCallback(
|
2021-03-25 14:22:43 +00:00
|
|
|
(item: T) => {
|
2022-03-16 06:26:15 +00:00
|
|
|
if (labelRef.current) {
|
|
|
|
return labelRef.current(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
return options.find((v) => v.value === item)?.label ?? "Unknown";
|
2021-03-25 14:22:43 +00:00
|
|
|
},
|
|
|
|
[options]
|
|
|
|
);
|
|
|
|
|
|
|
|
const wrapper = useCallback(
|
2022-03-16 06:26:15 +00:00
|
|
|
(
|
|
|
|
value: SelectorValueType<T, M> | undefined | null
|
|
|
|
):
|
|
|
|
| SelectorOption<T>
|
|
|
|
| ReadonlyArray<SelectorOption<T>>
|
|
|
|
| null
|
|
|
|
| undefined => {
|
|
|
|
if (value === null || value === undefined) {
|
|
|
|
return value as null | undefined;
|
|
|
|
} else {
|
|
|
|
if (multiple === true) {
|
2021-03-25 14:22:43 +00:00
|
|
|
return (value as SelectorValueType<T, true>).map((v) => ({
|
2022-03-16 06:26:15 +00:00
|
|
|
label: getName(v),
|
2021-03-25 14:22:43 +00:00
|
|
|
value: v,
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
const v = value as T;
|
|
|
|
return {
|
2022-03-16 06:26:15 +00:00
|
|
|
label: getName(v),
|
2021-03-25 14:22:43 +00:00
|
|
|
value: v,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2022-03-16 06:26:15 +00:00
|
|
|
[multiple, getName]
|
2021-03-25 14:22:43 +00:00
|
|
|
);
|
|
|
|
|
2021-11-03 02:33:24 +00:00
|
|
|
const defaultWrapper = useMemo(
|
|
|
|
() => wrapper(defaultValue),
|
|
|
|
[defaultValue, wrapper]
|
|
|
|
);
|
2021-03-25 14:22:43 +00:00
|
|
|
|
|
|
|
const valueWrapper = useMemo(() => wrapper(value), [wrapper, value]);
|
|
|
|
|
|
|
|
return (
|
2021-11-03 02:33:24 +00:00
|
|
|
<Select
|
2021-03-25 14:22:43 +00:00
|
|
|
isLoading={loading}
|
|
|
|
placeholder={placeholder}
|
|
|
|
isSearchable={options.length >= 10}
|
|
|
|
isMulti={multiple}
|
|
|
|
closeMenuOnSelect={!multiple}
|
|
|
|
defaultValue={defaultWrapper}
|
|
|
|
value={valueWrapper}
|
|
|
|
isClearable={clearable}
|
|
|
|
isDisabled={disabled}
|
|
|
|
options={options}
|
2021-04-21 15:07:51 +00:00
|
|
|
components={components}
|
2022-03-16 06:26:15 +00:00
|
|
|
className={clsx("custom-selector w-100", className)}
|
2021-03-25 14:22:43 +00:00
|
|
|
classNamePrefix="selector"
|
2021-08-14 12:59:08 +00:00
|
|
|
onFocus={onFocus}
|
2022-03-16 06:26:15 +00:00
|
|
|
onChange={(newValue) => {
|
2021-03-25 14:22:43 +00:00
|
|
|
if (onChange) {
|
2022-03-16 06:26:15 +00:00
|
|
|
if (multiple === true) {
|
|
|
|
const values = (
|
|
|
|
newValue as OnChangeValue<SelectorOption<T>, true>
|
|
|
|
).map((v) => v.value) as ReadonlyArray<T>;
|
|
|
|
|
|
|
|
onChange(values as SelectorValueType<T, M>);
|
2021-03-25 14:22:43 +00:00
|
|
|
} else {
|
2022-04-27 02:40:35 +00:00
|
|
|
const value =
|
|
|
|
(newValue as OnChangeValue<SelectorOption<T>, false>)?.value ??
|
|
|
|
null;
|
2022-03-16 06:26:15 +00:00
|
|
|
|
|
|
|
onChange(value as SelectorValueType<T, M>);
|
2021-03-25 14:22:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}}
|
2021-11-03 02:33:24 +00:00
|
|
|
></Select>
|
2021-03-25 14:22:43 +00:00
|
|
|
);
|
|
|
|
}
|