nexus/ui/src/components/ChatSlashCommandPopover.tsx
Nexus Dev 8f0367d0b4 feat(22-04): slash command routing utility and ChatSlashCommandPopover
- Create ui/src/lib/slash-commands.ts with SLASH_COMMANDS (5 commands) and resolveAgentFromContent
- Create ChatSlashCommandPopover (260px, opens upward, /search greyed with Coming soon)
- Add test coverage for routing logic (slash commands, @mentions, fallback)
2026-04-04 03:55:47 +00:00

69 lines
2 KiB
TypeScript

import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
Command,
CommandEmpty,
CommandGroup,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { SLASH_COMMANDS } from "../lib/slash-commands";
import { cn } from "../lib/utils";
interface ChatSlashCommandPopoverProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSelect: (command: string) => void;
query: string;
children: React.ReactNode;
}
export function ChatSlashCommandPopover({
open,
onOpenChange,
onSelect,
query,
children,
}: ChatSlashCommandPopoverProps) {
const filtered = SLASH_COMMANDS.filter((cmd) =>
cmd.command.toLowerCase().includes(query.toLowerCase()),
);
return (
<Popover open={open} onOpenChange={onOpenChange}>
<PopoverTrigger asChild>{children}</PopoverTrigger>
<PopoverContent
className="w-[260px] p-0"
align="start"
side="top"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<Command>
<CommandList>
<CommandEmpty>No matching commands</CommandEmpty>
<CommandGroup>
{filtered.map((cmd) => (
<CommandItem
key={cmd.command}
disabled={cmd.disabled}
onSelect={() => {
if (!cmd.disabled) {
onSelect(cmd.command);
onOpenChange(false);
}
}}
className={cn("flex flex-col items-start", cmd.disabled && "opacity-50")}
>
<span className="text-sm font-medium">{cmd.command}</span>
<span className="text-[13px] text-muted-foreground">
{cmd.description}
{cmd.disabled && " (Coming soon)"}
</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}