Search.tsx109 lines · 3.3 KB · raw
1/** @jsxImportSource react */
2import { useState, useCallback, useRef } from 'react';
3import { ALGOLIA } from '../../config';
4import '@docsearch/css';
5import './Search.css';
6
7import { createPortal } from 'react-dom';
8import * as docSearchReact from '@docsearch/react';
9const { BASE_URL } = import.meta.env;
10const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
11
12/** FIXME: This is still kinda nasty, but DocSearch is not ESM ready. */
13const DocSearchModal = docSearchReact.DocSearchModal || (docSearchReact as any).default.DocSearchModal;
14const useDocSearchKeyboardEvents =
15  docSearchReact.useDocSearchKeyboardEvents || (docSearchReact as any).default.useDocSearchKeyboardEvents;
16
17export default function Search() {
18  const [isOpen, setIsOpen] = useState(false);
19  const searchButtonRef = useRef<HTMLButtonElement>(null);
20  const [initialQuery, setInitialQuery] = useState('');
21
22  const onOpen = useCallback(() => {
23    setIsOpen(true);
24  }, [setIsOpen]);
25
26  const onClose = useCallback(() => {
27    setIsOpen(false);
28  }, [setIsOpen]);
29
30  const onInput = useCallback(
31    (e: any) => {
32      setIsOpen(true);
33      setInitialQuery(e.key);
34    },
35    [setIsOpen, setInitialQuery],
36  );
37
38  useDocSearchKeyboardEvents({
39    isOpen,
40    onOpen,
41    onClose,
42    onInput,
43    searchButtonRef,
44  });
45
46  return (
47    <>
48      <button
49        type="button"
50        ref={searchButtonRef}
51        onClick={onOpen}
52        className="rounded-md bg-slate-900 w-full px-2 search-button"
53      >
54        <svg width="24" height="24" fill="none">
55          <path
56            d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
57            stroke="currentColor"
58            strokeWidth="2"
59            strokeLinecap="round"
60            strokeLinejoin="round"
61          />
62        </svg>
63
64        {/* 				<span>Search</span> */}
65
66        <span className="search-hint">
67          <span className="sr-only">Press </span>
68
69          <kbd>/</kbd>
70
71          <span className="sr-only"> to search</span>
72        </span>
73      </button>
74
75      {isOpen &&
76        createPortal(
77          <DocSearchModal
78            initialQuery={initialQuery}
79            initialScrollY={window.scrollY}
80            onClose={onClose}
81            indexName={ALGOLIA.indexName}
82            appId={ALGOLIA.appId}
83            apiKey={ALGOLIA.apiKey}
84            getMissingResultsUrl={({ query }) => {
85              return `https://codeberg.org/uzu/strudel/issues/new?title=Missing%20doc%20for${encodeURIComponent(query)}`;
86            }}
87            transformItems={(items) => {
88              return items.map((item) => {
89                // We transform the absolute URL into a relative URL to
90                // work better on localhost, preview URLS.
91                const a = document.createElement('a');
92                a.href = item.url;
93                const hash = a.hash === '#overview' ? '' : a.hash;
94                let pathname = a.pathname;
95                pathname = pathname.startsWith('/') ? pathname.slice(1) : pathname;
96                pathname = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
97                const url = `${baseNoTrailing}/${pathname}/${hash}`;
98                return {
99                  ...item,
100                  url,
101                };
102              });
103            }}
104          />,
105          document.body,
106        )}
107    </>
108  );
109}