67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
import { useEffect, useMemo, useRef, useState } from "react";
|
|
import { availableNavigation } from "../navigation";
|
|
import { Icon } from "./Icon";
|
|
import { Modal } from "./Modal";
|
|
|
|
export function CommandMenu({
|
|
open,
|
|
canReadAudit,
|
|
navigate,
|
|
onClose,
|
|
}: {
|
|
open: boolean;
|
|
canReadAudit: boolean;
|
|
navigate: (next: string) => void;
|
|
onClose: () => void;
|
|
}) {
|
|
const [query, setQuery] = useState("");
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const items = useMemo(() => availableNavigation(canReadAudit), [canReadAudit]);
|
|
const results = useMemo(() => {
|
|
const needle = query.trim().toLowerCase();
|
|
if (!needle) return items;
|
|
return items.filter((item) => `${item.label} ${item.description}`.toLowerCase().includes(needle));
|
|
}, [items, query]);
|
|
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
setQuery("");
|
|
const focusTimer = window.setTimeout(() => inputRef.current?.focus(), 0);
|
|
return () => window.clearTimeout(focusTimer);
|
|
}, [open]);
|
|
|
|
const select = (href: string) => {
|
|
onClose();
|
|
navigate(href);
|
|
};
|
|
|
|
return (
|
|
<Modal open={open} title="Quick jump" onClose={onClose} className="command-modal">
|
|
<label className="command-search">
|
|
<span className="sr-only">Find a workspace</span>
|
|
<Icon name="search" size={17} />
|
|
<input
|
|
ref={inputRef}
|
|
value={query}
|
|
onChange={(event) => setQuery(event.target.value)}
|
|
placeholder="Find projects, catalog, policies…"
|
|
autoComplete="off"
|
|
/>
|
|
<kbd>Esc</kbd>
|
|
</label>
|
|
<div className="command-results" aria-live="polite">
|
|
{results.length ? results.map((item) => (
|
|
<button type="button" className="command-result" key={item.href} onClick={() => select(item.href)}>
|
|
<span className="command-result-icon"><Icon name={item.icon} /></span>
|
|
<span><strong>{item.label}</strong><small>{item.description}</small></span>
|
|
<Icon name="arrow" size={16} />
|
|
</button>
|
|
)) : (
|
|
<div className="command-empty"><strong>No workspace found</strong><span>Try a broader name or capability.</span></div>
|
|
)}
|
|
</div>
|
|
<p className="command-hint">Navigate without leaving the keyboard. Access-controlled destinations remain hidden.</p>
|
|
</Modal>
|
|
);
|
|
}
|