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}