import { forwardRef } from 'preact/compat'; import { useImperativeHandle, useRef, useState } from 'preact/hooks'; import { useSearchParams } from 'react-router-dom'; import { api } from '../utils/api'; import Icon from './icon'; import Link from './link'; const SearchForm = forwardRef((props, ref) => { const { instance } = api(); const [searchParams, setSearchParams] = useSearchParams(); const [searchMenuOpen, setSearchMenuOpen] = useState(false); const [query, setQuery] = useState(searchParams.get('q') || ''); const type = searchParams.get('type'); const formRef = useRef(null); const searchFieldRef = useRef(null); useImperativeHandle(ref, () => ({ setValue: (value) => { setQuery(value); }, focus: () => { searchFieldRef.current.focus(); }, select: () => { searchFieldRef.current.select(); }, blur: () => { searchFieldRef.current.blur(); }, })); return (
{ e.preventDefault(); const isSearchPage = /\/search/.test(location.hash); if (isSearchPage) { if (query) { const params = { q: query, }; if (type) params.type = type; // Preserve type setSearchParams(params); } else { setSearchParams({}); } } else { if (query) { location.hash = `/search?q=${encodeURIComponent(query)}${ type ? `&type=${type}` : '' }`; } else { location.hash = `/search`; } } props?.onSubmit?.(e); }} > { if (!e.target.value) { setSearchParams({}); } }} onInput={(e) => { setQuery(e.target.value); setSearchMenuOpen(true); }} onFocus={() => { setSearchMenuOpen(true); formRef.current ?.querySelector('.search-popover-item') ?.classList.add('focus'); }} onBlur={() => { setTimeout(() => { setSearchMenuOpen(false); }, 100); formRef.current ?.querySelector('.search-popover-item.focus') ?.classList.remove('focus'); }} onKeyDown={(e) => { const { key } = e; switch (key) { case 'Escape': setSearchMenuOpen(false); break; case 'Down': case 'ArrowDown': e.preventDefault(); if (searchMenuOpen) { const focusItem = formRef.current.querySelector( '.search-popover-item.focus', ); if (focusItem) { let nextItem = focusItem.nextElementSibling; while (nextItem && nextItem.hidden) { nextItem = nextItem.nextElementSibling; } if (nextItem) { nextItem.classList.add('focus'); const siblings = Array.from( nextItem.parentElement.children, ).filter((el) => el !== nextItem); siblings.forEach((el) => { el.classList.remove('focus'); }); } } else { const firstItem = formRef.current.querySelector( '.search-popover-item', ); if (firstItem) { firstItem.classList.add('focus'); } } } break; case 'Up': case 'ArrowUp': e.preventDefault(); if (searchMenuOpen) { const focusItem = document.querySelector( '.search-popover-item.focus', ); if (focusItem) { let prevItem = focusItem.previousElementSibling; while (prevItem && prevItem.hidden) { prevItem = prevItem.previousElementSibling; } if (prevItem) { prevItem.classList.add('focus'); const siblings = Array.from( prevItem.parentElement.children, ).filter((el) => el !== prevItem); siblings.forEach((el) => { el.classList.remove('focus'); }); } } else { const lastItem = document.querySelector( '.search-popover-item:last-child', ); if (lastItem) { lastItem.classList.add('focus'); } } } break; case 'Enter': if (searchMenuOpen) { const focusItem = document.querySelector( '.search-popover-item.focus', ); if (focusItem) { e.preventDefault(); focusItem.click(); } setSearchMenuOpen(false); props?.onSubmit?.(e); } break; } }} />
); }); export default SearchForm;