@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.
- package/README.md +231 -0
- package/dist/{ConversationSimulatorPanel-DnXElfY8.d.ts → ConversationSimulatorPanel-CrfFjiWh.d.ts} +2 -268
- package/dist/{chunk-PJNR6UMN.js → chunk-CBYEHPX6.js} +1 -63
- package/dist/{chunk-4P5KMWOI.js → chunk-FAQLC3TX.js} +327 -522
- package/dist/{chunk-HMCOTZAX.js → chunk-HOKNKXIT.js} +1 -1
- package/dist/chunk-MRVFFJEH.js +64 -0
- package/dist/chunk-O6457XHK.js +255 -0
- package/dist/chunk-OVFCLRQQ.js +84 -0
- package/dist/flows/index.js +5 -3
- package/dist/index.d.ts +21 -5
- package/dist/index.js +783 -807
- package/dist/participant/index.d.ts +186 -0
- package/dist/participant/index.js +1708 -0
- package/dist/preview/index.d.ts +4 -2
- package/dist/preview/index.js +4 -2
- package/dist/quickReply.types-B4gmZsSr.d.ts +274 -0
- package/dist/styles.css +689 -0
- package/package.json +8 -4
- package/src/ChannelIcon.tsx +2 -0
- package/src/MessageText.test.tsx +76 -0
- package/src/MessageText.tsx +28 -12
- package/src/MessageTextCopiedBadge.tsx +15 -0
- package/src/StatusTicks.test.tsx +51 -0
- package/src/StatusTicks.tsx +36 -6
- package/src/buildOutput.test.ts +41 -0
- package/src/channelCapability.test.ts +17 -0
- package/src/conversationChannel.test.ts +30 -1
- package/src/conversationChannel.ts +19 -1
- package/src/legacyChannels.golden.test.ts +89 -0
- package/src/legacyDefaults.golden.test.tsx +127 -0
- package/src/legacyStyles.fixture.css +1121 -0
- package/src/legacyStyles.golden.test.ts +121 -0
- package/src/messageTextCopyInteraction.test.ts +75 -0
- package/src/messageTextCopyInteraction.ts +37 -0
- package/src/participant/ParticipantAttachmentItem.tsx +77 -0
- package/src/participant/ParticipantChannelBadges.test.tsx +59 -0
- package/src/participant/ParticipantChannelBadges.tsx +75 -0
- package/src/participant/ParticipantComposer.test.tsx +97 -0
- package/src/participant/ParticipantComposer.tsx +167 -0
- package/src/participant/ParticipantConversations.test.tsx +90 -0
- package/src/participant/ParticipantConversations.tsx +49 -0
- package/src/participant/ParticipantConversationsScreen.tsx +78 -0
- package/src/participant/ParticipantInbox.test.tsx +244 -0
- package/src/participant/ParticipantInbox.tsx +149 -0
- package/src/participant/ParticipantInboxRow.tsx +75 -0
- package/src/participant/ParticipantInboxSearchField.tsx +34 -0
- package/src/participant/ParticipantLoadState.tsx +27 -0
- package/src/participant/ParticipantMessageBubble.test.tsx +167 -0
- package/src/participant/ParticipantMessageBubble.tsx +185 -0
- package/src/participant/ParticipantProtocolBadge.tsx +74 -0
- package/src/participant/ParticipantProtocolView.test.tsx +208 -0
- package/src/participant/ParticipantThread.test.tsx +149 -0
- package/src/participant/ParticipantThread.tsx +184 -0
- package/src/participant/ParticipantThreadScreen.tsx +135 -0
- package/src/participant/bindAttachmentUrlResolver.test.ts +25 -0
- package/src/participant/bindAttachmentUrlResolver.ts +7 -0
- package/src/participant/index.ts +26 -0
- package/src/participant/participant.types.ts +52 -0
- package/src/participant/participantApi.types.ts +59 -0
- package/src/participant/participantAttachmentUrl.test.ts +32 -0
- package/src/participant/participantAttachmentUrl.ts +10 -0
- package/src/participant/participantBubbleActions.test.ts +44 -0
- package/src/participant/participantBubbleActions.ts +40 -0
- package/src/participant/participantChannels.test.ts +42 -0
- package/src/participant/participantChannels.ts +33 -0
- package/src/participant/participantDrafts.test.ts +65 -0
- package/src/participant/participantDrafts.ts +50 -0
- package/src/participant/participantFileValidation.test.ts +86 -0
- package/src/participant/participantFileValidation.ts +72 -0
- package/src/participant/participantFixtures.test-helper.ts +41 -0
- package/src/participant/participantGrouping.test.ts +236 -0
- package/src/participant/participantGrouping.ts +90 -0
- package/src/participant/participantInboxState.test.ts +69 -0
- package/src/participant/participantInboxState.ts +74 -0
- package/src/participant/participantLabels.ts +115 -0
- package/src/participant/participantLoadView.test.ts +23 -0
- package/src/participant/participantLoadView.ts +21 -0
- package/src/participant/participantMessages.test.ts +128 -0
- package/src/participant/participantMessages.ts +73 -0
- package/src/participant/participantNewMessages.test.ts +54 -0
- package/src/participant/participantNewMessages.ts +29 -0
- package/src/participant/participantProtocol.test.ts +104 -0
- package/src/participant/participantProtocol.ts +52 -0
- package/src/participant/participantProtocolCopy.test.ts +45 -0
- package/src/participant/participantProtocolCopy.ts +32 -0
- package/src/participant/participantQuickReply.test.ts +23 -0
- package/src/participant/participantQuickReply.ts +6 -0
- package/src/participant/participantScroll.test.ts +69 -0
- package/src/participant/participantScroll.ts +43 -0
- package/src/participant/participantSendController.test.ts +201 -0
- package/src/participant/participantSendController.ts +134 -0
- package/src/participant/participantSendStates.ts +31 -0
- package/src/participant/participantTheme.ts +20 -0
- package/src/participant/participantThreadState.test.ts +75 -0
- package/src/participant/participantThreadState.ts +59 -0
- package/src/participant/shouldMarkRead.test.ts +24 -0
- package/src/participant/shouldMarkRead.ts +16 -0
- package/src/participant/useDocumentVisibility.ts +18 -0
- package/src/participant/useParticipantConversationView.ts +60 -0
- package/src/participant/useParticipantDraft.ts +45 -0
- package/src/participant/useParticipantInbox.ts +57 -0
- package/src/participant/useParticipantMarkRead.ts +26 -0
- package/src/participant/useParticipantRevalidation.ts +36 -0
- package/src/participant/useParticipantSend.ts +84 -0
- package/src/participant/useParticipantThread.ts +116 -0
- package/src/participant/useParticipantThreadScroll.ts +26 -0
- package/src/participant/useStickToBottom.ts +77 -0
- package/src/preview/ConversationSimulatorPanel.tsx +8 -0
- package/src/styles.css +178 -0
- package/src/types.ts +3 -1
|
@@ -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
|
+
};
|
package/dist/flows/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TooltipLayer,
|
|
3
3
|
WhatsAppMessagePreview
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-HOKNKXIT.js";
|
|
5
5
|
import {
|
|
6
|
-
parseWhatsAppFormatting,
|
|
7
6
|
useIsDarkTheme
|
|
8
|
-
} from "../chunk-
|
|
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,
|
|
4
|
-
export {
|
|
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
|
-
|
|
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;
|