learning_ai_common_plat/packages/rich-text/src/MentionList.tsx
saravanakumardb1 fc8502ac0c feat(rich-text): @bytelyst/rich-text@0.1.0 on Tiptap v3
RichTextEditor (toolbar + slash menu + async mentions, SSR-safe via
immediatelyRender:false) + RichTextViewer (generateHTML, server-renderable) +
standalone Toolbar. Pure filterSlashItems/filterUsers helpers. 12/12 vitest
(incl. live editor mount + bold toggle in happy-dom); tsc clean; published to
Gitea.
2026-05-28 18:20:34 -07:00

75 lines
2.3 KiB
TypeScript

import * as React from 'react';
import type { SuggestionProps, SuggestionKeyDownProps } from '@tiptap/suggestion';
import type { MentionUser } from './mention.js';
export interface MentionListHandle {
onKeyDown: (props: SuggestionKeyDownProps) => boolean;
}
/** Keyboard-navigable people picker shown while typing `@`. */
export const MentionList = React.forwardRef<MentionListHandle, SuggestionProps<MentionUser>>(
function MentionList(props, ref) {
const { items, command } = props;
const [selected, setSelected] = React.useState(0);
React.useEffect(() => setSelected(0), [items]);
const select = React.useCallback(
(index: number) => {
const item = items[index];
if (item) command({ id: item.id, label: item.label });
},
[items, command]
);
React.useImperativeHandle(ref, () => ({
onKeyDown: ({ event }) => {
if (event.key === 'ArrowUp') {
setSelected(s => (s + items.length - 1) % items.length);
return true;
}
if (event.key === 'ArrowDown') {
setSelected(s => (s + 1) % items.length);
return true;
}
if (event.key === 'Enter') {
select(selected);
return true;
}
return false;
},
}));
return (
<div
role="listbox"
aria-label="Mention people"
className="min-w-48 overflow-hidden rounded-lg border border-[var(--bl-border,rgba(0,0,0,0.12))] bg-[var(--bl-surface-card,#fff)] p-1 shadow-lg"
>
{items.length === 0 ? (
<div className="px-2.5 py-1.5 text-sm text-[var(--bl-text-tertiary,#999)]">No people</div>
) : (
items.map((item, index) => (
<button
key={item.id}
type="button"
role="option"
aria-selected={index === selected}
onMouseEnter={() => setSelected(index)}
onClick={() => select(index)}
className={
'flex w-full items-center rounded-md px-2.5 py-1.5 text-left text-sm ' +
(index === selected
? 'bg-[var(--bl-accent-muted,rgba(99,102,241,0.12))]'
: 'bg-transparent')
}
>
{item.label}
</button>
))
)}
</div>
);
}
);