on master help sign in
add open and close search functions, clear search on route change
commitd24dc272c3af5e745c888a6f7065ba601139c380 authorGavin Atkinson <gvnatknsn@protonmail.com> dateFri 9 Jul 2021, 11:12 (5 years ago) parent24edfbd
17 files changed+203−125
components/ContentArea.js+1−1 components/Header.js+31−4 components/NewletterSignup.js+1−1 components/Search.js+90−69 package.json+1 pages/404.js+2−2 pages/_app.js+46−11 pages/blog.js+2−2 pages/blog/[slug].js+4−4 pages/docs/[[...slug]].js+2−8 pages/events.js+2−2 pages/events/[slug].js+5−5 pages/faq.js+7−7 pages/grants.js+2−2 pages/grants/[slug].js+4−4 pages/index.js+2−2 styles/globals.css+1−1
components/ContentArea.js+1−1
@@ -6,7 +6,7 @@ export default function ContentArea(props) {
66 <button
77 onClick={(e) => {
88 e.stopPropagation();
9− props.toggleSearch();
9+ props.search.toggleSearch();
1010 }}
1111 className="button-sm bg-wall text-gray"
1212 >
components/Header.js+31−4
@@ -67,6 +67,12 @@ function MenuTray(props) {
6767// This is the header for the index page
6868export default function Header(props) {
6969 const [isOpen, toggleTray] = useState(false);
70+
71+ const mobileSearchButtonClasses = classnames({
72+ visible: isOpen,
73+ invisible: !isOpen,
74+ });
75+
7076 return (
7177 <header className="layout-wide flex justify-between items-center pt-12">
7278 <Link href="/">
@@ -89,7 +95,7 @@ export default function Header(props) {
8995 <button
9096 onClick={(e) => {
9197 e.stopPropagation();
92− props.toggleSearch();
98+ props.search.toggleSearch(e);
9399 }}
94100 className="button-sm bg-wall text-gray"
95101 >
@@ -113,10 +119,31 @@ export default function Header(props) {
113119 <ActiveLink className="mr-5 type-h3" href="/events">
114120 Events
115121 </ActiveLink>
116− <button className="fixed bottom-4 left-4 right-4 bg-white rounded-xl items-center justify-between flex px-4 h-16 type-ui text-gray">
117− Search...
118− </button>
119122 </MenuTray>
123+ <button
124+ onClick={(e) => {
125+ e.stopPropagation();
126+ props.search.toggleSearch(e);
127+ }}
128+ className={`z-10 fixed bottom-24 right-6 w-12 h-12 bg-ultraDeepWall items-center justify-center rounded-full flex ${mobileSearchButtonClasses}`}
129+ >
130+ <svg
131+ width="16"
132+ height="16"
133+ viewBox="0 0 16 16"
134+ fill="none"
135+ xmlns="http://www.w3.org/2000/svg"
136+ >
137+ <circle cx="7" cy="7" r="5.5" stroke="white" stroke-width="3" />
138+ <path
139+ d="M11 11L14 14"
140+ stroke="white"
141+ stroke-width="3"
142+ stroke-linecap="round"
143+ />
144+ </svg>
145+ </button>
146+
120147 <button
121148 onClick={() => toggleTray(!isOpen)}
122149 className="z-10 fixed bottom-4 right-4 w-16 h-16 bg-ultraDeepWall flex items-center justify-center rounded-full"
components/NewletterSignup.js+1−1
@@ -7,7 +7,7 @@ export default function NewsletterSignup(props) {
77 name="mc-embedded-subscribe-form"
88 className="validate form max-w-screen-sm"
99 target="_blank"
10− novalidate
10+ noValidate
1111 >
1212 <div className="input-group" id="mc_embed_signup_scroll">
1313 <div className="mc-field-group w-full relative">
components/Search.js+90−69
@@ -1,6 +1,7 @@
1−import { useCallback, useRef, useState } from "react";
1+import { useCallback, useRef, useState, useEffect } from "react";
22import { glossary } from "../lib/glossary";
33import Link from "next/link";
4+import { useRouter } from "next/router";
45
56const glossarySearch = (query) => {
67 return glossary.filter((entry) => {
@@ -10,15 +11,31 @@ const glossarySearch = (query) => {
1011
1112export default function Search(props) {
1213 const searchRef = useRef(null);
13− const [query, SetQuery] = useState("");
14+ const [query, setQuery] = useState("");
1415 const [active, setActive] = useState(false);
1516 const [results, setResults] = useState([]);
1617 const searchEndpoint = (query) => `/api/search?q=${query}`;
1718
19+ const router = useRouter();
20+
21+ useEffect(() => {
22+ router.events.on("routeChangeComplete", handleRouteChange);
23+ return () => {
24+ router.events.off("routeChangeComplete", handleRouteChange);
25+ };
26+ }, [router.events]);
27+
28+ const handleRouteChange = () => {
29+ setQuery("");
30+ setResults([]);
31+ setActive(false);
32+ props.closeSearch();
33+ };
34+
1835 const onChange = useCallback((event) => {
1936 const query = event.target.value;
2037
21− SetQuery(query);
38+ setQuery(query);
2239 if (query.length) {
2340 fetch(searchEndpoint(query))
2441 .then((res) => res.json())
@@ -50,7 +67,7 @@ export default function Search(props) {
5067
5168 const clear = useCallback((e) => {
5269 e.stopPropagation();
53− SetQuery("");
70+ setQuery("");
5471 }, []);
5572
5673 // Locks document scrolling when search is open
@@ -64,9 +81,12 @@ export default function Search(props) {
6481
6582 if (props.showSearch) {
6683 return (
67− <div className="fixed w-screen h-screen bg-washedWall z-50 flex flex-col items-center">
84+ <div
85+ onClick={props.closeSearch}
86+ className="fixed w-screen h-screen bg-washedWall z-50 flex flex-col items-center"
87+ >
6888 <div
69− className="relative my-32 flex flex-col max-w-screen-lg w-10/12 md:w-10/12 lg:w-8/12 xl:w-6/12 rounded-xl bg-white min-h-0"
89+ className="relative flex flex-col max-w-screen-lg w-12/12 m-4 md:my-32 md:w-10/12 lg:w-8/12 xl:w-6/12 rounded-xl bg-white min-h-0"
7090 ref={searchRef}
7191 >
7292 <div className="">
@@ -77,12 +97,13 @@ export default function Search(props) {
7797 placeholder="Search..."
7898 type="text"
7999 value={query}
100+ onClick={(e) => e.stopPropagation()}
80101 autoFocus={true}
81102 />
82103
83104 <span
84105 onClick={props.toggleSearch}
85− className="absolute right-2 top-2 cursor-pointer"
106+ className="absolute right-2.5 top-2.5 cursor-pointer"
86107 >
87108 <p className="px-2 h-8 bg-wall flex items-center justify-center text-sm rounded">
88109 ESC
@@ -91,75 +112,75 @@ export default function Search(props) {
91112 </div>
92113
93114 <div
94− className={
95− "overflow-scroll min-h-0 pb-2" +
96− (query.length > 0 ? "" : "hidden")
97− }
115+ className={"overflow-y-scroll" + (query.length > 0 ? "" : "hidden")}
98116 >
99− {active &&
100− results.length === 0 &&
101− glossaryResults.length === 0 &&
102− query !== "" ? (
103− <div className="flex font-semibold text-sm p-3 py-8 w-full">
104− <p className="mr-2 text-gray">No Results found for</p>
105− <p className="font-semibold">"{query}"</p>
106− </div>
107− ) : null}
108− {active && glossaryResults.length > 0 ? (
109− <div>
110− <div className="flex font-semibold text-sm p-3 py-1 w-full bg-ultraDeepWall text-white">
111− <p className="font-semibold text-sm">Glossary</p>
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>
112125 </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>
113132
114− {glossaryResults.map(({ name, link, desc, symbol }) => {
115− return (
116− <Link href={link} as={link} key={link}>
117− <div className="cursor-pointer hover:bg-wall">
118− <div className="font-semibold p-3">
119− {symbol.length > 0 && (
120− <code className="mr-1 bg-wall rounded px-1 py-0.5">
121− {symbol}
122− </code>
123− )}
124− {name}
125− <p className="font-normal text-base mt-1">{desc}</p>
126− </div>
127− </div>
128− </Link>
129− );
130− })}
131− </div>
132− ) : null}
133− {active && results.length > 0 && (
134− <ul className="">
135− <div>
136− <p className="font-semibold text-sm p-3 py-1 bg-ultraDeepWall text-white">
137− {results.length} results for "{query}"
138− </p>
139− </div>
140− {results
141− .filter((e) => e.parent !== "Glossary")
142− .map(({ title, slug, content, parent }) => {
133+ {glossaryResults.map(({ name, link, desc, symbol }) => {
143134 return (
144− <li
145− key={slug}
146− className="cursor-pointer hover:bg-wall p-3"
147− >
148− <Link href={slug} as={slug}>
149− <div>
150− <p className="font-medium text-base">
151− {parent} / {title}
152− </p>
153− <p className="text-base font-regular text-small text-gray">
154− {content}
155− </p>
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>
156145 </div>
157− </Link>
158− </li>
146+ </div>
147+ </Link>
159148 );
160149 })}
161− </ul>
162− )}
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} / {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>
163184 </div>
164185 </div>
165186 </div>
package.json+1
@@ -5,6 +5,7 @@
55 "scripts": {
66 "dev": "npm run cache-posts && next dev",
77 "build": "npm run cache-posts && next build",
8+ "export": "npm run cache-posts && next build && next export",
89 "start": "next start",
910 "prepare": "husky install",
1011 "cache-posts": "node lib/cache.js",
pages/404.js+2−2
@@ -3,11 +3,11 @@ import SingleColumn from "../components/SingleColumn";
33import Header from "../components/Header";
44import Footer from "../components/Footer";
55
6−export default function NotFound() {
6+export default function NotFound(props) {
77 return (
88 <Container>
99 <SingleColumn>
10− <Header toggleSearch={() => toggleSearch(true)} />
10+ <Header search={props.search} />
1111 <section className="layout-wide pt-48">
1212 <h1>404</h1>
1313 <p className="mt-12">It looks like nothing is here.</p>
pages/_app.js+46−11
@@ -1,24 +1,50 @@
11import "tailwindcss/tailwind.css";
2−import { useState } from "react";
2+import { useState, useEffect } from "react";
33import { configure, GlobalHotKeys } from "react-hotkeys";
44import Search from "../components/Search";
55import "../styles/globals.css";
66import "../styles/prism.css";
77
88function MyApp({ Component, pageProps }) {
9− const [showSearch, toggleSearch] = useState(false);
9+ const [showSearch, setSearch] = useState(false);
1010
11− const invertToggle = (event) => {
12− if (event.preventDefault) {
11+ const closeSearch = (event) => {
12+ if (event?.preventDefault) {
1313 event.preventDefault();
1414 }
15− toggleSearch((state) => !state);
15+ setSearch(false);
1616 };
17− const keyMap = { search: ["command+k", "ctrl+k", "esc"] };
18− const handlers = { search: (event) => invertToggle(event) };
17+
18+ const openSearch = (event) => {
19+ if (event?.preventDefault) {
20+ event.preventDefault();
21+ }
22+ setSearch(true);
23+ };
24+
25+ const toggleSearch = (event) => {
26+ if (event?.preventDefault) {
27+ event.preventDefault();
28+ }
29+ setSearch((state) => !state);
30+ };
31+
32+ const keyMap = {
33+ // openSearch: [],
34+ closeSearch: ["esc"],
35+ toggleSearch: ["command+k", "ctrl+k"],
36+ };
37+
38+ const handlers = {
39+ closeSearch: (event) => closeSearch(event),
40+ openSearch: (event) => openSearch(event),
41+ toggleSearch: (event) => toggleSearch(event),
42+ };
43+
1944 configure({
20− ignoreTags: [],
21− stopEventPropagationAfterHandling: true,
45+ // ignoreTags: [],
46+ ignoreTags: ["input", "select", "textarea"],
47+ ignoreEventsCondition: function () {},
2248 });
2349
2450 return (
@@ -26,9 +52,18 @@ function MyApp({ Component, pageProps }) {
2652 <GlobalHotKeys keyMap={keyMap} handlers={handlers} />
2753 <Search
2854 showSearch={showSearch}
29− toggleSearch={() => toggleSearch(false)}
55+ toggleSearch={toggleSearch}
56+ closeSearch={closeSearch}
57+ openSearch={openSearch}
58+ />
59+ <Component
60+ {...pageProps}
61+ search={{
62+ toggleSearch: toggleSearch,
63+ closeSearch: closeSearch,
64+ openSearch: openSearch,
65+ }}
3066 />
31− <Component {...pageProps} toggleSearch={invertToggle} />
3267 </>
3368 );
3469}
pages/blog.js+2−2
@@ -7,11 +7,11 @@ import SingleColumn from "../components/SingleColumn";
77import BackgroundImage from "../components/BackgroundImage";
88import { getAllPosts, formatDate } from "../lib/lib";
99
10−export default function Blog({ posts, toggleSearch }) {
10+export default function Blog({ posts, search }) {
1111 return (
1212 <Container>
1313 <SingleColumn>
14− <Header toggleSearch={() => toggleSearch(true)} />
14+ <Header search={search} />
1515 <section className="layout-narrow">
1616 <div className="measure">
1717 <h2 className="pb-16">Urbit Blog</h2>
pages/blog/[slug].js+4−4
@@ -26,7 +26,7 @@ export default function Post({
2626 nextPost,
2727 previousPost,
2828 markdown,
29− toggleSearch,
29+ search,
3030}) {
3131 const router = useRouter();
3232 if (!router.isFallback && !post?.slug) {
@@ -35,8 +35,8 @@ export default function Post({
3535 return (
3636 <Container>
3737 <SingleColumn>
38− <Header toggleSearch={() => toggleSearch(true)} />
39− <section className="flex flex-col layout-wide">
38+ <Header search={search} />
39+ <section className="flex flex-col layout-narrow">
4040 <h1>{post.title}</h1>
4141 {post.extra.author ? (
4242 <div className="type-ui text-gray mt-4">{post.extra.author}</div>
@@ -48,7 +48,7 @@ export default function Post({
4848 {formatDate(new Date(post.date))}
4949 </div>
5050 </section>
51− <div className={"layout-wide " + markdownStyles["markdown"]}>
51+ <div className={"layout-narrow " + markdownStyles["markdown"]}>
5252 <article
5353 dangerouslySetInnerHTML={{ __html: decode(markdown) }}
5454 ></article>
pages/docs/[[...slug]].js+2−8
@@ -96,13 +96,7 @@ const pageTree = (thisLink, tree, level = 0) => {
9696 );
9797};
9898
99−export default function DocsLayout({
100− posts,
101− data,
102− params,
103− toggleSearch,
104− markdown,
105−}) {
99+export default function DocsLayout({ posts, data, params, search, markdown }) {
106100 return (
107101 <>
108102 <Head>
@@ -113,7 +107,7 @@ export default function DocsLayout({
113107 <ContentArea
114108 breadcrumbs={breadcrumbs(posts, params.slug?.slice(0, -1) || "")}
115109 title={data.title}
116− toggleSearch={() => toggleSearch(true)}
110+ search={search}
117111 >
118112 <div className={markdownStyles["markdown"]}>
119113 <article
pages/events.js+2−2
@@ -69,11 +69,11 @@ function EventCard(props) {
6969 );
7070}
7171
72−export default function Events({ pastEvents, currentEvents, toggleSearch }) {
72+export default function Events({ pastEvents, currentEvents, search }) {
7373 return (
7474 <Container>
7575 <SingleColumn>
76− <Header toggleSearch={() => toggleSearch(true)} />
76+ <Header search={search} />
7777 <section className="layout-narrow">
7878 <div className="measure">
7979 <h2 className="pb-16">Urbit Events</h2>
pages/events/[slug].js+5−5
@@ -26,7 +26,7 @@ export default function Event({
2626 nextPost,
2727 previousPost,
2828 markdown,
29− toggleSearch,
29+ search,
3030}) {
3131 const router = useRouter();
3232 if (!router.isFallback && !post?.slug) {
@@ -35,8 +35,8 @@ export default function Event({
3535 return (
3636 <Container>
3737 <SingleColumn>
38− <Header toggleSearch={() => toggleSearch(true)} />
39− <section className="flex flex-col layout-wide pt-24">
38+ <Header search={search} />
39+ <section className="flex flex-col layout-narrow pt-24">
4040 <h1>{post.title}</h1>
4141 {post.extra.author ? (
4242 <div className="type-ui text-gray mt-4">{post.extra.author}</div>
@@ -48,7 +48,7 @@ export default function Event({
4848 {formatDate(new Date(post.date))}
4949 </div>
5050 </section>
51− <section className="flex layout-wide pt-24">
51+ <section className="flex layout-narrow pt-24">
5252 {post.extra.youtube ? (
5353 <iframe
5454 className="rounded-xl"
@@ -61,7 +61,7 @@ export default function Event({
6161 ></iframe>
6262 ) : null}
6363 </section>
64− <div className={"layout-wide " + markdownStyles["markdown"]}>
64+ <div className={"layout-narrow " + markdownStyles["markdown"]}>
6565 <article
6666 className="flex flex-col items-center pt-12 w-full"
6767 dangerouslySetInnerHTML={{ __html: decode(markdown) }}
pages/faq.js+7−7
@@ -8,11 +8,11 @@ import SingleColumn from "../components/SingleColumn";
88import BackgroundImage from "../components/BackgroundImage";
99import { getAllPosts, formatDate } from "../lib/lib";
1010
11−export default function Blog({ posts, toggleSearch }) {
11+export default function Blog({ posts, search }) {
1212 return (
1313 <Container>
1414 <SingleColumn>
15− <Header toggleSearch={() => toggleSearch(true)} />
15+ <Header search={search} />
1616 <section className="layout-wide">
1717 <div className="measure">
1818 <h2 className="pb-16">Urbit Blog</h2>
@@ -75,12 +75,12 @@ export default function Blog({ posts, toggleSearch }) {
7575}
7676
7777export async function getStaticProps() {
78− const posts = getPostBySlug(
79− ["title", "slug", "date", "description", "extra"],
80− "blog"
81− );
78+ // const posts = getPostBySlug(
79+ // ["title", "slug", "date", "description", "extra"],
80+ // "blog"
81+ // );
8282
8383 return {
84− props: { posts },
84+ props: { posts: [] },
8585 };
8686}
pages/grants.js+2−2
@@ -64,7 +64,7 @@ export default function Grants({
6464 categories,
6565 types,
6666 featuredGrants,
67− toggleSearch,
67+ search,
6868}) {
6969 const [activeTags, setTags] = useState([]);
7070 const [activeTypes, setTypes] = useState(types);
@@ -92,7 +92,7 @@ export default function Grants({
9292 return (
9393 <Container>
9494 <SingleColumn>
95− <Header toggleSearch={() => toggleSearch(true)} />
95+ <Header search={search} />
9696 {
9797 // Heading and introduction
9898 }
pages/grants/[slug].js+4−4
@@ -26,7 +26,7 @@ export default function Grant({
2626 nextPost,
2727 markdown,
2828 previousPost,
29− toggleSearch,
29+ search,
3030}) {
3131 const router = useRouter();
3232 if (!router.isFallback && !post?.slug) {
@@ -36,8 +36,8 @@ export default function Grant({
3636 return (
3737 <Container>
3838 <SingleColumn>
39− <Header toggleSearch={() => toggleSearch(true)} />
40− <section className="flex flex-col layout-wide">
39+ <Header search={search} />
40+ <section className="flex flex-col layout-narrow">
4141 <h1>{post.title}</h1>
4242 {post.extra.author ? (
4343 <div className="type-ui text-gray mt-4">{post.extra.author}</div>
@@ -49,7 +49,7 @@ export default function Grant({
4949 {formatDate(new Date(post.date))}
5050 </div>
5151 </section>
52− <div className={"layout-wide " + markdownStyles["markdown"]}>
52+ <div className={"layout-narrow " + markdownStyles["markdown"]}>
5353 <article
5454 dangerouslySetInnerHTML={{ __html: decode(markdown) }}
5555 ></article>
pages/index.js+2−2
@@ -12,7 +12,7 @@ import PostPreview from "../components/PostPreview";
1212import { getAllPosts, formatDate, getOpenGrantsCount } from "../lib/lib";
1313import { contact } from "../lib/constants";
1414
15−export default function Home({ posts, events, openGrantsCount, toggleSearch }) {
15+export default function Home({ posts, events, openGrantsCount, search }) {
1616 const [tab, setTab] = useState(0);
1717 const [heroButton, setHeroButton] = useState(<div />);
1818
@@ -48,7 +48,7 @@ export default function Home({ posts, events, openGrantsCount, toggleSearch }) {
4848 return (
4949 <Container>
5050 <SingleColumn>
51− <Header toggleSearch={() => toggleSearch(true)} />
51+ <Header search={search} />
5252
5353 {
5454 // Hero Statement
styles/globals.css+1−1
@@ -145,7 +145,7 @@ a:hover {
145145}
146146
147147:root {
148− --tray-time: 50ms;
148+ --tray-time: 0ms;
149149}
150150
151151.tray-menu-open {