Skip to content

Commit 6fe572f

Browse files
authored
chore(docs): use algolia instantsearch (#10764)
1 parent de4a968 commit 6fe572f

File tree

8 files changed

+532
-5
lines changed

8 files changed

+532
-5
lines changed

docs/components/Docsearch/hit.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Highlight } from "react-instantsearch"
2+
import Link from "next/link"
3+
4+
export default function Hit({ hit }) {
5+
const hierarchy = Object.values(hit.hierarchy)?.filter(Boolean)
6+
const path = new URL(hit.url_without_anchor).pathname
7+
8+
return (
9+
<Link
10+
key={hit.objectID}
11+
href={hit.url}
12+
className="flex flex-col gap-2 p-4 px-2 rounded-md text-neutral-800 group dark:text-neutral-100 hover:dark:bg-neutral-900 hover:bg-neutral-200"
13+
>
14+
<h3 className="flex items-center text-sm font-semibold after:content-[''] whitespace-nowrap after:absolute after:bg-gradient-to-r after:h-6 after:right-0 after:w-12 after:from-transparent after:to-neutral-100 after:dark:to-neutral-800 relative overflow-hidden group-hover:dark:after:to-neutral-900 group-hover:after:to-neutral-200">
15+
{hierarchy.map((label: string, idx: number) => (
16+
<>
17+
<span className="">{label}</span>
18+
{idx < hierarchy.length - 1 ? <CaretRight /> : null}
19+
</>
20+
))}
21+
</h3>
22+
<Highlight
23+
classNames={{
24+
highlighted: "dark:text-white dark:bg-violet-800 bg-violet-300",
25+
}}
26+
attribute="content"
27+
className="text-sm line-clamp-4"
28+
hit={hit}
29+
/>
30+
<p className="text-xs text-neutral-400 dark:text-neutral-600">{path}</p>
31+
</Link>
32+
)
33+
}
34+
35+
function CaretRight() {
36+
return (
37+
<svg
38+
className="size-3 min-w-3"
39+
xmlns="http://www.w3.org/2000/svg"
40+
viewBox="0 0 256 256"
41+
>
42+
<rect width="256" height="256" fill="none" />
43+
<polyline
44+
points="96 48 176 128 96 208"
45+
fill="none"
46+
stroke="currentColor"
47+
strokeLinecap="round"
48+
strokeLinejoin="round"
49+
strokeWidth="16"
50+
/>
51+
</svg>
52+
)
53+
}

docs/components/Docsearch/index.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import dynamic from "next/dynamic"
2+
3+
const DocSearch = dynamic(() => import("./search").then((mod) => mod.default), {
4+
ssr: false,
5+
loading: () => (
6+
<div className="relative max-md:ml-6 h-8 appearance-none rounded-lg px-3 py-1.5 transition-colors text-base leading-tight md:text-sm bg-black/[.05] dark:bg-gray-50/10 focus:!bg-transparent pr-2 w-48 text-gray-500 dark:text-gray-400">
7+
Search...
8+
<kbd className="flex absolute top-0 right-0 gap-1 items-center px-1.5 my-1.5 h-5 font-mono font-medium text-gray-500 bg-white rounded border transition-opacity pointer-events-none select-none ltr:right-1.5 rtl:left-1.5 text-[10px] contrast-more:border-current contrast-more:text-current contrast-more:dark:border-current max-sm:hidden dark:border-gray-100/20 dark:bg-black/50">
9+
CTRL K
10+
</kbd>
11+
</div>
12+
),
13+
})
14+
15+
export default DocSearch

