selectors: add fuzzy-find search capabilities to filteredQueryResults
Using the Fuse package, we now support fuzzy icon search. Results are weighted in favor of icon names and sorted by match score, improving search utility by surfacing best matches to the top of the list. There is still some fine-tuning to do, as threshold often matches unrelated strings, while missing more related but less-similar string queries. In future, we should play with the threshold, location, distance, and possibly the extendedSearch options.
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
"dependencies": {
|
||||
"file-saver": "^2.0.2",
|
||||
"framer-motion": "^2.1.0",
|
||||
"fuse.js": "^6.4.1",
|
||||
"phosphor-react": "^0.2.2",
|
||||
"react": "^17.0.0-rc.0",
|
||||
"react-dom": "^17.0.0-rc.0",
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
import { selector, selectorFamily } from "recoil";
|
||||
import TinyColor from "tinycolor2";
|
||||
import Fuse from "fuse.js";
|
||||
|
||||
import { searchQueryAtom, iconStyleAtom, iconColorAtom } from "./atoms";
|
||||
import { searchQueryAtom, iconColorAtom } from "./atoms";
|
||||
import { IconEntry, IconCategory } from "../lib";
|
||||
import { icons } from "../lib/icons";
|
||||
|
||||
const isQueryMatch = (icon: IconEntry, query: string): boolean => {
|
||||
return (
|
||||
icon.name.includes(query) ||
|
||||
icon.tags.some((tag) => tag.toLowerCase().includes(query)) ||
|
||||
icon.categories.some((category) => category.toLowerCase().includes(query))
|
||||
);
|
||||
};
|
||||
const fuse = new Fuse(icons, {
|
||||
keys: [{ name: "name", weight: 2 }, "tags", "categories"],
|
||||
threshold: 0.2, // Tweak this to what feels like the right number of results
|
||||
// shouldSort: false, // We may want to sort if we find too many results?
|
||||
});
|
||||
|
||||
export const filteredQueryResultsSelector = selector<Readonly<IconEntry[]>>({
|
||||
key: "filteredQueryResultsSelector",
|
||||
get: ({ get }) => {
|
||||
const query = get(searchQueryAtom).trim().toLowerCase();
|
||||
const style = get(iconStyleAtom);
|
||||
|
||||
if (!query && !style) return icons;
|
||||
if (!query) return icons;
|
||||
|
||||
return new Promise((resolve) =>
|
||||
resolve(icons.filter((icon) => isQueryMatch(icon, query)))
|
||||
resolve(fuse.search(query).map((value) => value.item))
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user