97 lines
2.6 KiB
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}
|