Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
fa80eb4
Improve search results formatting for better readability and user exp…
devin-ai-integration[bot] May 6, 2025
95df69f
Fix TypeScript errors with CSS implementation in search results
devin-ai-integration[bot] May 6, 2025
9469d70
Add feature to clear search bar after clicking a result
devin-ai-integration[bot] May 6, 2025
a4f30b0
Fix linting issues in RegistrySearch component
devin-ai-integration[bot] May 6, 2025
52c032f
Implement command palette/spotlight search triggered by Cmd+K
devin-ai-integration[bot] May 6, 2025
7112ffa
Fix TypeScript errors in CommandPalette component
devin-ai-integration[bot] May 6, 2025
2d0c43b
Fix UI issues in command palette: prevent double scrollbars and impro…
devin-ai-integration[bot] May 6, 2025
3ed7f2d
Fix command palette overlay implementation and UI issues
devin-ai-integration[bot] May 6, 2025
04395f7
Remove unused EuiOverlayMask import
devin-ai-integration[bot] May 6, 2025
e8f2b87
Fix command palette UI issues and improve user experience
devin-ai-integration[bot] May 6, 2025
75bdaee
Fix command palette modal closing when clicking on search results
devin-ai-integration[bot] May 6, 2025
3bf4c83
Apply formatting to command palette components
devin-ai-integration[bot] May 6, 2025
c98a5b0
Use React Router navigation instead of window.location.href to preven…
devin-ai-integration[bot] May 6, 2025
fff8f06
Format CommandPalette.tsx and clean up code
devin-ai-integration[bot] May 6, 2025
087bfb6
Remove test button from Layout component
devin-ai-integration[bot] May 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
288 changes: 288 additions & 0 deletions ui/src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
import React, { useRef, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import {
EuiFieldSearch,
EuiText,
EuiSpacer,
EuiHorizontalRule,
EuiPanel,
EuiFlexGroup,
EuiFlexItem,
EuiBadge,
EuiTitle,
} from "@elastic/eui";

const commandPaletteStyles: Record<string, React.CSSProperties> = {
overlay: {
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0, 0, 0, 0.7)",
zIndex: 9999,
display: "flex",
alignItems: "center",
justifyContent: "center",
},
modal: {
width: "600px",
maxWidth: "90vw",
maxHeight: "80vh",
backgroundColor: "white",
borderRadius: "8px",
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
overflow: "hidden",
display: "flex",
flexDirection: "column",
},
modalHeader: {
padding: "16px",
borderBottom: "1px solid #D3DAE6",
position: "sticky",
top: 0,
backgroundColor: "white",
zIndex: 1,
},
modalBody: {
padding: "0 16px 16px",
maxHeight: "calc(80vh - 60px)",
overflowY: "auto",
},
searchResults: {
marginTop: "8px",
},
categoryGroup: {
marginBottom: "8px",
},
searchResultItem: {
padding: "8px 0",
borderBottom: "1px solid #eee",
},
searchResultItemLast: {
padding: "8px 0",
borderBottom: "none",
},
itemDescription: {
fontSize: "0.85em",
color: "#666",
marginTop: "4px",
},
};

interface CommandPaletteProps {
isOpen: boolean;
onClose: () => void;
categories: {
name: string;
data: any[];
getLink: (item: any) => string;
}[];
}

const getItemType = (item: any, category: string): string | undefined => {
if (category === "Features" && "valueType" in item) {
return item.valueType;
}
if (category === "Feature Views" && "type" in item) {
return item.type;
}
return undefined;
};

const CommandPalette: React.FC<CommandPaletteProps> = ({
isOpen,
onClose,
categories,
}) => {
const [searchText, setSearchText] = React.useState("");
const inputRef = useRef<HTMLInputElement | null>(null);
const navigate = useNavigate();

useEffect(() => {
if (isOpen && inputRef.current) {
setTimeout(() => {
inputRef.current?.focus();
}, 100);
}
}, [isOpen]);

useEffect(() => {
if (!isOpen) {
setSearchText("");
}
}, [isOpen]);

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Escape") {
onClose();
}
};

