Files
root-org/src/lib/components/matrix/MentionAutocomplete.svelte

97 lines
2.6 KiB
Svelte

<script lang="ts">
import { MatrixAvatar } from "$lib/components/ui";
import type { RoomMember } from "$lib/matrix/types";
interface Props {
members: RoomMember[];
query: string;
onSelect: (member: RoomMember) => void;
onClose: () => void;
position?: { top: number; left: number };
}
let {
members,
query,
onSelect,
onClose,
position = { top: 0, left: 0 },
}: Props = $props();
let selectedIndex = $state(0);
// Filter members based on query
const filteredMembers = $derived(
members
.filter(
(m) =>
m.name.toLowerCase().includes(query.toLowerCase()) ||
m.userId.toLowerCase().includes(query.toLowerCase()),
)
.slice(0, 8),
);
// Reset selection when query changes
$effect(() => {
query;
selectedIndex = 0;
});
function handleKeyDown(e: KeyboardEvent) {
if (filteredMembers.length === 0) return;
switch (e.key) {
case "ArrowDown":
e.preventDefault();
selectedIndex = (selectedIndex + 1) % filteredMembers.length;
break;
case "ArrowUp":
e.preventDefault();
selectedIndex =
(selectedIndex - 1 + filteredMembers.length) % filteredMembers.length;
break;
case "Enter":
case "Tab":
e.preventDefault();
if (filteredMembers[selectedIndex]) {
onSelect(filteredMembers[selectedIndex]);
}
break;
case "Escape":
e.preventDefault();
onClose();
break;
}
}
// Expose keyboard handler for parent to call
export { handleKeyDown };
</script>
{#if filteredMembers.length > 0}
<div
class="absolute z-50 bg-dark border border-light/10 rounded-lg shadow-xl overflow-hidden max-h-64 overflow-y-auto"
style="bottom: 100%; left: 0; margin-bottom: 8px; min-width: 250px;"
>
<div class="p-2 text-xs text-light/50 border-b border-light/10">
Members matching @{query}
</div>
{#each filteredMembers as member, i}
<button
class="w-full flex items-center gap-3 px-3 py-2 text-left transition-colors {i ===
selectedIndex
? 'bg-primary/20'
: 'hover:bg-light/5'}"
onclick={() => onSelect(member)}
onmouseenter={() => (selectedIndex = i)}
>
<MatrixAvatar mxcUrl={member.avatarUrl} name={member.name} size="sm" />
<div class="flex-1 min-w-0">
<p class="text-light truncate">{member.name}</p>
<p class="text-xs text-light/40 truncate">{member.userId}</p>
</div>
</button>
{/each}
</div>
{/if}