81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
import { type Component, createSignal, For, createEffect } from "solid-js";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export interface MentionItem {
|
|
id: string;
|
|
label: string;
|
|
sublabel?: string;
|
|
type: 'user' | 'note';
|
|
}
|
|
|
|
interface MentionListProps {
|
|
items: MentionItem[];
|
|
command: (item: MentionItem) => void;
|
|
ref: (handlers: { onKeyDown: (e: KeyboardEvent) => boolean }) => void;
|
|
}
|
|
|
|
export const MentionList: Component<MentionListProps> = (props) => {
|
|
const [selectedIndex, setSelectedIndex] = createSignal(0);
|
|
|
|
createEffect(() => {
|
|
props.items;
|
|
setSelectedIndex(0);
|
|
});
|
|
|
|
const selectItem = (index: number) => {
|
|
const item = props.items[index];
|
|
if (item) {
|
|
props.command(item);
|
|
}
|
|
};
|
|
|
|
props.ref({
|
|
onKeyDown: (e: KeyboardEvent) => {
|
|
if (e.key === "ArrowUp") {
|
|
setSelectedIndex((prev) => (prev + props.items.length - 1) % props.items.length);
|
|
return true;
|
|
}
|
|
if (e.key === "ArrowDown") {
|
|
setSelectedIndex((prev) => (prev + 1) % props.items.length);
|
|
return true;
|
|
}
|
|
if (e.key === "Enter") {
|
|
selectItem(selectedIndex());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
|
|
return (
|
|
<div class="z-[200] w-64 bg-popover border border-border rounded-lg shadow-xl overflow-hidden p-1 bg-background pointer-events-auto">
|
|
<div class="max-h-[300px] overflow-y-auto scrollbar-thin scrollbar-thumb-muted-foreground/20 overscroll-contain">
|
|
<For each={props.items}>
|
|
{(item, index) => (
|
|
<button
|
|
class={cn(
|
|
"flex flex-col w-full px-2 py-1.5 text-left rounded-md transition-colors outline-none",
|
|
index() === selectedIndex() ? "bg-accent text-accent-foreground" : "hover:bg-muted"
|
|
)}
|
|
onMouseDown={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
selectItem(index());
|
|
}}
|
|
onMouseEnter={() => setSelectedIndex(index())}
|
|
>
|
|
<span class="text-sm font-medium leading-none">{item.label}</span>
|
|
{item.sublabel && (
|
|
<span class="text-[0.6875rem] text-muted-foreground truncate mt-1">{item.sublabel}</span>
|
|
)}
|
|
</button>
|
|
)}
|
|
</For>
|
|
{props.items.length === 0 && (
|
|
<div class="p-2 text-xs text-muted-foreground text-center">No results found</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|