feat(messaging-ui): rendered Messenger components — the drop-in UI
Turns the SDK from hooks-only into a real UI SDK: <Messenger> (conversation list + open thread + composer), plus <ConversationList> and <Thread>, all driven by the existing useConversations/useMessages hooks over the injected adapter — zero transport imports (boundary intact). - themeable via --miu-* CSS variables; default theme ships as ./styles.css - optimistic send, typing indicator, seen ticks, reactions display, unread badges - render smoke tests (jsdom + MockAdapter): mounts, auto-selects first thread, sends a message, switches threads (3 tests) — 48 total green - tsup: css bundled to dist/styles.css; dts scoped to TS entries Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import type { Conversation } from '../types';
|
||||
|
||||
/** Initials for the avatar chip — first letters of the first two words. */
|
||||
function initials(title: string): string {
|
||||
const parts = title.trim().split(/\s+/).filter(Boolean);
|
||||
const chars = (parts[0]?.[0] ?? '') + (parts[1]?.[0] ?? '');
|
||||
return (chars || '?').toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Presentational conversation list. Owns no data fetching — the parent passes the
|
||||
* conversations (from useConversations) so a host can also drive it from its own store.
|
||||
*/
|
||||
export function ConversationList({
|
||||
conversations,
|
||||
selectedId,
|
||||
onSelect,
|
||||
}: {
|
||||
conversations: Conversation[];
|
||||
selectedId?: string | null;
|
||||
onSelect: (threadId: string) => void;
|
||||
}) {
|
||||
if (conversations.length === 0) {
|
||||
return <div className="miu-empty">No conversations yet.</div>;
|
||||
}
|
||||
return (
|
||||
<ul className="miu-convlist" role="list">
|
||||
{conversations.map((c) => (
|
||||
<li key={c.threadId}>
|
||||
<button
|
||||
type="button"
|
||||
className={`miu-convrow${c.threadId === selectedId ? ' is-active' : ''}`}
|
||||
onClick={() => onSelect(c.threadId)}
|
||||
>
|
||||
<span className="miu-avatar" aria-hidden="true">
|
||||
{initials(c.title)}
|
||||
</span>
|
||||
<span className="miu-convrow-main">
|
||||
<span className="miu-convrow-title">{c.title}</span>
|
||||
{c.lastMessage ? <span className="miu-convrow-preview">{c.lastMessage}</span> : null}
|
||||
</span>
|
||||
{c.unread > 0 ? (
|
||||
<span className="miu-badge" aria-label={`${c.unread} unread`}>
|
||||
{c.unread}
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user