components/search.tsx (113 lines of code) (raw):
import { useState, useRef, useMemo, useContext, useEffect } from "react";
import { useRouter } from "next/router";
import tinykeys from "tinykeys";
import { ThemeProvider, LightThemeMove } from "baseui";
import { StatefulMenu } from "baseui/menu";
import { Select, TYPE, SIZE } from "baseui/select";
import { SearchIcon } from "./icons";
import { PageContext } from "./layout";
import * as gtag from "../lib/gtag";
function Search() {
const router = useRouter();
const controlRef = useRef<HTMLInputElement>();
const { siteMap = [], activePage = { key: null } } = useContext(PageContext);
useEffect(() => {
const unsubscribe = tinykeys(window, {
"/": (event) => {
if (event.target !== controlRef.current) {
event.preventDefault();
controlRef.current.focus();
}
},
});
return () => {
unsubscribe();
};
}, []);
// Create list of options
const [options, activeIndex] = useMemo(() => {
const options = { __ungrouped: [] };
let count = 0;
let activeIndex = 0;
for (const section of siteMap) {
options[section.name] = [];
for (const page of section.children) {
if (page.key === activePage.key) activeIndex = count;
count += 1;
options[section.name].push({
id: page.key, // IDs may not be unique, so use `key` for this.
name: page.name,
self: `${section.name} → ${page.name}`,
href: `/${page.key}`,
});
}
}
return [options, activeIndex];
}, [activePage.key]);
const [placeholder, setPlaceholder] = useState("Search components...");
return (
<Select
searchable
openOnClick
valueKey="id"
labelKey="self"
clearable={false}
options={options}
type={TYPE.search}
size={SIZE.compact}
controlRef={controlRef}
placeholder={activePage.key ? placeholder : "Search components..."}
maxDropdownHeight="300px"
getOptionLabel={({ option }) => option.name}
onFocus={(event) => {
// Opens the dropdown
event.target.click();
}}
onChange={({ value }) => {
if (value[0]) {
gtag.event({
action: "click_link_search",
category: "navigation",
label: value[0].href as string,
});
router.push("/[pageKey]", value[0].href);
controlRef.current && controlRef.current.blur();
setPlaceholder(value[0].self);
}
}}
overrides={{
Input: {
props: {
id: "search",
"aria-label": "Search through components and pages.",
},
},
Placeholder: {
style: ({ $theme }) => ({
// Default fails Lighthouse contrast ratio
color: $theme.colors.backgroundInverseSecondary,
}),
},
SearchIcon: {
component: function SearchIconOverride() {
return <SearchIcon />;
},
},
StatefulMenu: {
component: function StatefulMenuOverride(props) {
return (
<ThemeProvider theme={LightThemeMove}>
<StatefulMenu
{...props}
initialState={{
highlightedIndex: activeIndex,
isFocused: true,
activedescendantId: activePage.key,
}}
/>
</ThemeProvider>
);
},
},
}}
/>
);
}
export default Search;