Skip to content

Commit 61552be

Browse files
committed
add fuzzy finding
1 parent c5e7612 commit 61552be

File tree

3 files changed

+63
-5
lines changed

3 files changed

+63
-5
lines changed

site/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"ts-prune": "0.10.3",
9090
"tzdata": "1.0.30",
9191
"ua-parser-js": "1.0.33",
92+
"ufuzzy": "npm:@leeoniya/ufuzzy@1.0.10",
9293
"unique-names-generator": "4.7.1",
9394
"uuid": "9.0.0",
9495
"vite": "4.4.2",

site/pnpm-lock.yaml

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/pages/IconsPage/IconsPage.tsx

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,71 @@
1+
import { type ReactNode, useMemo, useState } from "react";
2+
import uFuzzy from "ufuzzy";
13
import { CopyableValue } from "components/CopyableValue/CopyableValue";
24
import { Margins } from "components/Margins/Margins";
35
import { Stack } from "components/Stack/Stack";
46
import { colors } from "theme/colors";
57
import icons from "theme/icons.json";
68

9+
const iconsWithoutSuffix = icons.map((icon) => icon.split(".")[0]);
10+
const fuzzyFinder = new uFuzzy({
11+
intraMode: 1,
12+
intraIns: 1,
13+
intraSub: 1,
14+
intraTrn: 1,
15+
intraDel: 1,
16+
});
17+
718
export default function () {
19+
const [searchText, setSearchText] = useState("");
20+
21+
const searchedIcons = useMemo(() => {
22+
if (!searchText.trim()) {
23+
return icons.map((icon) => ({ url: `/icon/${icon}`, description: icon }));
24+
}
25+
26+
const [map, info, sorted] = fuzzyFinder.search(
27+
iconsWithoutSuffix,
28+
searchText,
29+
);
30+
31+
// We hit an invalid state somehow
32+
if (!map || !info || !sorted) {
33+
return [];
34+
}
35+
36+
return sorted.map((i) => {
37+
const iconName = icons[info.idx[i]];
38+
const ranges = info.ranges[i];
39+
40+
const nodes: ReactNode[] = [];
41+
let cursor = 0;
42+
for (let j = 0; j < ranges.length; j += 2) {
43+
nodes.push(iconName.slice(cursor, ranges[j]));
44+
nodes.push(
45+
<mark key={j + 1}>{iconName.slice(ranges[j], ranges[j + 1])}</mark>,
46+
);
47+
cursor = ranges[j + 1];
48+
}
49+
nodes.push(iconName.slice(cursor));
50+
return { url: `/icon/${iconName}`, description: nodes };
51+
});
52+
}, [searchText.trim()]);
53+
854
return (
955
<Margins>
56+
<input
57+
type="search"
58+
onChange={(event) => setSearchText(event.target.value)}
59+
/>
1060
<Stack direction="row" wrap="wrap" spacing={1} justifyContent="center">
11-
{icons.map((icon) => (
12-
<CopyableValue value={`/icon/${icon}`} placement="bottom">
61+
{searchedIcons.map((icon) => (
62+
<CopyableValue key={icon.url} value={icon.url} placement="bottom">
1363
<Stack
1464
alignItems="center"
1565
css={(theme) => ({ margin: theme.spacing(1.5) })}
1666
>
1767
<img
18-
src={`/icon/${icon}`}
68+
src={icon.url}
1969
css={{
2070
width: 64,
2171
height: 64,
@@ -35,7 +85,7 @@ export default function () {
3585
overflow: "hidden",
3686
}}
3787
>
38-
{icon}
88+
{icon.description}
3989
</p>
4090
</Stack>
4191
</CopyableValue>

0 commit comments

Comments
 (0)