on master help sign in
font size, search refactor
commitdb807f336db4bc6e9b8cc5491be99b6330fe00e6 authorGavin Atkinson <gvnatknsn@protonmail.com> dateMon 2 Aug 2021, 14:33 (5 years ago) parent8ffef41
3 files changed+225−114
components/Search.js+219−112 components/Section.js+2−2 styles/globals.css+4
components/Search.js+219−112
@@ -1,7 +1,11 @@
1−import { useCallback, useRef, useState, useEffect } from "react";
1+import { useCallback, useRef, useState, useEffect, createContext } from "react";
22import { glossary } from "../lib/glossary";
33import Link from "next/link";
44import { useRouter } from "next/router";
5+import { configure, HotKeys } from "react-hotkeys";
6+import classnames from "classnames";
7+
8+const SearchContext = createContext();
59
610const glossarySearch = (query) => {
711 return glossary.filter((entry) => {
@@ -9,16 +13,109 @@ const glossarySearch = (query) => {
913 });
1014};
1115
16+function GlossaryResult(props) {
17+ const selectedClassnames = classnames({
18+ "bg-green": props.selected,
19+ });
20+
21+ const codeBg = classnames({
22+ "bg-wall": !props.selected,
23+ "bg-washedWhite": props.selected,
24+ });
25+
26+ const text = classnames({
27+ "text-black": !props.selected,
28+ "text-white": props.selected,
29+ });
30+
31+ return (
32+ <div
33+ className={`cursor-pointer ${selectedClassnames}`}
34+ onMouseEnter={() => props.setCursor(props.index)}
35+ >
36+ <div className="font-semibold p-3">
37+ <p className={`text-base ${text}`}>
38+ {props.symbol.length > 0 && (
39+ <code className={`mr-1 rounded px-1 py-0.5 ${codeBg}`}>
40+ {props.symbol}
41+ </code>
42+ )}
43+ {props.name}
44+ </p>
45+ <p className={`font-normal text-base mt-1 ${text}`}>{props.desc}</p>
46+ </div>
47+ </div>
48+ );
49+}
50+
51+function Result(props) {
52+ const selectedClassnames = classnames({
53+ "bg-green": props.selected,
54+ });
55+ const text = classnames({
56+ "text-black": !props.selected,
57+ "text-white": props.selected,
58+ });
59+ const description = classnames({
60+ "text-gray": !props.selected,
61+ "text-midWhite": props.selected,
62+ });
63+ return (
64+ <div
65+ className={`cursor-pointer ${selectedClassnames}`}
66+ onMouseEnter={() => props.setCursor(props.index)}
67+ >
68+ <div className="p-3">
69+ <p className={`font-medium text-base ${text}`}>
70+ {props.parent !== "Content" ? `${props.parent} /` : ""} {props.title}
71+ </p>
72+ <p
73+ className={`text-base font-regular text-small text-gray ${description}`}
74+ >
75+ {props.content}
76+ </p>
77+ </div>
78+ </div>
79+ );
80+}
81+
82+function Subheading(props) {
83+ return (
84+ <div className="sticky top-0 flex font-semibold text-sm p-3 py-1 w-full bg-ultraDeepWall text-white">
85+ <p className="font-semibold text-sm">{props.title}</p>
86+ </div>
87+ );
88+}
89+
1290export default function Search(props) {
1391 const searchRef = useRef(null);
1492 const [query, setQuery] = useState("");
1593 const [active, setActive] = useState(false);
1694 const [results, setResults] = useState([]);
95+ const [cursor, setCursor] = useState(0);
1796 const searchEndpoint = (query) => `/api/search?q=${query}`;
18−
1997 const router = useRouter();
2098
21− useEffect(() => {
99+ const keyMap = {
100+ onNavUp: ["up"],
101+ onNavDown: ["down"],
102+ onSelect: ["enter"],
103+ };
104+
105+ const handlers = {
106+ onNavUp: (event) => onNavUp(event),
107+ onNavDown: (event) => onNavDown(event),
108+ onSelect: (event) => onSelect(event, results[cursor]),
109+ };
110+
111+ configure({
112+ // ignoreTags: [],
113+ ignoreTags: ["input", "select", "textarea"],
114+ ignoreEventsCondition: function () {},
115+ });
116+
117+ {
118+ /* useEffect(() => {
22119 router.events.on("routeChangeComplete", handleRouteChange);
23120 return () => {
24121 router.events.off("routeChangeComplete", handleRouteChange);
@@ -30,17 +127,32 @@ export default function Search(props) {
30127 setResults([]);
31128 setActive(false);
32129 props.closeSearch();
33− };
130+ }; */
131+ }
34132
35133 const onChange = useCallback((event) => {
36134 const query = event.target.value;
37135
38136 setQuery(query);
137+
39138 if (query.length) {
40139 fetch(searchEndpoint(query))
41140 .then((res) => res.json())
42141 .then((res) => {
43− setResults(res.results);
142+ // Wrap results in an object which will tell React what component to use to render results.
143+ const results = res.results.map((item) => ({
144+ type: "RESULT",
145+ content: item,
146+ }));
147+
148+ const glossaryResults = glossarySearch(query).map((item) => ({
149+ type: "GLOSSARY_RESULT",
150+ content: item,
151+ }));
152+
153+ const list = [...glossaryResults, ...results];
154+
155+ setResults(list);
44156 });
45157 } else {
46158 setResults([]);
@@ -49,7 +161,7 @@ export default function Search(props) {
49161
50162 const onFocus = useCallback(() => {
51163 setActive(true);
52− window.addEventListener("click", onClick);
164+ // window.addEventListener("click", onClick);
53165 }, []);
54166
55167 const onClick = useCallback((event) => {
@@ -60,15 +172,46 @@ export default function Search(props) {
60172 }
61173 }, []);
62174
63− let glossaryResults = [];
64− if (query.length) {
65− glossaryResults = glossarySearch(query);
66− }
175+ const onSelect = () => {
176+ console.log(results, cursor);
177+
178+ {
179+ /* const cursorItem = results[cursor].content
180+
181+
182+
183+ if (cursorItem.link) {
184+ window.location.href = cursorItem.link
185+ }
186+
187+ if (cursorItem.slug) {
188+ router.push(cursorItem.slug)
189+ }
190+
191+ props.toggleSearch();
192+ setActive(false);
193+ setQuery("");
194+ setResults([])
195+ props.closeSearch(); */
196+ }
197+ };
67198
68− const clear = useCallback((e) => {
199+ const onNavUp = useCallback((event) => {
200+ // prevents cursor from moving in search input
201+ event.preventDefault();
202+ setCursor((state) => state - 1);
203+ });
204+
205+ const onNavDown = useCallback((event) => {
206+ // prevents cursor from moving in search input
207+ event.preventDefault();
208+ setCursor((state) => state + 1);
209+ });
210+
211+ const clear = (e) => {
69212 e.stopPropagation();
70213 setQuery("");
71− }, []);
214+ };
72215
73216 // Locks document scrolling when search is open
74217 if (typeof document !== "undefined") {
@@ -81,110 +224,74 @@ export default function Search(props) {
81224
82225 if (props.showSearch) {
83226 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− />
103−
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>
113−
227+ <SearchContext.Provider value={42}>
228+ <HotKeys handlers={handlers} keyMap={keyMap}>
114229 <div
115− className={"overflow-y-scroll" + (query.length > 0 ? "" : "hidden")}
230+ onClick={props.closeSearch}
231+ className="fixed w-screen h-screen bg-washedWall z-50 flex flex-col items-center p-4"
116232 >
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>
132−
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>
159−
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 !== "Content" ? `${parent} /` : ""}{" "}
172− {title}
173− </p>
174− <p className="text-base font-regular text-small text-gray">
175− {content}
176− </p>
177− </div>
178− </Link>
179− </div>
180− );
181− })}
182− </>
183− )}
233+ <div 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">
234+ <div className="relative">
235+ <input
236+ className="text-lg md:text-xl lg:text-2xl font-medium text-green bg-transparent py-2 px-4 outline-none relative w-full"
237+ onChange={onChange}
238+ onFocus={onFocus}
239+ placeholder="Search..."
240+ type="text"
241+ value={query}
242+ onClick={(e) => e.stopPropagation()}
243+ autoFocus={true}
244+ />
245+
246+ <span
247+ onClick={props.toggleSearch}
248+ className="absolute cursor-pointer right-1.5 top-1.5 md:right-2 md:top-2 lg:right-2.5 lg:top-2.5"
249+ >
250+ <p className="px-2 h-8 bg-wall flex items-center justify-center text-sm rounded">
251+ ESC
252+ </p>
253+ </span>
254+ </div>
255+
256+ <div
257+ className={
258+ "overflow-y-scroll border-t-2 border-wall" +
259+ (query.length > 0 ? "" : "hidden border-transparent")
260+ }
261+ >
262+ {active && query !== ""
263+ ? results.map((item, index) => {
264+ if (item.type === "GLOSSARY_RESULT") {
265+ return (
266+ <GlossaryResult
267+ key={item.link + "-" + index}
268+ {...item.content}
269+ index={index}
270+ setCursor={setCursor}
271+ selected={index === cursor}
272+ />
273+ );
274+ }
275+
276+ if (item.type === "RESULT") {
277+ return (
278+ <Result
279+ key={item.link + "-" + index}
280+ {...item.content}
281+ index={index}
282+ setCursor={setCursor}
283+ selected={index === cursor}
284+ />
285+ );
286+ }
287+ return null;
288+ })
289+ : null}
290+ </div>
184291 </div>
185292 </div>
186− </div>
187− </div>
293+ </HotKeys>
294+ </SearchContext.Provider>
188295 );
189296 }
190297 return null;
components/Section.js+2−2
@@ -10,8 +10,8 @@ export default function Section({
1010 const spacing = classnames({
1111 "py-6 md:py-8 lg:py-10": short,
1212 "py-10 md:py-12 lg:py-24": !short,
13− "md:w-8/12": narrow,
14− "md:w-10/12": !narrow,
13+ "md:w-7/12": narrow,
14+ "md:w-9/12": !narrow,
1515 });
1616 return (
1717 <section className={`w-full px-4 md:px-8 ${spacing} ${className}`}>
styles/globals.css+4
@@ -1,6 +1,10 @@
11@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;500;600;700&display=swap");
22@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Source+Code+Pro:wght@400;500;600;700&display=swap");
33
4+:root {
5+ font-size: 12px;
6+}
7+
48html,
59body {
610 padding: 0;