Files
DataClaw/frontend/src/components/SlashCommandMenu.tsx
T

72 lines
2.2 KiB
TypeScript
Raw Normal View History

2026-03-21 21:26:57 +08:00
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
2026-03-18 17:28:48 +08:00
import { cn } from "@/lib/utils";
interface Skill {
id: string;
name: string;
description?: string;
type: string;
}
interface SlashCommandMenuProps {
isOpen: boolean;
skills: Skill[];
selectedIndex: number;
onSelect: (skill: Skill) => void;
onClose: () => void;
}
export function SlashCommandMenu({ isOpen, skills, selectedIndex, onSelect, onClose }: SlashCommandMenuProps) {
2026-03-21 21:26:57 +08:00
const { t } = useTranslation();
2026-03-18 17:28:48 +08:00
const menuRef = useRef<HTMLDivElement>(null);
const selectedRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (isOpen && selectedRef.current) {
selectedRef.current.scrollIntoView({ block: 'nearest' });
}
}, [isOpen, selectedIndex]);
// Click outside to close
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
onClose();
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen, onClose]);
if (!isOpen || skills.length === 0) return null;
return (
<div
ref={menuRef}
2026-03-28 16:25:35 +08:00
className="absolute bottom-full left-0 mb-2 w-full max-w-md overflow-hidden rounded-xl border border-border bg-popover shadow-2xl animate-in fade-in slide-in-from-bottom-2 duration-100 z-50"
2026-03-18 17:28:48 +08:00
>
<div className="max-h-[240px] overflow-y-auto py-1.5 custom-scrollbar">
{skills.map((skill, index) => (
<button
key={skill.id}
ref={index === selectedIndex ? selectedRef : null}
onClick={() => onSelect(skill)}
className={cn(
"w-full flex items-center gap-3 px-3 py-2.5 text-left text-sm transition-colors",
2026-03-28 16:25:35 +08:00
index === selectedIndex ? "bg-accent" : "hover:bg-accent/50"
2026-03-18 17:28:48 +08:00
)}
>
<span className="font-bold text-blue-400 shrink-0 font-mono">/{skill.name}</span>
2026-03-28 16:25:35 +08:00
<span className="text-muted-foreground truncate text-xs">{skill.description || t('noDescription')}</span>
2026-03-18 17:28:48 +08:00
</button>
))}
</div>
</div>
);
}