@adatechnology/conversations-ui 0.5.0 → 0.6.1
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 +82 -20
- package/dist/{chunk-JXJCPN34.js → chunk-FAQLC3TX.js} +1 -1
- package/dist/{chunk-RJMSM3YA.js → chunk-O6457XHK.js} +17 -11
- package/dist/chunk-OVFCLRQQ.js +84 -0
- package/dist/index.d.ts +14 -4
- package/dist/index.js +7 -7
- package/dist/participant/index.d.ts +25 -1
- package/dist/participant/index.js +454 -194
- package/dist/preview/index.js +2 -2
- package/dist/styles.css +106 -0
- package/package.json +3 -3
- package/src/MessageBubble.test.tsx +2 -2
- package/src/MessageText.test.tsx +27 -10
- package/src/MessageText.tsx +25 -17
- package/src/MessageTextCopiedBadge.tsx +10 -2
- package/src/StatusTicks.test.tsx +27 -6
- package/src/StatusTicks.tsx +30 -15
- package/src/cssBlocks.test-helper.ts +31 -0
- 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 +92 -0
- package/src/messageTextCopyInteraction.test.ts +75 -0
- package/src/messageTextCopyInteraction.ts +37 -0
- package/src/participant/ParticipantChannelBadges.test.tsx +59 -0
- package/src/participant/ParticipantChannelBadges.tsx +75 -0
- package/src/participant/ParticipantConversations.tsx +13 -6
- package/src/participant/ParticipantConversationsScreen.tsx +18 -7
- package/src/participant/ParticipantInbox.test.tsx +99 -2
- package/src/participant/ParticipantInbox.tsx +27 -46
- package/src/participant/ParticipantInboxRow.tsx +75 -0
- package/src/participant/ParticipantInboxSearchField.tsx +34 -0
- package/src/participant/ParticipantMessageBubble.test.tsx +2 -0
- package/src/participant/ParticipantMessageBubble.tsx +3 -3
- package/src/participant/ParticipantProtocolBadge.tsx +74 -0
- package/src/participant/ParticipantProtocolView.test.tsx +208 -0
- package/src/participant/ParticipantThread.test.tsx +19 -2
- package/src/participant/ParticipantThread.tsx +25 -15
- package/src/participant/index.ts +1 -0
- package/src/participant/participant.types.ts +9 -0
- package/src/participant/participantChannels.test.ts +42 -0
- package/src/participant/participantChannels.ts +33 -0
- package/src/participant/participantGrouping.test.ts +101 -3
- package/src/participant/participantGrouping.ts +20 -7
- package/src/participant/participantLabels.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/participantLayoutShrink.test.ts +82 -0
- package/src/styles.css +22 -1
- package/dist/chunk-WSFGNR5R.js +0 -55
package/README.md
CHANGED
|
@@ -9,11 +9,18 @@ Tela de conversa do lado de **quem conversa com a empresa** (app do motorista, p
|
|
|
9
9
|
`ConversationsProvider`, não puxa `@xyflow/react` nem o `ConversationsWorkspace`, e não depende de Tailwind:
|
|
10
10
|
todas as classes são `.cv-p-*` e vêm de `@adatechnology/conversations-ui/styles.css`.
|
|
11
11
|
|
|
12
|
-
`@adatechnology/conversations-ui/styles.css` é **requisito**: sem ele a tela fica sem layout
|
|
13
|
-
|
|
12
|
+
`@adatechnology/conversations-ui/styles.css` é **requisito da visão do participante**: sem ele a tela fica sem layout.
|
|
13
|
+
A visão do participante renderiza `MessageText` e `StatusTicks` com `appearance="stylesheet"` (classes `.cv-*`, sem
|
|
14
|
+
Tailwind). Fora dela, os dois componentes mantêm o padrão da 0.4.2 (classes Tailwind do host).
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
Props opt-in (o padrão é sempre o comportamento da 0.4.2):
|
|
17
|
+
|
|
18
|
+
- `MessageText`: `appearance?: 'tailwind' | 'stylesheet'` (padrão `'tailwind'`); `accessibleCopy?: boolean` (padrão
|
|
19
|
+
`false`; liga `role="button"`, `tabIndex=0` e Enter/Espaço para copiar); `copyOnClick` (padrão `true`; `false`
|
|
20
|
+
desliga a cópia); `copiedLabel` (padrão `Copiado!`).
|
|
21
|
+
- `StatusTicks`: `appearance?: 'tailwind' | 'stylesheet'` (padrão `'tailwind'`). Só em `'stylesheet'` valem o estado
|
|
22
|
+
`queued` (relógio), `bounced` tratado como `failed` e o `aria-label`; `queuedLabel?: string` (padrão `Queued`) define
|
|
23
|
+
o rótulo do relógio. No padrão, qualquer status diferente de `sent` e `failed` desenha dois tiques, como na 0.4.2; em `'stylesheet'`, um status desconhecido cai em `sent` (um tique).
|
|
17
24
|
|
|
18
25
|
### Uso mínimo
|
|
19
26
|
|
|
@@ -86,11 +93,13 @@ export const participantApi: ParticipantConversationsApi = {
|
|
|
86
93
|
await request(`/conversations${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ''}`),
|
|
87
94
|
),
|
|
88
95
|
fetchMessages: async (subject, { before, limit } = {}) =>
|
|
89
|
-
z
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
z
|
|
97
|
+
.array(participantMessageSchema)
|
|
98
|
+
.parse(
|
|
99
|
+
await request(
|
|
100
|
+
`${conversationPath(subject)}/messages?limit=${limit ?? 30}${before ? `&before=${encodeURIComponent(before)}` : ''}`,
|
|
101
|
+
),
|
|
92
102
|
),
|
|
93
|
-
),
|
|
94
103
|
sendMessage: async ({ subject, clientMessageId, text }) => {
|
|
95
104
|
const message = participantMessageSchema.parse(
|
|
96
105
|
await request(`${conversationPath(subject)}/messages`, {
|
|
@@ -140,24 +149,77 @@ render refaz a carga da lista e reinscreve os eventos de `subscribe` a cada rend
|
|
|
140
149
|
empresa, ou seja, foi escrita pelo participante (é a bolha "minha"); `outbound` saiu da empresa. O adapter do
|
|
141
150
|
app **não inverte nada**.
|
|
142
151
|
|
|
152
|
+
### Protocolo legível da conversa
|
|
153
|
+
|
|
154
|
+
`ParticipantConversationSummary.protocol?` (1 a 32 caracteres, `[A-Za-z0-9-]`, ex.: `261009-K7M2`) é um código
|
|
155
|
+
curto para citar a conversa por telefone ou mensagem. **Quem gera é o produto, no servidor, uma vez e imutável**: o
|
|
156
|
+
pacote nunca gera nem conhece o formato, só **exibe, copia e busca**. Campo ausente = nada é desenhado.
|
|
157
|
+
|
|
158
|
+
- **Lista:** a linha mostra o protocolo abaixo do título (fonte mono); o prefixo `labels.protocolPrefix` é um `<span>` só para
|
|
159
|
+
leitor de tela (sem `aria-label` em elemento genérico, que o ARIA 1.2 proíbe).
|
|
160
|
+
- **Cabeçalho da conversa:** protocolo abaixo do título e botão de copiar (alvo de 44px) que usa
|
|
161
|
+
`navigator.clipboard.writeText`; falha de cópia é silenciosa. Durante 3 s o texto visível do botão vira
|
|
162
|
+
`labels.protocolCopied` (o nome acessível segue "`copyProtocol` + código") e o aviso também vai numa região
|
|
163
|
+
`aria-live="polite"` sempre na árvore; copiar de novo dentro dos 3 s reinicia o prazo e reanuncia.
|
|
164
|
+
- **Busca:** um campo `type="search"` acima da lista filtra por título (sem acento nem caixa) **ou** protocolo
|
|
165
|
+
(parcial, sem traço, sem caixa: `k7m2` acha `261009-K7M2`). Aparece só quando alguma conversa tem `protocol` ou
|
|
166
|
+
há mais de 8 conversas na lista; sem nada disso, nada é desenhado. Sem resultado: `labels.noResults`.
|
|
167
|
+
- Os chips de assunto e suas contagens são sempre calculados sobre **todas** as conversas carregadas; a busca só
|
|
168
|
+
restringe as linhas. Se o chip ativo não contém o que a busca acha (mas outro assunto contém), os chips continuam
|
|
169
|
+
visíveis e o estado vazio mostra `labels.noResultsInFilter` ("tente Todas") em vez de esconder a barra.
|
|
170
|
+
- O campo permanece enquanto houver consulta, mesmo que um refresh reduza a lista abaixo do limiar.
|
|
171
|
+
- **Limitação:** a busca cobre só as conversas já carregadas. Com `hasMore` e sem resultado, o estado vazio mostra
|
|
172
|
+
`labels.noResultsLoadedOnly` e o botão "carregar mais" fica à mão. Busca no servidor fica como evolução.
|
|
173
|
+
- **Labels novas** (defaults em inglês): `searchLabel`, `searchPlaceholder`, `noResults`, `noResultsInFilter`,
|
|
174
|
+
`noResultsLoadedOnly`, `protocolPrefix`, `copyProtocol` (padrão "Copy protocol"), `protocolCopied`.
|
|
175
|
+
- **Classes novas:** `.cv-p-protocol`, `.cv-p-protocol__copy`, `.cv-p-search` (mesmos tokens `--cv-p-*`).
|
|
176
|
+
|
|
177
|
+
### Canais e ícone do assunto na lista
|
|
178
|
+
|
|
179
|
+
`ParticipantConversationSummary.channels?` (até 5 canais do vocabulário `email | whatsapp | app | portal | webchat`, sem
|
|
180
|
+
repetição, pode ser vazio) e `iconName?` (1 a 32 caracteres, `[a-z0-9-]`, nome **opaco**: o pacote não conhece o
|
|
181
|
+
catálogo) chegam **calculados pelo servidor**: `channels` são os canais distintos das mensagens da conversa e
|
|
182
|
+
`iconName` o nome escolhido pelo produto para o assunto. **O pacote não deduz nada**; campo ausente = nada é desenhado.
|
|
183
|
+
|
|
184
|
+
- **Lista:** a linha mostra à esquerda o ícone do assunto e, ao lado do protocolo, os selos de canal. O ícone vem de
|
|
185
|
+
`renderSubjectIcon?: (conversation) => ReactNode` (em `ParticipantConversations`); se a prop faltar, ou devolver
|
|
186
|
+
`null`/`undefined`, cai no ícone do grupo (`subjectGroups[].icon`). O host usa `conversation.iconName` para escolher.
|
|
187
|
+
- **Selos de canal:** um por canal, na ordem fixa app, whatsapp, email, portal, webchat (duplicata e canal desconhecido são
|
|
188
|
+
ignorados). O sinal visual é o ícone (cor é só reforço); o nome do canal está sempre em `<span class="cv-p-sr-only">`.
|
|
189
|
+
Sem `channels` ou vazio, nada é desenhado. Duas marcações (`ParticipantChannelBadges variant`): no **cabeçalho** da
|
|
190
|
+
conversa, `<ul class="cv-p-channels" aria-label>` com `<li class="cv-p-channel cv-p-channel--{canal}">`; na **linha** da
|
|
191
|
+
lista (dentro de um `<button>`, onde lista não é HTML válido), `<span class="cv-p-channels">` com `<span class="cv-p-channel
|
|
192
|
+
cv-p-channel--{canal}">`, um prefixo sr-only `Channels in this conversation: ` e o nome seguido de vírgula e espaço, para
|
|
193
|
+
o leitor dizer "Channels in this conversation: App, WhatsApp".
|
|
194
|
+
- **`renderSubjectIcon`** deve ser **puro e não lançar** (o pacote não tem error boundary); `null`, `undefined`, `false` e
|
|
195
|
+
`''` valem como ausente.
|
|
196
|
+
- **Cabeçalho da conversa:** os mesmos selos ao lado do protocolo.
|
|
197
|
+
- **Labels novas** (defaults em inglês): `channelsGroup` ("Channels in this conversation"), `channelApp`,
|
|
198
|
+
`channelWhatsapp`, `channelEmail`, `channelPortal`, `channelWebchat`.
|
|
199
|
+
- **Classes novas:** `.cv-p-channels`, `.cv-p-channel`, `.cv-p-row__tags`, `.cv-p-inbox__icon`; cores opcionais
|
|
200
|
+
`--cv-p-channel-app` (padrão: o destaque) e `--cv-p-channel-whatsapp` (padrão `#20914f`, ≥ 3:1 sobre as superfícies claras e
|
|
201
|
+
escuras; ao trocar, mantenha 3:1).
|
|
202
|
+
|
|
143
203
|
### Variáveis `--cv-p-*`
|
|
144
204
|
|
|
145
205
|
Defina no `.cv-p`, no wrapper (`className`) ou em qualquer ancestral. Há também a prop `theme`
|
|
146
206
|
(`primaryColor` → `--cv-p-accent`, `backgroundColor` → `--cv-p-surface`, `textPrimary` → `--cv-p-text`,
|
|
147
207
|
`textSecondary` → `--cv-p-text-muted`).
|
|
148
208
|
|
|
149
|
-
| Variável
|
|
150
|
-
|
|
|
151
|
-
| `--cv-p-surface`
|
|
152
|
-
| `--cv-p-surface-raised`
|
|
153
|
-
| `--cv-p-text`
|
|
154
|
-
| `--cv-p-text-muted`
|
|
155
|
-
| `--cv-p-border`
|
|
156
|
-
| `--cv-p-accent`
|
|
157
|
-
| `--cv-p-accent-contrast`
|
|
158
|
-
| `--cv-p-highlight`
|
|
159
|
-
| `--cv-p-danger`
|
|
160
|
-
| `--cv-p-radius`
|
|
209
|
+
| Variável | Para que serve | Padrão (claro) |
|
|
210
|
+
| ------------------------- | --------------------------------------- | -------------- |
|
|
211
|
+
| `--cv-p-surface` | fundo da tela | `#ffffff` |
|
|
212
|
+
| `--cv-p-surface-raised` | fundo de bolhas recebidas e campos | `#f3f4f6` |
|
|
213
|
+
| `--cv-p-text` | texto principal | `#111827` |
|
|
214
|
+
| `--cv-p-text-muted` | texto secundário | `#5b6573` |
|
|
215
|
+
| `--cv-p-border` | bordas | `#d5d9df` |
|
|
216
|
+
| `--cv-p-accent` | destaque, botão primário, bolha própria | `#a85a1c` |
|
|
217
|
+
| `--cv-p-accent-contrast` | texto sobre o destaque | `#ffffff` |
|
|
218
|
+
| `--cv-p-highlight` | fundo de "espera sua resposta" | `#fbefe2` |
|
|
219
|
+
| `--cv-p-danger` | erro e falha de envio | `#c62828` |
|
|
220
|
+
| `--cv-p-radius` | raio dos cantos | `0` |
|
|
221
|
+
| `--cv-p-channel-app` | cor do selo do canal app | o destaque |
|
|
222
|
+
| `--cv-p-channel-whatsapp` | cor do selo do canal WhatsApp | `#20914f` |
|
|
161
223
|
|
|
162
224
|
O tema escuro segue a classe `.dark` (mesma do restante do pacote). Os nomes `.cv-p-*` e `--cv-p-*` são API
|
|
163
225
|
pública e não mudam sem nova versão maior.
|
|
@@ -156,7 +156,13 @@ function channelCapabilityFor(channel) {
|
|
|
156
156
|
// src/StatusTicks.tsx
|
|
157
157
|
import { AlertTriangle, Clock } from "lucide-react";
|
|
158
158
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
159
|
-
var
|
|
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 = {
|
|
160
166
|
queued: "queued",
|
|
161
167
|
sent: "sent",
|
|
162
168
|
delivered: "delivered",
|
|
@@ -164,7 +170,6 @@ var STATUS_MODIFIER = {
|
|
|
164
170
|
failed: "failed",
|
|
165
171
|
bounced: "failed"
|
|
166
172
|
};
|
|
167
|
-
var QUEUED_LABEL = "Queued";
|
|
168
173
|
function Ticks({ double }) {
|
|
169
174
|
return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 20 12", width: "15", height: "9", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
|
|
170
175
|
double && /* @__PURE__ */ jsx("path", { d: "M1 6.5L4.5 10L11 2", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
@@ -180,24 +185,25 @@ function Ticks({ double }) {
|
|
|
180
185
|
)
|
|
181
186
|
] });
|
|
182
187
|
}
|
|
183
|
-
function
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
function StatusTicks({ status, title }) {
|
|
189
|
-
const modifier = STATUS_MODIFIER[status] ?? "sent";
|
|
190
|
-
const ariaLabel = modifier === "queued" ? QUEUED_LABEL : void 0;
|
|
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" });
|
|
191
192
|
return /* @__PURE__ */ jsx(
|
|
192
193
|
"span",
|
|
193
194
|
{
|
|
194
195
|
className: `cv-status-ticks cv-status-ticks--${modifier}`,
|
|
195
196
|
"data-cv-tooltip": title ?? ariaLabel,
|
|
196
197
|
"aria-label": ariaLabel,
|
|
197
|
-
children:
|
|
198
|
+
children: icon
|
|
198
199
|
}
|
|
199
200
|
);
|
|
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
|
+
}
|
|
201
207
|
|
|
202
208
|
// src/lib/format.ts
|
|
203
209
|
function formatTimestamp(timestamp) {
|
|
@@ -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/index.d.ts
CHANGED
|
@@ -602,8 +602,12 @@ declare function TooltipLayer(): react.ReactPortal | null;
|
|
|
602
602
|
interface StatusTicksProps {
|
|
603
603
|
status: string;
|
|
604
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;
|
|
605
609
|
}
|
|
606
|
-
declare function StatusTicks({ status, title }: StatusTicksProps): react.JSX.Element;
|
|
610
|
+
declare function StatusTicks({ status, title, appearance, queuedLabel }: StatusTicksProps): react.JSX.Element;
|
|
607
611
|
|
|
608
612
|
interface LightboxLabels {
|
|
609
613
|
/** Texto alternativo quando a imagem não tem legenda — sem ele o leitor de tela anuncia a URL. */
|
|
@@ -627,14 +631,20 @@ interface FileIconProps {
|
|
|
627
631
|
}
|
|
628
632
|
declare function FileIcon({ filename, mimeType, size, className }: FileIconProps): react.JSX.Element;
|
|
629
633
|
|
|
634
|
+
type MessageTextAppearance = 'tailwind' | 'stylesheet';
|
|
635
|
+
|
|
630
636
|
interface MessageTextProps {
|
|
631
637
|
message: MessagePayload;
|
|
632
|
-
/** Defaults to true (tap copies the text, as in 0.
|
|
638
|
+
/** Defaults to true (tap copies the text, as in 0.4.2); false disables copying. */
|
|
633
639
|
copyOnClick?: boolean;
|
|
634
|
-
/**
|
|
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. */
|
|
635
643
|
copiedLabel?: string;
|
|
644
|
+
/** 'tailwind' (default) keeps the 0.4.2 utility classes; 'stylesheet' uses the .cv-* rules from styles.css. */
|
|
645
|
+
appearance?: MessageTextAppearance;
|
|
636
646
|
}
|
|
637
|
-
declare function MessageText({ message, copyOnClick, copiedLabel }: MessageTextProps): react.JSX.Element;
|
|
647
|
+
declare function MessageText({ message, copyOnClick, accessibleCopy, copiedLabel, appearance, }: MessageTextProps): react.JSX.Element;
|
|
638
648
|
|
|
639
649
|
interface MessageTimestampProps {
|
|
640
650
|
timestamp: string;
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,6 @@ import {
|
|
|
3
3
|
TooltipLayer,
|
|
4
4
|
WhatsAppMessagePreview
|
|
5
5
|
} from "./chunk-HOKNKXIT.js";
|
|
6
|
-
import {
|
|
7
|
-
CHAT_TEXT_PRIMARY_CLASS,
|
|
8
|
-
CHAT_TEXT_SECONDARY_CLASS,
|
|
9
|
-
MessageText
|
|
10
|
-
} from "./chunk-WSFGNR5R.js";
|
|
11
6
|
import {
|
|
12
7
|
AudioPlayer,
|
|
13
8
|
AudioRecorderButton,
|
|
@@ -64,12 +59,17 @@ import {
|
|
|
64
59
|
useConversationLocales,
|
|
65
60
|
useConversations,
|
|
66
61
|
useQuickRepliesPicker
|
|
67
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-FAQLC3TX.js";
|
|
68
63
|
import {
|
|
69
64
|
cn,
|
|
70
65
|
useDarkMode,
|
|
71
66
|
useIsDarkTheme
|
|
72
67
|
} from "./chunk-MRVFFJEH.js";
|
|
68
|
+
import {
|
|
69
|
+
CHAT_TEXT_PRIMARY_CLASS,
|
|
70
|
+
CHAT_TEXT_SECONDARY_CLASS,
|
|
71
|
+
MessageText
|
|
72
|
+
} from "./chunk-OVFCLRQQ.js";
|
|
73
73
|
import {
|
|
74
74
|
CHANNEL_CAPABILITIES,
|
|
75
75
|
CHANNEL_FILTER_ALL,
|
|
@@ -89,7 +89,7 @@ import {
|
|
|
89
89
|
formatTimestamp,
|
|
90
90
|
isSameDay,
|
|
91
91
|
phoneInitials
|
|
92
|
-
} from "./chunk-
|
|
92
|
+
} from "./chunk-O6457XHK.js";
|
|
93
93
|
import {
|
|
94
94
|
ZERO_WIDTH_SPACE,
|
|
95
95
|
htmlToWA,
|
|
@@ -9,6 +9,8 @@ type ParticipantSubjectGroup = {
|
|
|
9
9
|
readonly label: string;
|
|
10
10
|
readonly icon?: ReactNode;
|
|
11
11
|
};
|
|
12
|
+
/** Host-drawn icon for a list row; null/undefined falls back to the subject group icon. */
|
|
13
|
+
type ParticipantSubjectIconRenderer = (conversation: ParticipantConversationSummary) => ReactNode;
|
|
12
14
|
type ParticipantInboxSection = {
|
|
13
15
|
/** 'awaiting' | 'closed' | 'other' or the subjectType of a group. */
|
|
14
16
|
readonly key: string;
|
|
@@ -26,12 +28,18 @@ type ParticipantInboxView = {
|
|
|
26
28
|
readonly sections: readonly ParticipantInboxSection[];
|
|
27
29
|
readonly filters: readonly ParticipantInboxFilter[];
|
|
28
30
|
readonly showFilters: boolean;
|
|
31
|
+
/** The filter actually applied: 'all' when the requested one has no conversations left. */
|
|
32
|
+
readonly activeFilter: string;
|
|
33
|
+
/** The search finds conversations, but none under the active subject filter. */
|
|
34
|
+
readonly hasMatchesOutsideFilter: boolean;
|
|
29
35
|
};
|
|
30
36
|
type GroupParticipantConversationsParams = {
|
|
31
37
|
readonly conversations: readonly ParticipantConversationSummary[];
|
|
32
38
|
readonly subjectGroups: readonly ParticipantSubjectGroup[];
|
|
33
39
|
/** subjectType, or 'all'/undefined for everything. */
|
|
34
40
|
readonly filter?: string;
|
|
41
|
+
/** Narrows the rows only: chips and their counts always describe the whole list. */
|
|
42
|
+
readonly searchQuery?: string;
|
|
35
43
|
};
|
|
36
44
|
type ParticipantConversationsClassNames = {
|
|
37
45
|
readonly root: string;
|
|
@@ -133,6 +141,21 @@ type ParticipantConversationsLabels = {
|
|
|
133
141
|
readonly loading: string;
|
|
134
142
|
readonly loadError: string;
|
|
135
143
|
readonly loadMore: string;
|
|
144
|
+
readonly searchLabel: string;
|
|
145
|
+
readonly searchPlaceholder: string;
|
|
146
|
+
readonly noResults: string;
|
|
147
|
+
readonly noResultsInFilter: string;
|
|
148
|
+
readonly noResultsLoadedOnly: string;
|
|
149
|
+
/** Spoken before the protocol code, e.g. "Protocol 261009-K7M2". */
|
|
150
|
+
readonly protocolPrefix: string;
|
|
151
|
+
readonly copyProtocol: string;
|
|
152
|
+
readonly protocolCopied: string;
|
|
153
|
+
readonly channelsGroup: string;
|
|
154
|
+
readonly channelApp: string;
|
|
155
|
+
readonly channelWhatsapp: string;
|
|
156
|
+
readonly channelEmail: string;
|
|
157
|
+
readonly channelPortal: string;
|
|
158
|
+
readonly channelWebchat: string;
|
|
136
159
|
};
|
|
137
160
|
declare const DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS: ParticipantConversationsLabels;
|
|
138
161
|
|
|
@@ -152,6 +175,7 @@ type ParticipantConversationsProps = {
|
|
|
152
175
|
pendingMessages?: readonly ParticipantPendingMessage[];
|
|
153
176
|
onRetryPending?: (clientMessageId: string) => void;
|
|
154
177
|
quickReplies?: readonly QuickReply[];
|
|
178
|
+
renderSubjectIcon?: ParticipantSubjectIconRenderer;
|
|
155
179
|
renderSubjectCard?: (conversation: ParticipantConversationSummary) => ReactNode;
|
|
156
180
|
onOpenSubject?: (subject: ParticipantSubjectRef) => void;
|
|
157
181
|
};
|
|
@@ -159,4 +183,4 @@ declare function ParticipantConversations(props: ParticipantConversationsProps):
|
|
|
159
183
|
|
|
160
184
|
declare function groupParticipantConversations(params: GroupParticipantConversationsParams): ParticipantInboxView;
|
|
161
185
|
|
|
162
|
-
export { DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, type GroupParticipantConversationsParams, type ParticipantConversationEvent, ParticipantConversations, type ParticipantConversationsApi, type ParticipantConversationsClassNames, type ParticipantConversationsLabels, type ParticipantConversationsProps, type ParticipantInboxSection, type ParticipantInboxView, type ParticipantLocalPendingMessage, type ParticipantPendingMessage, type ParticipantSendInput, type ParticipantSendResult, type ParticipantSubjectGroup, groupParticipantConversations };
|
|
186
|
+
export { DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, type GroupParticipantConversationsParams, type ParticipantConversationEvent, ParticipantConversations, type ParticipantConversationsApi, type ParticipantConversationsClassNames, type ParticipantConversationsLabels, type ParticipantConversationsProps, type ParticipantInboxSection, type ParticipantInboxView, type ParticipantLocalPendingMessage, type ParticipantPendingMessage, type ParticipantSendInput, type ParticipantSendResult, type ParticipantSubjectGroup, type ParticipantSubjectIconRenderer, groupParticipantConversations };
|