feat(search): add global titlebar search filters
This commit is contained in:
@@ -1,54 +1,21 @@
|
||||
import { Search, X } from "lucide-react";
|
||||
import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type KeyboardEvent as ReactKeyboardEvent
|
||||
} from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import {
|
||||
useGuardedNavigate,
|
||||
usePlatformUiCapabilities,
|
||||
type GlobalSearchProps,
|
||||
type SearchContextContribution,
|
||||
type SearchContextsUiCapability
|
||||
type GlobalSearchProps
|
||||
} from "@govoplan/core-webui";
|
||||
import { search, type SearchResult } from "../api/search";
|
||||
|
||||
|
||||
const MIN_SUGGESTION_LENGTH = 2;
|
||||
|
||||
function currentContext(
|
||||
pathname: string,
|
||||
contexts: SearchContextContribution[]
|
||||
): SearchContextContribution | null {
|
||||
return [...contexts].
|
||||
filter((context) =>
|
||||
context.pathPrefixes.some((prefix) =>
|
||||
pathname === prefix || pathname.startsWith(`${prefix}/`)
|
||||
)
|
||||
).
|
||||
sort((left, right) => {
|
||||
const leftLength = Math.max(...left.pathPrefixes.map((item) => item.length));
|
||||
const rightLength = Math.max(...right.pathPrefixes.map((item) => item.length));
|
||||
return rightLength - leftLength || (left.order ?? 100) - (right.order ?? 100);
|
||||
})[0] ?? null;
|
||||
}
|
||||
|
||||
export default function GlobalSearch({ settings }: GlobalSearchProps) {
|
||||
const navigate = useGuardedNavigate();
|
||||
const location = useLocation();
|
||||
const contextCapabilities =
|
||||
usePlatformUiCapabilities<SearchContextsUiCapability>("search.contexts");
|
||||
const contexts = useMemo(
|
||||
() => contextCapabilities.flatMap((capability) => capability.contexts),
|
||||
[contextCapabilities]
|
||||
);
|
||||
const context = useMemo(
|
||||
() => currentContext(location.pathname, contexts),
|
||||
[contexts, location.pathname]
|
||||
);
|
||||
const [value, setValue] = useState("");
|
||||
const [results, setResults] = useState<SearchResult[]>([]);
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -58,9 +25,12 @@ export default function GlobalSearch({ settings }: GlobalSearchProps) {
|
||||
|
||||
useEffect(() => {
|
||||
function focusSearch(event: KeyboardEvent) {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
|
||||
const commandSearch =
|
||||
(event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k";
|
||||
if (event.key === "F3" || commandSearch) {
|
||||
event.preventDefault();
|
||||
inputRef.current?.focus();
|
||||
inputRef.current?.select();
|
||||
}
|
||||
}
|
||||
window.addEventListener("keydown", focusSearch);
|
||||
@@ -90,10 +60,7 @@ export default function GlobalSearch({ settings }: GlobalSearchProps) {
|
||||
settings,
|
||||
{
|
||||
query,
|
||||
modules: context ? [context.moduleId] : undefined,
|
||||
resourceTypes: context?.resourceTypes,
|
||||
contextKind: context ? "module" : "global",
|
||||
contextId: context?.id,
|
||||
contextKind: "global",
|
||||
limit: 6
|
||||
},
|
||||
controller.signal
|
||||
@@ -113,7 +80,7 @@ export default function GlobalSearch({ settings }: GlobalSearchProps) {
|
||||
window.clearTimeout(timer);
|
||||
controller.abort();
|
||||
};
|
||||
}, [context, settings, value]);
|
||||
}, [settings, value]);
|
||||
|
||||
function openResult(result: SearchResult) {
|
||||
setOpen(false);
|
||||
@@ -125,13 +92,6 @@ export default function GlobalSearch({ settings }: GlobalSearchProps) {
|
||||
const query = value.trim();
|
||||
if (!query) return;
|
||||
const params = new URLSearchParams({ q: query });
|
||||
if (context) {
|
||||
params.set("module", context.moduleId);
|
||||
params.set("context", context.id);
|
||||
for (const resourceType of context.resourceTypes ?? []) {
|
||||
params.append("resource_type", resourceType);
|
||||
}
|
||||
}
|
||||
setOpen(false);
|
||||
navigate(`/search?${params.toString()}`);
|
||||
}
|
||||
@@ -172,8 +132,9 @@ export default function GlobalSearch({ settings }: GlobalSearchProps) {
|
||||
ref={inputRef}
|
||||
type="search"
|
||||
value={value}
|
||||
placeholder={context?.placeholder ?? "Search"}
|
||||
aria-label={context ? `Search ${context.label}` : "Search"}
|
||||
placeholder="Search"
|
||||
aria-label="Global search"
|
||||
aria-keyshortcuts="F3 Control+K Meta+K"
|
||||
aria-expanded={open}
|
||||
aria-controls="global-search-results"
|
||||
onFocus={() => setOpen(results.length > 0)}
|
||||
|
||||
Reference in New Issue
Block a user