Files
phosphor-icons/src/state/selectors.ts
rektdeckard b39224073e 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.
2020-09-15 01:13:34 -04:00

68 lines
2.0 KiB
TypeScript

import { selector, selectorFamily } from "recoil";
import TinyColor from "tinycolor2";
import Fuse from "fuse.js";
import { searchQueryAtom, iconColorAtom } from "./atoms";
import { IconEntry, IconCategory } from "../lib";
import { icons } from "../lib/icons";
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();
if (!query) return icons;
return new Promise((resolve) =>
resolve(fuse.search(query).map((value) => value.item))
);
},
});
type CategorizedIcons = Partial<Record<IconCategory, IconEntry[]>>;
export const categorizedQueryResultsSelector = selector<
Readonly<CategorizedIcons>
>({
key: "categorizedQueryResultsSelector",
get: ({ get }) => {
const filteredResults = get(filteredQueryResultsSelector);
return new Promise((resolve) =>
resolve(
filteredResults.reduce<CategorizedIcons>((acc, curr) => {
curr.categories.forEach((category) => {
if (!acc[category]) acc[category] = [];
acc[category]!!.push(curr);
});
return acc;
}, {})
)
);
},
});
export const singleCategoryQueryResultsSelector = selectorFamily<
Readonly<IconEntry[]>,
IconCategory
>({
key: "singleCategoryQueryResultsSelector",
get: (category: IconCategory) => ({ get }) => {
const filteredResults = get(filteredQueryResultsSelector);
return new Promise((resolve) =>
resolve(
filteredResults.filter((icon) => icon.categories.includes(category))
)
);
},
});
export const isDarkThemeSelector = selector<boolean>({
key: "isDarkThemeSelector",
get: ({ get }) => TinyColor(get(iconColorAtom)).isLight(),
});