@adatechnology/conversations-ui 0.4.2 → 0.6.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.
Files changed (110) hide show
  1. package/README.md +231 -0
  2. package/dist/{ConversationSimulatorPanel-DnXElfY8.d.ts → ConversationSimulatorPanel-CrfFjiWh.d.ts} +2 -268
  3. package/dist/{chunk-PJNR6UMN.js → chunk-CBYEHPX6.js} +1 -63
  4. package/dist/{chunk-4P5KMWOI.js → chunk-FAQLC3TX.js} +327 -522
  5. package/dist/{chunk-HMCOTZAX.js → chunk-HOKNKXIT.js} +1 -1
  6. package/dist/chunk-MRVFFJEH.js +64 -0
  7. package/dist/chunk-O6457XHK.js +255 -0
  8. package/dist/chunk-OVFCLRQQ.js +84 -0
  9. package/dist/flows/index.js +5 -3
  10. package/dist/index.d.ts +21 -5
  11. package/dist/index.js +783 -807
  12. package/dist/participant/index.d.ts +186 -0
  13. package/dist/participant/index.js +1708 -0
  14. package/dist/preview/index.d.ts +4 -2
  15. package/dist/preview/index.js +4 -2
  16. package/dist/quickReply.types-B4gmZsSr.d.ts +274 -0
  17. package/dist/styles.css +689 -0
  18. package/package.json +8 -4
  19. package/src/ChannelIcon.tsx +2 -0
  20. package/src/MessageText.test.tsx +76 -0
  21. package/src/MessageText.tsx +28 -12
  22. package/src/MessageTextCopiedBadge.tsx +15 -0
  23. package/src/StatusTicks.test.tsx +51 -0
  24. package/src/StatusTicks.tsx +36 -6
  25. package/src/buildOutput.test.ts +41 -0
  26. package/src/channelCapability.test.ts +17 -0
  27. package/src/conversationChannel.test.ts +30 -1
  28. package/src/conversationChannel.ts +19 -1
  29. package/src/legacyChannels.golden.test.ts +89 -0
  30. package/src/legacyDefaults.golden.test.tsx +127 -0
  31. package/src/legacyStyles.fixture.css +1121 -0
  32. package/src/legacyStyles.golden.test.ts +121 -0
  33. package/src/messageTextCopyInteraction.test.ts +75 -0
  34. package/src/messageTextCopyInteraction.ts +37 -0
  35. package/src/participant/ParticipantAttachmentItem.tsx +77 -0
  36. package/src/participant/ParticipantChannelBadges.test.tsx +59 -0
  37. package/src/participant/ParticipantChannelBadges.tsx +75 -0
  38. package/src/participant/ParticipantComposer.test.tsx +97 -0
  39. package/src/participant/ParticipantComposer.tsx +167 -0
  40. package/src/participant/ParticipantConversations.test.tsx +90 -0
  41. package/src/participant/ParticipantConversations.tsx +49 -0
  42. package/src/participant/ParticipantConversationsScreen.tsx +78 -0
  43. package/src/participant/ParticipantInbox.test.tsx +244 -0
  44. package/src/participant/ParticipantInbox.tsx +149 -0
  45. package/src/participant/ParticipantInboxRow.tsx +75 -0
  46. package/src/participant/ParticipantInboxSearchField.tsx +34 -0
  47. package/src/participant/ParticipantLoadState.tsx +27 -0
  48. package/src/participant/ParticipantMessageBubble.test.tsx +167 -0
  49. package/src/participant/ParticipantMessageBubble.tsx +185 -0
  50. package/src/participant/ParticipantProtocolBadge.tsx +74 -0
  51. package/src/participant/ParticipantProtocolView.test.tsx +208 -0
  52. package/src/participant/ParticipantThread.test.tsx +149 -0
  53. package/src/participant/ParticipantThread.tsx +184 -0
  54. package/src/participant/ParticipantThreadScreen.tsx +135 -0
  55. package/src/participant/bindAttachmentUrlResolver.test.ts +25 -0
  56. package/src/participant/bindAttachmentUrlResolver.ts +7 -0
  57. package/src/participant/index.ts +26 -0
  58. package/src/participant/participant.types.ts +52 -0
  59. package/src/participant/participantApi.types.ts +59 -0
  60. package/src/participant/participantAttachmentUrl.test.ts +32 -0
  61. package/src/participant/participantAttachmentUrl.ts +10 -0
  62. package/src/participant/participantBubbleActions.test.ts +44 -0
  63. package/src/participant/participantBubbleActions.ts +40 -0
  64. package/src/participant/participantChannels.test.ts +42 -0
  65. package/src/participant/participantChannels.ts +33 -0
  66. package/src/participant/participantDrafts.test.ts +65 -0
  67. package/src/participant/participantDrafts.ts +50 -0
  68. package/src/participant/participantFileValidation.test.ts +86 -0
  69. package/src/participant/participantFileValidation.ts +72 -0
  70. package/src/participant/participantFixtures.test-helper.ts +41 -0
  71. package/src/participant/participantGrouping.test.ts +236 -0
  72. package/src/participant/participantGrouping.ts +90 -0
  73. package/src/participant/participantInboxState.test.ts +69 -0
  74. package/src/participant/participantInboxState.ts +74 -0
  75. package/src/participant/participantLabels.ts +115 -0
  76. package/src/participant/participantLoadView.test.ts +23 -0
  77. package/src/participant/participantLoadView.ts +21 -0
  78. package/src/participant/participantMessages.test.ts +128 -0
  79. package/src/participant/participantMessages.ts +73 -0
  80. package/src/participant/participantNewMessages.test.ts +54 -0
  81. package/src/participant/participantNewMessages.ts +29 -0
  82. package/src/participant/participantProtocol.test.ts +104 -0
  83. package/src/participant/participantProtocol.ts +52 -0
  84. package/src/participant/participantProtocolCopy.test.ts +45 -0
  85. package/src/participant/participantProtocolCopy.ts +32 -0
  86. package/src/participant/participantQuickReply.test.ts +23 -0
  87. package/src/participant/participantQuickReply.ts +6 -0
  88. package/src/participant/participantScroll.test.ts +69 -0
  89. package/src/participant/participantScroll.ts +43 -0
  90. package/src/participant/participantSendController.test.ts +201 -0
  91. package/src/participant/participantSendController.ts +134 -0
  92. package/src/participant/participantSendStates.ts +31 -0
  93. package/src/participant/participantTheme.ts +20 -0
  94. package/src/participant/participantThreadState.test.ts +75 -0
  95. package/src/participant/participantThreadState.ts +59 -0
  96. package/src/participant/shouldMarkRead.test.ts +24 -0
  97. package/src/participant/shouldMarkRead.ts +16 -0
  98. package/src/participant/useDocumentVisibility.ts +18 -0
  99. package/src/participant/useParticipantConversationView.ts +60 -0
  100. package/src/participant/useParticipantDraft.ts +45 -0
  101. package/src/participant/useParticipantInbox.ts +57 -0
  102. package/src/participant/useParticipantMarkRead.ts +26 -0
  103. package/src/participant/useParticipantRevalidation.ts +36 -0
  104. package/src/participant/useParticipantSend.ts +84 -0
  105. package/src/participant/useParticipantThread.ts +116 -0
  106. package/src/participant/useParticipantThreadScroll.ts +26 -0
  107. package/src/participant/useStickToBottom.ts +77 -0
  108. package/src/preview/ConversationSimulatorPanel.tsx +8 -0
  109. package/src/styles.css +178 -0
  110. package/src/types.ts +3 -1
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  cn
3
- } from "./chunk-PJNR6UMN.js";
3
+ } from "./chunk-MRVFFJEH.js";
4
4
 
