Use downshift-js for better search bar experience
This commit is contained in:
parent
86a7516c3b
commit
6005028439
@ -1,3 +1,4 @@
|
|||||||
|
import { useCombobox } from "downshift";
|
||||||
import React, { useEffect, useState, useRef } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import type mapboxgl from "mapbox-gl";
|
import type mapboxgl from "mapbox-gl";
|
||||||
@ -5,8 +6,6 @@ import MiniSearch, { SearchResult } from "minisearch";
|
|||||||
import useSWRImmutable from "swr/immutable";
|
import useSWRImmutable from "swr/immutable";
|
||||||
|
|
||||||
import styles from "../styles/SearchBar.module.css";
|
import styles from "../styles/SearchBar.module.css";
|
||||||
import { join } from "path/posix";
|
|
||||||
import { countReset } from "console";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
clearSelectedCell: () => void;
|
clearSelectedCell: () => void;
|
||||||
@ -59,93 +58,87 @@ const SearchBar: React.FC<Props> = ({ clearSelectedCell, counts, map }) => {
|
|||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const searchInput = useRef<HTMLInputElement | null>(null);
|
const searchInput = useRef<HTMLInputElement | null>(null);
|
||||||
const [search, setSearch] = useState<string>("");
|
|
||||||
const [searchFocused, setSearchFocused] = useState<boolean>(false);
|
const [searchFocused, setSearchFocused] = useState<boolean>(false);
|
||||||
const [clickingResult, setClickingResult] = useState<boolean>(false);
|
|
||||||
const [results, setResults] = useState<SearchResult[]>([]);
|
const [results, setResults] = useState<SearchResult[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
if (searchInput.current) {
|
isOpen,
|
||||||
if (
|
getMenuProps,
|
||||||
searchFocused &&
|
getInputProps,
|
||||||
global.document.activeElement !== searchInput.current
|
getComboboxProps,
|
||||||
) {
|
highlightedIndex,
|
||||||
searchInput.current.focus();
|
getItemProps,
|
||||||
} else if (
|
reset,
|
||||||
!searchFocused &&
|
} = useCombobox({
|
||||||
global.document.activeElement === searchInput.current
|
items: results,
|
||||||
) {
|
itemToString: (item) => (item ? item.name : ""),
|
||||||
searchInput.current.blur();
|
onInputValueChange: ({ inputValue }) => {
|
||||||
|
if (searchEngine.current && inputValue) {
|
||||||
|
const results: SearchResult[] = searchEngine.current.search(inputValue);
|
||||||
|
setResults(results);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, [searchFocused]);
|
onSelectedItemChange: ({ selectedItem }) => {
|
||||||
|
if (selectedItem) {
|
||||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
setSearchFocused(false);
|
||||||
setSearch(e.target.value);
|
router.push({ query: { mod: selectedItem.id } });
|
||||||
if (searchEngine.current) {
|
if (searchInput.current) searchInput.current.blur();
|
||||||
const results: SearchResult[] = searchEngine.current.search(
|
reset();
|
||||||
e.target.value
|
}
|
||||||
);
|
},
|
||||||
setResults(results);
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onChooseResult =
|
|
||||||
(item: Mod) =>
|
|
||||||
(e: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>) => {
|
|
||||||
router.push({ query: { mod: item.id } });
|
|
||||||
setSearch("");
|
|
||||||
setResults([]);
|
|
||||||
setClickingResult(false);
|
|
||||||
setSearchFocused(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={`${styles["search-bar"]} ${
|
<div
|
||||||
searchFocused ? styles["search-bar-focused"] : ""
|
className={`${styles["search-bar"]} ${
|
||||||
}`}
|
searchFocused ? styles["search-bar-focused"] : ""
|
||||||
>
|
}`}
|
||||||
<input
|
{...getComboboxProps()}
|
||||||
type="text"
|
>
|
||||||
placeholder="Search mods or cells…"
|
<input
|
||||||
onChange={onChange}
|
{...getInputProps({
|
||||||
onFocus={() => setSearchFocused(true)}
|
type: "text",
|
||||||
onBlur={() => {
|
placeholder: "Search mods or cells…",
|
||||||
if (!clickingResult) {
|
onFocus: () => setSearchFocused(true),
|
||||||
setSearch("");
|
onBlur: () => {
|
||||||
setResults([]);
|
if (!isOpen) setSearchFocused(false);
|
||||||
setSearchFocused(false);
|
},
|
||||||
}
|
disabled: !data,
|
||||||
}}
|
ref: searchInput,
|
||||||
value={search}
|
})}
|
||||||
ref={searchInput}
|
/>
|
||||||
disabled={!data}
|
<ul
|
||||||
/>
|
className={styles["search-results"]}
|
||||||
{results.length > 0 && (
|
style={!isOpen ? { display: "none" } : {}}
|
||||||
<ul className={styles["search-results"]}>
|
{...getMenuProps()}
|
||||||
{results
|
>
|
||||||
.sort((resultA, resultB) => {
|
{isOpen &&
|
||||||
if (counts) {
|
results
|
||||||
const countA = counts[resultA.id];
|
.sort((resultA, resultB) => {
|
||||||
const countB = counts[resultB.id];
|
if (counts) {
|
||||||
if (countA && countB) return countB[2] - countA[2];
|
const countA = counts[resultA.id];
|
||||||
}
|
const countB = counts[resultB.id];
|
||||||
return 0;
|
if (countA && countB) return countB[2] - countA[2];
|
||||||
})
|
}
|
||||||
.map((result) => (
|
return 0;
|
||||||
<li
|
})
|
||||||
key={result.id}
|
.map((result, index) => (
|
||||||
onClick={onChooseResult({ id: result.id, name: result.name })}
|
<li
|
||||||
onTouchStart={() => setClickingResult(true)}
|
key={result.id}
|
||||||
onMouseDown={() => setClickingResult(true)}
|
{...getItemProps({ item: result, index })}
|
||||||
>
|
className={`${
|
||||||
{result.name}
|
highlightedIndex === index
|
||||||
</li>
|
? styles["highlighted-result"]
|
||||||
))}
|
: ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{result.name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
32
package-lock.json
generated
32
package-lock.json
generated
@ -25,7 +25,6 @@
|
|||||||
"version": "7.16.7",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
|
||||||
"integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
|
"integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
@ -555,6 +554,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
|
||||||
"integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="
|
"integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="
|
||||||
},
|
},
|
||||||
|
"compute-scroll-into-view": {
|
||||||
|
"version": "1.0.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
|
||||||
|
"integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg=="
|
||||||
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@ -657,6 +661,25 @@
|
|||||||
"esutils": "^2.0.2"
|
"esutils": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downshift": {
|
||||||
|
"version": "6.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.7.tgz",
|
||||||
|
"integrity": "sha512-cVprZg/9Lvj/uhYRxELzlu1aezRcgPWBjTvspiGTVEU64gF5pRdSRKFVLcxqsZC637cLAGMbL40JavEfWnqgNg==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.14.8",
|
||||||
|
"compute-scroll-into-view": "^1.0.17",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react-is": "^17.0.2",
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"earcut": {
|
"earcut": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.3.tgz",
|
||||||
@ -1976,7 +1999,6 @@
|
|||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@ -1986,8 +2008,7 @@
|
|||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2055,8 +2076,7 @@
|
|||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.13.9",
|
"version": "0.13.9",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
||||||
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
|
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"regexp.prototype.flags": {
|
"regexp.prototype.flags": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"@types/javascript-color-gradient": "^1.3.0",
|
"@types/javascript-color-gradient": "^1.3.0",
|
||||||
"@types/mapbox-gl": "^2.6.0",
|
"@types/mapbox-gl": "^2.6.0",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
|
"downshift": "^6.1.7",
|
||||||
"javascript-color-gradient": "^1.3.2",
|
"javascript-color-gradient": "^1.3.2",
|
||||||
"mapbox-gl": "^2.6.1",
|
"mapbox-gl": "^2.6.1",
|
||||||
"minisearch": "^3.2.0",
|
"minisearch": "^3.2.0",
|
||||||
|
@ -44,3 +44,7 @@
|
|||||||
.search-results li:first-child {
|
.search-results li:first-child {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.highlighted-result {
|
||||||
|
background-color: #bde4ff;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user