import searchData from ".json/search.json"; import React, { useEffect, useState } from "react"; import SearchResult, { type ISearchItem } from "./SearchResult"; const SearchModal = () => { const [searchString, setSearchString] = useState(""); // handle input change const handleSearch = (e: React.FormEvent) => { setSearchString(e.currentTarget.value.replace("\\", "").toLowerCase()); }; // generate search result const doSearch = (searchData: ISearchItem[]) => { const regex = new RegExp(`${searchString}`, "gi"); if (searchString === "") { return []; } else { const searchResult = searchData.filter((item) => { const title = item.frontmatter.title.toLowerCase().match(regex); const description = item.frontmatter.description ?.toLowerCase() .match(regex); const categories = item.frontmatter.categories ?.join(" ") .toLowerCase() .match(regex); const tags = item.frontmatter.tags ?.join(" ") .toLowerCase() .match(regex); const content = item.content.toLowerCase().match(regex); if (title || content || description || categories || tags) { return item; } }); return searchResult; } }; // get search result const startTime = performance.now(); const searchResult = doSearch(searchData); const endTime = performance.now(); const totalTime = ((endTime - startTime) / 1000).toFixed(3); // search dom manipulation useEffect(() => { const searchModal = document.getElementById("searchModal"); const searchInput = document.getElementById("searchInput"); const searchModalOverlay = document.getElementById("searchModalOverlay"); const searchResultItems = document.querySelectorAll("#searchItem"); const searchModalTriggers = document.querySelectorAll( "[data-search-trigger]", ); // search modal open searchModalTriggers.forEach((button) => { button.addEventListener("click", function () { const searchModal = document.getElementById("searchModal"); searchModal!.classList.add("show"); searchInput!.focus(); }); }); // search modal close searchModalOverlay!.addEventListener("click", function () { searchModal!.classList.remove("show"); }); // keyboard navigation let selectedIndex = -1; const updateSelection = () => { searchResultItems.forEach((item, index) => { if (index === selectedIndex) { item.classList.add("search-result-item-active"); } else { item.classList.remove("search-result-item-active"); } }); searchResultItems[selectedIndex]?.scrollIntoView({ behavior: "smooth", block: "nearest", }); }; document.addEventListener("keydown", function (event) { if ((event.metaKey || event.ctrlKey) && event.key === "k") { searchModal!.classList.add("show"); searchInput!.focus(); updateSelection(); } if (event.key === "ArrowUp" || event.key === "ArrowDown") { event.preventDefault(); } if (event.key === "Escape") { searchModal!.classList.remove("show"); } if (event.key === "ArrowUp" && selectedIndex > 0) { selectedIndex--; } else if ( event.key === "ArrowDown" && selectedIndex < searchResultItems.length - 1 ) { selectedIndex++; } else if (event.key === "Enter") { const activeLink = document.querySelector( ".search-result-item-active a", ) as HTMLAnchorElement; if (activeLink) { activeLink?.click(); } } updateSelection(); }); }, [searchString]); return (
to navigate to select {searchString && ( {searchResult.length} results - in{" "} {totalTime} seconds )} ESC to close
); }; export default SearchModal;