5
5
  // src/Tooltip.tsx
6
6
  import { useEffect, useRef, useState } from "react";
@@ -0,0 +1,64 @@
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
+ // src/lib/cn.ts
54
+ import { clsx } from "clsx";
55
+ import { twMerge } from "tailwind-merge";
56
+ function cn(...inputs) {
57
+ return twMerge(clsx(inputs));
58
+ }
59
+
60
+ export {
61
+ cn,
62
+ useIsDarkTheme,
63
+ useDarkMode
64
+ };
@@ -0,0 +1,255 @@
1
+ // src/lib/phone.ts
2
+ function formatPhone(number) {
3
+ const digits = number.replace(/\D/g, "");
4
+ if (digits.length === 13) {
5
+ return `+${digits.slice(0, 2)} (${digits.slice(2, 4)}) ${digits.slice(4, 9)}-${digits.slice(9, 13)}`;
6
+ }
7
+ if (digits.length === 12) {
8
+ return `+${digits.slice(0, 2)} (${digits.slice(2, 4)}) ${digits.slice(4, 8)}-${digits.slice(8, 12)}`;
9
+ }
10
+ if (digits.length === 11) {
11
+ return `(${digits.slice(0, 2)}) ${digits.slice(2, 7)}-${digits.slice(7, 11)}`;
12
+ }
13
+ if (digits.length === 10) {
14
+ return `(${digits.slice(0, 2)}) ${digits.slice(2, 6)}-${digits.slice(6, 10)}`;
15
+ }
16
+ return number;
17
+ }
18
+ var COUNTRY_FLAG_BY_DIAL_CODE = {
19
+ "55": "\u{1F1E7}\u{1F1F7}",
20
+ "351": "\u{1F1F5}\u{1F1F9}",
21
+ "34": "\u{1F1EA}\u{1F1F8}",
22
+ "54": "\u{1F1E6}\u{1F1F7}",
23
+ "56": "\u{1F1E8}\u{1F1F1}",
24
+ "57": "\u{1F1E8}\u{1F1F4}",
25
+ "52": "\u{1F1F2}\u{1F1FD}",
26
+ "598": "\u{1F1FA}\u{1F1FE}",
27
+ "595": "\u{1F1F5}\u{1F1FE}",
28
+ "44": "\u{1F1EC}\u{1F1E7}",
29
+ "49": "\u{1F1E9}\u{1F1EA}",
30
+ "39": "\u{1F1EE}\u{1F1F9}",
31
+ "33": "\u{1F1EB}\u{1F1F7}"
32
+ };
33
+ function phoneCountryFlag(number) {
34
+ const digits = number.replace(/\D/g, "");
35
+ for (const length of [3, 2, 1]) {
36
+ const flag = COUNTRY_FLAG_BY_DIAL_CODE[digits.slice(0, length)];
37
+ if (flag) return flag;
38
+ }
39
+ return "";
40
+ }
41
+ function phoneInitials(number) {
42
+ const digits = number.replace(/\D/g, "");
43
+ return digits.slice(-2);
44
+ }
45
+
46
+ // src/conversationChannel.ts
47
+ var CONVERSATION_CHANNEL = {
48
+ WHATSAPP: "whatsapp",
49
+ MESSENGER: "messenger",
50
+ INSTAGRAM: "instagram",
51
+ APP: "app",
52
+ PORTAL: "portal",
53
+ WEBCHAT: "webchat",
54
+ EMAIL: "email"
55
+ };
56
+ var DEFAULT_CONVERSATION_CHANNEL = CONVERSATION_CHANNEL.WHATSAPP;
57
+ var REOPEN_MECHANISM = {
58
+ TEMPLATE: "template",
59
+ TAG: "tag",
60
+ NONE: "none"
61
+ };
62
+ var HANDLE_KIND = {
63
+ PHONE: "phone",
64
+ USERNAME: "username",
65
+ SESSION: "session",
66
+ EMAIL: "email"
67
+ };
68
+ var CHANNEL_CAPABILITIES = {
69
+ [CONVERSATION_CHANNEL.WHATSAPP]: {
70
+ label: "WhatsApp",
71
+ icon: "\u{1F4AC}",
72
+ reopenMechanism: REOPEN_MECHANISM.TEMPLATE,
73
+ handleKind: HANDLE_KIND.PHONE
74
+ },
75
+ [CONVERSATION_CHANNEL.MESSENGER]: {
76
+ // Messenger também tem 24h, mas reabre com message tag — não com template aprovado.
77
+ label: "Messenger",
78
+ icon: "\u{1F4E8}",
79
+ reopenMechanism: REOPEN_MECHANISM.TAG,
80
+ handleKind: HANDLE_KIND.USERNAME
81
+ },
82
+ [CONVERSATION_CHANNEL.INSTAGRAM]: {
83
+ label: "Instagram",
84
+ icon: "\u{1F4F7}",
85
+ reopenMechanism: REOPEN_MECHANISM.TAG,
86
+ handleKind: HANDLE_KIND.USERNAME
87
+ },
88
+ [CONVERSATION_CHANNEL.APP]: {
89
+ label: "App",
90
+ icon: "\u{1F4F1}",
91
+ reopenMechanism: REOPEN_MECHANISM.NONE,
92
+ handleKind: HANDLE_KIND.SESSION
93
+ },
94
+ [CONVERSATION_CHANNEL.PORTAL]: {
95
+ label: "Portal",
96
+ icon: "\u{1F517}",
97
+ reopenMechanism: REOPEN_MECHANISM.NONE,
98
+ handleKind: HANDLE_KIND.SESSION
99
+ },
100
+ [CONVERSATION_CHANNEL.WEBCHAT]: {
101
+ // Chat próprio: sem intermediário, sem janela. Bloquear o composer aqui seria inventar limite.
102
+ label: "Chat do site",
103
+ icon: "\u{1F310}",
104
+ reopenMechanism: REOPEN_MECHANISM.NONE,
105
+ handleKind: HANDLE_KIND.SESSION
106
+ },
107
+ [CONVERSATION_CHANNEL.EMAIL]: {
108
+ // RF10: e-mail entra no vocabulário; ele não reabre por template nem por tag — é só resposta.
109
+ label: "E-mail",
110
+ icon: "\u2709\uFE0F",
111
+ reopenMechanism: REOPEN_MECHANISM.NONE,
112
+ handleKind: HANDLE_KIND.EMAIL
113
+ }
114
+ };
115
+ function capabilitiesOf(channel) {
116
+ return CHANNEL_CAPABILITIES[channel ?? DEFAULT_CONVERSATION_CHANNEL];
117
+ }
118
+ var CHANNEL_FILTER_ALL = "all";
119
+ function channelFiltersFor(conversations) {
120
+ const present = new Set(
121
+ conversations.map((conversation) => conversation.channel ?? DEFAULT_CONVERSATION_CHANNEL)
122
+ );
123
+ if (present.size < 2) return [];
124
+ const ordered = Object.keys(CHANNEL_CAPABILITIES).filter((channel) => present.has(channel));
125
+ return [
126
+ { value: CHANNEL_FILTER_ALL, label: "Todos" },
127
+ ...ordered.map((channel) => ({ value: channel, label: CHANNEL_CAPABILITIES[channel].label }))
128
+ ];
129
+ }
130
+ function formatContactHandle(params) {
131
+ const { handleKind } = capabilitiesOf(params.channel);
132
+ if (handleKind === HANDLE_KIND.PHONE) return formatPhone(params.handle);
133
+ if (handleKind === HANDLE_KIND.USERNAME) return params.handle.startsWith("@") ? params.handle : `@${params.handle}`;
134
+ if (handleKind === HANDLE_KIND.EMAIL) return params.handle;
135
+ return `Visitante ${params.handle.slice(-6)}`;
136
+ }
137
+ function contactFlag(params) {
138
+ return capabilitiesOf(params.channel).handleKind === HANDLE_KIND.PHONE ? phoneCountryFlag(params.handle) : "";
139
+ }
140
+
141
+ // src/channelCapability.ts
142
+ import {
143
+ CHANNEL_CAPABILITIES as CORE_CHANNEL_CAPABILITIES,
144
+ CONVERSATION_CHANNEL as CORE_CONVERSATION_CHANNEL
145
+ } from "@adatechnology/conversation-contracts";
146
+ var CORE_CHANNELS = CORE_CONVERSATION_CHANNEL;
147
+ function isCoreChannel(channel) {
148
+ return CORE_CHANNELS.includes(channel);
149
+ }
150
+ var META_ONLY_CHANNEL_CAPABILITY = CORE_CHANNEL_CAPABILITIES.whatsapp;
151
+ function channelCapabilityFor(channel) {
152
+ const resolved = channel ?? DEFAULT_CONVERSATION_CHANNEL;
153
+ return isCoreChannel(resolved) ? CORE_CHANNEL_CAPABILITIES[resolved] : META_ONLY_CHANNEL_CAPABILITY;
154
+ }
155
+
156
+ // src/StatusTicks.tsx
157
+ import { AlertTriangle, Clock } from "lucide-react";
158
+ import { jsx, jsxs } from "react/jsx-runtime";
159
+ var TAILWIND_COLOR_CLASS = {
160
+ sent: "text-black/40 dark:text-white/40",
161
+ delivered: "text-black/40 dark:text-white/40",
162
+ read: "text-sky-500",
163
+ failed: "text-red-500"
164
+ };
165
+ var STYLESHEET_MODIFIER = {
166
+ queued: "queued",
167
+ sent: "sent",
168
+ delivered: "delivered",
169
+ read: "read",
170
+ failed: "failed",
171
+ bounced: "failed"
172
+ };
173
+ function Ticks({ double }) {
174
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 20 12", width: "15", height: "9", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
175
+ double && /* @__PURE__ */ jsx("path", { d: "M1 6.5L4.5 10L11 2", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }),
176
+ /* @__PURE__ */ jsx(
177
+ "path",
178
+ {
179
+ d: double ? "M6 6.5L9.5 10L19 1" : "M1 6.5L5 10.5L14.5 1",
180
+ stroke: "currentColor",
181
+ strokeWidth: "1.6",
182
+ strokeLinecap: "round",
183
+ strokeLinejoin: "round"
184
+ }
185
+ )
186
+ ] });
187
+ }
188
+ function StylesheetStatusTicks({ status, title, queuedLabel }) {
189
+ const modifier = STYLESHEET_MODIFIER[status] ?? "sent";
190
+ const ariaLabel = modifier === "queued" ? queuedLabel : void 0;
191
+ const icon = modifier === "failed" ? /* @__PURE__ */ jsx(AlertTriangle, { size: 11 }) : modifier === "queued" ? /* @__PURE__ */ jsx(Clock, { size: 11 }) : /* @__PURE__ */ jsx(Ticks, { double: modifier !== "sent" });
192
+ return /* @__PURE__ */ jsx(
193
+ "span",
194
+ {
195
+ className: `cv-status-ticks cv-status-ticks--${modifier}`,
196
+ "data-cv-tooltip": title ?? ariaLabel,
197
+ "aria-label": ariaLabel,
198
+ children: icon
199
+ }
200
+ );
201
+ }
202
+ function StatusTicks({ status, title, appearance = "tailwind", queuedLabel = "Queued" }) {
203
+ if (appearance === "stylesheet") return /* @__PURE__ */ jsx(StylesheetStatusTicks, { status, title, queuedLabel });
204
+ const colorClass = TAILWIND_COLOR_CLASS[status] ?? TAILWIND_COLOR_CLASS.sent;
205
+ return /* @__PURE__ */ jsx("span", { className: `cursor-help leading-none flex items-center ${colorClass}`, "data-cv-tooltip": title, children: status === "failed" ? /* @__PURE__ */ jsx(AlertTriangle, { size: 11 }) : /* @__PURE__ */ jsx(Ticks, { double: status !== "sent" }) });
206
+ }
207
+
208
+ // src/lib/format.ts
209
+ function formatTimestamp(timestamp) {
210
+ try {
211
+ const date = new Date(timestamp);
212
+ const hours = date.getHours().toString().padStart(2, "0");
213
+ const minutes = date.getMinutes().toString().padStart(2, "0");
214
+ return `${hours}:${minutes}`;
215
+ } catch {
216
+ return timestamp;
217
+ }
218
+ }
219
+ function formatDateTime(iso) {
220
+ const d = new Date(iso);
221
+ return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
222
+ }
223
+ function isSameDay(a, b) {
224
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
225
+ }
226
+ var FILE_SIZE_UNITS = ["B", "KB", "MB", "GB"];
227
+ function formatFileSize(bytes) {
228
+ if (!isFinite(bytes) || bytes < 0) return "";
229
+ if (bytes < 1) return "0 B";
230
+ const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), FILE_SIZE_UNITS.length - 1);
231
+ const value = bytes / 1024 ** exponent;
232
+ const formatted = exponent === 0 ? value.toString() : value.toFixed(value < 10 ? 1 : 0);
233
+ return `${formatted} ${FILE_SIZE_UNITS[exponent]}`;
234
+ }
235
+
236
+ export {
237
+ formatPhone,
238
+ phoneInitials,
239
+ CONVERSATION_CHANNEL,
240
+ DEFAULT_CONVERSATION_CHANNEL,
241
+ REOPEN_MECHANISM,
242
+ HANDLE_KIND,
243
+ CHANNEL_CAPABILITIES,
244
+ capabilitiesOf,
245
+ CHANNEL_FILTER_ALL,
246
+ channelFiltersFor,
247
+ formatContactHandle,
248
+ contactFlag,
249
+ channelCapabilityFor,
250
+ StatusTicks,
251
+ formatTimestamp,
252
+ formatDateTime,
253
+ isSameDay,
254
+ formatFileSize
255
+ };
@@ -0,0 +1,84 @@
1
+ import {
2
+ parseWhatsAppFormatting
3
+ } from "./chunk-CBYEHPX6.js";
4
+
5
+ // src/MessageText.tsx
6
+ import { useCallback, useState } from "react";
7
+
8
+ // src/theme.ts
9
+ var CHAT_TEXT_PRIMARY_CLASS = "text-[#111b21] dark:text-[#e9edef]";
10
+ var CHAT_TEXT_SECONDARY_CLASS = "text-[#667781] dark:text-[#8696a0]";
11
+
12
+ // src/messageTextCopyInteraction.ts
13
+ function resolveCopyInteraction({
14
+ copyOnClick,
15
+ accessibleCopy,
16
+ onCopy
17
+ }) {
18
+ if (!copyOnClick) return {};
19
+ if (!accessibleCopy) return { onClick: onCopy };
20
+ return {
21
+ onClick: onCopy,
22
+ role: "button",
23
+ tabIndex: 0,
24
+ onKeyDown: (event) => {
25
+ if (event.key !== "Enter" && event.key !== " ") return;
26
+ event.preventDefault();
27
+ onCopy();
28
+ }
29
+ };
30
+ }
31
+
32
+ // src/MessageTextCopiedBadge.tsx
33
+ import { jsx } from "react/jsx-runtime";
34
+ var TAILWIND_CLASS = "absolute top-0 right-0 -translate-y-full bg-[#3b4a54] text-white text-[11px] px-1.5 py-0.5 rounded shadow-lg";
35
+ function MessageTextCopiedBadge({ label = "Copiado!", appearance = "tailwind" }) {
36
+ const className = appearance === "stylesheet" ? "cv-message-text__copied" : TAILWIND_CLASS;
37
+ return /* @__PURE__ */ jsx("span", { className, children: label });
38
+ }
39
+
40
+ // src/MessageText.tsx
41
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
42
+ var TAILWIND_CLASS2 = "text-[14.2px] leading-[19px] whitespace-pre-wrap break-words select-all [&_strong]:font-bold [&_em]:italic [&_del]:line-through";
43
+ function resolveClassName(appearance, copyOnClick) {
44
+ if (appearance === "tailwind") return TAILWIND_CLASS2;
45
+ return copyOnClick ? "cv-message-text cv-message-text--copyable" : "cv-message-text";
46
+ }
47
+ function MessageText({
48
+ message,
49
+ copyOnClick = true,
50
+ accessibleCopy = false,
51
+ copiedLabel,
52
+ appearance = "tailwind"
53
+ }) {
54
+ const [copied, setCopied] = useState(false);
55
+ const handleCopy = useCallback(async () => {
56
+ if (!copyOnClick || !message.content) return;
57
+ try {
58
+ await navigator.clipboard.writeText(message.content);
59
+ setCopied(true);
60
+ setTimeout(() => setCopied(false), 2e3);
61
+ } catch {
62
+ }
63
+ }, [copyOnClick, message.content]);
64
+ if (message.type === "template")
65
+ return /* @__PURE__ */ jsx2("p", { className: `text-sm italic ${CHAT_TEXT_SECONDARY_CLASS}`, children: message.content ?? "Template message" });
66
+ const interactionProps = resolveCopyInteraction({ copyOnClick, accessibleCopy, onCopy: handleCopy });
67
+ return /* @__PURE__ */ jsxs(
68
+ "div",
69
+ {
70
+ className: resolveClassName(appearance, copyOnClick),
71
+ ...interactionProps,
72
+ children: [
73
+ /* @__PURE__ */ jsx2("div", { children: parseWhatsAppFormatting(message.content ?? "") }),
74
+ copied && /* @__PURE__ */ jsx2(MessageTextCopiedBadge, { label: copiedLabel, appearance })
75
+ ]
76
+ }
77
+ );
78
+ }
79
+
80
+ export {
81
+ CHAT_TEXT_PRIMARY_CLASS,
82
+ CHAT_TEXT_SECONDARY_CLASS,
83
+ MessageText
84
+ };
@@ -1,11 +1,13 @@
1
1
  import {
2
2
  TooltipLayer,
3
3
  WhatsAppMessagePreview
4
- } from "../chunk-HMCOTZAX.js";
4
+ } from "../chunk-HOKNKXIT.js";
5
5
  import {
6
- parseWhatsAppFormatting,
7
6
  useIsDarkTheme
8
- } from "../chunk-PJNR6UMN.js";
7
+ } from "../chunk-MRVFFJEH.js";
8
+ import {
9
+ parseWhatsAppFormatting
10
+ } from "../chunk-CBYEHPX6.js";
9
11
 
