import { EuiTitle, EuiInputPopover, EuiSelectable } from "@elastic/eui";
import React, { useEffect, useRef, useState } from "react";
import {
SuggestionModes,
TagSuggestionInstance,
} from "../hooks/useSearchInputWithTags";
interface TagSearchInterface {
currentTag: string;
tagsString: string;
setTagsString: (tagsString: string) => void;
acceptSuggestion: (suggestion: TagSuggestionInstance) => void;
tagSuggestions: TagSuggestionInstance[];
suggestionMode: SuggestionModes;
setCursorPosition: (position: number | undefined) => void;
}
interface SelectableOption {
label: string;
checked?: "on" | "off" | undefined;
suggestion: TagSuggestionInstance;
}
// Helper Functions
const suggestionFormatter = (item: TagSuggestionInstance) => {
return {
label: item.suggestion,
suggestion: item,
showIcons: false,
append: {item.description},
};
};
const getCursorPosition = (
inputNode: React.MutableRefObject
) => {
return inputNode.current?.selectionStart || undefined;
};
const computePlaceholderText = (
tagSuggestions: TagSuggestionInstance[] | undefined
) => {
return !tagSuggestions
? ""
: "e.g. " +
tagSuggestions
.slice(0, 2)
.map((s) => `"${s.suggestion}"`)
.join(" or ");
};
const generateResultsCount = (
currentTag: string,
suggestionMode: SuggestionModes,
tagSuggestions: TagSuggestionInstance[]
) => {
let resultsCount = undefined;
const currentTagIsEmpty = currentTag.length <= 0;
const currentTagHasNoValue = currentTag.split(":")[1] === "";
const operatingWord =
currentTagIsEmpty || currentTagHasNoValue ? "possible" : "matching";
const counterWord = suggestionMode === "KEY" ? `key` : `value`;
if (tagSuggestions.length > 0) {
const isPlural = tagSuggestions.length > 1 ? "s" : "";
resultsCount = (
{`${tagSuggestions.length} ${operatingWord} ${counterWord}${isPlural}`}
);
}
return resultsCount;
};
// Hooks
const useInputHack = (
setTagsString: (s: string) => void,
setCursorPosition: (n: number | undefined) => void
) => {
// HACK --- route around the lack of onChange
// See: https://github.com/elastic/eui/issues/5651
const inputNode = useRef(null);
useEffect(() => {
const cb = () => {
const s: string = inputNode.current?.value || "";
setTagsString(s);
setCursorPosition(getCursorPosition(inputNode));
};
const copiedNode = inputNode.current;
if (copiedNode) {
copiedNode.addEventListener("input", cb);
}
return () => {
if (copiedNode) {
copiedNode.removeEventListener("input", cb);
}
};
}, [inputNode, setTagsString, setCursorPosition]);
return inputNode;
};
const useSelectableOptions = (
tagSuggestions: TagSuggestionInstance[],
acceptSuggestion: (suggestion: TagSuggestionInstance) => void
) => {
const [options, setOptions] = useState(
tagSuggestions ? tagSuggestions.map(suggestionFormatter) : []
);
const onSelectableChange = (options: SelectableOption[]) => {
// Get the thing that just got "checked"
const clickedItem = options.find((option) => option.checked === "on");
if (clickedItem) {
acceptSuggestion(clickedItem.suggestion);
}
setOptions(options);
};
useEffect(() => {
// Update options when new set of suggestions are passed down
setOptions(tagSuggestions.map(suggestionFormatter));
}, [tagSuggestions, setOptions]);
return {
options,
onSelectableChange,
};
};
const TagSearch = ({
currentTag,
tagsString,
setTagsString,
acceptSuggestion,
tagSuggestions,
suggestionMode,
setCursorPosition,
}: TagSearchInterface) => {
// HACK --- route around the lack of onChange
const inputNode = useInputHack(setTagsString, setCursorPosition);
// Handling Suggestion Options
const { options, onSelectableChange } = useSelectableOptions(
tagSuggestions, // Gets turned into options
acceptSuggestion // Get triggered when an option is selected
);
// Using EuiInputPopover: https://elastic.github.io/eui/#/layout/popover
const [hasFocus, setHasFocus] = useState(false);
// Props for EuiFieldSearch
const searchProps = {
value: tagsString,
inputRef: (node: HTMLInputElement | null) => {
// HTMLInputElement is hooked into useInputHack
inputNode.current = node;
},
onFocus: () => {
setHasFocus(true);
},
fullWidth: true,
placeholder: computePlaceholderText(tagSuggestions),
};
const resultsCount = generateResultsCount(
currentTag,
suggestionMode,
tagSuggestions
);
return (
<>
Filter by Tags
{
setHasFocus(true);
}}
onBlur={() => {
setHasFocus(false);
}}
searchable={true}
isPreFiltered={true}
searchProps={searchProps}
aria-label="Filter by "
onChange={onSelectableChange}
options={options}
singleSelection={true}
listProps={{ bordered: true }}
>
{(list, search) => {
return (
{search}>}
isOpen={hasFocus}
closePopover={() => {
setHasFocus(false);
}}
>
{resultsCount}
{list}
);
}}
>
);
};
export default TagSearch;