on master help sign in
font size, search refactor
db807f3Gavin Atkinson
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";
2import { glossary } from "../lib/glossary";
2import { glossary } from "../lib/glossary";
3import Link from "next/link";
3import Link from "next/link";
4import { useRouter } from "next/router";
4import { useRouter } from "next/router";
5+import { configure, HotKeys } from "react-hotkeys";
6+import classnames from "classnames";
7+
8+const SearchContext = createContext();
5
9
6const glossarySearch = (query) => {
10const glossarySearch = (query) => {
7 return glossary.filter((entry) => {
11 return glossary.filter((entry) => {
@@ -9,16 +13,109 @@ const glossarySearch = (query) => {
9 });
13 });
10};
14};
11
15
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+
12export default function Search(props) {
90export default function Search(props) {
13 const searchRef = useRef(null);
91 const searchRef = useRef(null);
14 const [query, setQuery] = useState("");
92 const [query, setQuery] = useState("");
15 const [active, setActive] = useState(false);
93 const [active, setActive] = useState(false);
16 const [results, setResults] = useState([]);
94 const [results, setResults] = useState([]);
95+ const [cursor, setCursor] = useState(0);
17 const searchEndpoint = (query) => `/api/search?q=${query}`;
96 const searchEndpoint = (query) => `/api/search?q=${query}`;
18−
19 const router = useRouter();
97 const router = useRouter();
20
98
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+ {
21− useEffect(() => {
118+ /* useEffect(() => {
22 router.events.on("routeChangeComplete", handleRouteChange);
119 router.events.on("routeChangeComplete", handleRouteChange);
23 return () => {
120 return () => {
24 router.events.off("routeChangeComplete", handleRouteChange);
121 router.events.off("routeChangeComplete", handleRouteChange);
@@ -30,17 +127,32 @@ export default function Search(props) {
30 setResults([]);
127 setResults([]);
31 setActive(false);
128 setActive(false);
32 props.closeSearch();
129 props.closeSearch();
33− };
130+ }; */
131+ }
34
132
35 const onChange = useCallback((event) => {
133 const onChange = useCallback((event) => {
36 const query = event.target.value;
134 const query = event.target.value;
37
135
38 setQuery(query);
136 setQuery(query);
137+
39 if (query.length) {
138 if (query.length) {
40 fetch(searchEndpoint(query))
139 fetch(searchEndpoint(query))
41 .then((res) => res.json())
140 .then((res) => res.json())
42 .then((res) => {
141 .then((res) => {
142+ // Wrap results in an object which will tell React what component to use to render results.
43− setResults(res.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);
44 });
156 });
45 } else {
157 } else {
46 setResults([]);
158 setResults([]);
@@ -49,7 +161,7 @@ export default function Search(props) {
49
161
50 const onFocus = useCallback(() => {
162 const onFocus = useCallback(() => {
51 setActive(true);
163 setActive(true);
52− window.addEventListener("click", onClick);
164+ // window.addEventListener("click", onClick);
53 }, []);
165 }, []);
54
166
55 const onClick = useCallback((event) => {
167 const onClick = useCallback((event) => {
@@ -60,15 +172,46 @@ export default function Search(props) {
60 }
172 }
61 }, []);
173 }, []);
62
174
63− let glossaryResults = [];
175+ const onSelect = () => {
64− if (query.length) {
176+ console.log(results, cursor);
65− glossaryResults = glossarySearch(query);
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+ }
66− }
197+ };
67
198
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) => {
69 e.stopPropagation();
212 e.stopPropagation();
70 setQuery("");
213 setQuery("");
71− }, []);
214+ };
72
215
73 // Locks document scrolling when search is open
216 // Locks document scrolling when search is open
74 if (typeof document !== "undefined") {
217 if (typeof document !== "undefined") {
@@ -81,110 +224,74 @@ export default function Search(props) {
81
224
82 if (props.showSearch) {
225 if (props.showSearch) {
83 return (
226 return (
84− <div
227+ <SearchContext.Provider value={42}>
85− onClick={props.closeSearch}
228+ <HotKeys handlers={handlers} keyMap={keyMap}>
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−
114 <div
229 <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"
116 >
232 >
117− <div>
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">
118− {active &&
234+ <div className="relative">
119− results.length === 0 &&
235+ <input
120− glossaryResults.length === 0 &&
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"
121− query !== "" ? (
237+ onChange={onChange}
122− <div className="flex font-semibold text-sm p-3 py-8 w-full">
238+ onFocus={onFocus}
123− <p className="mr-2 text-gray">No Results found for</p>
239+ placeholder="Search..."
124− <p className="font-semibold">"{query}"</p>
240+ type="text"
125− </div>
241+ value={query}
126− ) : null}
242+ onClick={(e) => e.stopPropagation()}
127− {active && glossaryResults.length > 0 ? (
243+ autoFocus={true}
128− <>
244+ />
129− <div className="sticky top-0 flex font-semibold text-sm p-3 py-1 w-full bg-ultraDeepWall text-white">
245+
130− <p className="font-semibold text-sm">Glossary</p>
246+ <span
131− </div>
247+ onClick={props.toggleSearch}
132−
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"
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>
249+ >
174− <p className="text-base font-regular text-small text-gray">
250+ <p className="px-2 h-8 bg-wall flex items-center justify-center text-sm rounded">
175− {content}
251+ ESC
176− </p>
252+ </p>
177− </div>
253+ </span>
178− </Link>
254+ </div>
255+
179− </div>
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+ />
180− );
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+ );
181− })}
286+ }
182− </>
287+ return null;
183− )}
288+ })
289+ : null}
290+ </div>
184 </div>
291 </div>
185 </div>
292 </div>
186− </div>
293+ </HotKeys>
187− </div>
294+ </SearchContext.Provider>
188 );
295 );
189 }
296 }
190 return null;
297 return null;
components/Section.js+2−2
@@ -10,8 +10,8 @@ export default function Section({
10 const spacing = classnames({
10 const spacing = classnames({
11 "py-6 md:py-8 lg:py-10": short,
11 "py-6 md:py-8 lg:py-10": short,
12 "py-10 md:py-12 lg:py-24": !short,
12 "py-10 md:py-12 lg:py-24": !short,
13− "md:w-8/12": narrow,
13+ "md:w-7/12": narrow,
14− "md:w-10/12": !narrow,
14+ "md:w-9/12": !narrow,
15 });
15 });
16 return (
16 return (
17 <section className={`w-full px-4 md:px-8 ${spacing} ${className}`}>
17 <section className={`w-full px-4 md:px-8 ${spacing} ${className}`}>
styles/globals.css+4
@@ -1,6 +1,10 @@
1@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;500;600;700&display=swap");
1@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;500;600;700&display=swap");
2@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");
2@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");
3
3
4+:root {
5+ font-size: 12px;
6+}
7+
4html,
8html,
5body {
9body {
6 padding: 0;
10 padding: 0;