10
12
  // src/flows/FlowNodeCard.tsx
11
13
  import { useState } from "react";
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import * as react from 'react';
2
2
  import react__default, { ReactNode, CSSProperties, UIEvent, FormEvent, RefObject, KeyboardEvent } from 'react';
3
- import { M as MessagePayload, R as ResolveMediaUrl, U as InteractiveSelection, V as MessageTranscription, W as ConversationChannel, X as InteractivePayload, Y as ConversationsFeatures, Z as QuickReply$1, C as ConversationSummary, L as ListConversationsParams, b as ConversationsApi, S as SSEProvider, _ as TranscriptionMode, $ as ListDocumentsParams, d as ConversationDocument, a0 as ConversationTemplate, a1 as QueuedAttachment, a2 as QuickReplyInput, a3 as QuickReplyAttachment, a4 as ConversationVariable, a5 as ChannelFilter, a6 as ChannelFilterOption, k as ConversationSimulatorClient, m as ConversationSimulatorPanelLabels, P as PreviewUploadedMedia } from './ConversationSimulatorPanel-DnXElfY8.js';
4
- export { A as AudioRecorderButton, g as AudioRecorderButtonLabels, h as AudioRecorderButtonProps, a7 as CHANNEL_CAPABILITIES, a8 as CHANNEL_FILTER_ALL, a9 as CONVERSATION_CHANNEL, aa as ChannelCapabilities, ab as CompanyDocument, ac as CompanyDocumentPage, ad as ConversationDocumentPage, a as ConversationEventSource, c as ConversationPage, ae as ConversationsTheme, af as ConversationsUIConfig, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, ag as DEFAULT_CONVERSATION_CHANNEL, ah as DEFAULT_MAX_RECORDING_MILLISECONDS, ai as FormatContactHandleParams, aj as HANDLE_KIND, ak as HandleKind, al as InteractiveOption, am as InteractiveSection, an as MediaRenderer, ao as MediaRendererProps, ap as QUICK_REPLY_ATTACHMENT_LIMIT, aq as REOPEN_MECHANISM, ar as ReopenMechanism, y as SendSimulatorMediaParams, z as SimulatorMediaKind, as as StoredAttachmentSendResult, at as TranscriptionStatus, au as capabilitiesOf, av as channelFiltersFor, aw as contactFlag, ax as formatContactHandle } from './ConversationSimulatorPanel-DnXElfY8.js';
3
+ import { M as MessagePayload, I as InteractiveSelection, b as MessageTranscription, C as ConversationChannel, e as InteractivePayload, f as ConversationsFeatures, Q as QuickReply$1, T as TranscriptionMode, g as QueuedAttachment, c as QuickReplyInput, d as QuickReplyAttachment, h as ConversationVariable, i as ChannelFilter, j as ChannelFilterOption } from './quickReply.types-B4gmZsSr.js';
4
+ export { k as CHANNEL_CAPABILITIES, l as CHANNEL_FILTER_ALL, m as CONVERSATION_CHANNEL, n as ChannelCapabilities, a as ConversationsTheme, o as ConversationsUIConfig, D as DEFAULT_CONVERSATION_CHANNEL, F as FormatContactHandleParams, H as HANDLE_KIND, p as HandleKind, q as InteractiveOption, r as InteractiveSection, s as QUICK_REPLY_ATTACHMENT_LIMIT, R as REOPEN_MECHANISM, t as ReopenMechanism, S as StoredAttachmentSendResult, u as TranscriptionStatus, v as capabilitiesOf, w as channelFiltersFor, x as contactFlag, y as formatContactHandle } from './quickReply.types-B4gmZsSr.js';
5
+ import { R as ResolveMediaUrl, C as ConversationSummary, L as ListConversationsParams, b as ConversationsApi, S as SSEProvider, Q as ListDocumentsParams, d as ConversationDocument, U as ConversationTemplate, k as ConversationSimulatorClient, m as ConversationSimulatorPanelLabels, P as PreviewUploadedMedia } from './ConversationSimulatorPanel-CrfFjiWh.js';
6
+ export { A as AudioRecorderButton, g as AudioRecorderButtonLabels, h as AudioRecorderButtonProps, V as CompanyDocument, W as CompanyDocumentPage, X as ConversationDocumentPage, a as ConversationEventSource, c as ConversationPage, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, Y as DEFAULT_MAX_RECORDING_MILLISECONDS, Z as MediaRenderer, _ as MediaRendererProps, y as SendSimulatorMediaParams, z as SimulatorMediaKind } from './ConversationSimulatorPanel-CrfFjiWh.js';
5
7
  import { ChannelCapability } from '@adatechnology/conversation-contracts';