const searchResults = categories.map(({ name, data, getLink }) => {
const filteredItems = searchText
? data.filter((item) => {
const itemName =
"name" in item
? String(item.name)
: "spec" in item && item.spec && "name" in item.spec
? String(item.spec.name ?? "Unknown")
: "Unknown";

return itemName.toLowerCase().includes(searchText.toLowerCase());
})
: [];

const items = filteredItems.map((item) => {
const itemName =
"name" in item
? String(item.name)
: "spec" in item && item.spec && "name" in item.spec
? String(item.spec.name ?? "Unknown")
: "Unknown";

return {
name: itemName,
link: getLink(item),
description:
"spec" in item && item.spec && "description" in item.spec
? String(item.spec.description || "")
: "",
type: getItemType(item, name),
};
});

return {
title: name,
items,
};
});

console.log(
"CommandPalette isOpen:",
isOpen,
"categories:",
categories.length,
); // Debug log

if (!isOpen) {
console.log("CommandPalette not rendering due to isOpen=false");
return null;
}

return (
<div style={commandPaletteStyles.overlay} onClick={onClose}>
<div
style={commandPaletteStyles.modal}
onClick={(e) => e.stopPropagation()}
onKeyDown={handleKeyDown}
>
<div style={commandPaletteStyles.modalHeader}>
<h2 style={{ margin: 0 }}>Search Registry</h2>
</div>
<div style={commandPaletteStyles.modalBody}>
<EuiFieldSearch
placeholder="Search across Feature Views, Features, Entities, etc."
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
isClearable
fullWidth
inputRef={(node) => {
inputRef.current = node;
}}
aria-label="Search registry"
autoFocus
/>
<EuiSpacer size="s" />
{searchText ? (
<div style={commandPaletteStyles.searchResults}>
{searchResults.filter((result) => result.items.length > 0)
.length > 0 ? (
searchResults
.filter((result) => result.items.length > 0)
.map((result) => (
<div
key={result.title}
style={commandPaletteStyles.categoryGroup}
>
<EuiPanel hasBorder={true} paddingSize="m">
<EuiTitle size="xs">
<h3>
{result.title} ({result.items.length})
</h3>
</EuiTitle>
<EuiHorizontalRule margin="xs" />
{result.items.map((item, idx) => (
<div
key={item.name}
style={
idx === result.items.length - 1
? commandPaletteStyles.searchResultItemLast
: commandPaletteStyles.searchResultItem
}
>
<EuiFlexGroup>
<EuiFlexItem>
<a
href={item.link}
onClick={(e) => {
e.preventDefault();
console.log(
"Search result clicked:",
item.name,
);

onClose();

setSearchText("");

console.log("Navigating to:", item.link);
navigate(item.link);
}}
style={{
color: "#0077cc",
textDecoration: "none",
}}
>
<strong>{item.name}</strong>
</a>
{item.description && (
<div
style={commandPaletteStyles.itemDescription}
>
{item.description}
</div>
)}
</EuiFlexItem>
{item.type && (
<EuiFlexItem grow={false}>
<EuiBadge>{item.type}</EuiBadge>
</EuiFlexItem>
)}
</EuiFlexGroup>
</div>
))}
</EuiPanel>
<EuiSpacer size="m" />
</div>
))
) : (
<EuiPanel hasBorder={true} paddingSize="m" color="subdued">
<EuiText textAlign="center">
<p>No matches found for "{searchText}"</p>
</EuiText>
</EuiPanel>
)}
</div>
) : (
<EuiText color="subdued" textAlign="center">
<p>Start typing to search...</p>
</EuiText>
)}
</div>
</div>
</div>
);
};

export default CommandPalette;
17 changes: 14 additions & 3 deletions ui/src/components/GlobalSearchShortcut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,26 @@ const GlobalSearchShortcut: React.FC<GlobalSearchShortcutProps> = ({
}) => {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && event.key === "k") {
console.log(
"Key pressed:",
event.key,
"metaKey:",
event.metaKey,
"ctrlKey:",
event.ctrlKey,
);
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
console.log("Cmd+K detected, preventing default and calling onOpen");
event.preventDefault();
event.stopPropagation();
onOpen();
}
};

document.addEventListener("keydown", handleKeyDown);
console.log("Adding keydown event listener to window");
window.addEventListener("keydown", handleKeyDown, true);
return () => {
document.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keydown", handleKeyDown, true);
};
}, [onOpen]);

Expand Down
Loading