@nuvira/chat-components
v0.1.10
Published
Chat UI for React (MUI): components and theme; dual entry for Next.js App Router — root is a Client bundle, @nuvira/chat-components/server for RSC-safe utilities and types
Readme
@nuvira/chat-components
React chat UI for Nuvira: MUI-based theming, message renderers, conversation list (ChatList, ChatListItem, ContactBadgeGroup), CRM sidebar (ChatSidebar), and full thread shell (ChatWindow, header, messages, input).
Install
npm install @nuvira/chat-componentsPeer dependencies
The package expects your app to provide compatible versions of React, MUI, and Emotion (install these in your application; they are not bundled):
reactandreact-dom(>= 18)@mui/materialand@mui/icons-material(>= 5)@emotion/reactand@emotion/styled(>= 11)
Runtime dependencies shipped with the package: lodash, date-fns.
Usage
Wrap your app (or subtree) with MUI’s ThemeProvider and CssBaseline, using createChatTheme for defaults or overrides:
import { ThemeProvider, CssBaseline } from "@mui/material";
import {
createChatTheme,
TextMessage,
type TextMessageProps,
} from "@nuvira/chat-components";
const theme = createChatTheme({
palette: { primary: { main: "#1976d2" } },
});
export function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<TextMessage message="Hello" intent="general_inquiry" />
</ThemeProvider>
);
}Public API
- Server / RSC:
@nuvira/chat-components/server— types,mergeChatThreadAlerts,createChatTheme, pure helpers (pickIntegration, …), and other non-UI exports you can run in Server Components (see Server Components below). Fetch-based Nuvira defaults (createNuviraChatIntegration,nuviraDefault*) stay on the root entry. UI and client hooks stay on the root. - Theme:
createChatTheme,ChatThemeOptions - Message components:
TextMessage,ImageMessage,AudioMessage,VideoMessage,DocumentMessage, and their*Propstypes - Conversation list:
ChatList,ChatListItem,ChatListProps,ChatListItemProps,ChatListItemData - Contact badges:
ContactBadgeGroup,ContactBadgeGroupProps(status, funnel, and stage chips in one row) - Layout:
CollapsibleEdgePanel,CollapsibleEdgePanelProps - Agent / thread chrome:
ChatAgentSwitch,ChatAiCover,ChatMessage,ChatMessagesContainer,ChatInput,ChatWindowHeader,ChatWindow,ChatThreadAlerts, and their*Propstypes (ChatInputMediaFilefor attachments) - Thread alerts (typed):
ChatThreadAlert,ChatThreadAlertSeverity,mergeChatThreadAlerts,MergeChatThreadAlertsInput, reserved id constants (CHAT_THREAD_ALERT_ID_LAST_MESSAGE_ERROR,CHAT_THREAD_ALERT_ID_REACHABILITY_WINDOW),CHAT_THREAD_ALERT_REACHABILITY_MESSAGE - AI summary:
AISummary,AISummaryProps - Funnel & stage:
FunnelStageSelector,FunnelSelector,StageSelector, and their*Propstypes - CRM sidebar (accordion sections):
ChatSidebar(canonical name;ConsolidatedChatActionsis a deprecated alias), plusChatContactStatus,StatusChangeDisplay,ContactStatusHistoryList,ContactStatusHistoryButton,ContactInfoEditor,ChatContactNotes, and their prop types - Types & constants:
MessageType,Contact,ContactMessage,ContactNotes,Workspace,CustomFunnel,CustomStage,ContactStatusHistory,ContactStatus,MediaState,MediaFile,ChatSidebarSectionId,ChatSidebarSectionConfig,ChatSidebarCustomSection,ChatSidebarProps, andCHAT_SIDEBAR_*constants - Integration (typed callbacks):
ChatIntegrationAdapter,createNuviraChatIntegration, payload types (SaveContactInput,SendChatMessageInput, …), and Nuvira default implementations (nuviraDefaultSaveContact,nuviraDefaultSendChatMessage, …). HelperspickIntegrationandpickOnIntegrationErrorare mainly for advanced composition.mergeOnIntegrationErrorremains as a deprecated alias forpickOnIntegrationError(it picks section-over-root; it does not merge handlers). - Legacy integration helpers:
ContactStatusHistoryListItem,UseTimelineStreamOptions,useTimelineStream,useIsMobile,uploadMediaFileWithUrls,CONTACT_UPDATED_BROADCAST_MESSAGE_TYPE.fetchContactStatusHistoryDefaultis deprecated; usenuviraDefaultLoadContactStatusHistoryinstead (same implementation).
ChatWindow accepts optional integration, onSendMessage, onUpdateTalkingToAgent, plus useTimelineStream, useIsMobile, uploadMediaFileWithUrls, and contactUpdatedBroadcastType for realtime, layout, and uploads. It also accepts alerts (ChatThreadAlert[]), showReachabilityWindow, onThreadAlertDismissed, and components.chatThreadAlerts for the thread alert strip (see Thread alerts below).
SSR / Next.js (App Router)
CollapsibleEdgePanel: The width animation honorsprefers-reduced-motionthrough CSS insx, notuseMediaQuery, so the server render and the first client paint stay aligned for that concern (no extramatchMediadivergence in React output).ChatWindowand viewport layout: Responsive branching (for example split inbox onmd+vs stacked mobile) is your app’s responsibility. PassuseIsMobile(or equivalent) in an SSR-safe way—for example MUI’suseMediaQuerywithnoSsrand an explicitdefaultMatchesso server HTML matches the initial client render, or a stable default until client-only logic runs. See MUI’s useMediaQuery and server-side rendering.
Server Components (React / Next.js App Router)
The root export (@nuvira/chat-components) is built as a Client Component bundle: the published dist/index.mjs / dist/index.js start with "use client". Import ChatWindow, ChatList, hooks, and the rest of the UI from a file that begins with "use client" (or from another client component).
Use the @nuvira/chat-components/server subpath in Server Components for anything that must not pull the client graph:
mergeChatThreadAlerts, alert types and id constants- Domain types (
Contact,ChatThreadAlert,ChatListItemData, sidebar types, …) and sidebar section constants createChatTheme/ChatThemeOptions(theme object only; wrap with MUIThemeProviderin a client layout)pickIntegration,pickOnIntegrationError/mergeOnIntegrationError(pure; nofetch)uploadMediaFileWithUrls(stub — no network),CONTACT_UPDATED_BROADCAST_MESSAGE_TYPE- Type-only props:
ChatListAvatarComponentProps,ContactBadgeGroupComponents,ChatMessageUseMediaUrl,ChatAiCoverProps,ContactStatusHistoryListItem, and integration payload types
Root entry only (client-oriented): createNuviraChatIntegration, nuviraDefault* helpers, and deprecated fetchContactStatusHistoryDefault use fetch("/api/...") with relative URLs. They are for browser execution; in Node / RSC, relative fetch is invalid — build your adapter with absolute URLs or call these only from client code.
Not on the server entry: useTimelineStream, useIsMobile, and every React component.
// app/thread/alerts.ts — Server Component module
import {
mergeChatThreadAlerts,
type ChatThreadAlert,
} from "@nuvira/chat-components/server";Thread alerts
Thread alerts are typed values rendered in a strip below ChatWindowHeader and above ChatMessagesContainer. Use them for standardized warnings, errors, and info (severity maps to MUI Alert).
Shape (ChatThreadAlert):
| Field | Type | Purpose |
| --- | --- | --- |
| id | string | Stable key for React lists, deduplication, and replacing built-ins |
| severity | "error" \| "warning" \| "info" \| "success" | Visual level |
| message | string | Main body text |
| title | string (optional) | Optional title above the body |
| dismissible | boolean (optional) | When true, shows a close control; dismissal is stored inside ChatThreadAlerts (no parent state). If that id later disappears from alerts, the dismissal clears for that id so it can show again |
Dismissal: ChatThreadAlerts keeps a set of dismissed ids. Optional onAlertDismissed(id) on ChatThreadAlerts, or onThreadAlertDismissed on ChatWindow, runs at most once per id each time that id newly enters the dismissed set (after React commits state)—for example analytics. Redundant close actions do not invoke the callback again. Custom components.chatThreadAlerts.Alert receives onDismiss when dismissible is true — call it from your close control and do not forward dismissible / onDismiss to DOM nodes.
Merging (mergeChatThreadAlerts): ChatWindow merges alerts with built-ins from contact and flags:
- Last message error — If
contact.lastMessageErrorReasonis set, a warning with idCHAT_THREAD_ALERT_ID_LAST_MESSAGE_ERROR(nuvira:last-message-error) is included unless you pass your own alert with thatid(e.g. localized copy). - Reachability — If
showReachabilityWindowis true, a warning with idCHAT_THREAD_ALERT_ID_REACHABILITY_WINDOW(nuvira:reachability-window) and messageCHAT_THREAD_ALERT_REACHABILITY_MESSAGEis included unless you pass an alert with thatid. - Additional app alerts — Remaining entries from
alertsare appended in order. For duplicateidvalues insidealerts, the last occurrence wins. Alerts whoseidwas already emitted from steps 1–2 are skipped.
Migration (ChatWindowHeader): The showAlert prop was removed from ChatWindowHeader. Use ChatWindow with showReachabilityWindow and/or typed alerts instead (see CHANGELOG.md).
You can call mergeChatThreadAlerts yourself when building custom layouts; ChatWindow uses it internally.
Example:
import { ChatWindow, type ChatThreadAlert } from "@nuvira/chat-components";
const extraAlerts: ChatThreadAlert[] = [
{
id: "billing-hold",
severity: "info",
title: "Billing",
message: "Invoice pending review.",
dismissible: true
}
];
<ChatWindow
{...windowProps}
alerts={extraAlerts}
showReachabilityWindow
onThreadAlertDismissed={(id) => {
/* optional: telemetry */
}}
/>;i18n: Set showReachabilityWindow={false} and pass an alert with id: CHAT_THREAD_ALERT_ID_REACHABILITY_WINDOW and your translated message, or pass the same id with localized text to override the default string.
Customization: ChatWindow supports components.chatThreadAlerts.Alert — a component receiving ChatThreadAlert fields plus sx and optional onDismiss (same pattern as other components maps). You can also render ChatThreadAlerts standalone with your own alerts array and onAlertDismissed.
Thread vs CRM: On ChatWindow, onSendMessage and onUpdateTalkingToAgent are resolved in order: window prop → integration → Nuvira default. That resolution applies to the thread/header (messages, agent toggle). The CRM sidebar receives the same integration for mutations and loaders (saveContact, onGenerateSummary, funnel/stage, status history, etc.). The sidebar does not get separate onSendMessage / onUpdateTalkingToAgent props; include those on ChatIntegrationAdapter only if a sidebar-related code path should call them.
ChatIntegrationAdapter and Nuvira defaults
Pass a single integration object to ChatSidebar or ChatWindow to override how the CRM sidebar and thread talk to your backend. createNuviraChatIntegration() returns an adapter wired to the same Nuvira fetch routes the library used internally before (you can still override individual fields).
- Per-section overrides:
ChatSidebarsupportssectionConfig[sectionId].integration— each field there wins over the rootintegrationfor that section only. - Per-component props (e.g.
saveContactonContactInfoEditor,onGenerateSummaryonAISummary) override both root and section adapter values when set.
Set onIntegrationError on the adapter (or section override) to receive mutation/load failures in one place instead of relying on alert or console-only behavior.
ContactInfoEditor persists via saveContact (default: nuviraDefaultSaveContact → PUT /api/v1/contact/info). Storybook and tests should pass a no-op or mock saveContact when the API is unavailable.
UI components maps (stub substitution)
Optional components props let you swap MUI/stub primitives without forking:
ChatList:itemComponents→ forwarded to eachChatListItem(Avatar, nestedbadgeGroupchips).ChatListItem/ChatWindowHeader:components.Avatar(seeChatListAvatarComponentProps), header alsocomponents.Loading,components.badgeGroup.ContactBadgeGroup:StatusChip,FunnelDisplay,StageDisplay(ContactBadgeGroupComponents).ChatInput:TextField(defaults toNvTextField).ChatMessage:useMediaUrlhook (same signature as the package default; must follow rules of hooks).ChatMessagesContainer:components.AiCover,components.useMediaUrlfor child messages.ChatWindow:components.chatThreadAlerts.Alert→ forwarded toChatThreadAlerts(ChatThreadAlertRenderProps).ChatAiCover:components.Loading.AISummary:components.Loadingon the generate button.StatusChangeDisplay:ContactStatusChip,CustomStageChip.ChatContactNotes:NoteCard,NoteForm,Modal.
Stubs and app integration
Several components still default to Nuvira-branded stubs under src/stubs/ when you do not pass components. They ship with the package for quick demos.
Status history: ContactStatusHistoryList / ContactStatusHistoryButton accept optional loadHistory; default is nuviraDefaultLoadContactStatusHistory. The same loader is ChatIntegrationAdapter.loadContactStatusHistory. (fetchContactStatusHistoryDefault is deprecated but still exported.)
Not published: ChatWindowSC (server/prisma demo) remains internal to this repo only.
Conversation list and badges
These components are presentational: your app owns fetching conversations, selection state, and routing.
ChatListItemData
Type for each row. It extends contact-like fields (id, name, status, customFunnelId, customStageId, lastMessageErrored, lastMessageErrorReason) with optional list fields:
| Field | Purpose |
| --- | --- |
| avatarUrl | Image URL for the avatar; initials fallback when absent |
| subtitle | Last message preview (single line, ellipsis) |
| updatedAt | Shown as relative time (today / yesterday / date) |
| unreadCount | Badge on the avatar when greater than 0 |
ChatList
Scrollable MUI List of ChatListItem rows.
items–ChatListItemData[]selectedId– highlights the matching rowonSelect(id)– called when a row is activatedemptyState– custom node whenitemsis empty (default copy: “No conversations”)sx– root stylesitemComponents– optionalcomponentsfor eachChatListItem(avatar / badge chips)
ChatListItem
One conversation row: ListItemButton, avatar (with optional unread badge), name, optional subtitle/time, ContactBadgeGroup, and optional error icon when lastMessageErrored / lastMessageErrorReason is set.
item–ChatListItemDataselected,onClick,slotBadges(extra chips after the default badges),sx
ContactBadgeGroup
Single row of chips: contact status, then funnel and stage when IDs are present. Use slotEnd for extra chips (e.g. channel or “VIP”).
Example: list + selection
import {
ChatList,
type ChatListItemData,
} from "@nuvira/chat-components";
const items: ChatListItemData[] = [
{
id: "c1",
name: "Ada",
status: "LEAD_NEW",
subtitle: "See you tomorrow",
updatedAt: new Date(),
unreadCount: 2,
},
];
export function Inbox() {
const [selectedId, setSelectedId] = useState<string | null>(null);
return (
<ChatList
items={items}
selectedId={selectedId}
onSelect={setSelectedId}
sx={{ maxHeight: 400 }}
/>
);
}Storybook and internal layout demos
Run npm run storybook to see:
- Chat list / Chat list item stories
- Chat list with ChatWindow – composite layout (list +
ChatWindow, collapsible panels,sidebarPosition, etc.)
Story demos may import from @/ paths in this repo; published consumers should import from @nuvira/chat-components per the list above (src/index.ts).
Development
npm install
npm run storybookQuality checks:
npm run check
npm run buildPublishing (maintainers)
Publishing is manual. Before the first (or any) release:
- Ensure
mainis green (npm run check,npm run build). - Bump
versioninpackage.jsonas appropriate. - Inspect the tarball:
npm pack --dry-run - Publish:
npm publish(scoped public access is set inpublishConfig).
prepublishOnly runs npm run build so the published package always includes a fresh dist/.
Pull requests: If you change package.json exports, the root vs @nuvira/chat-components/server split, or the "use client" build banner, say so in the PR title or description (not only a component name like ChatWindow) so reviewers and release notes match the packaging impact.
License
MIT. See LICENSE.