6
8
  export { ChannelCapability } from '@adatechnology/conversation-contracts';
7
9
  import '@adatechnology/meta-whatsapp-contracts/testing';
@@ -600,8 +602,12 @@ declare function TooltipLayer(): react.ReactPortal | null;
600
602
  interface StatusTicksProps {
601
603
  status: string;
602
604
  title?: string;
605
+ /** 'tailwind' (default) keeps the 0.4.2 markup; 'stylesheet' uses .cv-status-ticks--* and adds queued/bounced handling. */
606
+ appearance?: 'tailwind' | 'stylesheet';
607
+ /** Accessible label of the queued state (stylesheet appearance only). Defaults to 'Queued'. */
608
+ queuedLabel?: string;
603
609
  }
604
- declare function StatusTicks({ status, title }: StatusTicksProps): react.JSX.Element;
610
+ declare function StatusTicks({ status, title, appearance, queuedLabel }: StatusTicksProps): react.JSX.Element;
605
611
 
606
612
  interface LightboxLabels {
607
613
  /** Texto alternativo quando a imagem não tem legenda — sem ele o leitor de tela anuncia a URL. */
@@ -625,10 +631,20 @@ interface FileIconProps {
625
631
  }
626
632
  declare function FileIcon({ filename, mimeType, size, className }: FileIconProps): react.JSX.Element;
627
633
 
634
+ type MessageTextAppearance = 'tailwind' | 'stylesheet';
635
+
628
636
  interface MessageTextProps {
629
637
  message: MessagePayload;
630
- }
631
- declare function MessageText({ message }: MessageTextProps): react.JSX.Element;
638
+ /** Defaults to true (tap copies the text, as in 0.4.2); false disables copying. */
639
+ copyOnClick?: boolean;
640
+ /** Opt-in: exposes the copy target as a keyboard-operable button (role, tabIndex, Enter/Space). Defaults to false. */
641
+ accessibleCopy?: boolean;
642
+ /** Text of the badge shown after copying. Defaults to 'Copiado!', as in 0.4.2. */
643
+ copiedLabel?: string;
644
+ /** 'tailwind' (default) keeps the 0.4.2 utility classes; 'stylesheet' uses the .cv-* rules from styles.css. */
645
+ appearance?: MessageTextAppearance;
646
+ }
647
+ declare function MessageText({ message, copyOnClick, accessibleCopy, copiedLabel, appearance, }: MessageTextProps): react.JSX.Element;
632
648
 
633
649
  interface MessageTimestampProps {
634
650
  timestamp: string;