Skip to content

Commit 0421325

Browse files
Add DocSearch by Algolia (#109)
1 parent 379cc11 commit 0421325

File tree

8 files changed

+599
-9
lines changed

8 files changed

+599
-9
lines changed

app/modules/docsearch.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { DocSearchProps } from "@docsearch/react";
2+
import "@docsearch/css/dist/style.css";
3+
import "~/styles/docsearch.css";
4+
import { useHydrated } from "~/ui/utils";
5+
import { Suspense, lazy } from "react";
6+
7+
const OriginalDocSearch = lazy(() =>
8+
import("@docsearch/react").then((module) => ({
9+
default: module.DocSearch,
10+
}))
11+
);
12+
13+
let docSearchProps = {
14+
appId: "RB6LOUCOL0",
15+
indexName: "reactrouter",
16+
apiKey: "b50c5d7d9f4610c9785fa945fdc97476",
17+
} satisfies DocSearchProps;
18+
19+
// TODO: Refactor a bit when we add Vite with css imports per component
20+
// This will allow us to have two versions of the component, one that has
21+
// the button with display: none, and the other with button styles
22+
export function DocSearch() {
23+
let hydrated = useHydrated();
24+
25+
if (!hydrated) {
26+
// The Algolia doc search container is hard-coded at 40px. It doesn't
27+
// render anything on the server, so we get a mis-match after hydration.
28+
// This placeholder prevents layout shift when the search appears.
29+
return <div className="h-10" />;
30+
}
31+
32+
return (
33+
<Suspense fallback={<div className="h-10" />}>
34+
<div className="animate-[fadeIn_100ms_ease-in_1]">
35+
<OriginalDocSearch {...docSearchProps} />
36+
</div>
37+
</Suspense>
38+
);
39+
}

app/routes/$lang.$ref.$.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const meta: MetaFunction<
5353
root: typeof rootLoader;
5454
"routes/$lang.$ref": typeof langRefLoader;
5555
}
56-
> = ({ data, matches }) => {
56+
> = ({ data, matches, params }) => {
5757
if (!data) return [{ title: "Not Found" }];
5858
let parentMatch = matches.find((m) => m.id === "routes/$lang.$ref");
5959
let parentData = parentMatch ? parentMatch.data : undefined;
@@ -100,6 +100,8 @@ export const meta: MetaFunction<
100100

101101
return [
102102
...meta,
103+
{ name: "docsearch:language", content: params.lang || "en" },
104+
{ name: "docsearch:version", content: params.ref || "v6" },
103105
{ name: "robots", content: robots },
104106
{ name: "googlebot", content: robots },
105107
];

app/routes/$lang.$ref.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { getLatestVersion } from "~/modules/gh-docs/.server/tags";
3131
import { useColorScheme } from "~/modules/color-scheme/components";
3232

3333
import docsStylesheet from "~/styles/docs.css?url";
34+
import { DocSearch } from "~/modules/docsearch";
3435

3536
export let links: LinksFunction = () => {
3637
return [{ rel: "stylesheet", href: docsStylesheet }];
@@ -180,6 +181,7 @@ function Header() {
180181
<div className="flex items-center gap-2">
181182
<VersionSelect />
182183
<ColorSchemeToggle />
184+
<DocSearchSection className="lg:hidden" />
183185
</div>
184186
</div>
185187
<VersionWarning />
@@ -209,6 +211,29 @@ function Header() {
209211
);
210212
}
211213

214+
function DocSearchSection({ className }: { className?: string }) {
215+
return (
216+
<div
217+
className={classNames(
218+
"relative -mx-3 lg:sticky lg:top-0 lg:z-10",
219+
className
220+
)}
221+
>
222+
<div className="absolute -top-24 hidden h-24 w-full bg-white dark:bg-gray-900 lg:block" />
223+
<div
224+
className={classNames(
225+
"relative lg:bg-white lg:dark:bg-gray-900",
226+
// This hides some of the underlying text when the user scrolls to the
227+
// bottom which results in the overscroll bounce
228+
"before:absolute before:bottom-0 before:left-0 before:-z-10 before:hidden before:h-[200%] before:w-full before:bg-inherit lg:before:block"
229+
)}
230+
>
231+
<DocSearch />
232+
</div>
233+
</div>
234+
);
235+
}
236+
212237
function ColorSchemeToggle() {
213238
let location = useLocation();
214239

@@ -329,6 +354,7 @@ function NavMenuDesktop() {
329354
"h-[calc(100vh-var(--header-height))]"
330355
)}
331356
>
357+
<DocSearchSection />
332358
<div className="[&_*:focus]:scroll-mt-[6rem]">
333359
<Menu />
334360
</div>

app/styles/docsearch.css

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* this was copy/paste/modified from @docsearch/css/dist/_variables.css */
2+
:root {
3+
&:where(.dark) {
4+
--docsearch-text-color: #f5f6f7;
5+
--docsearch-container-background: rgba(9, 10, 17, 0.8);
6+
--docsearch-modal-background: #15172a;
7+
--docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309;
8+
--docsearch-searchbox-background: #090a11;
9+
--docsearch-searchbox-focus-background: #000;
10+
--docsearch-hit-color: #bec3c9;
11+
--docsearch-hit-shadow: none;
12+
--docsearch-hit-background: #090a11;
13+
--docsearch-key-gradient: linear-gradient(-26.5deg, #565872, #31355b);
14+
--docsearch-key-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d,
15+
0 2px 2px 0 rgba(3, 4, 9, 0.3);
16+
--docsearch-footer-background: #1e2136;
17+
--docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5),
18+
0 -4px 8px 0 rgba(0, 0, 0, 0.2);
19+
--docsearch-logo-color: #fff;
20+
--docsearch-muted-color: #7f8497;
21+
}
22+
}
23+
24+
.DocSearch-Button {
25+
@apply ml-0 h-10 bg-gray-100 px-3 hover:bg-gray-200 hover:shadow-none focus:bg-gray-200 focus-visible:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus-visible:bg-gray-700;
26+
27+
@media screen and (min-width: theme(screens.lg)) {
28+
width: 100%;
29+
}
30+
@media not screen and (min-width: theme(screens.lg)) {
31+
@apply w-10 place-content-center px-0;
32+
.DocSearch-Button-Keys,
33+
.DocSearch-Button-Placeholder {
34+
display: none;
35+
}
36+
}
37+
@media print {
38+
display: none;
39+
}
40+
}
41+
42+
.DocSearch-Button-Placeholder {
43+
@apply text-sm font-normal text-black dark:text-gray-200;
44+
}
45+
46+
.DocSearch-Button-Keys {
47+
@apply justify-end gap-1;
48+
}
49+
50+
.DocSearch-Button-Key {
51+
all: unset;
52+
53+
@apply grid h-5 w-3.5 place-items-center rounded bg-white px-1 text-xs text-gray-600 dark:bg-gray-600 dark:text-gray-200;
54+
}
55+
56+
.DocSearch-Container {
57+
cursor: auto;
58+
}

app/ui/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { useEffect, useState } from "react";
2+
3+
let hydrating = true;
4+
5+
export function useHydrated() {
6+
let [hydrated, setHydrated] = useState(() => !hydrating);
7+
useEffect(() => {
8+
hydrating = false;
9+
setHydrated(true);
10+
}, []);
11+
return hydrated;
12+
}

0 commit comments

Comments
 (0)