on cb112e8 help sign in
190 lines 6.3 kB history raw
1import { useCallback, useRef, useState, useEffect } from "react";
2import { glossary } from "../lib/glossary";
3import Link from "next/link";
4import { useRouter } from "next/router";
6const glossarySearch = (query) => {
7 return glossary.filter((entry) => {
8 return query.toLowerCase() === entry.name || entry.symbol === query;
9 });
10};
12export default function Search(props) {
13 const searchRef = useRef(null);
14 const [query, setQuery] = useState("");
15 const [active, setActive] = useState(false);
16 const [results, setResults] = useState([]);
17 const searchEndpoint = (query) => `/api/search?q=${query}`;
19 const router = useRouter();
21 useEffect(() => {
22 router.events.on("routeChangeComplete", handleRouteChange);
23 return () => {
24 router.events.off("routeChangeComplete", handleRouteChange);
25 };
26 }, [router.events]);
28 const handleRouteChange = () => {
29 setQuery("");
30 setResults([]);
31 setActive(false);
32 props.closeSearch();
33 };
35 const onChange = useCallback((event) => {
36 const query = event.target.value;
38 setQuery(query);
39 if (query.length) {
40 fetch(searchEndpoint(query))
41 .then((res) => res.json())
42 .then((res) => {
43 setResults(res.results);
44 });
45 } else {
46 setResults([]);
47 }
48 }, []);
50 const onFocus = useCallback(() => {
51 setActive(true);
52 window.addEventListener("click", onClick);
53 }, []);
55 const onClick = useCallback((event) => {
56 if (searchRef.current && !searchRef.current.contains(event.target)) {
57 setActive(false);
58 window.removeEventListener("click", onClick);
59 props.toggleSearch();
60 }
61 }, []);
63 let glossaryResults = [];
64 if (query.length) {
65 glossaryResults = glossarySearch(query);
66 }
68 const clear = useCallback((e) => {
69 e.stopPropagation();
70 setQuery("");
71 }, []);
73 // Locks document scrolling when search is open
74 if (typeof document !== "undefined") {
75 if (props.showSearch) {
76 document.body.style.overflow = "hidden";
77 } else {
78 document.body.style.overflow = "visible";
79 }
80 }
82 if (props.showSearch) {
83 return (
84 <div
85 onClick={props.closeSearch}
86 className="fixed w-screen h-screen bg-washedWall z-50 flex flex-col items-center p-4"
87 >
88 <div
89 className="relative flex flex-col max-w-screen-lg md:my-32 w-full md:w-10/12 lg:w-8/12 xl:w-6/12 rounded-xl bg-white min-h-0"
90 ref={searchRef}
91 >
92 <div className="relative">
93 <input
94 className="text-lg md:text-xl lg:text-2xl font-medium text-green bg-transparent py-2 px-4 outline-none relative w-full"
95 onChange={onChange}
96 onFocus={onFocus}
97 placeholder="Search..."
98 type="text"
99 value={query}
100 onClick={(e) => e.stopPropagation()}
101 autoFocus={true}
102 />
104 <span
105 onClick={props.toggleSearch}
106 className="absolute cursor-pointer right-1.5 top-1.5 md:right-2 md:top-2 lg:right-2.5 lg:top-2.5"
107 >
108 <p className="px-2 h-8 bg-wall flex items-center justify-center text-sm rounded">
109 ESC
110 </p>
111 </span>
112 </div>
114 <div
115 className={"overflow-y-scroll" + (query.length > 0 ? "" : "hidden")}
116 >
117 <div>
118 {active &&
119 results.length === 0 &&
120 glossaryResults.length === 0 &&
121 query !== "" ? (
122 <div className="flex font-semibold text-sm p-3 py-8 w-full">
123 <p className="mr-2 text-gray">No Results found for</p>
124 <p className="font-semibold">"{query}"</p>
125 </div>
126 ) : null}
127 {active && glossaryResults.length > 0 ? (
128 <>
129 <div className="sticky top-0 flex font-semibold text-sm p-3 py-1 w-full bg-ultraDeepWall text-white">
130 <p className="font-semibold text-sm">Glossary</p>
131 </div>
133 {glossaryResults.map(({ name, link, desc, symbol }) => {
134 return (
135 <Link href={link} as={link} key={link}>
136 <div className="cursor-pointer hover:bg-wall">
137 <div className="font-semibold p-3">
138 {symbol.length > 0 && (
139 <code className="mr-1 bg-wall rounded px-1 py-0.5">
140 {symbol}
141 </code>
142 )}
143 {name}
144 <p className="font-normal text-base mt-1">{desc}</p>
145 </div>
146 </div>
147 </Link>
148 );
149 })}
150 </>
151 ) : null}
152 {active && results.length > 0 && (
153 <>
154 <div className="sticky top-0 flex font-semibold text-sm p-3 py-1 w-full bg-ultraDeepWall text-white">
155 <p className="font-semibold text-sm">
156 {results.length} results for "{query}"
157 </p>
158 </div>
160 {results
161 .filter((e) => e.parent !== "Glossary")
162 .map(({ title, slug, content, parent }) => {
163 return (
164 <div
165 key={slug}
166 className="cursor-pointer hover:bg-wall p-3"
167 >
168 <Link href={slug} as={slug}>
169 <div>
170 <p className="font-medium text-base">
171 {parent} / {title}
172 </p>
173 <p className="text-base font-regular text-small text-gray">
174 {content}
175 </p>
176 </div>
177 </Link>
178 </div>
179 );
180 })}
181 </>
182 )}
183 </div>
184 </div>
185 </div>
186 </div>
187 );
188 }
189 return null;