on master help sign in
major search ui refactor
commitd4b872579759b05ba2cd9403d3fd0cca5d26f694 authorGavin Atkinson <gvnatknsn@protonmail.com> dateThu 5 Aug 2021, 11:10 (5 years ago) parentdb807f3
5 files changed+526−264
components/Search copy.js+323 components/Search.js+167−260 package-lock.json+31 package.json+1 styles/globals.css+4−4
components/Search copy.jsnew file+323
@@ -0,0 +1,323 @@
1+import { Component, createRef } from "react";
2+import { glossary } from "../lib/glossary";
3+import Link from "next/link";
4+import { withRouter } from "next/router";
5+import { configure, HotKeys } from "react-hotkeys";
6+import classnames from "classnames";
7+
8+{
9+ /* const SearchContext = createContext(); */
10+}
11+
12+const glossarySearch = (query) => {
13+ return glossary.filter((entry) => {
14+ return query.toLowerCase() === entry.name || entry.symbol === query;
15+ });
16+};
17+
18+function GlossaryResult(props) {
19+ const selectedClassnames = classnames({
20+ "bg-green": props.selected,
21+ });
22+
23+ const codeBg = classnames({
24+ "bg-wall": !props.selected,
25+ "bg-washedWhite": props.selected,
26+ });
27+
28+ const text = classnames({
29+ "text-black": !props.selected,
30+ "text-white": props.selected,
31+ });
32+
33+ return (
34+ <button
35+ tabIndex={props.index}
36+ id={props.link + "-" + props.index}
37+ className={`cursor-pointer flex text-left w-full ${selectedClassnames}`}
38+ onMouseEnter={() => props.setCursor(props.index)}
39+ onClick={() => props.onSelect()}
40+ >
41+ <div className="font-semibold p-3">
42+ <p className={`text-base ${text}`}>
43+ {props.symbol.length > 0 && (
44+ <code className={`mr-1 rounded px-1 py-0.5 ${codeBg}`}>
45+ {props.symbol}
46+ </code>
47+ )}
48+ {props.name}
49+ </p>
50+ <p className={`font-normal text-base mt-1 ${text}`}>{props.desc}</p>
51+ </div>
52+ </button>
53+ );
54+}
55+
56+function Result(props) {
57+ const selectedClassnames = classnames({
58+ "bg-green": props.selected,
59+ });
60+ const text = classnames({
61+ "text-black": !props.selected,
62+ "text-white": props.selected,
63+ });
64+ const description = classnames({
65+ "text-gray": !props.selected,
66+ "text-midWhite": props.selected,
67+ });
68+ return (
69+ <button
70+ tabIndex={props.index}
71+ id={props.slug + "-" + props.index}
72+ className={`cursor-pointer flex text-left w-full ${selectedClassnames}`}
73+ onMouseEnter={() => props.setCursor(props.index)}
74+ onClick={() => props.onSelect()}
75+ >
76+ <div className="p-3">
77+ <p className={`font-medium text-base ${text}`}>
78+ {props.parent !== "Content" ? `${props.parent} /` : ""} {props.title}
79+ </p>
80+ <p
81+ className={`text-base font-regular text-small text-gray ${description}`}
82+ >
83+ {props.content}
84+ </p>
85+ </div>
86+ </button>
87+ );
88+}
89+
90+function Subheading(props) {
91+ return (
92+ <div className="sticky top-0 flex font-semibold text-sm p-3 py-1 w-full bg-ultraDeepWall text-white">
93+ <p className="font-semibold text-sm">{props.title}</p>
94+ </div>
95+ );
96+}
97+
98+class Search extends Component {
99+ constructor(props) {
100+ super(props);
101+ this.state = {
102+ query: "",
103+ active: false,
104+ results: [],
105+ cursor: 0,
106+ };
107+ this.scrollBox = createRef();
108+ this.searchEndpoint = this.searchEndpoint.bind(this);
109+ this.onChange = this.onChange.bind(this);
110+ this.onFocus = this.onFocus.bind(this);
111+ this.onNavUp = this.onNavUp.bind(this);
112+ this.onNavDown = this.onNavDown.bind(this);
113+ this.onSelect = this.onSelect.bind(this);
114+ this.setCursor = this.setCursor.bind(this);
115+ }
116+
117+ searchEndpoint(query) {
118+ return `/api/search?q=${this.state.query}`;
119+ }
120+
121+ onChange(event) {
122+ const query = event.target.value;
123+ this.setState({ query });
124+
125+ if (query.length) {
126+ fetch(this.searchEndpoint(query))
127+ .then((res) => res.json())
128+ .then((res) => {
129+ // Wrap results in an object which will tell React what component to use to render results.
130+ const results = res.results.map((item) => ({
131+ type: "RESULT",
132+ content: item,
133+ }));
134+
135+ const glossaryResults = glossarySearch(query).map((item) => ({
136+ type: "GLOSSARY_RESULT",
137+ content: item,
138+ }));
139+
140+ const list = [...glossaryResults, ...results];
141+
142+ this.setState({ results: list });
143+ });
144+ } else {
145+ this.setState({ results: [] });
146+ }
147+ }
148+
149+ onFocus() {
150+ this.setState({ active: true });
151+ }
152+
153+ onNavUp(event) {
154+ // prevents cursor from moving in search input
155+ event.preventDefault();
156+ this.setState({ cursor: this.state.cursor - 1 });
157+ }
158+
159+ onNavDown(event) {
160+ const offset = this[`result-${this.state.cursor}`].offsetTop;
161+ const height = this[`result-${this.state.cursor}`].clientHeight;
162+
163+ this.scrollBox.current.scrollTop = offset - height;
164+ console.log(offset - height);
165+
166+ {
167+ /* const elem = this[`result-${this.state.cursor}`] */
168+ }
169+
170+ {
171+ /* console.log(elem) */
172+ }
173+ // prevents cursor from moving in search input
174+ {
175+ /* console.log(this[`result-${this.state.cursor}`].offsetTop) */
176+ }
177+ {
178+ /* this.scrollBox.scrollTop = this[`result-${this.state.cursor}`].offsetTop */
179+ }
180+ {
181+ /* this[`result-${this.state.cursor}`].scrollIntoView({behavior: 'smooth'}); */
182+ }
183+ {
184+ /* this[`result-${this.state.cursor}`].offsetTop = 0 */
185+ }
186+
187+ {
188+ /* this[`result-${this.state.cursor}`].scrollIntoView(); */
189+ }
190+ {
191+ /* elem.scrollTop = 0; */
192+ }
193+ event.preventDefault();
194+ this.setState({ cursor: this.state.cursor + 1 });
195+ }
196+
197+ onSelect(event) {
198+ const { state, props } = this;
199+ const cursorItem = state.results[state.cursor].content;
200+
201+ if (cursorItem.link) {
202+ window.location.href = cursorItem.link;
203+ }
204+
205+ if (cursorItem.slug) {
206+ props.router.push(cursorItem.slug);
207+ }
208+
209+ this.setState({
210+ active: false,
211+ query: "",
212+ results: [],
213+ });
214+
215+ props.closeSearch();
216+ }
217+
218+ setCursor(num) {
219+ this.setState({ cursor: num });
220+ }
221+
222+ applyRef(index, ref) {
223+ this[`result-${index}`] = ref;
224+ }
225+
226+ render() {
227+ const { state, props } = this;
228+
229+ const keyMap = {
230+ onNavUp: ["up"],
231+ onNavDown: ["down"],
232+ onSelect: ["enter"],
233+ };
234+
235+ const handlers = {
236+ onNavUp: (event) => this.onNavUp(event),
237+ onNavDown: (event) => this.onNavDown(event),
238+ onSelect: (event) => this.onSelect(event),
239+ };
240+
241+ if (props.showSearch) {
242+ return (
243+ <HotKeys handlers={handlers} keyMap={keyMap}>
244+ <div
245+ onClick={props.closeSearch}
246+ className="fixed w-screen h-screen bg-washedWall z-50 flex flex-col items-center p-4"
247+ >
248+ <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 overflow-hidden">
249+ <div className="relative">
250+ <input
251+ className="text-lg md:text-xl lg:text-2xl font-medium text-green bg-transparent py-2 px-4 outline-none relative w-full"
252+ onChange={this.onChange}
253+ onFocus={this.onFocus}
254+ placeholder="Search..."
255+ type="text"
256+ value={state.query}
257+ onClick={(e) => e.stopPropagation()}
258+ autoFocus={true}
259+ />
260+
261+ <span
262+ onClick={props.toggleSearch}
263+ className="absolute cursor-pointer right-1.5 top-1.5 md:right-2 md:top-2 lg:right-2.5 lg:top-2.5"
264+ >
265+ <p className="px-2 h-8 bg-wall flex items-center justify-center text-sm rounded">
266+ ESC
267+ </p>
268+ </span>
269+ </div>
270+
271+ <div
272+ ref={this.scrollBox}
273+ className={
274+ "overflow-y-scroll border-t-2 border-wall" +
275+ (state.query.length > 0 ? "" : "hidden border-transparent")
276+ }
277+ >
278+ {state.active && state.query !== ""
279+ ? state.results.map((item, index) => {
280+ if (item.type === "GLOSSARY_RESULT") {
281+ return (
282+ <div ref={this.applyRef.bind(this, index)}>
283+ <GlossaryResult
284+ key={item.content.link + "-" + index}
285+ index={index}
286+ setCursor={this.setCursor}
287+ selected={index === state.cursor}
288+ onSelect={this.onSelect}
289+ {...item.content}
290+ />
291+ </div>
292+ );
293+ }
294+
295+ if (item.type === "RESULT") {
296+ return (
297+ <div ref={this.applyRef.bind(this, index)}>
298+ <Result
299+ key={item.content.slug + "-" + index}
300+ index={index}
301+ setCursor={this.setCursor}
302+ selected={index === state.cursor}
303+ onSelect={this.onSelect}
304+ {...item.content}
305+ />
306+ </div>
307+ );
308+ }
309+ return null;
310+ })
311+ : null}
312+ </div>
313+ </div>
314+ </div>
315+ </HotKeys>
316+ );
317+ } else {
318+ return null;
319+ }
320+ }
321+}
322+
323+export default withRouter(Search);
components/Search.js+167−260
@@ -1,142 +1,54 @@
1−import { useCallback, useRef, useState, useEffect, createContext } from "react";
1+import { Component, createRef } from "react";
22import { glossary } from "../lib/glossary";
33import Link from "next/link";
4−import { useRouter } from "next/router";
4+import { withRouter } from "next/router";
55import { configure, HotKeys } from "react-hotkeys";
66import classnames from "classnames";
7+import Downshift from "downshift";
78
8−const SearchContext = createContext();
9−
10−const glossarySearch = (query) => {
11− return glossary.filter((entry) => {
12− return query.toLowerCase() === entry.name || entry.symbol === query;
13− });
14−};
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−}
9+class Search extends Component {
10+ constructor(props) {
11+ super(props);
12+ this.state = {
13+ results: [],
14+ };
15+ this.searchEndpoint = this.searchEndpoint.bind(this);
16+ this.onInputValueChange = this.onInputValueChange.bind(this);
17+ this.onChange = this.onChange.bind(this);
18+ this.glossarySearch = this.glossarySearch.bind(this);
19+ }
8920
90−export default function Search(props) {
91− const searchRef = useRef(null);
92− const [query, setQuery] = useState("");
93− const [active, setActive] = useState(false);
94− const [results, setResults] = useState([]);
95− const [cursor, setCursor] = useState(0);
96− const searchEndpoint = (query) => `/api/search?q=${query}`;
97− const router = useRouter();
21+ searchEndpoint(query) {
22+ return `/api/search?q=${query}`;
23+ }
9824
99− const keyMap = {
100− onNavUp: ["up"],
101− onNavDown: ["down"],
102− onSelect: ["enter"],
103− };
25+ glossarySearch(query) {
26+ console.log(query);
27+ return glossary.filter((entry) => {
28+ return query.toLowerCase() === entry.name || entry.symbol === query;
29+ });
30+ }
10431
105− const handlers = {
106− onNavUp: (event) => onNavUp(event),
107− onNavDown: (event) => onNavDown(event),
108− onSelect: (event) => onSelect(event, results[cursor]),
109− };
32+ onChange(item) {
33+ if (item.link) {
34+ window.location.href = item.link;
35+ }
11036
111− configure({
112− // ignoreTags: [],
113− ignoreTags: ["input", "select", "textarea"],
114− ignoreEventsCondition: function () {},
115− });
37+ if (item.slug) {
38+ this.props.router.push(item.slug);
39+ }
11640
117− {
118− /* useEffect(() => {
119− router.events.on("routeChangeComplete", handleRouteChange);
120− return () => {
121− router.events.off("routeChangeComplete", handleRouteChange);
122− };
123− }, [router.events]);
41+ this.setState({
42+ query: "",
43+ results: [],
44+ });
12445
125− const handleRouteChange = () => {
126− setQuery("");
127− setResults([]);
128− setActive(false);
129− props.closeSearch();
130− }; */
46+ this.props.closeSearch();
13147 }
13248
133− const onChange = useCallback((event) => {
134− const query = event.target.value;
135−
136− setQuery(query);
137−
49+ onInputValueChange(query) {
13850 if (query.length) {
139− fetch(searchEndpoint(query))
51+ fetch(this.searchEndpoint(query))
14052 .then((res) => res.json())
14153 .then((res) => {
14254 // Wrap results in an object which will tell React what component to use to render results.
@@ -145,154 +57,149 @@ export default function Search(props) {
14557 content: item,
14658 }));
14759
148− const glossaryResults = glossarySearch(query).map((item) => ({
60+ const glossaryResults = this.glossarySearch(query).map((item) => ({
14961 type: "GLOSSARY_RESULT",
15062 content: item,
15163 }));
15264
15365 const list = [...glossaryResults, ...results];
15466
155− setResults(list);
67+ this.setState({ results: list });
15668 });
15769 } else {
158− setResults([]);
159− }
160− }, []);
161−
162− const onFocus = useCallback(() => {
163− setActive(true);
164− // window.addEventListener("click", onClick);
165− }, []);
166−
167− const onClick = useCallback((event) => {
168− if (searchRef.current && !searchRef.current.contains(event.target)) {
169− setActive(false);
170− window.removeEventListener("click", onClick);
171− props.toggleSearch();
172− }
173− }, []);
174−
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− };
198−
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) => {
212− e.stopPropagation();
213− setQuery("");
214− };
215−
216− // Locks document scrolling when search is open
217− if (typeof document !== "undefined") {
218− if (props.showSearch) {
219− document.body.style.overflow = "hidden";
220− } else {
221− document.body.style.overflow = "visible";
70+ this.setState({ results: [] });
22271 }
22372 }
22473
225− if (props.showSearch) {
226− return (
227− <SearchContext.Provider value={42}>
228− <HotKeys handlers={handlers} keyMap={keyMap}>
229− <div
230− onClick={props.closeSearch}
231− className="fixed w-screen h-screen bg-washedWall z-50 flex flex-col items-center p-4"
232− >
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"
74+ render() {
75+ const { state, props } = this;
76+
77+ if (props.isOpen) {
78+ return (
79+ <Downshift
80+ onChange={(selection) => this.onChange(selection)}
81+ onInputValueChange={(event) => this.onInputValueChange(event)}
82+ itemToString={(item) =>
83+ item ? item.content.slug || item.content.link : ""
84+ }
85+ defaultHighlightedIndex={0}
86+ >
87+ {({
88+ getInputProps,
89+ getItemProps,
90+ getLabelProps,
91+ getMenuProps,
92+ isOpen,
93+ inputValue,
94+ highlightedIndex,
95+ selectedItem,
96+ getRootProps,
97+ }) => (
98+ <div className="fixed w-screen h-screen bg-washedWall z-50 flex flex-col items-center p-4">
99+ <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 overflow-hidden">
100+ <div
101+ style={{ display: "inline-block" }}
102+ {...getRootProps({}, { suppressRefError: true })}
249103 >
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}
104+ <input
105+ autoFocus
106+ className="text-lg md:text-xl lg:text-2xl font-medium text-green bg-transparent py-2 px-4 outline-none relative w-full"
107+ {...getInputProps()}
108+ />
109+ </div>
110+ <ul {...getMenuProps()} className="overflow-y-scroll">
111+ {isOpen
112+ ? state.results.map((item, index) => {
113+ const selected = highlightedIndex === index;
114+ if (item.type === "GLOSSARY_RESULT") {
115+ return (
116+ <li
117+ className={`cursor-pointer flex text-left w-full ${
118+ selected ? "bg-green" : ""
119+ }`}
120+ {...getItemProps({
121+ key: item.content.link + "-" + index,
122+ index,
123+ item: item.content,
124+ selected: highlightedIndex === index,
125+ })}
126+ >
127+ <div className="font-semibold p-3">
128+ <p
129+ className={`text-base ${
130+ selected ? "text-white" : "text-black"
131+ }`}
132+ >
133+ {item.content.symbol.length > 0 && (
134+ <code
135+ className={`mr-1 rounded px-1 py-0.5 ${
136+ selected ? "bg-washedWhite" : "bg-wall"
137+ }`}
138+ >
139+ {item.content.symbol}
140+ </code>
141+ )}
142+ {item.content.name}
143+ </p>
144+ <p
145+ className={`font-normal text-base mt-1 ${
146+ selected ? "text-white" : "text-black"
147+ }`}
148+ >
149+ {item.content.desc}
150+ </p>
151+ </div>
152+ </li>
153+ );
154+ }
155+ if (item.type === "RESULT") {
156+ return (
157+ <li
158+ className={`cursor-pointer flex text-left w-full ${
159+ selected ? "bg-green" : ""
160+ }`}
161+ {...getItemProps({
162+ key: item.content.link + "-" + index,
163+ index,
164+ item: item.content,
165+ selected,
166+ })}
167+ >
168+ <div className="p-3">
169+ <p
170+ className={`font-medium text-base ${
171+ selected ? "text-white" : "text-black"
172+ }`}
173+ >
174+ {item.content.parent !== "Content"
175+ ? `${item.content.parent} /`
176+ : ""}{" "}
177+ {item.content.title}
178+ </p>
179+ <p
180+ className={`text-base font-regular text-small ${
181+ selected ? "text-midWhite" : "text-gray"
182+ }`}
183+ >
184+ {item.content.content}
185+ </p>
186+ </div>
187+ </li>
188+ );
189+ }
190+ return null;
191+ })
192+ : null}
193+ </ul>
290194 </div>
291195 </div>
292− </div>
293− </HotKeys>
294− </SearchContext.Provider>
295− );
196+ )}
197+ </Downshift>
198+ );
199+ } else {
200+ return null;
201+ }
296202 }
297− return null;
298203}
204+
205+export default withRouter(Search);
package-lock.json+31
@@ -2205,6 +2205,11 @@
22052205 "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
22062206 "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
22072207 },
2208+ "compute-scroll-into-view": {
2209+ "version": "1.0.17",
2210+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
2211+ "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg=="
2212+ },
22082213 "concat-map": {
22092214 "version": "0.0.1",
22102215 "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2609,6 +2614,32 @@
26092614 }
26102615 }
26112616 },
2617+ "downshift": {
2618+ "version": "6.1.5",
2619+ "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.5.tgz",
2620+ "integrity": "sha512-9hpSCQLQ0KzlnI9ebpdbLuw5ogm7MXXI1D5N1zGWYSjHph+Xe02enXKyfDDE14akGr0jppAg0S1t/cjXp677ew==",
2621+ "requires": {
2622+ "@babel/runtime": "^7.14.8",
2623+ "compute-scroll-into-view": "^1.0.17",
2624+ "prop-types": "^15.7.2",
2625+ "react-is": "^17.0.2"
2626+ },
2627+ "dependencies": {
2628+ "@babel/runtime": {
2629+ "version": "7.14.8",
2630+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz",
2631+ "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==",
2632+ "requires": {
2633+ "regenerator-runtime": "^0.13.4"
2634+ }
2635+ },
2636+ "react-is": {
2637+ "version": "17.0.2",
2638+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
2639+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
2640+ }
2641+ }
2642+ },
26122643 "ecdsa-sig-formatter": {
26132644 "version": "1.0.11",
26142645 "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
package.json+1
@@ -18,6 +18,7 @@
1818 "autoprefixer": "^10.2.6",
1919 "classnames": "^2.3.1",
2020 "critters": "0.0.10",
21+ "downshift": "^6.1.5",
2122 "feed": "^4.2.2",
2223 "fuzzy-search": "^3.2.1",
2324 "gray-matter": "^4.0.3",
styles/globals.css+4−4
@@ -109,11 +109,11 @@ a:hover {
109109}
110110
111111.hero-card-height {
112− height: 750px;
112+ height: 562px;
113113}
114114
115115.hero-image-height {
116− height: 686px;
116+ height: 512px;
117117}
118118
119119.hero-image {
@@ -158,11 +158,11 @@ a:hover {
158158}
159159
160160.tray-menu-width {
161− width: calc(100vw - 64px - 16px - 16px);
161+ width: calc(100vw - 4rem - 1rem - 1rem);
162162}
163163
164164.mobile-search-button-width {
165− width: calc(100vw - 64px - 16px - 16px - 16px - 16px);
165+ width: calc(100vw - 4rem - 1rem - 1rem - 1rem - 1rem);
166166}
167167
168168.tray-overlay-open {