@adatechnology/conversations-ui 0.1.0-rc.1 → 0.1.0-rc.3

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.
Files changed (52) hide show
  1. package/dist/chunk-N7B24WYD.js +719 -0
  2. package/dist/chunk-NV2RZ5KT.js +56 -0
  3. package/dist/{chunk-ZDURDZTM.js → chunk-OGRRHQQW.js} +1 -41
  4. package/dist/flows/index.js +6 -4
  5. package/dist/index.d.ts +316 -111
  6. package/dist/index.js +1032 -954
  7. package/dist/preview/index.d.ts +172 -0
  8. package/dist/preview/index.js +576 -0
  9. package/dist/styles.css +198 -0
  10. package/dist/types-C0PtaO7S.d.ts +207 -0
  11. package/package.json +10 -3
  12. package/src/Avatar.tsx +18 -3
  13. package/src/ChannelIcon.tsx +87 -0
  14. package/src/ConversationContextPanel.tsx +106 -0
  15. package/src/ConversationDocumentsPanel.tsx +107 -0
  16. package/src/ConversationHeader.tsx +239 -0
  17. package/src/ConversationListItem.tsx +36 -5
  18. package/src/ConversationLocalesProvider.tsx +2 -0
  19. package/src/ConversationRow.tsx +137 -0
  20. package/src/DateDivider.tsx +16 -3
  21. package/src/MessageBubble.tsx +24 -2
  22. package/src/MessageComposer.tsx +15 -2
  23. package/src/Wallpaper.tsx +4 -2
  24. package/src/WindowExpiredNotice.tsx +57 -0
  25. package/src/conversationChannel.test.ts +53 -0
  26. package/src/conversationChannel.ts +146 -0
  27. package/src/conversationTranscript.test.ts +65 -0
  28. package/src/conversationTranscript.ts +64 -0
  29. package/src/conversationWindow.test.ts +90 -0
  30. package/src/conversationWindow.ts +78 -0
  31. package/src/flows/FlowMapCanvas.tsx +2 -2
  32. package/src/hooks/useConversationDocuments.ts +4 -2
  33. package/src/index.ts +73 -4
  34. package/src/lib/cn.ts +15 -0
  35. package/src/lib/phone.ts +34 -0
  36. package/src/preview/ConversationPreview.tsx +148 -0
  37. package/src/preview/createMockConversationsApi.ts +111 -0
  38. package/src/preview/createMockSSEProvider.ts +40 -0
  39. package/src/preview/createPreviewWebhookClient.test.ts +105 -0
  40. package/src/preview/createPreviewWebhookClient.ts +99 -0
  41. package/src/preview/index.ts +40 -0
  42. package/src/preview/mockEventSource.ts +53 -0
  43. package/src/preview/preview.test.ts +175 -0
  44. package/src/preview/previewFixtures.ts +153 -0
  45. package/src/preview/previewStore.ts +193 -0
  46. package/src/preview/startPreviewScript.ts +60 -0
  47. package/src/providers/types.ts +36 -2
  48. package/src/settings/WhatsAppTemplatesSettings.tsx +106 -0
  49. package/src/styles.css +136 -0
  50. package/src/types.ts +8 -0
  51. package/src/useDarkMode.ts +26 -0
  52. package/src/useIsNarrow.ts +29 -0
