/** * @usevyre/react — Conversation * * AI CONTEXT: * ┌─────────────────────────────────────────────────────────────────┐ * │ Component: Conversation (chat / inbox message thread) │ * │ Import: import { Conversation } from "@usevyre/react" │ * │ │ * │ CONTROLLED & data-driven (like Kanban/DataGrid). No internal │ * │ message state — you own `value`, append in your own handler. │ * │ │ * │ Props: │ * │ value = ConversationMessage[] (required) │ * │ currentUserId = string (required — whose messages align right)│ * │ composer? = boolean (built-in input + Send; default false) │ * │ onSend? = (text: string, files: File[]) => void │ * │ placeholder? = string (composer placeholder) │ * │ typing? = boolean | string (show typing indicator) │ * │ allowAttachments? = boolean (📎 button + staged-file chips) │ * │ accept? = string (file input accept, e.g. "image/*") │ * │ renderMessage?= (msg, meta) => ReactNode (custom bubble body) │ * │ renderComposer? = (api) => ReactNode (replace composer) │ * │ api = { value, setValue, files, setFiles, send } │ * │ │ * │ ConversationMessage = { │ * │ id; authorId; text?; │ * │ authorName?; authorAvatar?; │ * │ timestamp?: Date | string | number; │ * │ status?: "sending"|"sent"|"delivered"|"read"; │ * │ attachments?: ConversationAttachment[] │ * │ } │ * │ ConversationAttachment = { │ * │ kind: "image"|"audio"|"video"|"file"; │ * │ url; name?; size? │ * │ } → rendered inside the bubble (image preview, audio/video │ * │ player, or a download chip for files) │ * │ │ * │ Consecutive messages from the same author are grouped (avatar + │ * │ name shown once). Day separators inserted on date change. │ * │ Outgoing = authorId === currentUserId (aligned right). │ * └─────────────────────────────────────────────────────────────────┘ * * @example * const [messages, setMessages] = useState([ * { id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" }, * { id: "2", authorId: "me", text: "Hi 👋", status: "read" }, * ]); * setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])} * /> */ import React from "react"; import type { BaseProps } from "../../types"; export type ConversationStatus = "sending" | "sent" | "delivered" | "read"; export type ConversationAttachmentKind = "image" | "audio" | "video" | "file"; export interface ConversationAttachment { kind: ConversationAttachmentKind; /** Source/href for the media or download. */ url: string; /** Display name (file attachments) / alt text (image). */ name?: string; /** Optional human-readable size, e.g. "2.4 MB". */ size?: string; } export interface ConversationMessage { id: string; /** Identifies the sender. Matched against currentUserId for alignment. */ authorId: string; text?: string; authorName?: string; authorAvatar?: string; timestamp?: Date | string | number; status?: ConversationStatus; /** Image / audio / video / file attachments rendered inside the bubble. */ attachments?: ConversationAttachment[]; } /** Per-message layout info passed to renderMessage. */ export interface ConversationMessageMeta { outgoing: boolean; /** First message of a same-author run (avatar/name shown). */ isGroupStart: boolean; /** Last message of a same-author run (timestamp/status shown). */ isGroupEnd: boolean; } export interface ConversationComposerApi { value: string; setValue: (v: string) => void; /** Files staged via the attach button (when allowAttachments). */ files: File[]; setFiles: (f: File[]) => void; send: () => void; } export interface ConversationProps extends BaseProps { value: ConversationMessage[]; currentUserId: string; composer?: boolean; /** * Called when the composer submits. `files` holds anything staged via the * attach button (empty array when allowAttachments is off). You own the * upload + how staged files become message attachments. */ onSend?: (text: string, files: File[]) => void; placeholder?: string; typing?: boolean | string; /** Show an attach (📎) button + staged-file chips in the built-in composer. */ allowAttachments?: boolean; /** `accept` attribute forwarded to the file input (e.g. "image/*"). */ accept?: string; renderMessage?: (message: ConversationMessage, meta: ConversationMessageMeta) => React.ReactNode; renderComposer?: (api: ConversationComposerApi) => React.ReactNode; } export declare const Conversation: React.ForwardRefExoticComponent>;