blob: 50f5ba43dfec23e50f8279749f78de7d8c201f40 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {
useCallback,
useEffect,
useState,
} from "react";
import { useLocation } from "@docusaurus/router";
import useIsBrowser from "@docusaurus/useIsBrowser";
import { HighlightSearchResults } from "docusaurus-lunr-search/src/theme/SearchBar/HighlightSearchResults";
import useLunrSearch from "./useLunrSearch";
function classNames(...values) {
return values
.flatMap((value) => {
if (!value) {
return [];
}
if (typeof value === "string") {
return [value];
}
return Object.entries(value)
.filter(([, enabled]) => enabled)
.map(([className]) => className);
})
.join(" ");
}
/* Shortcut hints are desktop-only; mobile search should not imply a keyboard. */
const DESKTOP_SEARCH_SHORTCUT_QUERY =
"(min-width: 997px) and (hover: hover) and (pointer: fine)";
function getDesktopSearchShortcut() {
const platform =
window.navigator.userAgentData?.platform || window.navigator.platform || "";
return /mac/i.test(platform) ? "Cmd+K" : "Ctrl+K";
}
function getSearchPlaceholder(desktopShortcutMedia) {
if (!desktopShortcutMedia?.matches) {
return "Search";
}
return `Search (${getDesktopSearchShortcut()})`;
}
export default function SearchBar(props) {
const [placeholder, setPlaceholder] = useState("Search");
const [isMobileLayout, setIsMobileLayout] = useState(false);
const location = useLocation();
const isBrowser = useIsBrowser();
const inputId = props.inputId || "search_input_react";
const inputSelector = `#${inputId}`;
const showSearchIcon = props.showIcon !== false;
const shouldHideSearchBar =
(!props.standalone && /\/search\/?$/.test(location.pathname)) ||
(isMobileLayout && !props.mobile && !props.standalone);
const { indexReady, loadSearch, searchBarRef } = useLunrSearch({
disabled: shouldHideSearchBar,
inputSelector,
});
const toggleSearchIconClick = useCallback(
(event) => {
if (
searchBarRef.current &&
!searchBarRef.current.contains(event.target)
) {
searchBarRef.current.focus();
}
props.handleSearchBarToggle?.(!props.isSearchBarExpanded);
},
[props],
);
useEffect(() => {
loadSearch();
}, [loadSearch]);
useEffect(() => {
if (!isBrowser) {
return undefined;
}
const desktopShortcutMedia = window.matchMedia?.(
DESKTOP_SEARCH_SHORTCUT_QUERY,
);
const updatePlaceholder = () => {
setPlaceholder(getSearchPlaceholder(desktopShortcutMedia));
};
updatePlaceholder();
if (desktopShortcutMedia?.addEventListener) {
desktopShortcutMedia.addEventListener("change", updatePlaceholder);
return () => {
desktopShortcutMedia.removeEventListener("change", updatePlaceholder);
};
}
if (desktopShortcutMedia?.addListener) {
desktopShortcutMedia.addListener(updatePlaceholder);
return () => {
desktopShortcutMedia.removeListener(updatePlaceholder);
};
}
return undefined;
}, [isBrowser]);
useEffect(() => {
if (!isBrowser) {
return undefined;
}
const mobileMedia = window.matchMedia?.("(max-width: 996px)");
const updateMobileLayout = () => {
setIsMobileLayout(Boolean(mobileMedia?.matches));
};
updateMobileLayout();
if (mobileMedia?.addEventListener) {
mobileMedia.addEventListener("change", updateMobileLayout);
return () => {
mobileMedia.removeEventListener("change", updateMobileLayout);
};
}
if (mobileMedia?.addListener) {
mobileMedia.addListener(updateMobileLayout);
return () => {
mobileMedia.removeListener(updateMobileLayout);
};
}
return undefined;
}, [isBrowser]);
useEffect(() => {
if (props.autoFocus && indexReady) {
searchBarRef.current?.focus();
}
}, [indexReady, props.autoFocus]);
if (shouldHideSearchBar) {
return null;
}
return (
<div
className={classNames("navbar__search", {
"navbar__search--no-icon": !showSearchIcon,
})}
key="search-box"
>
{showSearchIcon && (
<span
aria-label="expand searchbar"
role="button"
className={classNames("search-icon", {
"search-icon-hidden": props.isSearchBarExpanded,
})}
onClick={toggleSearchIconClick}
onKeyDown={toggleSearchIconClick}
tabIndex={0}
/>
)}
<input
id={inputId}
type="search"
placeholder={placeholder}
aria-label="Search"
className={classNames(
"navbar__search-input",
{ "search-bar-expanded": props.isSearchBarExpanded },
{ "search-bar": !props.isSearchBarExpanded },
)}
onClick={loadSearch}
onMouseOver={loadSearch}
onFocus={toggleSearchIconClick}
onBlur={toggleSearchIconClick}
ref={searchBarRef}
readOnly={!indexReady}
aria-disabled={!indexReady}
/>
<HighlightSearchResults />
</div>
);
}