cf2f1c96a3
Arrow up/down cycles through items with highlight. Enter selects the highlighted item. Escape dismisses the dropdown. Index resets when the query changes.
53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
import type { Member } from "../stores/member.ts";
|
|
|
|
interface MentionDropdownProps {
|
|
query: string;
|
|
members: Member[];
|
|
selectedIndex: number;
|
|
onSelect: (username: string) => void;
|
|
}
|
|
|
|
export function MentionDropdown({ query, members, selectedIndex, onSelect }: MentionDropdownProps) {
|
|
const q = query.toLowerCase();
|
|
const filtered = members
|
|
.filter(
|
|
(m) =>
|
|
m.username.toLowerCase().includes(q) ||
|
|
m.display_name?.toLowerCase().includes(q),
|
|
)
|
|
.slice(0, 6);
|
|
|
|
if (filtered.length === 0) return null;
|
|
|
|
return (
|
|
<div className="absolute bottom-full left-0 mb-1 z-50 w-64 max-h-48 overflow-y-auto bg-gb-bg-s border border-gb-bg-t shadow-lg">
|
|
<div className="px-2 py-1 text-xs text-gb-fg-s font-mono border-b border-gb-bg-t">
|
|
MENTION
|
|
</div>
|
|
{filtered.map((m, i) => (
|
|
<button
|
|
key={m.id}
|
|
type="button"
|
|
onMouseDown={(e) => {
|
|
e.preventDefault();
|
|
onSelect(m.username);
|
|
}}
|
|
className={`w-full px-2 py-1 text-left text-sm font-mono flex items-center gap-2 transition-colors ${
|
|
i === selectedIndex
|
|
? "bg-gb-orange text-gb-bg"
|
|
: "hover:bg-gb-orange hover:text-gb-bg"
|
|
}`}
|
|
>
|
|
<span className={i === selectedIndex ? "text-gb-bg" : "text-gb-green"}>●</span>
|
|
<span className="truncate">{m.display_name || m.username}</span>
|
|
{m.display_name && m.display_name !== m.username && (
|
|
<span className={`text-xs ${i === selectedIndex ? "text-gb-bg" : "text-gb-fg-f"}`}>
|
|
({m.username})
|
|
</span>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|