Repository navigation
Expand file tree
/
Copy pathSearch.tsx
More file actions
146 lines (130 loc) · 4.2 KB
/
Copy pathSearch.tsx
File metadata and controls
146 lines (130 loc) · 4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import Fuse from "fuse.js";
import { useRouter } from "next/router";
import React, { useCallback, useEffect, useState } from "react";
import { ArrowRight, Search as SearchIcon } from "react-feather";
import tinykeys from "tinykeys";
import tw from "twin.macro";
import { useStore } from "../store";
import { IPage } from "../types";
import { Link } from "./Link";
export const Search: React.FC = () => {
const { setIsSearchOpen } = useStore();
return (
<>
<button
=> setIsSearchOpen(true)}
css={[
tw`flex items-center justify-between space-x-4 w-full`,
tw`rounded border border-gray-200 cursor-pointer`,
tw`px-2 py-2 md:py-1 text-gray-300 text-left`,
tw`focus:outline-none md:hover:border-pink-300`,
]}
>
<div tw="flex items-center space-x-2">
<SearchIcon tw="w-4 h-4" />
<span tw="text-sm">Search</span>
</div>
<div tw="text-gray-300 text-sm hidden md:block">⌘K</div>
</button>
</>
);
};
const MAX_NUM_RESULTS = 5;
export const SearchModal: React.FC<{
fuse: Fuse<IPage>;
closeModal: () => void;
}> = ({ fuse, closeModal }) => {
const [query, setQuery] = useState("");
const [results, setResults] = useState<IPage[]>([]);
const [selected, setSelected] = useState(0);
const router = useRouter();
const => {
setSelected(Math.max(0, selected - 1));
}, [selected, results, setSelected]);
const => {
setSelected(Math.min(results.length - 1, selected + 1));
}, [selected, results, setSelected]);
const handleEnter = useCallback(() => {
const item = results[selected];
if (item != null) {
closeModal();
router.push(item.slug);
}
}, [selected, results]);
useEffect(() => {
const preventDefault = (fn: () => void) => (e: KeyboardEvent) => {
e.preventDefault();
fn();
};
const unsubscribe = tinykeys(window, {
Enter: () => handleEnter(),
ArrowUp: () => onPressUp(),
"Control+k": () => onPressUp(),
"Control+p": preventDefault(() => onPressUp()),
ArrowDown: () => onPressDown(),
"Control+j": preventDefault(() => onPressDown()),
"Control+n": preventDefault(() => onPressDown()),
});
return () => unsubscribe();
}, [handleEnter, onPressUp, onPressDown]);
useEffect(() => {
const results = fuse.search(query);
const pages = results.map(r => r.item).slice(0, MAX_NUM_RESULTS);
setResults(pages);
setSelected(Math.max(0, Math.min(pages.length - 1, selected)));
}, [query]);
return (
<div
css={[tw`px-2 sm:px-4 md:px-0 mt-12 md:mt-28`]}
=> {
closeModal();
}}
>
<div
css={[
tw`bg-background rounded w-full md:w-1/2 mx-auto overflow-hidden`,
`max-width: 550px`,
]}
=> e.stopPropagation()}
>
<input
placeholder="Search"
value={query}
=> setQuery(e.target.value)}
autoFocus
tw="px-4 py-4 w-full bg-transparent focus:outline-none"
/>
<div className="results">
<ul>
{results.map((item, index) => (
<li key={item.slug} => setSelected(index)}>
<Link
href={item.slug}
=> {
closeModal();
}}
css={[
tw`flex items-center justify-between px-3 h-16 relative`,
index === selected
? tw`text-pink-900 bg-pink-100`
: tw`text-gray-500`,
]}
>
<div tw="flex flex-col">
{item.category != null && (
<span tw="w-auto mb-1 text-xs font-medium opacity-50 rounded-sm">
{item.category}
</span>
)}
<span>{item.title}</span>
</div>
<ArrowRight tw="w-4 h-4" />
</Link>
</li>
))}
</ul>
</div>
</div>
</div>
);
};