+import { Choice } from "@utils/types";
import classNames from "classnames";
import {
ChangeEvent,
linkEvent,
RefObject,
} from "inferno";
-import { i18n } from "../../i18next";
-import { Choice } from "../../utils";
+import { I18NextService } from "../../services";
import { Icon, Spinner } from "./icon";
interface SearchableSelectProps {
});
}
+function focusSearch(i: SearchableSelect) {
+ if (i.toggleButtonRef.current?.ariaExpanded !== "true") {
+ i.searchInputRef.current?.focus();
+
+ if (i.props.onSearch) {
+ i.props.onSearch("");
+ }
+
+ i.setState({
+ searchText: "",
+ });
+ }
+}
+
+function handleChange({ option, i }: { option: Choice; i: SearchableSelect }) {
+ const { onChange, value } = i.props;
+
+ if (option.value !== value?.toString()) {
+ if (onChange) {
+ onChange(option);
+ }
+
+ i.setState({ searchText: "" });
+ }
+}
+
export class SearchableSelect extends Component<
SearchableSelectProps,
SearchableSelectState
> {
- private searchInputRef: RefObject<HTMLInputElement> = createRef();
- private toggleButtonRef: RefObject<HTMLButtonElement> = createRef();
+ searchInputRef: RefObject<HTMLInputElement> = createRef();
+ toggleButtonRef: RefObject<HTMLButtonElement> = createRef();
private loadingEllipsesInterval?: NodeJS.Timer = undefined;
state: SearchableSelectState = {
constructor(props: SearchableSelectProps, context: any) {
super(props, context);
- this.handleChange = this.handleChange.bind(this);
- this.focusSearch = this.focusSearch.bind(this);
-
if (props.value) {
let selectedIndex = props.options.findIndex(
({ value }) => value === props.value?.toString()
const { searchText, selectedIndex, loadingEllipses } = this.state;
return (
- <div className="dropdown">
+ <div className="searchable-select dropdown col-12 col-sm-auto flex-grow-1">
<button
id={id}
type="button"
- className="custom-select text-start"
+ role="combobox"
+ className="form-select d-inline-block text-start"
aria-haspopup="listbox"
+ aria-controls="searchable-select-input"
+ aria-activedescendant={options[selectedIndex].label}
+ aria-expanded={false}
data-bs-toggle="dropdown"
- onClick={this.focusSearch}
+ onClick={linkEvent(this, focusSearch)}
+ ref={this.toggleButtonRef}
>
{loading
- ? `${i18n.t("loading")}${loadingEllipses}`
+ ? `${I18NextService.i18n.t("loading")}${loadingEllipses}`
: options[selectedIndex].label}
</button>
- <div
- role="combobox"
- aria-activedescendant={options[selectedIndex].label}
- className="modlog-choices-font-size dropdown-menu w-100 p-2"
- >
+ <div className="modlog-choices-font-size dropdown-menu w-100 p-2">
<div className="input-group">
<span className="input-group-text">
{loading ? <Spinner /> : <Icon icon="search" />}
</span>
<input
type="text"
+ id="searchable-select-input"
className="form-control"
ref={this.searchInputRef}
onInput={linkEvent(this, handleSearch)}
value={searchText}
- placeholder={`${i18n.t("search")}...`}
+ placeholder={`${I18NextService.i18n.t("search")}...`}
/>
</div>
{!loading &&
aria-disabled={option.disabled}
disabled={option.disabled}
aria-selected={selectedIndex === index}
- onClick={() => this.handleChange(option)}
+ onClick={linkEvent({ i: this, option }, handleChange)}
type="button"
>
{option.label}
);
}
- focusSearch() {
- if (this.toggleButtonRef.current?.ariaExpanded !== "true") {
- this.searchInputRef.current?.focus();
-
- if (this.props.onSearch) {
- this.props.onSearch("");
- }
-
- this.setState({
- searchText: "",
- });
- }
- }
-
static getDerivedStateFromProps({
value,
options,
clearInterval(this.loadingEllipsesInterval);
}
}
-
- handleChange(option: Choice) {
- const { onChange, value } = this.props;
-
- if (option.value !== value?.toString()) {
- if (onChange) {
- onChange(option);
- }
-
- this.setState({ searchText: "" });
- }
- }
}