2021-03-25 14:22:43 +00:00
|
|
|
import { isArray } from "lodash";
|
|
|
|
import React, { useCallback, useMemo } from "react";
|
2021-11-03 02:33:24 +00:00
|
|
|
import Select from "react-select";
|
|
|
|
import { SelectComponents } from "react-select/dist/declarations/src/components";
|
2021-03-25 14:22:43 +00:00
|
|
|
import "./selector.scss";
|
|
|
|
|
|
|
|
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;
|
2021-08-14 12:59:08 +00:00
|
|
|
onFocus?: (e: React.FocusEvent<HTMLElement>) => void;
|
2021-03-25 14:22:43 +00:00
|
|
|
label?: (item: T) => string;
|
|
|
|
defaultValue?: SelectorValueType<T, M>;
|
|
|
|
value?: SelectorValueType<T, M>;
|
2021-11-03 02:33:24 +00:00
|
|
|
components?: Partial<SelectComponents<T, M, any>>;
|
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;
|
|
|
|
|
|
|
|
const nameFromItems = useCallback(
|
|
|
|
(item: T) => {
|
|
|
|
return options.find((v) => v.value === item)?.label;
|
|
|
|
},
|
|
|
|
[options]
|
|
|
|
);
|
|
|
|
|
|
|
|
// TODO: Force as any
|
|
|
|
const wrapper = useCallback(
|
|
|
|
(value: SelectorValueType<T, M> | undefined | null): any => {
|
|
|
|
if (value !== null && value !== undefined) {
|
|
|
|
if (multiple) {
|
|
|
|
return (value as SelectorValueType<T, true>).map((v) => ({
|
|
|
|
label: label ? label(v) : nameFromItems(v) ?? "Unknown",
|
|
|
|
value: v,
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
const v = value as T;
|
|
|
|
return {
|
|
|
|
label: label ? label(v) : nameFromItems(v) ?? "Unknown",
|
|
|
|
value: v,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
},
|
|
|
|
[label, multiple, nameFromItems]
|
|
|
|
);
|
|
|
|
|
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}
|
2021-03-25 14:22:43 +00:00
|
|
|
className={`custom-selector w-100 ${className ?? ""}`}
|
|
|
|
classNamePrefix="selector"
|
2021-08-14 12:59:08 +00:00
|
|
|
onFocus={onFocus}
|
2021-11-03 02:33:24 +00:00
|
|
|
onChange={(v: SelectorOption<T>[]) => {
|
2021-03-25 14:22:43 +00:00
|
|
|
if (onChange) {
|
|
|
|
let res: T | T[] | null = null;
|
|
|
|
if (isArray(v)) {
|
|
|
|
res = (v as ReadonlyArray<SelectorOption<T>>).map(
|
|
|
|
(val) => val.value
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
res = (v as SelectorOption<T>)?.value ?? null;
|
|
|
|
}
|
|
|
|
// TODO: Force as any
|
|
|
|
onChange(res as any);
|
|
|
|
}
|
|
|
|
}}
|
2021-11-03 02:33:24 +00:00
|
|
|
></Select>
|
2021-03-25 14:22:43 +00:00
|
|
|
);
|
|
|
|
}
|