@convokitapp/react-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/PARITY.md +32 -0
- package/README.md +110 -0
- package/dist/index.cjs +1412 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +476 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +316 -0
- package/dist/index.d.ts +316 -0
- package/dist/index.js +1372 -0
- package/dist/index.js.map +1 -0
- package/package.json +99 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { Message, Conversation as Conversation$1, MessageMedia, RealtimeSubscription, ReadEvent, TypingEvent, ConvoKitClient } from '@convokitapp/sdk';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { CSSProperties, HTMLAttributes, ReactNode, RefObject, TextareaHTMLAttributes, PropsWithChildren } from 'react';
|
|
4
|
+
|
|
5
|
+
interface ConvoKitUiClient {
|
|
6
|
+
readonly currentUserId: string;
|
|
7
|
+
getConversations(options: {
|
|
8
|
+
limit: number;
|
|
9
|
+
offset: number;
|
|
10
|
+
archived: boolean;
|
|
11
|
+
}): Promise<Conversation$1[]>;
|
|
12
|
+
getConversation(conversationId: string): Promise<Conversation$1>;
|
|
13
|
+
getMessages(options: {
|
|
14
|
+
conversationId: string;
|
|
15
|
+
limit: number;
|
|
16
|
+
offset: number;
|
|
17
|
+
}): Promise<Message[]>;
|
|
18
|
+
sendMessage(input: {
|
|
19
|
+
conversationId: string;
|
|
20
|
+
text?: string;
|
|
21
|
+
media?: MessageMedia[];
|
|
22
|
+
}): Promise<Message>;
|
|
23
|
+
markConversationRead(conversationId: string): Promise<void>;
|
|
24
|
+
sendTyping(input: {
|
|
25
|
+
conversationId: string;
|
|
26
|
+
isTyping: boolean;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
onMessage(conversationId: string, handler: (message: Message) => void, onError?: (error: Error) => void): RealtimeSubscription;
|
|
29
|
+
onReadReceipt(conversationId: string, handler: (event: ReadEvent) => void, onError?: (error: Error) => void): RealtimeSubscription;
|
|
30
|
+
onTyping(conversationId: string, handler: (event: TypingEvent) => void, onError?: (error: Error) => void): RealtimeSubscription;
|
|
31
|
+
}
|
|
32
|
+
interface ConversationFilter {
|
|
33
|
+
query?: string;
|
|
34
|
+
archived?: boolean;
|
|
35
|
+
participantIds?: ReadonlySet<string> | readonly string[];
|
|
36
|
+
requireAllParticipants?: boolean;
|
|
37
|
+
predicate?: (conversation: Conversation$1) => boolean;
|
|
38
|
+
comparator?: (left: Conversation$1, right: Conversation$1) => number;
|
|
39
|
+
}
|
|
40
|
+
interface ConversationPageRequest {
|
|
41
|
+
limit: number;
|
|
42
|
+
offset: number;
|
|
43
|
+
filter: ConversationFilter;
|
|
44
|
+
}
|
|
45
|
+
type ConversationPageLoader = (request: ConversationPageRequest) => Promise<Conversation$1[]>;
|
|
46
|
+
interface ConversationListState {
|
|
47
|
+
conversations: Conversation$1[];
|
|
48
|
+
filter: ConversationFilter;
|
|
49
|
+
isInitialLoading: boolean;
|
|
50
|
+
isLoadingMore: boolean;
|
|
51
|
+
hasMore: boolean;
|
|
52
|
+
hasLoaded: boolean;
|
|
53
|
+
error: unknown;
|
|
54
|
+
}
|
|
55
|
+
interface ConversationState {
|
|
56
|
+
conversation: Conversation$1 | null;
|
|
57
|
+
messages: Message[];
|
|
58
|
+
typingUserIds: ReadonlySet<string>;
|
|
59
|
+
readAtByUserId: ReadonlyMap<string, Date>;
|
|
60
|
+
isInitialLoading: boolean;
|
|
61
|
+
isLoadingOlder: boolean;
|
|
62
|
+
isSending: boolean;
|
|
63
|
+
hasOlderMessages: boolean;
|
|
64
|
+
hasLoaded: boolean;
|
|
65
|
+
error: unknown;
|
|
66
|
+
currentUserId: string;
|
|
67
|
+
readerIdsFor(message: Message): ReadonlySet<string>;
|
|
68
|
+
}
|
|
69
|
+
type ConvoKitUiDensity = 'comfortable' | 'compact';
|
|
70
|
+
type ConvoKitUiPart = 'root' | 'list' | 'listItem' | 'avatar' | 'header' | 'messages' | 'message' | 'incomingMessage' | 'outgoingMessage' | 'media' | 'receipt' | 'typing' | 'composer' | 'input' | 'button' | 'empty' | 'loading' | 'error';
|
|
71
|
+
interface ConvoKitAppearanceProps {
|
|
72
|
+
classNames?: Partial<Record<ConvoKitUiPart, string>>;
|
|
73
|
+
styles?: Partial<Record<ConvoKitUiPart, CSSProperties>>;
|
|
74
|
+
density?: ConvoKitUiDensity;
|
|
75
|
+
unstyled?: boolean;
|
|
76
|
+
}
|
|
77
|
+
interface ConversationItemRenderProps {
|
|
78
|
+
conversation: Conversation$1;
|
|
79
|
+
index: number;
|
|
80
|
+
selected: boolean;
|
|
81
|
+
onSelect: () => void;
|
|
82
|
+
}
|
|
83
|
+
interface MessageRenderProps {
|
|
84
|
+
message: Message;
|
|
85
|
+
chronologicalIndex: number;
|
|
86
|
+
isCurrentUser: boolean;
|
|
87
|
+
sender: Conversation$1['participants'][number] | undefined;
|
|
88
|
+
readerIds: ReadonlySet<string>;
|
|
89
|
+
}
|
|
90
|
+
interface MediaRenderProps {
|
|
91
|
+
media: MessageMedia;
|
|
92
|
+
message: Message;
|
|
93
|
+
isCurrentUser: boolean;
|
|
94
|
+
onOpen?: () => void;
|
|
95
|
+
}
|
|
96
|
+
interface ComposerRenderProps {
|
|
97
|
+
value: string;
|
|
98
|
+
setValue: (value: string) => void;
|
|
99
|
+
isSending: boolean;
|
|
100
|
+
send: () => void;
|
|
101
|
+
addAttachment?: () => void;
|
|
102
|
+
}
|
|
103
|
+
interface TypingRenderProps {
|
|
104
|
+
userIds: ReadonlySet<string>;
|
|
105
|
+
displayNameForUser: (userId: string) => string;
|
|
106
|
+
}
|
|
107
|
+
interface ReadReceiptRenderProps {
|
|
108
|
+
message: Message;
|
|
109
|
+
readerIds: ReadonlySet<string>;
|
|
110
|
+
}
|
|
111
|
+
interface ErrorRenderProps {
|
|
112
|
+
error: unknown;
|
|
113
|
+
retry?: () => void;
|
|
114
|
+
}
|
|
115
|
+
interface BaseViewProps extends ConvoKitAppearanceProps, Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'style'> {
|
|
116
|
+
style?: CSSProperties;
|
|
117
|
+
}
|
|
118
|
+
interface ScrollOptions {
|
|
119
|
+
scrollRef?: RefObject<HTMLDivElement | null>;
|
|
120
|
+
paginationThreshold?: number;
|
|
121
|
+
}
|
|
122
|
+
type StateRenderer = () => ReactNode;
|
|
123
|
+
type ErrorRenderer = (props: ErrorRenderProps) => ReactNode;
|
|
124
|
+
|
|
125
|
+
/** Adapts a connected `@convokitapp/sdk` client to the replaceable UI boundary. */
|
|
126
|
+
declare function createConvoKitUiClient(client: ConvoKitClient): ConvoKitUiClient;
|
|
127
|
+
|
|
128
|
+
interface ConvoKitAvatarProps extends HTMLAttributes<HTMLSpanElement> {
|
|
129
|
+
name: string;
|
|
130
|
+
src?: string | null;
|
|
131
|
+
}
|
|
132
|
+
/** Accessible avatar built on Radix Avatar. */
|
|
133
|
+
declare function ConvoKitAvatar({ name, src, className, ...props }: ConvoKitAvatarProps): react.JSX.Element;
|
|
134
|
+
|
|
135
|
+
interface UseConversationOptions {
|
|
136
|
+
client: ConvoKitUiClient;
|
|
137
|
+
conversationId: string;
|
|
138
|
+
messagePageSize?: number;
|
|
139
|
+
markReadOnLoad?: boolean;
|
|
140
|
+
markReadOnReceive?: boolean;
|
|
141
|
+
typingTimeoutMs?: number;
|
|
142
|
+
autoLoad?: boolean;
|
|
143
|
+
}
|
|
144
|
+
interface ConversationController extends ConversationState {
|
|
145
|
+
loadInitial(): Promise<void>;
|
|
146
|
+
refresh(): Promise<void>;
|
|
147
|
+
loadOlderMessages(): Promise<void>;
|
|
148
|
+
sendMessage(input: {
|
|
149
|
+
text?: string;
|
|
150
|
+
media?: MessageMedia[];
|
|
151
|
+
}): Promise<Message | null>;
|
|
152
|
+
markRead(): Promise<void>;
|
|
153
|
+
updateTyping(isTyping: boolean): Promise<void>;
|
|
154
|
+
}
|
|
155
|
+
/** Owns one conversation's history, realtime state, sending, and read positions. */
|
|
156
|
+
declare function useConversation({ client, conversationId, messagePageSize, markReadOnLoad, markReadOnReceive, typingTimeoutMs, autoLoad, }: UseConversationOptions): ConversationController;
|
|
157
|
+
|
|
158
|
+
interface MessageListViewProps extends BaseViewProps, ScrollOptions {
|
|
159
|
+
conversation: Conversation$1;
|
|
160
|
+
messages: readonly Message[];
|
|
161
|
+
currentUserId: string;
|
|
162
|
+
readAtByUserId?: ReadonlyMap<string, Date>;
|
|
163
|
+
readersResolver?: (message: Message) => ReadonlySet<string>;
|
|
164
|
+
onLoadOlder?: () => void | Promise<void>;
|
|
165
|
+
hasOlderMessages?: boolean;
|
|
166
|
+
isLoadingOlder?: boolean;
|
|
167
|
+
error?: unknown;
|
|
168
|
+
renderMessage?: (props: MessageRenderProps) => ReactNode;
|
|
169
|
+
renderMedia?: (props: MediaRenderProps) => ReactNode;
|
|
170
|
+
renderReadReceipt?: (props: ReadReceiptRenderProps) => ReactNode;
|
|
171
|
+
onAttachmentClick?: (media: MessageMedia, message: Message) => void;
|
|
172
|
+
renderEmpty?: StateRenderer;
|
|
173
|
+
renderLoadingOlder?: StateRenderer;
|
|
174
|
+
renderError?: ErrorRenderer;
|
|
175
|
+
reverse?: boolean;
|
|
176
|
+
stickToBottom?: boolean;
|
|
177
|
+
formatTime?: (date: Date) => string;
|
|
178
|
+
imageLoading?: 'eager' | 'lazy';
|
|
179
|
+
}
|
|
180
|
+
/** Controlled message history with read receipts, structured media, and older-page loading. */
|
|
181
|
+
declare function MessageListView({ conversation, messages, currentUserId, readAtByUserId, readersResolver, onLoadOlder, hasOlderMessages, isLoadingOlder, error, renderMessage, renderMedia, renderReadReceipt, onAttachmentClick, renderEmpty, renderLoadingOlder, renderError, scrollRef, paginationThreshold, reverse, stickToBottom, formatTime, imageLoading, className, style, classNames, styles, density, unstyled, onScroll, ...rootProps }: MessageListViewProps): react.JSX.Element;
|
|
182
|
+
|
|
183
|
+
interface ConversationViewProps extends BaseViewProps {
|
|
184
|
+
conversation: Conversation$1;
|
|
185
|
+
messages: readonly Message[];
|
|
186
|
+
currentUserId: string;
|
|
187
|
+
onSendMessage: (text: string) => boolean | void | Promise<boolean | void>;
|
|
188
|
+
typingUserIds?: ReadonlySet<string>;
|
|
189
|
+
readAtByUserId?: ReadonlyMap<string, Date>;
|
|
190
|
+
readersResolver?: (message: Message) => ReadonlySet<string>;
|
|
191
|
+
onBack?: () => void;
|
|
192
|
+
onRefresh?: () => void | Promise<void>;
|
|
193
|
+
onLoadOlder?: () => void | Promise<void>;
|
|
194
|
+
onTypingChange?: (isTyping: boolean) => void | Promise<void>;
|
|
195
|
+
onAddAttachment?: () => void;
|
|
196
|
+
onAttachmentClick?: (media: MessageMedia, message: Message) => void;
|
|
197
|
+
isInitialLoading?: boolean;
|
|
198
|
+
isLoadingOlder?: boolean;
|
|
199
|
+
isSending?: boolean;
|
|
200
|
+
hasOlderMessages?: boolean;
|
|
201
|
+
error?: unknown;
|
|
202
|
+
messageError?: unknown;
|
|
203
|
+
renderHeader?: (props: {
|
|
204
|
+
conversation: Conversation$1;
|
|
205
|
+
onBack?: () => void;
|
|
206
|
+
onRefresh?: () => void | Promise<void>;
|
|
207
|
+
}) => ReactNode;
|
|
208
|
+
renderMessage?: (props: MessageRenderProps) => ReactNode;
|
|
209
|
+
renderMedia?: (props: MediaRenderProps) => ReactNode;
|
|
210
|
+
renderReadReceipt?: (props: ReadReceiptRenderProps) => ReactNode;
|
|
211
|
+
renderComposer?: (props: ComposerRenderProps) => ReactNode;
|
|
212
|
+
renderTypingIndicator?: (props: TypingRenderProps) => ReactNode;
|
|
213
|
+
renderEmpty?: StateRenderer;
|
|
214
|
+
renderLoading?: StateRenderer;
|
|
215
|
+
renderError?: ErrorRenderer;
|
|
216
|
+
renderLoadingOlder?: StateRenderer;
|
|
217
|
+
renderMessageError?: ErrorRenderer;
|
|
218
|
+
displayNameForUser?: (userId: string) => string | null | undefined;
|
|
219
|
+
reverseMessages?: boolean;
|
|
220
|
+
stickToBottom?: boolean;
|
|
221
|
+
paginationThreshold?: number;
|
|
222
|
+
formatTime?: MessageListViewProps['formatTime'];
|
|
223
|
+
imageLoading?: MessageListViewProps['imageLoading'];
|
|
224
|
+
composerPlaceholder?: string;
|
|
225
|
+
composerAriaLabel?: string;
|
|
226
|
+
composerProps?: Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'defaultValue' | 'onChange' | 'disabled'>;
|
|
227
|
+
draft?: string;
|
|
228
|
+
defaultDraft?: string;
|
|
229
|
+
onDraftChange?: (value: string) => void;
|
|
230
|
+
}
|
|
231
|
+
/** Controlled, complete selected-conversation surface. */
|
|
232
|
+
declare function ConversationView({ conversation, messages, currentUserId, onSendMessage, typingUserIds, readAtByUserId, readersResolver, onBack, onRefresh, onLoadOlder, onTypingChange, onAddAttachment, onAttachmentClick, isInitialLoading, isLoadingOlder, isSending, hasOlderMessages, error, messageError, renderHeader, renderMessage, renderMedia, renderReadReceipt, renderComposer, renderTypingIndicator, renderEmpty, renderLoading, renderError, renderLoadingOlder, renderMessageError, displayNameForUser, reverseMessages, stickToBottom, paginationThreshold, formatTime, imageLoading, composerPlaceholder, composerAriaLabel, composerProps, draft: controlledDraft, defaultDraft, onDraftChange, className, classNames, styles, density, unstyled, ...rootProps }: ConversationViewProps): react.JSX.Element;
|
|
233
|
+
interface ConversationProps extends Omit<ConversationViewProps, 'conversation' | 'messages' | 'currentUserId' | 'onSendMessage' | 'typingUserIds' | 'readAtByUserId' | 'onRefresh' | 'onLoadOlder' | 'onTypingChange' | 'isInitialLoading' | 'isLoadingOlder' | 'isSending' | 'hasOlderMessages' | 'error'>, UseConversationOptions {
|
|
234
|
+
renderLoading?: StateRenderer;
|
|
235
|
+
onControllerChange?: (controller: ConversationController) => void;
|
|
236
|
+
}
|
|
237
|
+
/** Plug-and-play SDK-backed UI for one conversation. */
|
|
238
|
+
declare function Conversation({ client, conversationId, messagePageSize, markReadOnLoad, markReadOnReceive, typingTimeoutMs, autoLoad, renderLoading, onControllerChange, ...viewProps }: ConversationProps): react.JSX.Element;
|
|
239
|
+
declare function defaultReadersResolver(readAtByUserId: ReadonlyMap<string, Date>): (message: Message) => ReadonlySet<string>;
|
|
240
|
+
|
|
241
|
+
interface UseConversationListOptions {
|
|
242
|
+
client: ConvoKitUiClient;
|
|
243
|
+
pageLoader?: ConversationPageLoader;
|
|
244
|
+
initialFilter?: ConversationFilter;
|
|
245
|
+
pageSize?: number;
|
|
246
|
+
autoLoad?: boolean;
|
|
247
|
+
}
|
|
248
|
+
interface ConversationListController extends ConversationListState {
|
|
249
|
+
loadInitial(): Promise<void>;
|
|
250
|
+
refresh(): Promise<void>;
|
|
251
|
+
loadMore(): Promise<void>;
|
|
252
|
+
setFilter(filter: ConversationFilter): Promise<void>;
|
|
253
|
+
setQuery(query: string): Promise<void>;
|
|
254
|
+
}
|
|
255
|
+
/** Loads, de-duplicates, filters, and paginates conversations. */
|
|
256
|
+
declare function useConversationList({ client, pageLoader, initialFilter, pageSize, autoLoad, }: UseConversationListOptions): ConversationListController;
|
|
257
|
+
|
|
258
|
+
interface ConversationListViewProps extends BaseViewProps, ScrollOptions {
|
|
259
|
+
conversations: readonly Conversation$1[];
|
|
260
|
+
onConversationSelect: (conversation: Conversation$1) => void;
|
|
261
|
+
selectedConversationId?: string;
|
|
262
|
+
onRefresh?: () => void | Promise<void>;
|
|
263
|
+
onLoadMore?: () => void | Promise<void>;
|
|
264
|
+
isInitialLoading?: boolean;
|
|
265
|
+
isLoadingMore?: boolean;
|
|
266
|
+
hasMore?: boolean;
|
|
267
|
+
error?: unknown;
|
|
268
|
+
renderConversationItem?: (props: ConversationItemRenderProps) => ReactNode;
|
|
269
|
+
renderSeparator?: (index: number) => ReactNode;
|
|
270
|
+
renderEmpty?: StateRenderer;
|
|
271
|
+
renderInitialLoading?: StateRenderer;
|
|
272
|
+
renderError?: ErrorRenderer;
|
|
273
|
+
renderLoadMore?: StateRenderer;
|
|
274
|
+
ariaLabel?: string;
|
|
275
|
+
}
|
|
276
|
+
/** Controlled conversation list. It can be used with any state-management layer. */
|
|
277
|
+
declare function ConversationListView({ conversations, onConversationSelect, selectedConversationId, onRefresh, onLoadMore, isInitialLoading, isLoadingMore, hasMore, error, renderConversationItem, renderSeparator, renderEmpty, renderInitialLoading, renderError, renderLoadMore, scrollRef, paginationThreshold, ariaLabel, className, style, classNames, styles, density, unstyled, onScroll, ...rootProps }: ConversationListViewProps): react.JSX.Element;
|
|
278
|
+
interface ConversationListProps extends Omit<ConversationListViewProps, 'conversations' | 'onRefresh' | 'onLoadMore' | 'isInitialLoading' | 'isLoadingMore' | 'hasMore' | 'error'>, UseConversationListOptions {
|
|
279
|
+
onControllerChange?: (controller: ConversationListController) => void;
|
|
280
|
+
}
|
|
281
|
+
/** Plug-and-play SDK-backed conversation list. */
|
|
282
|
+
declare function ConversationList({ client, pageLoader, initialFilter, pageSize, autoLoad, onControllerChange, ...viewProps }: ConversationListProps): react.JSX.Element;
|
|
283
|
+
|
|
284
|
+
interface ConvoKitTheme {
|
|
285
|
+
background: string;
|
|
286
|
+
surface: string;
|
|
287
|
+
primary: string;
|
|
288
|
+
text: string;
|
|
289
|
+
mutedText: string;
|
|
290
|
+
border: string;
|
|
291
|
+
error: string;
|
|
292
|
+
incomingBubble: string;
|
|
293
|
+
outgoingBubble: string;
|
|
294
|
+
outgoingText: string;
|
|
295
|
+
radius: string;
|
|
296
|
+
avatarSize: string;
|
|
297
|
+
fontFamily: string;
|
|
298
|
+
}
|
|
299
|
+
declare const defaultConvoKitTheme: ConvoKitTheme;
|
|
300
|
+
interface ConvoKitThemeProviderProps extends PropsWithChildren {
|
|
301
|
+
theme?: Partial<ConvoKitTheme>;
|
|
302
|
+
className?: string;
|
|
303
|
+
style?: CSSProperties;
|
|
304
|
+
}
|
|
305
|
+
/** Supplies theme tokens as CSS variables. Nest providers to override a subtree. */
|
|
306
|
+
declare function ConvoKitThemeProvider({ children, theme, className, style, }: ConvoKitThemeProviderProps): react.JSX.Element;
|
|
307
|
+
declare function useConvoKitTheme(): ConvoKitTheme;
|
|
308
|
+
|
|
309
|
+
declare function matchesConversation(conversation: Conversation$1, filter: ConversationFilter): boolean;
|
|
310
|
+
declare function applyConversationFilter(conversations: readonly Conversation$1[], filter: ConversationFilter): Conversation$1[];
|
|
311
|
+
declare function mergeConversations(current: readonly Conversation$1[], incoming: readonly Conversation$1[]): Conversation$1[];
|
|
312
|
+
declare function mergeMessages(current: readonly Message[], incoming: readonly Message[]): Message[];
|
|
313
|
+
declare function readerIdsFor(message: Message, readAtByUserId: ReadonlyMap<string, Date>): ReadonlySet<string>;
|
|
314
|
+
declare function formatFileSize(size: number | undefined): string | null;
|
|
315
|
+
|
|
316
|
+
export { type BaseViewProps, type ComposerRenderProps, Conversation, type ConversationController, type ConversationFilter, type ConversationItemRenderProps, ConversationList, type ConversationListController, type ConversationListProps, type ConversationListState, ConversationListView, type ConversationListViewProps, type ConversationPageLoader, type ConversationPageRequest, type ConversationProps, type ConversationState, ConversationView, type ConversationViewProps, type ConvoKitAppearanceProps, ConvoKitAvatar, type ConvoKitAvatarProps, type ConvoKitTheme, ConvoKitThemeProvider, type ConvoKitThemeProviderProps, type ConvoKitUiClient, type ConvoKitUiDensity, type ConvoKitUiPart, type ErrorRenderProps, type ErrorRenderer, type MediaRenderProps, MessageListView, type MessageListViewProps, type MessageRenderProps, type ReadReceiptRenderProps, type ScrollOptions, type StateRenderer, type TypingRenderProps, type UseConversationListOptions, type UseConversationOptions, applyConversationFilter, createConvoKitUiClient, defaultConvoKitTheme, defaultReadersResolver, formatFileSize, matchesConversation, mergeConversations, mergeMessages, readerIdsFor, useConversation, useConversationList, useConvoKitTheme };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { Message, Conversation as Conversation$1, MessageMedia, RealtimeSubscription, ReadEvent, TypingEvent, ConvoKitClient } from '@convokitapp/sdk';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { CSSProperties, HTMLAttributes, ReactNode, RefObject, TextareaHTMLAttributes, PropsWithChildren } from 'react';
|
|
4
|
+
|
|
5
|
+
interface ConvoKitUiClient {
|
|
6
|
+
readonly currentUserId: string;
|
|
7
|
+
getConversations(options: {
|
|
8
|
+
limit: number;
|
|
9
|
+
offset: number;
|
|
10
|
+
archived: boolean;
|
|
11
|
+
}): Promise<Conversation$1[]>;
|
|
12
|
+
getConversation(conversationId: string): Promise<Conversation$1>;
|
|
13
|
+
getMessages(options: {
|
|
14
|
+
conversationId: string;
|
|
15
|
+
limit: number;
|
|
16
|
+
offset: number;
|
|
17
|
+
}): Promise<Message[]>;
|
|
18
|
+
sendMessage(input: {
|
|
19
|
+
conversationId: string;
|
|
20
|
+
text?: string;
|
|
21
|
+
media?: MessageMedia[];
|
|
22
|
+
}): Promise<Message>;
|
|
23
|
+
markConversationRead(conversationId: string): Promise<void>;
|
|
24
|
+
sendTyping(input: {
|
|
25
|
+
conversationId: string;
|
|
26
|
+
isTyping: boolean;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
onMessage(conversationId: string, handler: (message: Message) => void, onError?: (error: Error) => void): RealtimeSubscription;
|
|
29
|
+
onReadReceipt(conversationId: string, handler: (event: ReadEvent) => void, onError?: (error: Error) => void): RealtimeSubscription;
|
|
30
|
+
onTyping(conversationId: string, handler: (event: TypingEvent) => void, onError?: (error: Error) => void): RealtimeSubscription;
|
|
31
|
+
}
|
|
32
|
+
interface ConversationFilter {
|
|
33
|
+
query?: string;
|
|
34
|
+
archived?: boolean;
|
|
35
|
+
participantIds?: ReadonlySet<string> | readonly string[];
|
|
36
|
+
requireAllParticipants?: boolean;
|
|
37
|
+
predicate?: (conversation: Conversation$1) => boolean;
|
|
38
|
+
comparator?: (left: Conversation$1, right: Conversation$1) => number;
|
|
39
|
+
}
|
|
40
|
+
interface ConversationPageRequest {
|
|
41
|
+
limit: number;
|
|
42
|
+
offset: number;
|
|
43
|
+
filter: ConversationFilter;
|
|
44
|
+
}
|
|
45
|
+
type ConversationPageLoader = (request: ConversationPageRequest) => Promise<Conversation$1[]>;
|
|
46
|
+
interface ConversationListState {
|
|
47
|
+
conversations: Conversation$1[];
|
|
48
|
+
filter: ConversationFilter;
|
|
49
|
+
isInitialLoading: boolean;
|
|
50
|
+
isLoadingMore: boolean;
|
|
51
|
+
hasMore: boolean;
|
|
52
|
+
hasLoaded: boolean;
|
|
53
|
+
error: unknown;
|
|
54
|
+
}
|
|
55
|
+
interface ConversationState {
|
|
56
|
+
conversation: Conversation$1 | null;
|
|
57
|
+
messages: Message[];
|
|
58
|
+
typingUserIds: ReadonlySet<string>;
|
|
59
|
+
readAtByUserId: ReadonlyMap<string, Date>;
|
|
60
|
+
isInitialLoading: boolean;
|
|
61
|
+
isLoadingOlder: boolean;
|
|
62
|
+
isSending: boolean;
|
|
63
|
+
hasOlderMessages: boolean;
|
|
64
|
+
hasLoaded: boolean;
|
|
65
|
+
error: unknown;
|
|
66
|
+
currentUserId: string;
|
|
67
|
+
readerIdsFor(message: Message): ReadonlySet<string>;
|
|
68
|
+
}
|
|
69
|
+
type ConvoKitUiDensity = 'comfortable' | 'compact';
|
|
70
|
+
type ConvoKitUiPart = 'root' | 'list' | 'listItem' | 'avatar' | 'header' | 'messages' | 'message' | 'incomingMessage' | 'outgoingMessage' | 'media' | 'receipt' | 'typing' | 'composer' | 'input' | 'button' | 'empty' | 'loading' | 'error';
|
|
71
|
+
interface ConvoKitAppearanceProps {
|
|
72
|
+
classNames?: Partial<Record<ConvoKitUiPart, string>>;
|
|
73
|
+
styles?: Partial<Record<ConvoKitUiPart, CSSProperties>>;
|
|
74
|
+
density?: ConvoKitUiDensity;
|
|
75
|
+
unstyled?: boolean;
|
|
76
|
+
}
|
|
77
|
+
interface ConversationItemRenderProps {
|
|
78
|
+
conversation: Conversation$1;
|
|
79
|
+
index: number;
|
|
80
|
+
selected: boolean;
|
|
81
|
+
onSelect: () => void;
|
|
82
|
+
}
|
|
83
|
+
interface MessageRenderProps {
|
|
84
|
+
message: Message;
|
|
85
|
+
chronologicalIndex: number;
|
|
86
|
+
isCurrentUser: boolean;
|
|
87
|
+
sender: Conversation$1['participants'][number] | undefined;
|
|
88
|
+
readerIds: ReadonlySet<string>;
|
|
89
|
+
}
|
|
90
|
+
interface MediaRenderProps {
|
|
91
|
+
media: MessageMedia;
|
|
92
|
+
message: Message;
|
|
93
|
+
isCurrentUser: boolean;
|
|
94
|
+
onOpen?: () => void;
|
|
95
|
+
}
|
|
96
|
+
interface ComposerRenderProps {
|
|
97
|
+
value: string;
|
|
98
|
+
setValue: (value: string) => void;
|
|
99
|
+
isSending: boolean;
|
|
100
|
+
send: () => void;
|
|
101
|
+
addAttachment?: () => void;
|
|
102
|
+
}
|
|
103
|
+
interface TypingRenderProps {
|
|
104
|
+
userIds: ReadonlySet<string>;
|
|
105
|
+
displayNameForUser: (userId: string) => string;
|
|
106
|
+
}
|
|
107
|
+
interface ReadReceiptRenderProps {
|
|
108
|
+
message: Message;
|
|
109
|
+
readerIds: ReadonlySet<string>;
|
|
110
|
+
}
|
|
111
|
+
interface ErrorRenderProps {
|
|
112
|
+
error: unknown;
|
|
113
|
+
retry?: () => void;
|
|
114
|
+
}
|
|
115
|
+
interface BaseViewProps extends ConvoKitAppearanceProps, Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'style'> {
|
|
116
|
+
style?: CSSProperties;
|
|
117
|
+
}
|
|
118
|
+
interface ScrollOptions {
|
|
119
|
+
scrollRef?: RefObject<HTMLDivElement | null>;
|
|
120
|
+
paginationThreshold?: number;
|
|
121
|
+
}
|
|
122
|
+
type StateRenderer = () => ReactNode;
|
|
123
|
+
type ErrorRenderer = (props: ErrorRenderProps) => ReactNode;
|
|
124
|
+
|
|
125
|
+
/** Adapts a connected `@convokitapp/sdk` client to the replaceable UI boundary. */
|
|
126
|
+
declare function createConvoKitUiClient(client: ConvoKitClient): ConvoKitUiClient;
|
|
127
|
+
|
|
128
|
+
interface ConvoKitAvatarProps extends HTMLAttributes<HTMLSpanElement> {
|
|
129
|
+
name: string;
|
|
130
|
+
src?: string | null;
|
|
131
|
+
}
|
|
132
|
+
/** Accessible avatar built on Radix Avatar. */
|
|
133
|
+
declare function ConvoKitAvatar({ name, src, className, ...props }: ConvoKitAvatarProps): react.JSX.Element;
|
|
134
|
+
|
|
135
|
+
interface UseConversationOptions {
|
|
136
|
+
client: ConvoKitUiClient;
|
|
137
|
+
conversationId: string;
|
|
138
|
+
messagePageSize?: number;
|
|
139
|
+
markReadOnLoad?: boolean;
|
|
140
|
+
markReadOnReceive?: boolean;
|
|
141
|
+
typingTimeoutMs?: number;
|
|
142
|
+
autoLoad?: boolean;
|
|
143
|
+
}
|
|
144
|
+
interface ConversationController extends ConversationState {
|
|
145
|
+
loadInitial(): Promise<void>;
|
|
146
|
+
refresh(): Promise<void>;
|
|
147
|
+
loadOlderMessages(): Promise<void>;
|
|
148
|
+
sendMessage(input: {
|
|
149
|
+
text?: string;
|
|
150
|
+
media?: MessageMedia[];
|
|
151
|
+
}): Promise<Message | null>;
|
|
152
|
+
markRead(): Promise<void>;
|
|
153
|
+
updateTyping(isTyping: boolean): Promise<void>;
|
|
154
|
+
}
|
|
155
|
+
/** Owns one conversation's history, realtime state, sending, and read positions. */
|
|
156
|
+
declare function useConversation({ client, conversationId, messagePageSize, markReadOnLoad, markReadOnReceive, typingTimeoutMs, autoLoad, }: UseConversationOptions): ConversationController;
|
|
157
|
+
|
|
158
|
+
interface MessageListViewProps extends BaseViewProps, ScrollOptions {
|
|
159
|
+
conversation: Conversation$1;
|
|
160
|
+
messages: readonly Message[];
|
|
161
|
+
currentUserId: string;
|
|
162
|
+
readAtByUserId?: ReadonlyMap<string, Date>;
|
|
163
|
+
readersResolver?: (message: Message) => ReadonlySet<string>;
|
|
164
|
+
onLoadOlder?: () => void | Promise<void>;
|
|
165
|
+
hasOlderMessages?: boolean;
|
|
166
|
+
isLoadingOlder?: boolean;
|
|
167
|
+
error?: unknown;
|
|
168
|
+
renderMessage?: (props: MessageRenderProps) => ReactNode;
|
|
169
|
+
renderMedia?: (props: MediaRenderProps) => ReactNode;
|
|
170
|
+
renderReadReceipt?: (props: ReadReceiptRenderProps) => ReactNode;
|
|
171
|
+
onAttachmentClick?: (media: MessageMedia, message: Message) => void;
|
|
172
|
+
renderEmpty?: StateRenderer;
|
|
173
|
+
renderLoadingOlder?: StateRenderer;
|
|
174
|
+
renderError?: ErrorRenderer;
|
|
175
|
+
reverse?: boolean;
|
|
176
|
+
stickToBottom?: boolean;
|
|
177
|
+
formatTime?: (date: Date) => string;
|
|
178
|
+
imageLoading?: 'eager' | 'lazy';
|
|
179
|
+
}
|
|
180
|
+
/** Controlled message history with read receipts, structured media, and older-page loading. */
|
|
181
|
+
declare function MessageListView({ conversation, messages, currentUserId, readAtByUserId, readersResolver, onLoadOlder, hasOlderMessages, isLoadingOlder, error, renderMessage, renderMedia, renderReadReceipt, onAttachmentClick, renderEmpty, renderLoadingOlder, renderError, scrollRef, paginationThreshold, reverse, stickToBottom, formatTime, imageLoading, className, style, classNames, styles, density, unstyled, onScroll, ...rootProps }: MessageListViewProps): react.JSX.Element;
|
|
182
|
+
|
|
183
|
+
interface ConversationViewProps extends BaseViewProps {
|
|
184
|
+
conversation: Conversation$1;
|
|
185
|
+
messages: readonly Message[];
|
|
186
|
+
currentUserId: string;
|
|
187
|
+
onSendMessage: (text: string) => boolean | void | Promise<boolean | void>;
|
|
188
|
+
typingUserIds?: ReadonlySet<string>;
|
|
189
|
+
readAtByUserId?: ReadonlyMap<string, Date>;
|
|
190
|
+
readersResolver?: (message: Message) => ReadonlySet<string>;
|
|
191
|
+
onBack?: () => void;
|
|
192
|
+
onRefresh?: () => void | Promise<void>;
|
|
193
|
+
onLoadOlder?: () => void | Promise<void>;
|
|
194
|
+
onTypingChange?: (isTyping: boolean) => void | Promise<void>;
|
|
195
|
+
onAddAttachment?: () => void;
|
|
196
|
+
onAttachmentClick?: (media: MessageMedia, message: Message) => void;
|
|
197
|
+
isInitialLoading?: boolean;
|
|
198
|
+
isLoadingOlder?: boolean;
|
|
199
|
+
isSending?: boolean;
|
|
200
|
+
hasOlderMessages?: boolean;
|
|
201
|
+
error?: unknown;
|
|
202
|
+
messageError?: unknown;
|
|
203
|
+
renderHeader?: (props: {
|
|
204
|
+
conversation: Conversation$1;
|
|
205
|
+
onBack?: () => void;
|
|
206
|
+
onRefresh?: () => void | Promise<void>;
|
|
207
|
+
}) => ReactNode;
|
|
208
|
+
renderMessage?: (props: MessageRenderProps) => ReactNode;
|
|
209
|
+
renderMedia?: (props: MediaRenderProps) => ReactNode;
|
|
210
|
+
renderReadReceipt?: (props: ReadReceiptRenderProps) => ReactNode;
|
|
211
|
+
renderComposer?: (props: ComposerRenderProps) => ReactNode;
|
|
212
|
+
renderTypingIndicator?: (props: TypingRenderProps) => ReactNode;
|
|
213
|
+
renderEmpty?: StateRenderer;
|
|
214
|
+
renderLoading?: StateRenderer;
|
|
215
|
+
renderError?: ErrorRenderer;
|
|
216
|
+
renderLoadingOlder?: StateRenderer;
|
|
217
|
+
renderMessageError?: ErrorRenderer;
|
|
218
|
+
displayNameForUser?: (userId: string) => string | null | undefined;
|
|
219
|
+
reverseMessages?: boolean;
|
|
220
|
+
stickToBottom?: boolean;
|
|
221
|
+
paginationThreshold?: number;
|
|
222
|
+
formatTime?: MessageListViewProps['formatTime'];
|
|
223
|
+
imageLoading?: MessageListViewProps['imageLoading'];
|
|
224
|
+
composerPlaceholder?: string;
|
|
225
|
+
composerAriaLabel?: string;
|
|
226
|
+
composerProps?: Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'defaultValue' | 'onChange' | 'disabled'>;
|
|
227
|
+
draft?: string;
|
|
228
|
+
defaultDraft?: string;
|
|
229
|
+
onDraftChange?: (value: string) => void;
|
|
230
|
+
}
|
|
231
|
+
/** Controlled, complete selected-conversation surface. */
|
|
232
|
+
declare function ConversationView({ conversation, messages, currentUserId, onSendMessage, typingUserIds, readAtByUserId, readersResolver, onBack, onRefresh, onLoadOlder, onTypingChange, onAddAttachment, onAttachmentClick, isInitialLoading, isLoadingOlder, isSending, hasOlderMessages, error, messageError, renderHeader, renderMessage, renderMedia, renderReadReceipt, renderComposer, renderTypingIndicator, renderEmpty, renderLoading, renderError, renderLoadingOlder, renderMessageError, displayNameForUser, reverseMessages, stickToBottom, paginationThreshold, formatTime, imageLoading, composerPlaceholder, composerAriaLabel, composerProps, draft: controlledDraft, defaultDraft, onDraftChange, className, classNames, styles, density, unstyled, ...rootProps }: ConversationViewProps): react.JSX.Element;
|
|
233
|
+
interface ConversationProps extends Omit<ConversationViewProps, 'conversation' | 'messages' | 'currentUserId' | 'onSendMessage' | 'typingUserIds' | 'readAtByUserId' | 'onRefresh' | 'onLoadOlder' | 'onTypingChange' | 'isInitialLoading' | 'isLoadingOlder' | 'isSending' | 'hasOlderMessages' | 'error'>, UseConversationOptions {
|
|
234
|
+
renderLoading?: StateRenderer;
|
|
235
|
+
onControllerChange?: (controller: ConversationController) => void;
|
|
236
|
+
}
|
|
237
|
+
/** Plug-and-play SDK-backed UI for one conversation. */
|
|
238
|
+
declare function Conversation({ client, conversationId, messagePageSize, markReadOnLoad, markReadOnReceive, typingTimeoutMs, autoLoad, renderLoading, onControllerChange, ...viewProps }: ConversationProps): react.JSX.Element;
|
|
239
|
+
declare function defaultReadersResolver(readAtByUserId: ReadonlyMap<string, Date>): (message: Message) => ReadonlySet<string>;
|
|
240
|
+
|
|
241
|
+
interface UseConversationListOptions {
|
|
242
|
+
client: ConvoKitUiClient;
|
|
243
|
+
pageLoader?: ConversationPageLoader;
|
|
244
|
+
initialFilter?: ConversationFilter;
|
|
245
|
+
pageSize?: number;
|
|
246
|
+
autoLoad?: boolean;
|
|
247
|
+
}
|
|
248
|
+
interface ConversationListController extends ConversationListState {
|
|
249
|
+
loadInitial(): Promise<void>;
|
|
250
|
+
refresh(): Promise<void>;
|
|
251
|
+
loadMore(): Promise<void>;
|
|
252
|
+
setFilter(filter: ConversationFilter): Promise<void>;
|
|
253
|
+
setQuery(query: string): Promise<void>;
|
|
254
|
+
}
|
|
255
|
+
/** Loads, de-duplicates, filters, and paginates conversations. */
|
|
256
|
+
declare function useConversationList({ client, pageLoader, initialFilter, pageSize, autoLoad, }: UseConversationListOptions): ConversationListController;
|
|
257
|
+
|
|
258
|
+
interface ConversationListViewProps extends BaseViewProps, ScrollOptions {
|
|
259
|
+
conversations: readonly Conversation$1[];
|
|
260
|
+
onConversationSelect: (conversation: Conversation$1) => void;
|
|
261
|
+
selectedConversationId?: string;
|
|
262
|
+
onRefresh?: () => void | Promise<void>;
|
|
263
|
+
onLoadMore?: () => void | Promise<void>;
|
|
264
|
+
isInitialLoading?: boolean;
|
|
265
|
+
isLoadingMore?: boolean;
|
|
266
|
+
hasMore?: boolean;
|
|
267
|
+
error?: unknown;
|
|
268
|
+
renderConversationItem?: (props: ConversationItemRenderProps) => ReactNode;
|
|
269
|
+
renderSeparator?: (index: number) => ReactNode;
|
|
270
|
+
renderEmpty?: StateRenderer;
|
|
271
|
+
renderInitialLoading?: StateRenderer;
|
|
272
|
+
renderError?: ErrorRenderer;
|
|
273
|
+
renderLoadMore?: StateRenderer;
|
|
274
|
+
ariaLabel?: string;
|
|
275
|
+
}
|
|
276
|
+
/** Controlled conversation list. It can be used with any state-management layer. */
|
|
277
|
+
declare function ConversationListView({ conversations, onConversationSelect, selectedConversationId, onRefresh, onLoadMore, isInitialLoading, isLoadingMore, hasMore, error, renderConversationItem, renderSeparator, renderEmpty, renderInitialLoading, renderError, renderLoadMore, scrollRef, paginationThreshold, ariaLabel, className, style, classNames, styles, density, unstyled, onScroll, ...rootProps }: ConversationListViewProps): react.JSX.Element;
|
|
278
|
+
interface ConversationListProps extends Omit<ConversationListViewProps, 'conversations' | 'onRefresh' | 'onLoadMore' | 'isInitialLoading' | 'isLoadingMore' | 'hasMore' | 'error'>, UseConversationListOptions {
|
|
279
|
+
onControllerChange?: (controller: ConversationListController) => void;
|
|
280
|
+
}
|
|
281
|
+
/** Plug-and-play SDK-backed conversation list. */
|
|
282
|
+
declare function ConversationList({ client, pageLoader, initialFilter, pageSize, autoLoad, onControllerChange, ...viewProps }: ConversationListProps): react.JSX.Element;
|
|
283
|
+
|
|
284
|
+
interface ConvoKitTheme {
|
|
285
|
+
background: string;
|
|
286
|
+
surface: string;
|
|
287
|
+
primary: string;
|
|
288
|
+
text: string;
|
|
289
|
+
mutedText: string;
|
|
290
|
+
border: string;
|
|
291
|
+
error: string;
|
|
292
|
+
incomingBubble: string;
|
|
293
|
+
outgoingBubble: string;
|
|
294
|
+
outgoingText: string;
|
|
295
|
+
radius: string;
|
|
296
|
+
avatarSize: string;
|
|
297
|
+
fontFamily: string;
|
|
298
|
+
}
|
|
299
|
+
declare const defaultConvoKitTheme: ConvoKitTheme;
|
|
300
|
+
interface ConvoKitThemeProviderProps extends PropsWithChildren {
|
|
301
|
+
theme?: Partial<ConvoKitTheme>;
|
|
302
|
+
className?: string;
|
|
303
|
+
style?: CSSProperties;
|
|
304
|
+
}
|
|
305
|
+
/** Supplies theme tokens as CSS variables. Nest providers to override a subtree. */
|
|
306
|
+
declare function ConvoKitThemeProvider({ children, theme, className, style, }: ConvoKitThemeProviderProps): react.JSX.Element;
|
|
307
|
+
declare function useConvoKitTheme(): ConvoKitTheme;
|
|
308
|
+
|
|
309
|
+
declare function matchesConversation(conversation: Conversation$1, filter: ConversationFilter): boolean;
|
|
310
|
+
declare function applyConversationFilter(conversations: readonly Conversation$1[], filter: ConversationFilter): Conversation$1[];
|
|
311
|
+
declare function mergeConversations(current: readonly Conversation$1[], incoming: readonly Conversation$1[]): Conversation$1[];
|
|
312
|
+
declare function mergeMessages(current: readonly Message[], incoming: readonly Message[]): Message[];
|
|
313
|
+
declare function readerIdsFor(message: Message, readAtByUserId: ReadonlyMap<string, Date>): ReadonlySet<string>;
|
|
314
|
+
declare function formatFileSize(size: number | undefined): string | null;
|
|
315
|
+
|
|
316
|
+
export { type BaseViewProps, type ComposerRenderProps, Conversation, type ConversationController, type ConversationFilter, type ConversationItemRenderProps, ConversationList, type ConversationListController, type ConversationListProps, type ConversationListState, ConversationListView, type ConversationListViewProps, type ConversationPageLoader, type ConversationPageRequest, type ConversationProps, type ConversationState, ConversationView, type ConversationViewProps, type ConvoKitAppearanceProps, ConvoKitAvatar, type ConvoKitAvatarProps, type ConvoKitTheme, ConvoKitThemeProvider, type ConvoKitThemeProviderProps, type ConvoKitUiClient, type ConvoKitUiDensity, type ConvoKitUiPart, type ErrorRenderProps, type ErrorRenderer, type MediaRenderProps, MessageListView, type MessageListViewProps, type MessageRenderProps, type ReadReceiptRenderProps, type ScrollOptions, type StateRenderer, type TypingRenderProps, type UseConversationListOptions, type UseConversationOptions, applyConversationFilter, createConvoKitUiClient, defaultConvoKitTheme, defaultReadersResolver, formatFileSize, matchesConversation, mergeConversations, mergeMessages, readerIdsFor, useConversation, useConversationList, useConvoKitTheme };
|