Skip to content

Commit 10469b3

Browse files
committed
make docsearch universally accessible
1 parent 9621e0d commit 10469b3

File tree

5 files changed

+280
-179
lines changed

5 files changed

+280
-179
lines changed

app/components/docs-header/docs-header.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Link, useNavigate } from "react-router";
22
import iconsHref from "~/icons.svg";
33
import { ColorSchemeToggle } from "../color-scheme-toggle";
44
import classNames from "classnames";
5-
import { DocSearch } from "~/modules/docsearch";
5+
import { DocSearchButton } from "~/modules/docsearch";
66
import { VersionNav } from "../version-nav";
77

88
export function Header() {
@@ -16,7 +16,7 @@ export function Header() {
1616
</div>
1717

1818
<div className="flex gap-2 md:gap-4">
19-
<DocSearchSection />
19+
<DocSearchButton />
2020
<ColorSchemeToggle />
2121
<ExternalLinks />
2222
</div>
@@ -100,11 +100,3 @@ function HeaderSvgLink({
100100
</a>
101101
);
102102
}
103-
104-
function DocSearchSection() {
105-
return (
106-
<div className="lg:bg-white lg:dark:bg-gray-900">
107-
<DocSearch />
108-
</div>
109-
);
110-
}

app/modules/docsearch.tsx

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1+
import {
2+
Suspense,
3+
createContext,
4+
lazy,
5+
useCallback,
6+
useContext,
7+
useMemo,
8+
useRef,
9+
useState,
10+
} from "react";
11+
import { createPortal } from "react-dom";
12+
import { useDocSearchKeyboardEvents } from "@docsearch/react/dist/esm";
113
import type { DocSearchProps } from "@docsearch/react";
2-
import { useHydrated } from "~/ui/utils";
3-
import { Suspense, lazy } from "react";
414

5-
const OriginalDocSearch = lazy(() =>
15+
let OriginalDocSearchModal = lazy(() =>
616
import("@docsearch/react").then((module) => ({
7-
default: module.DocSearch,
17+
default: module.DocSearchModal,
18+
}))
19+
);
20+
21+
let OriginalDocSearchButton = lazy(() =>
22+
import("@docsearch/react").then((module) => ({
23+
default: module.DocSearchButton,
824
}))
925
);
1026

@@ -14,24 +30,81 @@ let docSearchProps = {
1430
apiKey: "b50c5d7d9f4610c9785fa945fdc97476",
1531
} satisfies DocSearchProps;
1632

17-
// TODO: Refactor a bit when we add Vite with css imports per component
18-
// This will allow us to have two versions of the component, one that has
19-
// the button with display: none, and the other with button styles
20-
export function DocSearch() {
21-
let hydrated = useHydrated();
22-
23-
if (!hydrated) {
24-
// The Algolia doc search container is hard-coded at 40px. It doesn't
25-
// render anything on the server, so we get a mis-match after hydration.
26-
// This placeholder prevents layout shift when the search appears.
27-
return <div className="h-10" />;
33+
const DocSearchContext = createContext<{
34+
onOpen: () => void;
35+
searchButtonRef: React.RefObject<HTMLButtonElement>;
36+
} | null>(null);
37+
38+
/**
39+
* DocSearch but only the modal accessible by keyboard command
40+
* Intended for people instinctively pressing cmd+k on a non-doc page
41+
*
42+
* If you need a DocSearch button to appear, use the DocSearch component
43+
* Modified from https://github.com/algolia/docsearch/blob/main/packages/docsearch-react/src/DocSearch.tsx
44+
*/
45+
export function DocSearch({ children }: { children: React.ReactNode }) {
46+
const searchButtonRef = useRef<HTMLButtonElement>(null);
47+
const [isOpen, setIsOpen] = useState(false);
48+
49+
const onOpen = useCallback(() => {
50+
setIsOpen(true);
51+
}, [setIsOpen]);
52+
53+
const onClose = useCallback(() => {
54+
setIsOpen(false);
55+
}, [setIsOpen]);
56+
57+
const onInput = useCallback(() => {
58+
setIsOpen(true);
59+
}, [setIsOpen]);
60+
61+
useDocSearchKeyboardEvents({
62+
isOpen,
63+
onOpen,
64+
onClose,
65+
onInput,
66+
searchButtonRef,
67+
});
68+
69+
const contextValue = useMemo(
70+
() => ({
71+
onOpen,
72+
searchButtonRef,
73+
}),
74+
[onOpen, searchButtonRef]
75+
);
76+
77+
return (
78+
<DocSearchContext.Provider value={contextValue}>
79+
{children}
80+
{isOpen
81+
? createPortal(
82+
<Suspense fallback={null}>
83+
<OriginalDocSearchModal
84+
initialScrollY={window.scrollY}
85+
onClose={onClose}
86+
{...docSearchProps}
87+
/>
88+
</Suspense>,
89+
document.body
90+
)
91+
: null}
92+
</DocSearchContext.Provider>
93+
);
94+
}
95+
96+
export function DocSearchButton() {
97+
const docSearchContext = useContext(DocSearchContext);
98+
99+
if (!docSearchContext) {
100+
throw new Error("DocSearch must be used within a DocSearchModal");
28101
}
29102

103+
const { onOpen, searchButtonRef } = docSearchContext;
104+
30105
return (
31106
<Suspense fallback={<div className="h-10" />}>
32-
<div className="animate-[fadeIn_100ms_ease-in_1]">
33-
<OriginalDocSearch {...docSearchProps} />
34-
</div>
107+
<OriginalDocSearchButton ref={searchButtonRef} onClick={onOpen} />
35108
</Suspense>
36109
);
37110
}

app/root.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ import { isHost } from "./modules/http-utils/is-host";
1818
import iconsHref from "~/icons.svg";
1919
import { useRef } from "react";
2020
import { useCodeBlockCopyButton } from "./ui/utils";
21+
import { DocSearch } from "./modules/docsearch";
2122

2223
import "~/styles/tailwind.css";
23-
// FIXUP: Importing in `root` because we have a bug where the styles get offloaded
24-
// see: https://github.com/remix-run/react-router-website/issues/139
25-
import "~/styles/docs.css";
2624
import "@docsearch/css/dist/style.css";
2725
import "~/styles/docsearch.css";
26+
// FIXUP: Styles need to all be imported in root until this is fixed:
27+
// https://github.com/remix-run/react-router/issues/12382
28+
import "~/styles/docs.css";
2829

2930
export async function loader({ request }: LoaderFunctionArgs) {
3031
await middlewares(request);
@@ -88,7 +89,9 @@ export default function App() {
8889
// eslint-disable-next-line react/no-unknown-property
8990
fetchpriority="high"
9091
/>
91-
<Outlet />
92+
<DocSearch>
93+
<Outlet />
94+
</DocSearch>
9295
<ScrollRestoration />
9396
<Scripts />
9497
</body>

0 commit comments

Comments
 (0)