SearchInput: debounce input and add wait indicator
This commit is contained in:
@@ -43,3 +43,8 @@
|
|||||||
.clear-icon {
|
.clear-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wait-icon {
|
||||||
|
cursor: wait;
|
||||||
|
/* opacity: 0.5; */
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { useRecoilState } from "recoil";
|
import { useRecoilState } from "recoil";
|
||||||
import { MagnifyingGlass, X } from "phosphor-react";
|
import { useDebounce } from "react-use";
|
||||||
|
import { MagnifyingGlass, X, HourglassHigh } from "phosphor-react";
|
||||||
|
|
||||||
import { searchQueryAtom } from "../../state/atoms";
|
import { searchQueryAtom } from "../../state/atoms";
|
||||||
import "./SearchInput.css";
|
import "./SearchInput.css";
|
||||||
@@ -8,11 +9,19 @@ import "./SearchInput.css";
|
|||||||
type SearchInputProps = {};
|
type SearchInputProps = {};
|
||||||
|
|
||||||
const SearchInput: React.FC<SearchInputProps> = () => {
|
const SearchInput: React.FC<SearchInputProps> = () => {
|
||||||
|
const [value, setValue] = useState<string>("");
|
||||||
const [query, setQuery] = useRecoilState(searchQueryAtom);
|
const [query, setQuery] = useRecoilState(searchQueryAtom);
|
||||||
|
void(query);
|
||||||
|
|
||||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const [isReady] = useDebounce(() => setQuery(value), 250, [value]);
|
||||||
setQuery(event.target.value);
|
|
||||||
|
const handleCancelSearch = () => {
|
||||||
|
setValue("");
|
||||||
|
// Should cancel pending debounce timeouts and immediately clear query
|
||||||
|
// without causing lag!
|
||||||
|
// setQuery("");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="search-bar">
|
<div className="search-bar">
|
||||||
<MagnifyingGlass size={24} />
|
<MagnifyingGlass size={24} />
|
||||||
@@ -21,11 +30,17 @@ const SearchInput: React.FC<SearchInputProps> = () => {
|
|||||||
aria-label="Search for an icon"
|
aria-label="Search for an icon"
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
value={query}
|
value={value}
|
||||||
placeholder="Search for an icon"
|
placeholder="Search for an icon"
|
||||||
onChange={handleSearchChange}
|
onChange={({ currentTarget }) => setValue(currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
{query && <X className="clear-icon" size={18} onClick={() => setQuery("")} />}
|
{value ? (
|
||||||
|
isReady() ? (
|
||||||
|
<X className="clear-icon" size={18} onClick={handleCancelSearch} />
|
||||||
|
) : (
|
||||||
|
<HourglassHigh className="wait-icon" weight="fill" size={18} />
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const filteredQueryResultsSelector = selector<Readonly<IconEntry[]>>({
|
|||||||
|
|
||||||
if (!query && !style) return icons;
|
if (!query && !style) return icons;
|
||||||
|
|
||||||
return await new Promise((resolve) =>
|
return new Promise((resolve) =>
|
||||||
resolve(icons.filter((icon) => isQueryMatch(icon, query)))
|
resolve(icons.filter((icon) => isQueryMatch(icon, query)))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -34,7 +34,7 @@ export const categorizedQueryResultsSelector = selector<
|
|||||||
key: "categorizedQueryResultsSelector",
|
key: "categorizedQueryResultsSelector",
|
||||||
get: async ({ get }) => {
|
get: async ({ get }) => {
|
||||||
const filteredResults = get(filteredQueryResultsSelector);
|
const filteredResults = get(filteredQueryResultsSelector);
|
||||||
return await new Promise((resolve) =>
|
return new Promise((resolve) =>
|
||||||
resolve(
|
resolve(
|
||||||
filteredResults.reduce<CategorizedIcons>((acc, curr) => {
|
filteredResults.reduce<CategorizedIcons>((acc, curr) => {
|
||||||
curr.categories.forEach((category) => {
|
curr.categories.forEach((category) => {
|
||||||
@@ -55,7 +55,7 @@ export const singleCategoryQueryResultsSelector = selectorFamily<
|
|||||||
key: "singleCategoryQueryResultsSelector",
|
key: "singleCategoryQueryResultsSelector",
|
||||||
get: (category: IconCategory) => async ({ get }) => {
|
get: (category: IconCategory) => async ({ get }) => {
|
||||||
const filteredResults = get(filteredQueryResultsSelector);
|
const filteredResults = get(filteredQueryResultsSelector);
|
||||||
return await new Promise((resolve) =>
|
return new Promise((resolve) =>
|
||||||
resolve(
|
resolve(
|
||||||
filteredResults.filter((icon) => icon.categories.includes(category))
|
filteredResults.filter((icon) => icon.categories.includes(category))
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user