Files
editor/src/components/InputAutocomplete.tsx
T
Harel M 9c1499b805 Replace default export with named exports (#1998)
## Launch Checklist

See title,
Also removed some "_" from some file names.
This is a pure refactoring, no logic changes.


 - [x] Briefly describe the changes in this PR.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-12 14:07:28 +03:00

117 lines
3.1 KiB
TypeScript

import React from "react";
import classnames from "classnames";
import {useCombobox} from "downshift";
const MAX_HEIGHT = 140;
export type InputAutocompleteProps = {
value?: string
options?: any[]
onChange?(value: string | undefined): unknown
"aria-label"?: string
};
export function InputAutocomplete({
value,
options = [],
onChange = () => {},
"aria-label": ariaLabel,
}: InputAutocompleteProps) {
const [input, setInput] = React.useState(value || "");
const menuRef = React.useRef<HTMLDivElement>(null);
const [maxHeight, setMaxHeight] = React.useState(MAX_HEIGHT);
const filteredItems = React.useMemo(() => {
const lv = input.toLowerCase();
return options.filter((item) => item[0].toLowerCase().includes(lv));
}, [options, input]);
const calcMaxHeight = React.useCallback(() => {
if (menuRef.current) {
const space = window.innerHeight - menuRef.current.getBoundingClientRect().top;
setMaxHeight(Math.min(space, MAX_HEIGHT));
}
}, []);
const {
isOpen,
getMenuProps,
getInputProps,
getItemProps,
highlightedIndex,
openMenu,
} = useCombobox({
items: filteredItems,
inputValue: input,
itemToString: (item) => (item ? item[0] : ""),
stateReducer: (_state, action) => {
if (action.type === useCombobox.stateChangeTypes.InputClick) {
return {...action.changes, isOpen: true};
}
return action.changes;
},
onSelectedItemChange: ({selectedItem}) => {
const v = selectedItem ? selectedItem[0] : "";
setInput(v);
onChange(selectedItem ? selectedItem[0] : undefined);
},
onInputValueChange: ({inputValue: v}) => {
if (typeof v === "string") {
setInput(v);
onChange(v === "" ? undefined : v);
openMenu();
}
},
});
React.useEffect(() => {
if (isOpen) {
calcMaxHeight();
}
}, [isOpen, calcMaxHeight]);
React.useEffect(() => {
window.addEventListener("resize", calcMaxHeight);
return () => window.removeEventListener("resize", calcMaxHeight);
}, [calcMaxHeight]);
React.useEffect(() => {
setInput(value || "");
}, [value]);
return (
<div className="maputnik-autocomplete">
<input
{...getInputProps({
"aria-label": ariaLabel,
className: "maputnik-string",
spellCheck: false,
onFocus: () => openMenu(),
})}
/>
<div
{...getMenuProps({}, {suppressRefError: true})}
ref={menuRef}
style={{position: "fixed", overflow: "auto", maxHeight, zIndex: 998}}
className="maputnik-autocomplete-menu"
>
{isOpen &&
filteredItems.map((item, index) => (
<div
key={item[0]}
{...getItemProps({
item,
index,
className: classnames("maputnik-autocomplete-menu-item", {
"maputnik-autocomplete-menu-item-selected": highlightedIndex === index,
}),
})}
>
{item[1]}
</div>
))}
</div>
</div>
);
}