docs/components/Docsearch/search.tsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { useEffect } from "react"
2+
import algoliasearch from "algoliasearch/lite"
3+
import { Hits, useInstantSearch } from "react-instantsearch"
4+
import { InstantSearchNext } from "react-instantsearch-nextjs"
5+
import { CustomSearchBox } from "./searchbox"
6+
import Hit from "./hit"
7+
8+
const algoliaClient = algoliasearch(
9+
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID,
10+
process.env.NEXT_PUBLIC_ALGOLIA_KEY
11+
)
12+
13+
const searchClient = {
14+
...algoliaClient,
15+
search(requests: any) {
16+
if (requests.every(({ params }) => !params.query)) {
17+
return Promise.resolve({
18+
results: requests.map(() => ({
19+
hits: [],
20+
nbHits: 0,
21+
nbPages: 0,
22+
page: 0,
23+
processingTimeMS: 0,
24+
hitsPerPage: 0,
25+
exhaustiveNbHits: false,
26+
query: "",
27+
params: "",
28+
})),
29+
})
30+
}
31+
return algoliaClient.search(requests)
32+
},
33+
}
34+
35+
export default function() {
36+
const ctrlKHandler = (e: KeyboardEvent) => {
37+
if (e.repeat || e.target instanceof HTMLInputElement) return
38+
if (e.ctrlKey && e.key === "k") {
39+
e.preventDefault()
40+
document.querySelector<HTMLInputElement>('input[type="search"]')?.focus()
41+
}
42+
}
43+
44+
useEffect(() => {
45+
window.addEventListener("keydown", ctrlKHandler)
46+
47+
return window.addEventListener("keydown", ctrlKHandler)
48+
}, [])
49+
50+
return (
51+
<div className="relative">
52+
<InstantSearchNext
53+
indexName="next-auth"
54+
// @ts-expect-error
55+
searchClient={searchClient}
56+
>
57+
<CustomSearchBox />
58+
<NoResultsBoundary>
59+
<Hits
60+
hitComponent={Hit}
61+
className="fixed top-28 left-2 md:left-auto md:absolute md:right-0 w-[calc(100vw_-_16px)] md:top-12 p-2 md:w-96 rounded-md shadow-lg bg-neutral-100 dark:bg-neutral-800 [&>ol]:flex [&>ol]:flex-col max-h-[calc(100dvh_-_120px)] overflow-y-auto [&>ol]:divide-y [&>ol]:divide-neutral-400/30 [&>ol]:dark:divide-neutral-900/50"
62+
/>
63+
</NoResultsBoundary>
64+
</InstantSearchNext>
65+
</div>
66+
)
67+
}
68+
69+
function NoResultsBoundary({ children }) {
70+
const { indexUiState, results } = useInstantSearch()
71+
72+
if (
73+
indexUiState.query !== undefined &&
74+
!results.__isArtificial &&
75+
results.nbHits === 0
76+
) {
77+
return (
78+
<div className="fixed text-center top-28 left-2 md:left-auto md:absolute md:right-0 w-[calc(100vw_-_16px)] md:top-12 p-2 md:w-96 rounded-md shadow-md bg-neutral-100 dark:bg-neutral-800 [&>ol]:flex [&>ol]:flex-col max-h-[calc(100dvh_-_120px)] overflow-y-auto [&>ol]:divide-y [&>ol]:divide-neutral-400/30 [&>ol]:dark:divide-neutral-900/50">
79+
No Results
80+
</div>
81+
)
82+
}
83+
84+
if (indexUiState.query === undefined) {
85+
return null
86+
}
87+
88+
return children
89+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React, { useState, useRef } from "react"
2+
import {
3+
useInstantSearch,
4+
useSearchBox,
5+
UseSearchBoxProps,
6+
} from "react-instantsearch"
7+
8+
export function CustomSearchBox(props: UseSearchBoxProps) {
9+
const { query, refine } = useSearchBox(props)
10+
const { status } = useInstantSearch()
11+
const [inputValue, setInputValue] = useState(query)
12+
const inputRef = useRef<HTMLInputElement>(null)
13+
14+
const isSearchStalled = status === "stalled"
15+
16+
function setQuery(newQuery: string) {
17+
setInputValue(newQuery)
18+
refine(newQuery)
19+
}
20+
21+
return (
22+
<div>
23+
<form
24+
action=""
25+
role="search"
26+
noValidate
27+
onSubmit={(event) => {
28+
event.preventDefault()
29+
event.stopPropagation()
30+
31+
if (inputRef.current) {
32+
inputRef.current.blur()
33+
}
34+
}}
35+
onReset={(event) => {
36+
event.preventDefault()
37+
event.stopPropagation()
38+
39+
setQuery("")
40+
41+
if (inputRef.current) {
42+
inputRef.current.focus()
43+
}
44+
}}
45+
className="relative max-md:ml-6"
46+
>
47+
<input
48+
ref={inputRef}
49+
autoComplete="off"
50+
autoCorrect="off"
51+
autoCapitalize="off"
52+
placeholder="Search..."
53+
spellCheck={false}
54+
maxLength={512}
55+
type="search"
56+
value={inputValue}
57+
onChange={(event) => {
58+
setQuery(event.currentTarget.value)
59+
}}
60+
className="max-w-48 w-full appearance-none rounded-lg px-3 py-1.5 transition-colors text-base leading-tight md:text-sm bg-black/[.05] dark:bg-gray-50/10 focus:!bg-transparent placeholder:text-gray-500 dark:placeholder:text-gray-400 pr-2"
61+
/>
62+
{inputValue.length ? (
63+
<button
64+
type="reset"
65+
hidden={inputValue.length === 0 || isSearchStalled}
66+
className="flex absolute top-0 right-2 gap-1 items-center px-1.5 my-1.5 h-5 font-mono font-medium text-gray-500 rounded transition-opacity select-none ltr:right-1.5 rtl:left-1.5 text-[10px] contrast-more:text-current"
67+
>
68+
<svg
69+
className="text-gray-800 dark:text-gray-200 size-4"
70+
xmlns="http://www.w3.org/2000/svg"
71+
viewBox="0 0 256 256"
72+
>
73+
<rect width="256" height="256" fill="none" />
74+
<line
75+
x1="200"
76+
y1="56"
77+
x2="56"
78+
y2="200"
79+
stroke="currentColor"
80+
strokeLinecap="round"
81+
strokeLinejoin="round"
82+
strokeWidth="16"
83+
/>
84+
<line
85+
x1="200"
86+
y1="200"
87+
x2="56"
88+
y2="56"
89+
stroke="currentColor"
90+
strokeLinecap="round"
91+
strokeLinejoin="round"
92+
strokeWidth="16"
93+
/>
94+
</svg>
95+
</button>
96+
) : (
97+
<kbd className="flex absolute top-0 right-0 gap-1 items-center px-1.5 my-1.5 h-5 font-mono font-medium text-gray-500 bg-white rounded border transition-opacity pointer-events-none select-none ltr:right-1.5 rtl:left-1.5 text-[10px] contrast-more:border-current contrast-more:text-current contrast-more:dark:border-current max-sm:hidden dark:border-gray-100/20 dark:bg-black/50">
98+
CTRL K
99+
</kbd>
100+
)}
101+
<span hidden={!isSearchStalled}>Searching…</span>
102+
</form>
103+
</div>
104+
)
105+
}

docs/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,16 @@
3131
"@radix-ui/react-accordion": "^1.1.2",
3232
"@radix-ui/react-tabs": "^1.0.4",
3333
"@vercel/analytics": "^1.2.2",
34+
"algoliasearch": "^4.23.3",
3435
"classnames": "^2.5.1",
3536
"next": "14.2.1",
3637
"next-sitemap": "^4.2.3",
3738
"nextra": "3.0.0-alpha.22",
3839
"nextra-theme-docs": "3.0.0-alpha.22",
3940
"react": "^18.2.0",
4041
"react-dom": "^18.2.0",
42+
"react-instantsearch": "^7.7.2",
43+
"react-instantsearch-nextjs": "^0.2.2",
4144
"react-marquee-slider": "^1.1.5"
4245
},
4346
"devDependencies": {

docs/pages/getting-started/session-management/protecting.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ export const GET: RequestHandler = async (event) => {
205205
</Code.Svelte>
206206
<Code.Express>
207207

208-
API Routes are protected in the same way as any other route in Express, see [the examples above](/getting-started/session-management/protecting&framework=express#pages).
208+
API Routes are protected in the same way as any other route in Express, see [the examples above](/getting-started/session-management/protecting?framework=express#pages).
209209

210210
</Code.Express>
211211
</Code>

docs/theme.config.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DocsThemeConfig, ThemeSwitch } from "nextra-theme-docs"
22
import { Link } from "@/components/Link"
33
import { ChildrenProps } from "@/utils/types"
44
import Footer from "@/components/Footer"
5+
import Docsearch from "@/components/Docsearch"
56
import dynamic from "next/dynamic"
67
import { usePathname } from "next/navigation"
78
import { useConfig } from "nextra-theme-docs"
@@ -42,6 +43,7 @@ const config: DocsThemeConfig = {
4243
},
4344
project: {
4445
link: "https://github.com/nextauthjs/next-auth",
46+
icon: null,
4547
},
4648
darkMode: false,
4749
color: {
@@ -54,9 +56,12 @@ const config: DocsThemeConfig = {
5456
dark: 50,
5557
},
5658
},
59+
search: {
60+
component: <Docsearch />,
61+
},
5762
navbar: {
5863
extraContent: (
59-
<div className="flex gap-4 items-center !h-12">
64+
<div className="flex md:gap-4 items-center !h-12 lg:-translate-x-4">
6065
<div className="hidden lg:block">
6166
<InkeepChatButton />
6267
</div>
@@ -78,7 +83,7 @@ const config: DocsThemeConfig = {
7883
<path d="M12 3C7.0275 3 3 7.12937 3 12.2276C3 16.3109 5.57625 19.7597 9.15374 20.9824C9.60374 21.0631 9.77249 20.7863 9.77249 20.5441C9.77249 20.3249 9.76125 19.5982 9.76125 18.8254C7.5 19.2522 6.915 18.2602 6.735 17.7412C6.63375 17.4759 6.19499 16.6569 5.8125 16.4378C5.4975 16.2647 5.0475 15.838 5.80124 15.8264C6.51 15.8149 7.01625 16.4954 7.18499 16.7723C7.99499 18.1679 9.28875 17.7758 9.80625 17.5335C9.885 16.9337 10.1212 16.53 10.38 16.2993C8.3775 16.0687 6.285 15.2728 6.285 11.7432C6.285 10.7397 6.63375 9.9092 7.20749 9.26326C7.1175 9.03257 6.8025 8.08674 7.2975 6.81794C7.2975 6.81794 8.05125 6.57571 9.77249 7.76377C10.4925 7.55615 11.2575 7.45234 12.0225 7.45234C12.7875 7.45234 13.5525 7.55615 14.2725 7.76377C15.9937 6.56418 16.7475 6.81794 16.7475 6.81794C17.2424 8.08674 16.9275 9.03257 16.8375 9.26326C17.4113 9.9092 17.76 10.7281 17.76 11.7432C17.76 15.2843 15.6563 16.0687 13.6537 16.2993C13.98 16.5877 14.2613 17.1414 14.2613 18.0065C14.2613 19.2407 14.25 20.2326 14.25 20.5441C14.25 20.7863 14.4188 21.0746 14.8688 20.9824C16.6554 20.364 18.2079 19.1866 19.3078 17.6162C20.4077 16.0457 20.9995 14.1611 21 12.2276C21 7.12937 16.9725 3 12 3Z"></path>
7984
</svg>
8085
</Link>
81-
<div className="hidden lg:block github-counter">21.3k</div>
86+
<div className="hidden lg:block github-counter">22.2k</div>
8287
</div>
8388
<a
8489
href="https://discord.authjs.dev/?utm_source=docs"

0 commit comments

Comments
 (0)