@@ -0,0 +1,56 @@
1
+ // src/useDarkMode.ts
2
+ import { useState, useEffect, useCallback } from "react";
3
+ var STORAGE_KEY = "conversations-ui-dark-mode";
4
+ function getInitialDark() {
5
+ if (typeof window === "undefined") return false;
6
+ const stored = localStorage.getItem(STORAGE_KEY);
7
+ if (stored !== null) {
8
+ return stored === "true";
9
+ }
10
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
11
+ }
12
+ function useIsDarkTheme() {
13
+ const [isDark, setIsDark] = useState(
14
+ () => typeof document !== "undefined" && document.documentElement.classList.contains("dark")
15
+ );
16
+ useEffect(() => {
17
+ const root = document.documentElement;
18
+ const observer = new MutationObserver(() => setIsDark(root.classList.contains("dark")));
19
+ observer.observe(root, { attributes: true, attributeFilter: ["class"] });
20
+ setIsDark(root.classList.contains("dark"));
21
+ return () => observer.disconnect();
22
+ }, []);
23
+ return isDark;
24
+ }
25
+ function useDarkMode() {
26
+ const [isDark, setIsDark] = useState(getInitialDark);
27
+ useEffect(() => {
28
+ const root = document.documentElement;
29
+ if (isDark) {
30
+ root.classList.add("dark");
31
+ } else {
32
+ root.classList.remove("dark");
33
+ }
34
+ localStorage.setItem(STORAGE_KEY, String(isDark));
35
+ }, [isDark]);
36
+ useEffect(() => {
37
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
38
+ const handleChange = (event) => {
39
+ const stored = localStorage.getItem(STORAGE_KEY);
40
+ if (stored === null) {
41
+ setIsDark(event.matches);
42
+ }
43
+ };
44
+ mediaQuery.addEventListener("change", handleChange);
45
+ return () => mediaQuery.removeEventListener("change", handleChange);
46
+ }, []);
47
+ const toggle = useCallback(() => {
48
+ setIsDark((prev) => !prev);
49
+ }, []);
50
+ return { isDark, toggle };
51
+ }
52
+
53
+ export {
54
+ useIsDarkTheme,
55
+ useDarkMode
56
+ };
@@ -151,49 +151,9 @@ function waToHTMLInline(text) {
151
151
  return escapeHtml(text).replace(/\*([^*\n]+)\*/g, "<strong>$1</strong>").replace(/_([^_\n]+)_/g, "<em>$1</em>").replace(/~([^~\n]+)~/g, "<del>$1</del>").replace(/`([^`\n]+)`/g, "<code>$1</code>");
152
152
  }
153
153
 
154
- // src/useDarkMode.ts
155
- import { useState, useEffect, useCallback } from "react";
156
- var STORAGE_KEY = "conversations-ui-dark-mode";
157
- function getInitialDark() {
158
- if (typeof window === "undefined") return false;
159
- const stored = localStorage.getItem(STORAGE_KEY);
160
- if (stored !== null) {
161
- return stored === "true";
162
- }
163
- return window.matchMedia("(prefers-color-scheme: dark)").matches;
164
- }
165
- function useDarkMode() {
166
- const [isDark, setIsDark] = useState(getInitialDark);
167
- useEffect(() => {
168
- const root = document.documentElement;
169
- if (isDark) {
170
- root.classList.add("dark");
171
- } else {
172
- root.classList.remove("dark");
173
- }
174
- localStorage.setItem(STORAGE_KEY, String(isDark));
175
- }, [isDark]);
176
- useEffect(() => {
177
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
178
- const handleChange = (event) => {
179
- const stored = localStorage.getItem(STORAGE_KEY);
180
- if (stored === null) {
181
- setIsDark(event.matches);
182
- }
183
- };
184
- mediaQuery.addEventListener("change", handleChange);
185
- return () => mediaQuery.removeEventListener("change", handleChange);
186
- }, []);
187
- const toggle = useCallback(() => {
188
- setIsDark((prev) => !prev);
189
- }, []);
190
- return { isDark, toggle };
191
- }
192
-
193
154
  export {
194
155
  parseWhatsAppFormatting,
195
156
  waToHTML,
196
157
  htmlToWA,
197
- waToHTMLInline,
198
- useDarkMode
158
+ waToHTMLInline
199
159
  };
@@ -1,7 +1,9 @@
1
1
  import {
2
- parseWhatsAppFormatting,
3
- useDarkMode
4
- } from "../chunk-ZDURDZTM.js";
2
+ useIsDarkTheme
3
+ } from "../chunk-NV2RZ5KT.js";
4
+ import {
5
+ parseWhatsAppFormatting
6
+ } from "../chunk-OGRRHQQW.js";
5
7
 
6
8
  // src/flows/FlowNodeCard.tsx
7
9
  import { Handle, Position } from "@xyflow/react";
@@ -524,7 +526,7 @@ var BACKGROUND_COLOR_LIGHT = "#cbd5e1";
524
526
  var BACKGROUND_COLOR_DARK = "#334155";
525
527
  function FlowMapCanvas({ graphs, rootKey, onOpenFlow, labels: labelsOverride }) {
526
528
  const labels = { ...DEFAULT_FLOW_EDITOR_LABELS, ...labelsOverride };
527
- const { isDark } = useDarkMode();
529
+ const isDark = useIsDarkTheme();
528
530
  const positions = useMemo(() => computeFlowMapLayout(graphs, rootKey), [graphs, rootKey]);
529
531
  const nodes = useMemo(
530
532
  () => Object.values(graphs).map((g) => ({
package/dist/index.d.ts CHANGED
@@ -1,47 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import react__default, { ReactNode, FormEvent } from 'react';
3
-
4
- interface ConversationsUIConfig {
5
- apiBaseUrl: string;
6
- theme?: ConversationsTheme;
7
- features?: ConversationsFeatures;
8
- }
9
- interface ConversationsTheme {
10
- primaryColor?: string;
11
- backgroundColor?: string;
12
- bubbleSent?: string;
13
- bubbleReceived?: string;
14
- textPrimary?: string;
15
- textSecondary?: string;
16
- }
17
- interface ConversationsFeatures {
18
- audio?: boolean;
19
- documents?: boolean;
20
- emoji?: boolean;
21
- darkMode?: boolean;
22
- }
23
- interface MessagePayload {
24
- id: string;
25
- type: 'text' | 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'template';
26
- content?: string;
27
- caption?: string;
28
- mediaUrl?: string;
29
- base64?: string;
30
- uploadId?: string;
31
- mediaId?: string;
32
- mimeType?: string;
33
- filename?: string;
34
- sizeBytes?: number;
35
- direction: 'inbound' | 'outbound';
36
- sender: 'bot' | 'customer' | 'agent';
37
- timestamp: string;
38
- status?: 'sent' | 'delivered' | 'read' | 'failed';
39
- readAt?: string;
40
- agentName?: string | null;
41
- templateName?: string;
42
- isFirstInGroup?: boolean;
43
- isLastInGroup?: boolean;
44
- }
3
+ import { M as MessagePayload, k as ConversationsFeatures, i as ConversationSummary, f as ConversationChannel, j as ConversationsApi, S as SSEProvider, g as ConversationDocument } from './types-C0PtaO7S.js';
4
+ export { C as CHANNEL_CAPABILITIES, a as CHANNEL_FILTER_ALL, b as CONVERSATION_CHANNEL, c as ChannelCapabilities, d as ChannelFilter, e as ChannelFilterOption, h as ConversationEventSource, l as ConversationsTheme, m as ConversationsUIConfig, D as DEFAULT_CONVERSATION_CHANNEL, F as FormatContactHandleParams, H as HANDLE_KIND, n as HandleKind, R as REOPEN_MECHANISM, o as ReopenMechanism, p as capabilitiesOf, q as channelFiltersFor, r as contactFlag, s as formatContactHandle } from './types-C0PtaO7S.js';
45
5
 
46
6
  type ResolveMediaUrl = (message: MessagePayload) => Promise<string | null>;
47
7
  interface MediaRendererProps {
@@ -60,8 +20,9 @@ interface MessageBubbleProps {
60
20
  isSelected?: boolean;
61
21
  onToggleSelect?: () => void;
62
22
  onResolveMediaUrl?: ResolveMediaUrl;
23
+ className?: string;
63
24
  }
64
- declare function MessageBubble({ message, isMine, senderName, isFirstInGroup, isSelecting, isSelected, onToggleSelect, onResolveMediaUrl, }: MessageBubbleProps): react.JSX.Element;
25
+ declare function MessageBubble({ message, isMine, senderName, isFirstInGroup, isSelecting, isSelected, onToggleSelect, onResolveMediaUrl, className, }: MessageBubbleProps): react.JSX.Element;
65
26
 
66
27
  interface ConversationWallpaperProps {
67
28
  children?: ReactNode;
@@ -82,6 +43,7 @@ interface ConversationLocales {
82
43
  viewImage: string;
83
44
  listenAudio: string;
84
45
  viewVideo: string;
46
+ moderationFlagged: string;
85
47
  };
86
48
  selection: {
87
49
  select: string;
@@ -123,8 +85,14 @@ interface MessageComposerProps {
123
85
  maxLength?: number;
124
86
  disabled?: boolean;
125
87
  acceptedFileTypes?: string;
88
+ className?: string;
89
+ classNames?: Partial<MessageComposerClassNames>;
90
+ }
91
+ interface MessageComposerClassNames {
92
+ root: string;
93
+ field: string;
126
94
  }
127
- declare const MessageComposer: ({ onSend, onAttach, value: externalValue, onChange: externalOnChange, features, placeholder, maxLength, disabled, acceptedFileTypes, }: MessageComposerProps) => react.JSX.Element;
95
+ declare const MessageComposer: ({ onSend, onAttach, value: externalValue, onChange: externalOnChange, features, placeholder, maxLength, disabled, acceptedFileTypes, className, classNames, }: MessageComposerProps) => react.JSX.Element;
128
96
 
129
97
  interface WhatsAppMessageEditorProps {
130
98
  value: string;
@@ -145,10 +113,16 @@ interface SimpleEmojiPickerProps {
145
113
  }
146
114
  declare function SimpleEmojiPicker({ onSelect, label, pickerWidth, pickerMaxHeight }: SimpleEmojiPickerProps): react.JSX.Element;
147
115
 
116
+ interface DateDividerClassNames {
117
+ root: string;
118
+ label: string;
119
+ }
148
120
  interface DateDividerProps {
149
121
  iso: string;
122
+ className?: string;
123
+ classNames?: Partial<DateDividerClassNames>;
150
124
  }
151
- declare function DateDivider({ iso }: DateDividerProps): react.JSX.Element;
125
+ declare function DateDivider({ iso, className, classNames }: DateDividerProps): react.JSX.Element;
152
126
 
153
127
  interface AvatarProps {
154
128
  name?: string | null;
@@ -158,76 +132,25 @@ interface AvatarProps {
158
132
  }
159
133
  declare function Avatar({ name, avatarUrl, size, className }: AvatarProps): react.JSX.Element;
160
134
 
161
- interface ConversationsApi {
162
- fetchMessages(conversationId: string, params?: {
163
- limit?: number;
164
- before?: string;
165
- }): Promise<MessagePayload[]>;
166
- fetchConversations(params?: {
167
- page?: number;
168
- limit?: number;
169
- waitingHuman?: boolean;
170
- search?: string;
171
- }): Promise<ConversationSummary[]>;
172
- sendMessage(conversationId: string, text: string): Promise<MessagePayload>;
173
- sendMedia(conversationId: string, data: {
174
- base64: string;
175
- mimeType: string;
176
- filename: string;
177
- caption?: string;
178
- }): Promise<MessagePayload>;
179
- sendTemplate(conversationId: string, data: {
180
- templateName: string;
181
- languageCode?: string;
182
- bodyParams?: string[];
183
- }): Promise<void>;
184
- markRead(conversationId: string): Promise<void>;
185
- getContext(conversationId: string): Promise<Record<string, unknown>>;
186
- getDocuments(conversationId: string, params?: {
187
- search?: string;
188
- page?: number;
189
- }): Promise<ConversationDocument[]>;
190
- getDocumentUrl(uploadId: string): Promise<string>;
191
- getMediaProxyUrl(mediaId: string): Promise<{
192
- mimeType: string;
193
- data: string;
194
- }>;
195
- }
196
- interface SSEProvider {
197
- connectConversationStream(conversationId: string): EventSource;
198
- connectGlobalStream(): EventSource;
199
- }
200
- interface ConversationSummary {
201
- id: string;
202
- whatsappNumber: string;
203
- clientName?: string;
204
- lastContent?: string;
205
- lastDirection?: 'inbound' | 'outbound';
206
- lastAt: string;
207
- lastInboundAt: string | null;
208
- mode: 'bot' | 'human';
209
- assignedUserId: string | null;
210
- waitingHuman: boolean;
211
- unread: number;
212
- currentState: string;
213
- }
214
- interface ConversationDocument {
215
- id: string;
216
- filename: string;
217
- mimeType: string;
218
- sizeBytes: number;
219
- source: string;
220
- linkedAt: string;
221
- }
222
-
223
135
  interface ConversationListItemProps {
224
136
  conversation: ConversationSummary;
225
137
  active?: boolean;
226
138
  selected?: boolean;
227
139
  onClick?: () => void;
228
140
  onSelect?: (id: string) => void;
229
- }
230
- declare const ConversationListItem: ({ conversation, active, selected, onClick, onSelect, }: ConversationListItemProps) => react.JSX.Element;
141
+ /**
142
+ * Desliga a borda inferior quando o item é composto dentro de outra linha (ver `ConversationRow`):
143
+ * com ela ligada, a borda corta a própria linha ao meio, separando o item do rodapé de status.
144
+ */
145
+ showDivider?: boolean;
146
+ /**
147
+ * Desliga o fundo de selecionado. Par do `showDivider`: quando o item é composto dentro de uma
148
+ * linha maior, quem pinta o realce é a linha — senão só o bloco do item fica cinza e o resto
149
+ * (checkbox, pills, barra lateral) continua branco, como se metade da linha estivesse selecionada.
150
+ */
151
+ highlightActive?: boolean;
152
+ }
153
+ declare const ConversationListItem: ({ conversation, active, selected, onClick, onSelect, showDivider, highlightActive, }: ConversationListItemProps) => react.JSX.Element;
231
154
 
232
155
  type ToastType = 'success' | 'error' | 'info';
233
156
  interface ToastContextValue {
@@ -279,11 +202,258 @@ interface MessageTailProps {
279
202
  }
280
203
  declare function MessageTail({ isOutbound }: MessageTailProps): react.JSX.Element;
281
204
 
205
+ /**
206
+ * Janela de sessão: o intervalo em que o canal aceita mensagem livre do atendente. No WhatsApp são
207
+ * 24h desde o último contato do cliente; fora dela só template. Cada canal tem a sua regra — e há
208
+ * canal sem janela nenhuma — então a política vem de `capabilitiesOf`, não de constante fixa.
209
+ *
210
+ * Mora no SDK porque é regra de plataforma, não de produto: todo projeto que usa este pacote
211
+ * precisa dela para saber o que o atendente ainda consegue fazer.
212
+ */
213
+
214
+ declare const CONVERSATION_WINDOW: {
215
+ readonly ALL: "all";
216
+ readonly FRESH: "fresh";
217
+ readonly WARNING: "warning";
218
+ readonly CRITICAL: "critical";
219
+ readonly EXPIRED: "expired";
220
+ };
221
+ type ConversationWindow = (typeof CONVERSATION_WINDOW)[keyof typeof CONVERSATION_WINDOW];
222
+ declare const WINDOW_FILTERS: readonly [{
223
+ readonly value: "all";
224
+ readonly label: "Todas";
225
+ readonly dotClass: "";
226
+ }, {
227
+ readonly value: "fresh";
228
+ readonly label: "<12h";
229
+ readonly dotClass: "bg-green-500";
230
+ }, {
231
+ readonly value: "warning";
232
+ readonly label: "12-21h";
233
+ readonly dotClass: "bg-yellow-500";
234
+ }, {
235
+ readonly value: "critical";
236
+ readonly label: "21-24h";
237
+ readonly dotClass: "bg-red-500";
238
+ }, {
239
+ readonly value: "expired";
240
+ readonly label: ">24h";
241
+ readonly dotClass: "bg-gray-400";
242
+ }];
243
+ type WindowOfParams = {
244
+ readonly lastInboundAt: string | null;
245
+ readonly now: number;
246
+ /** Ausente = `whatsapp`. */
247
+ readonly channel?: ConversationChannel | undefined;
248
+ };
249
+ /**
250
+ * Sem `lastInboundAt` o cliente nunca escreveu, então não há janela aberta — classificar como
251
+ * expirada é o comportamento seguro: evita o atendente tentar texto livre e receber recusa.
252
+ *
253
+ * Canal sem janela de sessão (chat de site) é sempre `fresh`: ali nada expira, e marcar expirado
254
+ * bloquearia o composer inventando um limite que a plataforma não impõe.
255
+ */
256
+ declare function windowOf(params: WindowOfParams): ConversationWindow;
257
+ declare function formatStalledFor(lastAt: string, now: number): string;
258
+
259
+ type ConversationRowClassNames = {
260
+ root: string;
261
+ windowBar: string;
262
+ };
263
+ type ConversationRowProps = {
264
+ conversation: ConversationSummary;
265
+ active: boolean;
266
+ selected: boolean;
267
+ now: number;
268
+ busy: boolean;
269
+ onOpen: () => void;
270
+ onToggleSelected: () => void;
271
+ onTakeover: () => void;
272
+ className?: string;
273
+ classNames?: Partial<ConversationRowClassNames>;
274
+ };
275
+ declare function ConversationRow({ conversation, active, selected, now, busy, onOpen, onToggleSelected, onTakeover, className, classNames, }: ConversationRowProps): react.JSX.Element;
276
+
277
+ declare const CHANNEL_BRAND_COLOR: Readonly<Record<ConversationChannel, string>>;
278
+ interface ChannelIconProps {
279
+ channel?: ConversationChannel | undefined;
280
+ size?: number;
281
+ className?: string;
282
+ }
283
+ declare function ChannelIcon({ channel, size, className }: ChannelIconProps): react.JSX.Element;
284
+
285
+ interface ConversationHeaderLabels {
286
+ botMode: string;
287
+ humanMode: string;
288
+ returnToBot: string;
289
+ finish: string;
290
+ takeover: string;
291
+ download: string;
292
+ documents: string;
293
+ back: string;
294
+ moreActions: string;
295
+ }
296
+ declare const DEFAULT_CONVERSATION_HEADER_LABELS: ConversationHeaderLabels;
297
+ /**
298
+ * Partes estilizáveis do cabeçalho. Cada chave recebe classes que o `cn` funde por cima da base, e
299
+ * conflito de utilitário (padding, gap, borda) fica com o valor do produto.
300
+ */
301
+ interface ConversationHeaderClassNames {
302
+ root: string;
303
+ identity: string;
304
+ name: string;
305
+ meta: string;
306
+ actions: string;
307
+ desktopActions: string;
308
+ mobileMenu: string;
309
+ }
310
+ interface ConversationHeaderProps {
311
+ conversation: ConversationSummary;
312
+ busy?: boolean;
313
+ onTakeover?: () => void;
314
+ onReturnToBot?: () => void;
315
+ onFinish?: () => void;
316
+ onDownload?: () => void;
317
+ onOpenDocuments?: () => void;
318
+ documentsOpen?: boolean;
319
+ onBack?: () => void;
320
+ labels?: Partial<ConversationHeaderLabels>;
321
+ className?: string;
322
+ classNames?: Partial<ConversationHeaderClassNames>;
323
+ }
324
+ declare function ConversationHeader({ conversation, busy, onTakeover, onReturnToBot, onFinish, onDownload, onOpenDocuments, documentsOpen, onBack, labels: labelsOverride, className, classNames, }: ConversationHeaderProps): react.JSX.Element;
325
+
326
+ /**
327
+ * "Suas Seleções": o que o bot já coletou na conversa. Para o atendente que assume no meio, é a
328
+ * diferença entre ler o transcript inteiro e ver o estado em duas linhas.
329
+ *
330
+ * O pacote não interpreta o contexto — ele é `Record<string, unknown>` e cada produto nomeia as
331
+ * próprias chaves. O host traduz para `entries`; aqui só se decide como mostrar.
332
+ */
333
+ interface ConversationContextEntry {
334
+ key: string;
335
+ label: string;
336
+ value?: string | undefined;
337
+ icon?: string;
338
+ }
339
+ interface ConversationContextPanelLabels {
340
+ title: string;
341
+ empty: string;
342
+ }
343
+ declare const DEFAULT_CONVERSATION_CONTEXT_LABELS: ConversationContextPanelLabels;
344
+ interface ConversationContextPanelClassNames {
345
+ root: string;
346
+ toggle: string;
347
+ counter: string;
348
+ body: string;
349
+ }
350
+ interface ConversationContextPanelProps {
351
+ entries: readonly ConversationContextEntry[];
352
+ labels?: Partial<ConversationContextPanelLabels>;
353
+ className?: string;
354
+ classNames?: Partial<ConversationContextPanelClassNames>;
355
+ }
356
+ declare function ConversationContextPanel({ entries, labels: labelsOverride, className, classNames, }: ConversationContextPanelProps): react.JSX.Element;
357
+
358
+ interface WindowExpiredNoticeLabels {
359
+ title: string;
360
+ description: string;
361
+ sendTemplate: string;
362
+ }
363
+ declare const DEFAULT_WINDOW_EXPIRED_LABELS: WindowExpiredNoticeLabels;
364
+ interface WindowExpiredNoticeProps {
365
+ onSendTemplate?: () => void;
366
+ disabled?: boolean;
367
+ labels?: Partial<WindowExpiredNoticeLabels>;
368
+ className?: string;
369
+ }
370
+ declare function WindowExpiredNotice({ onSendTemplate, disabled, labels: labelsOverride, className, }: WindowExpiredNoticeProps): react.JSX.Element;
371
+ /**
372
+ * Só a faixa `expired` bloqueia: 21-24h ainda aceita texto livre e merece alerta, não impedimento.
373
+ */
374
+ declare function isWindowBlocking(window: ConversationWindow): boolean;
375
+
376
+ /**
377
+ * Arquivos trocados na conversa. Sai do transcript e vira lista própria porque anexo é o que o
378
+ * atendente mais precisa reencontrar depois — rolar meses de mensagens para achar um comprovante é
379
+ * o caso que a busca por documento existe para eliminar.
380
+ *
381
+ * Usa `useConversationDocuments`, então funciona com qualquer `ConversationsApi`. Host sem
382
+ * biblioteca de documentos cai no estado vazio, sem quebrar.
383
+ */
384
+ interface ConversationDocumentsPanelLabels {
385
+ toggle: string;
386
+ title: string;
387
+ searchPlaceholder: string;
388
+ empty: string;
389
+ loading: string;
390
+ failure: string;
391
+ download: string;
392
+ }
393
+ declare const DEFAULT_CONVERSATION_DOCUMENTS_LABELS: ConversationDocumentsPanelLabels;
394
+ interface ConversationDocumentsPanelClassNames {
395
+ root: string;
396
+ body: string;
397
+ }
398
+ interface ConversationDocumentsPanelProps {
399
+ conversationId: string;
400
+ /** Controlado de fora porque o gatilho vive no cabeçalho, junto das outras ações da conversa. */
401
+ open: boolean;
402
+ labels?: Partial<ConversationDocumentsPanelLabels>;
403
+ className?: string;
404
+ classNames?: Partial<ConversationDocumentsPanelClassNames>;
405
+ }
406
+ declare function ConversationDocumentsPanel({ conversationId, open, labels: labelsOverride, className, classNames, }: ConversationDocumentsPanelProps): react.JSX.Element | null;
407
+
408
+ /**
409
+ * Serialização do transcript para download. Fica no pacote porque o formato de um histórico de
410
+ * WhatsApp legível não é regra de negócio de ninguém — e porque o host que já tem as mensagens em
411
+ * tela não deveria precisar de rota nova só para salvar um arquivo.
412
+ *
413
+ * Funções puras, separadas do disparo do download: é o que permite testá-las sem DOM.
414
+ */
415
+
416
+ type BuildTranscriptTextParams = {
417
+ readonly messages: readonly MessagePayload[];
418
+ readonly whatsappNumber: string;
419
+ readonly clientName?: string | undefined;
420
+ };
421
+ /**
422
+ * Formato próximo ao export nativo do WhatsApp (`[data hora] Autor: texto`), que é o que pessoas e
423
+ * ferramentas de suporte já sabem ler.
424
+ */
425
+ declare function buildTranscriptText(params: BuildTranscriptTextParams): string;
426
+ declare function buildTranscriptFilename(whatsappNumber: string, generatedAt: Date): string;
427
+ /**
428
+ * Dispara o download no navegador. `revokeObjectURL` no fim não é higiene opcional: sem ele cada
429
+ * export retém o blob inteiro em memória até a aba fechar.
430
+ */
431
+ declare function downloadTextFile(filename: string, content: string): void;
432
+
433
+ /**
434
+ * Leitura passiva do tema do host: observa a classe `dark` no `<html>` e não escreve nada.
435
+ *
436
+ * Existe porque `useDarkMode` é um controlador — ele grava a classe e persiste a preferência. Um
437
+ * componente que só precisa escolher cor não pode usá-lo: bastava renderizar o mapa de fluxos
438
+ * para o app inteiro do host trocar de tema, seguindo o `prefers-color-scheme` do sistema em vez
439
+ * da configuração da aplicação.
440
+ */
441
+ declare function useIsDarkTheme(): boolean;
282
442
  declare function useDarkMode(): {
283
443
  isDark: boolean;
284
444
  toggle: () => void;
285
445
  };
286
446
 
447
+ /**
448
+ * Detecta tela estreita para decisões que CSS não resolve — como abrir ou não um painel por padrão.
449
+ *
450
+ * O breakpoint é o mesmo das classes `cv-only-*` e `.cv-back` (1024px). Duplicado aqui porque
451
+ * JavaScript não lê media query do stylesheet; se um dia divergirem, o sintoma é painel abrindo
452
+ * numa largura onde o resto da UI já mudou de modo.
453
+ */
454
+ declare const NARROW_MAX_WIDTH_PX = 1023;
455
+ declare function useIsNarrow(): boolean;
456
+
287
457
  interface UseWaitingNotificationsResult {
288
458
  unreadCount: number;
289
459
  conversations: ConversationSummary[];
@@ -435,6 +605,41 @@ interface WelcomeFarewellFormProps {
435
605
  }
436
606
  declare function WelcomeFarewellForm({ welcomeMessage, onWelcomeMessageChange, farewellMessage, onFarewellMessageChange, onSave, saving, saveSuccess, welcomePlaceholders, farewellPlaceholders, labels: labelsOverride, }: WelcomeFarewellFormProps): react.JSX.Element;
437
607
 
608
+ declare const TEMPLATE_SETTINGS_TAB: {
609
+ readonly SELECT: "select";
610
+ readonly CREATE: "create";
611
+ };
612
+ type TemplateSettingsTab = (typeof TEMPLATE_SETTINGS_TAB)[keyof typeof TEMPLATE_SETTINGS_TAB];
613
+ interface WhatsAppTemplatesSettingsLabels {
614
+ selectTab: string;
615
+ createTab: string;
616
+ }
617
+ declare const DEFAULT_TEMPLATES_SETTINGS_LABELS: WhatsAppTemplatesSettingsLabels;
618
+ interface WhatsAppTemplatesSettingsProps {
619
+ templates: WhatsAppTemplateSummary[];
620
+ loadingTemplates?: boolean;
621
+ templatesError?: boolean;
622
+ onRefreshTemplates?: () => void;
623
+ selectedTemplateName: string;
624
+ onSelectTemplate: (name: string, template: WhatsAppTemplateSummary | undefined) => void;
625
+ variables: string[];
626
+ onVariablesChange: (variables: string[]) => void;
627
+ availableVariables?: WhatsAppTemplateVariableSuggestion[];
628
+ saving?: boolean;
629
+ saveSuccess?: boolean;
630
+ onSave: (event: FormEvent) => void;
631
+ /** Ausente = host não sabe criar template; a aba de criação nem aparece. */
632
+ create?: {
633
+ value: WhatsAppCreateTemplateState;
634
+ onChange: (value: WhatsAppCreateTemplateState) => void;
635
+ onSubmit: (event: FormEvent) => void;
636
+ submitting?: boolean;
637
+ result?: WhatsAppCreateTemplateResult | null;
638
+ };
639
+ labels?: Partial<WhatsAppTemplatesSettingsLabels>;
640
+ }
641
+ declare function WhatsAppTemplatesSettings({ labels: labelsOverride, create, ...settingsProps }: WhatsAppTemplatesSettingsProps): react.JSX.Element;
642
+
438
643
  interface TopicItem {
439
644
  key: string;
440
645
  label: string;
@@ -515,7 +720,7 @@ interface UseConversationDocumentsResult {
515
720
  error: Error | undefined;
516
721
  refetch: () => Promise<void>;
517
722
  }
518
- declare function useConversationDocuments(conversationId: string, params?: UseConversationDocumentsParams): UseConversationDocumentsResult;
723
+ declare function useConversationDocuments(conversationId: string | undefined, params?: UseConversationDocumentsParams): UseConversationDocumentsResult;
519
724
 
520
725
  type ConversationRealtimeHandler = (event: MessageEvent) => void;
521
726
  declare function useConversationRealtime(conversationId: string | undefined, onEvent: ConversationRealtimeHandler): void;
@@ -539,4 +744,4 @@ interface AsyncResourceState<T> {
539
744
  refetch: () => Promise<void>;
540
745
  }
541
746
 
542
- export { type AsyncResourceState, AudioPlayer, type AudioPlayerProps, Avatar, type AvatarProps, type ConversationDocument, ConversationListItem, type ConversationListItemProps, type ConversationLocales, ConversationLocalesProvider, type ConversationLocalesProviderProps, type ConversationRealtimeHandler, type ConversationSummary, ConversationWallpaper, type ConversationWallpaperProps, type ConversationsApi, type ConversationsFeatures, ConversationsProvider, type ConversationsTheme, type ConversationsUIConfig, DateDivider, type DateDividerProps, EmojiPicker, type EmojiPickerProps, FileIcon, type FileIconProps, Lightbox, type LightboxProps, MediaRenderer, type MediaRendererProps, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerProps, type MessagePayload, MessageTail, MessageText, type MessageTextProps, MessageTimestamp, type ResolveMediaUrl, type SSEProvider, SimpleEmojiPicker, type SimpleEmojiPickerProps, StatusTicks, type StatusTicksProps, ToastProvider, type TopicItem, TopicsForm, type TopicsFormLabels, type TopicsFormProps, type UseConversationContextResult, type UseConversationDocumentsParams, type UseConversationDocumentsResult, type UseConversationListParams, type UseConversationListResult, type UseConversationMessagesResult, WelcomeFarewellForm, type WelcomeFarewellFormLabels, type WelcomeFarewellFormProps, WhatsAppCreateTemplateForm, type WhatsAppCreateTemplateFormLabels, type WhatsAppCreateTemplateFormProps, type WhatsAppCreateTemplateResult, type WhatsAppCreateTemplateState, WhatsAppMessageEditor, type WhatsAppMessageEditorProps, type WhatsAppTemplateHeaderType, WhatsAppTemplateSettingsForm, type WhatsAppTemplateSettingsFormLabels, type WhatsAppTemplateSettingsFormProps, type WhatsAppTemplateSummary, type WhatsAppTemplateVariableSuggestion, formatFileSize, formatPhone, formatTimestamp, htmlToWA, parseWhatsAppFormatting, phoneInitials, toast, useConversationContext, useConversationDocuments, useConversationList, useConversationLocales, useConversationMessages, useConversationRealtime, useConversations, useDarkMode, useGlobalRealtime, useToast, useWaitingNotifications, waToHTML, waToHTMLInline };
747
+ export { type AsyncResourceState, AudioPlayer, type AudioPlayerProps, Avatar, type AvatarProps, type BuildTranscriptTextParams, CHANNEL_BRAND_COLOR, CONVERSATION_WINDOW, ChannelIcon, type ChannelIconProps, ConversationChannel, type ConversationContextEntry, ConversationContextPanel, type ConversationContextPanelClassNames, type ConversationContextPanelLabels, type ConversationContextPanelProps, ConversationDocument, ConversationDocumentsPanel, type ConversationDocumentsPanelClassNames, type ConversationDocumentsPanelLabels, type ConversationDocumentsPanelProps, ConversationHeader, type ConversationHeaderClassNames, type ConversationHeaderLabels, type ConversationHeaderProps, ConversationListItem, type ConversationListItemProps, type ConversationLocales, ConversationLocalesProvider, type ConversationLocalesProviderProps, type ConversationRealtimeHandler, ConversationRow, type ConversationRowClassNames, type ConversationRowProps, ConversationSummary, ConversationWallpaper, type ConversationWallpaperProps, type ConversationWindow, ConversationsApi, ConversationsFeatures, ConversationsProvider, DEFAULT_CONVERSATION_CONTEXT_LABELS, DEFAULT_CONVERSATION_DOCUMENTS_LABELS, DEFAULT_CONVERSATION_HEADER_LABELS, DEFAULT_TEMPLATES_SETTINGS_LABELS, DEFAULT_WINDOW_EXPIRED_LABELS, DateDivider, type DateDividerClassNames, type DateDividerProps, EmojiPicker, type EmojiPickerProps, FileIcon, type FileIconProps, Lightbox, type LightboxProps, MediaRenderer, type MediaRendererProps, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerClassNames, type MessageComposerProps, MessagePayload, MessageTail, MessageText, type MessageTextProps, MessageTimestamp, NARROW_MAX_WIDTH_PX, type ResolveMediaUrl, SSEProvider, SimpleEmojiPicker, type SimpleEmojiPickerProps, StatusTicks, type StatusTicksProps, TEMPLATE_SETTINGS_TAB, type TemplateSettingsTab, ToastProvider, type TopicItem, TopicsForm, type TopicsFormLabels, type TopicsFormProps, type UseConversationContextResult, type UseConversationDocumentsParams, type UseConversationDocumentsResult, type UseConversationListParams, type UseConversationListResult, type UseConversationMessagesResult, WINDOW_FILTERS, WelcomeFarewellForm, type WelcomeFarewellFormLabels, type WelcomeFarewellFormProps, WhatsAppCreateTemplateForm, type WhatsAppCreateTemplateFormLabels, type WhatsAppCreateTemplateFormProps, type WhatsAppCreateTemplateResult, type WhatsAppCreateTemplateState, WhatsAppMessageEditor, type WhatsAppMessageEditorProps, type WhatsAppTemplateHeaderType, WhatsAppTemplateSettingsForm, type WhatsAppTemplateSettingsFormLabels, type WhatsAppTemplateSettingsFormProps, type WhatsAppTemplateSummary, type WhatsAppTemplateVariableSuggestion, WhatsAppTemplatesSettings, type WhatsAppTemplatesSettingsLabels, type WhatsAppTemplatesSettingsProps, WindowExpiredNotice, type WindowExpiredNoticeLabels, type WindowExpiredNoticeProps, type WindowOfParams, buildTranscriptFilename, buildTranscriptText, downloadTextFile, formatFileSize, formatPhone, formatStalledFor, formatTimestamp, htmlToWA, isWindowBlocking, parseWhatsAppFormatting, phoneInitials, toast, useConversationContext, useConversationDocuments, useConversationList, useConversationLocales, useConversationMessages, useConversationRealtime, useConversations, useDarkMode, useGlobalRealtime, useIsDarkTheme, useIsNarrow, useToast, useWaitingNotifications, waToHTML, waToHTMLInline, windowOf };