@adatechnology/conversations-ui 0.6.1 → 0.7.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 (85) hide show
  1. package/README.md +126 -19
  2. package/dist/{chunk-HOKNKXIT.js → chunk-HMCOTZAX.js} +1 -1
  3. package/dist/{chunk-FAQLC3TX.js → chunk-L3OFAQPS.js} +2 -4
  4. package/dist/{chunk-CBYEHPX6.js → chunk-PJNR6UMN.js} +63 -1
  5. package/dist/flows/index.js +3 -5
  6. package/dist/index.js +834 -764
  7. package/dist/participant/index.d.ts +18 -1
  8. package/dist/participant/index.js +1188 -313
  9. package/dist/preview/index.js +2 -3
  10. package/dist/styles.css +583 -19
  11. package/package.json +3 -3
  12. package/src/participant/ParticipantAuthorAvatar.test.tsx +30 -0
  13. package/src/participant/ParticipantAuthorAvatar.tsx +24 -0
  14. package/src/participant/ParticipantBackButton.tsx +14 -0
  15. package/src/participant/ParticipantBubbleStatus.tsx +67 -0
  16. package/src/participant/ParticipantChannelBadges.tsx +13 -4
  17. package/src/participant/ParticipantComposer.tsx +8 -40
  18. package/src/participant/ParticipantComposerControls.tsx +83 -0
  19. package/src/participant/ParticipantConversationAvatar.tsx +13 -0
  20. package/src/participant/ParticipantConversations.tsx +7 -0
  21. package/src/participant/ParticipantConversationsOptions.test.tsx +87 -0
  22. package/src/participant/ParticipantCopyAnnouncer.tsx +48 -0
  23. package/src/participant/ParticipantCopyableToken.tsx +51 -0
  24. package/src/participant/ParticipantDayDivider.tsx +18 -0
  25. package/src/participant/ParticipantInbox.tsx +3 -1
  26. package/src/participant/ParticipantInboxRow.tsx +6 -4
  27. package/src/participant/ParticipantMessageBubble.test.tsx +52 -5
  28. package/src/participant/ParticipantMessageBubble.tsx +25 -81
  29. package/src/participant/ParticipantMessageNodes.tsx +54 -0
  30. package/src/participant/ParticipantMessageText.test.tsx +106 -0
  31. package/src/participant/ParticipantMessageText.tsx +52 -0
  32. package/src/participant/ParticipantProtocolBadge.tsx +15 -7
  33. package/src/participant/ParticipantProtocolView.test.tsx +4 -6
  34. package/src/participant/ParticipantScrollToLatest.test.tsx +153 -0
  35. package/src/participant/ParticipantScrollToLatest.tsx +40 -0
  36. package/src/participant/ParticipantThread.test.tsx +37 -0
  37. package/src/participant/ParticipantThread.tsx +45 -95
  38. package/src/participant/ParticipantThreadAvatarSequence.test.tsx +65 -0
  39. package/src/participant/ParticipantThreadHeader.test.tsx +181 -0
  40. package/src/participant/ParticipantThreadHeader.tsx +73 -0
  41. package/src/participant/ParticipantThreadScreen.tsx +24 -10
  42. package/src/participant/ParticipantTimeline.tsx +103 -0
  43. package/src/participant/index.ts +1 -0
  44. package/src/participant/participantAutoGrow.test.ts +43 -0
  45. package/src/participant/participantAutoGrow.ts +12 -0
  46. package/src/participant/participantAvatar.test.ts +74 -0
  47. package/src/participant/participantAvatar.ts +48 -0
  48. package/src/participant/participantBubbleTail.test.tsx +107 -0
  49. package/src/participant/participantChatDesign.test.tsx +198 -0
  50. package/src/participant/participantCopyAnnouncement.test.ts +52 -0
  51. package/src/participant/participantCopyAnnouncement.ts +37 -0
  52. package/src/participant/participantCss.test-helper.ts +34 -0
  53. package/src/participant/participantDay.test.ts +22 -0
  54. package/src/participant/participantDay.ts +8 -0
  55. package/src/participant/participantDocuments.ts +45 -0
  56. package/src/participant/participantHeaderA11y.test.tsx +85 -0
  57. package/src/participant/participantHeaderDesign.test.tsx +111 -0
  58. package/src/participant/participantLabels.ts +11 -0
  59. package/src/participant/participantMessageAtoms.ts +53 -0
  60. package/src/participant/participantMessageCopyables.test.ts +141 -0
  61. package/src/participant/participantMessageCopyables.ts +76 -0
  62. package/src/participant/participantMessageFormat.test-helper.ts +21 -0
  63. package/src/participant/participantMessageFormat.test.ts +177 -0
  64. package/src/participant/participantMessageFormat.ts +135 -0
  65. package/src/participant/participantMessageFormat.types.ts +22 -0
  66. package/src/participant/participantMessageInline.test.ts +82 -0
  67. package/src/participant/participantMessageInline.ts +99 -0
  68. package/src/participant/participantMessageLinks.test.ts +100 -0
  69. package/src/participant/participantMessageLinks.ts +54 -0
  70. package/src/participant/participantMessageTextStyle.test.ts +137 -0
  71. package/src/participant/participantScrollFocus.test.ts +22 -0
  72. package/src/participant/participantScrollFocus.ts +10 -0
  73. package/src/participant/participantScrollView.test.ts +74 -0
  74. package/src/participant/participantScrollView.ts +38 -0
  75. package/src/participant/participantScrollbars.test.ts +156 -0
  76. package/src/participant/participantSubjectIcon.test.tsx +27 -0
  77. package/src/participant/participantSubjectIcon.ts +26 -0
  78. package/src/participant/useParticipantCopyFeedback.ts +33 -0
  79. package/src/participant/useParticipantThreadScroll.ts +10 -2
  80. package/src/participant/useRevealActiveChip.ts +20 -0
  81. package/src/participant/useScrollAwayState.ts +24 -0
  82. package/src/participant/useStickToBottom.ts +16 -2
  83. package/src/styles.css +137 -21
  84. package/dist/chunk-MRVFFJEH.js +0 -64
  85. package/dist/chunk-OVFCLRQQ.js +0 -84
package/README.md CHANGED
@@ -5,7 +5,7 @@ Telas de conversa parametrizáveis por endpoint, tema e capacidades. Subpaths: `
5
5
 
6
6
  ## Visão do participante (`/participant`)
7
7
 
8
- Tela de conversa do lado de **quem conversa com a empresa** (app do motorista, portal do cliente). Não usa
8
+ Tela de conversa do lado de **quem conversa com a empresa** (app de campo, portal do cliente). Não usa
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
 
@@ -157,9 +157,9 @@ pacote nunca gera nem conhece o formato, só **exibe, copia e busca**. Campo aus
157
157
 
158
158
  - **Lista:** a linha mostra o protocolo abaixo do título (fonte mono); o prefixo `labels.protocolPrefix` é um `<span>` só para
159
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
160
+ - **Cabeçalho da conversa:** ver "Cabeçalho da conversa" abaixo. O botão de copiar usa `navigator.clipboard.writeText`;
161
+ falha de cópia é silenciosa. É **só um ícone** (`Copy`, vira `Check` por 3 s) com `aria-label` = "`copyProtocol` + código";
162
+ durante os 3 s aparece ao lado do ícone o texto curto `labels.protocolCopied` e o aviso também vai numa região
163
163
  `aria-live="polite"` sempre na árvore; copiar de novo dentro dos 3 s reinicia o prazo e reanuncia.
164
164
  - **Busca:** um campo `type="search"` acima da lista filtra por título (sem acento nem caixa) **ou** protocolo
165
165
  (parcial, sem traço, sem caixa: `k7m2` acha `261009-K7M2`). Aparece só quando alguma conversa tem `protocol` ou
@@ -193,33 +193,140 @@ cv-p-channel--{canal}">`, um prefixo sr-only `Channels in this conversation: ` e
193
193
  o leitor dizer "Channels in this conversation: App, WhatsApp".
194
194
  - **`renderSubjectIcon`** deve ser **puro e não lançar** (o pacote não tem error boundary); `null`, `undefined`, `false` e
195
195
  `''` valem como ausente.
196
- - **Cabeçalho da conversa:** os mesmos selos ao lado do protocolo.
196
+ - **Cabeçalho da conversa:** os mesmos selos, na variante `inline` (ícone de 16px), na mesma linha do protocolo.
197
197
  - **Labels novas** (defaults em inglês): `channelsGroup` ("Channels in this conversation"), `channelApp`,
198
198
  `channelWhatsapp`, `channelEmail`, `channelPortal`, `channelWebchat`.
199
199
  - **Classes novas:** `.cv-p-channels`, `.cv-p-channel`, `.cv-p-row__tags`, `.cv-p-inbox__icon`; cores opcionais
200
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
201
  escuras; ao trocar, mantenha 3:1).
202
202
 
203
+ ### Conversa no desenho do WhatsApp, no nosso estilo
204
+
205
+ A conversa segue os padrões de chat do WhatsApp, mas só com os tokens `--cv-p-*` (por exemplo, em um tema escuro de cantos
206
+ retos, rótulos em mono). Tudo é `.cv-p-*`; nenhum componente do painel do operador é usado.
207
+
208
+ **Cabeçalho** (barra única com piso de 4rem (com título longo, protocolo longo e 3 canais a linha meta quebra e a barra chega a cerca de 110px), fundo `--cv-p-surface-raised`, borda inferior de 1px), da esquerda para a direita:
209
+ (1) **voltar** só com ícone (seta de 24px, sem caixa nem borda, área de toque 44x44, `aria-label` = label `back`, foco visível;
210
+ ausente sem `onBack`); (2) **avatar da conversa**: tile de 2.5rem (`border-radius: var(--cv-p-radius)`) com o ícone do assunto, o
211
+ mesmo da lista (`renderSubjectIcon` do host; se devolver vazio, o `icon` do grupo em `subjectGroups`; sem nenhum dos dois não há
212
+ tile e o título recua). o `renderSubjectIcon` e o `icon` do grupo, que já existiam, passam a desenhar também esse tile (o cabeçalho não tem o
213
+ fallback de duas letras da linha da lista); (3) **título em duas linhas**: o `subjectLabel` (uma linha com reticências, cortada só no `span` interno
214
+ `.cv-p-thread__title-text` para o anel de foco do botão não ser recortado; clicável só com `onOpenSubject`) e a **linha meta**
215
+ (`.cv-p-thread__meta`) com o protocolo, o botão de copiar só com ícone e os selos de canal inline. O rótulo do grupo (eyebrow) some quando há tile: o tile já representa o grupo e o espaço em 320px é curto. A área de toque do copiar tem 44px de
216
+ largura e 36px de altura; a margem negativa de cima é coberta pelo `padding-top` da linha meta, então nunca invade o título.
217
+ Sem `protocol` não há protocolo nem botão; sem `channels` não há selos; sem nenhum dos dois a linha meta não existe. O cartão de
218
+ `renderSubjectCard` (do host) deve trazer só o que o cabeçalho não diz.
219
+
220
+ **Fundo**: a lista de mensagens tem um padrão geométrico de pontos em CSS puro, derivado de `--cv-p-border`, de contraste
221
+ mínimo. `--cv-p-wallpaper` o substitui (qualquer valor de `background-image`, em geral um gradiente; `none` desliga),
222
+ com `--cv-p-wallpaper-size` para o tamanho do ladrilho (padrão `24px 24px`). Some em `forced-colors: active` e na impressão.
223
+
224
+ **Separador de dia**: pílula centralizada (fundo elevado, borda de 1px, texto mono pequeno, raio do token), `role="separator"`
225
+ com um `<time datetime="AAAA-MM-DD">` do dia local.
226
+
227
+ **Rolagem**: as áreas verticais (mensagens, lista de conversas e campo de texto) têm barra fina (`scrollbar-width: thin`, 6px
228
+ em WebKit, trilho transparente, sem setas), polegar `--cv-p-scrollbar-thumb` (padrão `--cv-p-border`) e `--cv-p-scrollbar-thumb-hover`
229
+ (padrão `--cv-p-accent`) ao passar o mouse ou arrastar, sem reservar espaço (`scrollbar-gutter`) para as bolhas não se mexerem, e
230
+ `overscroll-behavior-y: contain` para não encadear a rolagem na página. As faixas horizontais (filtros e respostas rápidas) rolam
231
+ sem barra, com esmaecimento nas bordas, `scroll-snap-type: x proximity` e o filtro ativo trazido à vista ao ser escolhido. Em
232
+ `forced-colors: active` voltam a barra do sistema e a faixa sem máscara. `scroll-behavior: smooth` só vale sob
233
+ `prefers-reduced-motion: no-preference` (lista e faixas; o scroller da conversa continua instantâneo, pois o posicionamento inicial e a
234
+ âncora ao carregar mensagens antigas atribuem `scrollTop`).
235
+
236
+ **Ir para a última mensagem**: com o leitor longe do fim da conversa (o mesmo limiar de `shouldStickToBottom`, 80px), um botão
237
+ flutuante de 44x44 com `ChevronDown` aparece no canto inferior do scroller; o `aria-label` é o label novo `scrollToLatest`
238
+ (padrão "Scroll to latest message"). Um selo mostra quantas mensagens do outro lado chegaram enquanto o leitor estava longe (limite
239
+ "99+"); ele é decorativo, e a região `aria-live` continua anunciando só o label `newMessages`. O clique rola ao fim (suave só sem
240
+ `prefers-reduced-motion: reduce`) e o botão some ao chegar. Nada é desenhado perto do fim ou sem mensagens.
241
+
242
+ **Direção do texto (RTL)**: margens, alinhamento e hora da bolha usam propriedades lógicas e espelham sozinhos; o **rabinho** do balão
243
+ (triângulos e canto reto) usa posições físicas e em RTL continua à esquerda/direita como em LTR (limitação conhecida).
244
+
245
+ **Bolha**: hora e ticks ficam na mesma bolha, juntos ao pé e à direita (`.cv-p-bubble__meta`); se o texto cabe com folga, a
246
+ hora fica na mesma linha dele, senão cai para a linha de baixo, à direita, sem sobrepor. Largura máxima de 85%.
247
+
248
+ **Compositor**: `[anexar] [campo] [enviar]` alinhados pela base, em barra elevada com borda superior; anexar e enviar são ícones
249
+ (clipe e avião de papel, com `aria-label`), o campo é multilinha, parte de 44px e cresce até cerca de 4 linhas (depois rola),
250
+ enviar é o botão primário e fica desabilitado sem conteúdo. As respostas rápidas continuam como chips acima da linha.
251
+
252
+ Classes novas: `.cv-p-thread__avatar`, `.cv-p-thread__eyebrow`, `.cv-p-thread__meta`, `.cv-p-protocol__copied`, `.cv-p-day__label`,
253
+ `.cv-p-composer__attach`, `.cv-p-composer__send`.
254
+
255
+ ### Avatar do autor (opt-in)
256
+
257
+ **O SDK não tem foto de usuário**: o cadastro só traz `authorName`. O avatar é opt-in e o host fornece a foto pelo slot.
258
+
259
+ - `avatars="initials"` (em `ParticipantConversations`): desenha, à esquerda da bolha **recebida**, um quadrado de 32px
260
+ (`border-radius: var(--cv-p-radius)`, respeita tema reto) com até 2 iniciais do autor (primeiro e último nome, ignorando
261
+ preposições e pontuação); nome vazio, `?` ou só emoji cai no ícone neutro. Aparece só na **primeira** mensagem de uma
262
+ sequência do mesmo autor (as seguintes reservam o espaço para alinhar as bolhas); mensagem própria nunca tem avatar.
263
+ O avatar é `aria-hidden`: o nome do autor já está no texto da bolha.
264
+ - `renderAuthorAvatar?: (author: { name: string | null }) => ReactNode`: slot do host (uma `<img>`, por exemplo) desenhado
265
+ dentro do mesmo quadrado de 32px; tem precedência sobre as iniciais, liga o avatar sozinho e, se devolver `null`, `undefined`,
266
+ `false` ou `''`, cai nas iniciais. Deve ser puro e não lançar.
267
+ - Sem nenhuma das duas props o markup da bolha é idêntico ao anterior. Classes novas: `.cv-p-avatar`, `.cv-p-bubble-row`,
268
+ `.cv-p-bubble-row__avatar`. Nenhum label novo.
269
+
270
+ ### Rabinho da bolha (`tail`)
271
+
272
+ A bolha da conversa é um balão de fala: um rabinho triangular de 9px cola no canto **inferior esquerdo** da bolha recebida e
273
+ no **inferior direito** da própria (convenção de chat). Ligado por padrão; `tail={false}` (em `ParticipantConversations`)
274
+ remove o rabinho e devolve o canto arredondado (`.cv-p-bubble--no-tail`).
275
+
276
+ - O rabo são dois triângulos de `border` (`::before` na cor da borda, `::after` na cor do fundo, 1px deslocado), então a
277
+ borda de 1px continua contínua. As cores saem dos mesmos tokens da bolha (`--cv-p-border`, `--cv-p-surface-raised`; na
278
+ própria, `--cv-p-accent` e `--cv-p-highlight`) e acompanham `.dark`.
279
+ - Funciona com tema reto (`--cv-p-radius: 0`) e arredondado: o canto inferior correspondente fica reto para o rabo emendar.
280
+ - É decorativo (`pointer-events: none`, pseudo-elemento): não muda área de toque, conteúdo nem altura mínima. Com avatar a
281
+ bolha recua 0.25rem para o rabo não encostar no quadrado. Sem animação.
282
+ - Em `forced-colors: active` e na impressão os triângulos somem (viram quadrados sólidos sem `border-color` transparente) e o
283
+ canto volta ao raio do token.
284
+
285
+ ### Texto da mensagem (formatação, links e valores copiáveis)
286
+
287
+ A bolha usa um renderizador próprio da visão do participante (`ParticipantMessageText`); o `MessageText` e a formatação do painel e dos outros fluxos **não mudam**. O texto vira nós React (nunca `innerHTML`); acima de 8000 caracteres aparece como foi digitado.
288
+
289
+ | Digitado | Resultado |
290
+ |---|---|
291
+ | `*negrito*`, `_itálico_`, `~riscado~`, `*_ambos_*` | `<strong>`, `<em>`, `<del>`, aninhados; o marcador cola no texto (`* a *`, `2 * 3 * 4`, `snake_case_nome` ficam como estão) |
292
+ | `` `código` `` e bloco entre três crases | `<code>` / `<pre><code>`; nada é formatado nem detectado dentro |
293
+ | `> citação` (linhas seguidas = um bloco) | `<blockquote>` |
294
+ | `- item`, `* item`, `• item` / `1. item`, `1) item` | `<ul>` / `<ol>` (exige o espaço depois do marcador) |
295
+ | `http(s)://…` | `<a>` com `rel="noopener noreferrer nofollow ugc"` e `target="_blank"`, mais um botão de copiar a URL ao lado; pontuação final fica fora |
296
+ | telefone BR/internacional, e-mail, CPF, CNPJ (numérico e alfanumérico), chave de acesso de 44 dígitos | botão que copia o valor como escrito |
297
+
298
+ **Allowlist de links:** só `http:` e `https:` são gerados no texto (a allowlist de `isAllowedLinkHref` aceita também `mailto:` e `tel:`, mas o renderizador nunca os produz a partir do texto digitado); `javascript:`, `data:`, `vbscript:`, qualquer outro protocolo e URL com usuário/senha (`https://user:pass@host`) ficam como texto. Em bloco de código, um rótulo de linguagem sozinho na primeira linha (` ```js `) é descartado.
299
+
300
+ **Valores copiáveis:** a detecção é conservadora. CPF e CNPJ só valem com dígitos verificadores corretos (sequências repetidas não valem), a chave exige exatamente 44 dígitos (grupos de 4 opcionais) e o telefone exige 10 a 15 dígitos (BR: DDD 11–99) **com formatação**: sem máscara, só vale com `+55`, DDD entre parênteses ou separador depois do DDD (`1234567890` solto, como em "pedido 1234567890", não é telefone); número internacional exige `+`. Valores monetários, datas e números curtos não são destacados. O toque copia, mostra "Copied" no próprio valor e anuncia na região `aria-live` única da conversa. Os rótulos `copyValue`, `valueCopied` e `externalLinkHint` vêm de `labels` (padrão em inglês). O SDK só escreve na área de transferência quando a pessoa toca: não envia nem armazena nada.
301
+
302
+ ### Limitações conhecidas
303
+
304
+ - O avatar alinha pelo topo da bolha, não pelo rabinho no canto inferior.
305
+ - Sem suporte a RTL: o rabinho e o avatar assumem escrita da esquerda para a direita.
306
+
203
307
  ### Variáveis `--cv-p-*`
204
308
 
205
309
  Defina no `.cv-p`, no wrapper (`className`) ou em qualquer ancestral. Há também a prop `theme`
206
310
  (`primaryColor` → `--cv-p-accent`, `backgroundColor` → `--cv-p-surface`, `textPrimary` → `--cv-p-text`,
207
311
  `textSecondary` → `--cv-p-text-muted`).
208
312
 
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` |
313
+ | Variável | Para que serve | Padrão (claro) |
314
+ | ------------------------- | -------------------------------------------- | -------------- |
315
+ | `--cv-p-surface` | fundo da tela | `#ffffff` |
316
+ | `--cv-p-surface-raised` | fundo de bolhas recebidas e campos | `#f3f4f6` |
317
+ | `--cv-p-text` | texto principal | `#111827` |
318
+ | `--cv-p-text-muted` | texto secundário | `#5b6573` |
319
+ | `--cv-p-border` | bordas | `#d5d9df` |
320
+ | `--cv-p-accent` | destaque, botão primário, bolha própria | `#a85a1c` |
321
+ | `--cv-p-accent-contrast` | texto sobre o destaque | `#ffffff` |
322
+ | `--cv-p-highlight` | fundo de "espera sua resposta" | `#fbefe2` |
323
+ | `--cv-p-danger` | erro e falha de envio | `#c62828` |
324
+ | `--cv-p-radius` | raio dos cantos | `0` |
325
+ | `--cv-p-wallpaper` | fundo da lista de mensagens (`none` desliga) | pontos sutis |
326
+ | `--cv-p-scrollbar-thumb` | polegar das barras de rolagem finas | `--cv-p-border` |
327
+ | `--cv-p-scrollbar-thumb-hover` | polegar ao passar o mouse ou arrastar | `--cv-p-accent` |
328
+ | `--cv-p-channel-app` | cor do selo do canal app | o destaque |
329
+ | `--cv-p-channel-whatsapp` | cor do selo do canal WhatsApp | `#20914f` |
223
330
 
224
331
  O tema escuro segue a classe `.dark` (mesma do restante do pacote). Os nomes `.cv-p-*` e `--cv-p-*` são API
225
332
  pública e não mudam sem nova versão maior.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  cn
3
- } from "./chunk-MRVFFJEH.js";
3
+ } from "./chunk-PJNR6UMN.js";
4
4
 
5
5
  // src/Tooltip.tsx
6
6
  import { useEffect, useRef, useState } from "react";
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  cn,
3
+ parseWhatsAppFormatting,
3
4
  useIsDarkTheme
4
- } from "./chunk-MRVFFJEH.js";
5
+ } from "./chunk-PJNR6UMN.js";
5
6
  import {
6
7
  CONVERSATION_CHANNEL,
7
8
  DEFAULT_CONVERSATION_CHANNEL,
@@ -13,9 +14,6 @@ import {
13
14
  formatTimestamp,
14
15
  isSameDay
15
16
  } from "./chunk-O6457XHK.js";
16
- import {
17
- parseWhatsAppFormatting
18
- } from "./chunk-CBYEHPX6.js";
19
17
 
20
18
  // src/ConversationLocalesProvider.tsx
21
19
  import { createContext, useContext } from "react";
@@ -217,10 +217,72 @@ function waToHTMLInline(text) {
217
217
  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>");
218
218
  }
219
219
 
220
+ // src/useDarkMode.ts
221
+ import { useState, useEffect, useCallback } from "react";
222
+ var STORAGE_KEY = "conversations-ui-dark-mode";
223
+ function getInitialDark() {
224
+ if (typeof window === "undefined") return false;
225
+ const stored = localStorage.getItem(STORAGE_KEY);
226
+ if (stored !== null) {
227
+ return stored === "true";
228
+ }
229
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
230
+ }
231
+ function useIsDarkTheme() {
232
+ const [isDark, setIsDark] = useState(
233
+ () => typeof document !== "undefined" && document.documentElement.classList.contains("dark")
234
+ );
235
+ useEffect(() => {
236
+ const root = document.documentElement;
237
+ const observer = new MutationObserver(() => setIsDark(root.classList.contains("dark")));
238
+ observer.observe(root, { attributes: true, attributeFilter: ["class"] });
239
+ setIsDark(root.classList.contains("dark"));
240
+ return () => observer.disconnect();
241
+ }, []);
242
+ return isDark;
243
+ }
244
+ function useDarkMode() {
245
+ const [isDark, setIsDark] = useState(getInitialDark);
246
+ useEffect(() => {
247
+ const root = document.documentElement;
248
+ if (isDark) {
249
+ root.classList.add("dark");
250
+ } else {
251
+ root.classList.remove("dark");
252
+ }
253
+ localStorage.setItem(STORAGE_KEY, String(isDark));
254
+ }, [isDark]);
255
+ useEffect(() => {
256
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
257
+ const handleChange = (event) => {
258
+ const stored = localStorage.getItem(STORAGE_KEY);
259
+ if (stored === null) {
260
+ setIsDark(event.matches);
261
+ }
262
+ };
263
+ mediaQuery.addEventListener("change", handleChange);
264
+ return () => mediaQuery.removeEventListener("change", handleChange);
265
+ }, []);
266
+ const toggle = useCallback(() => {
267
+ setIsDark((prev) => !prev);
268
+ }, []);
269
+ return { isDark, toggle };
270
+ }
271
+
272
+ // src/lib/cn.ts
273
+ import { clsx } from "clsx";
274
+ import { twMerge } from "tailwind-merge";
275
+ function cn(...inputs) {
276
+ return twMerge(clsx(inputs));
277
+ }
278
+
220
279
  export {
280
+ cn,
221
281
  parseWhatsAppFormatting,
222
282
  ZERO_WIDTH_SPACE,
223
283
  waToHTML,
224
284
  htmlToWA,
225
- waToHTMLInline
285
+ waToHTMLInline,
286
+ useIsDarkTheme,
287
+ useDarkMode
226
288
  };
@@ -1,13 +1,11 @@
1
1
  import {
2
2
  TooltipLayer,
3
3
  WhatsAppMessagePreview
4
- } from "../chunk-HOKNKXIT.js";
4
+ } from "../chunk-HMCOTZAX.js";
5
5
  import {
6
+ parseWhatsAppFormatting,
6
7
  useIsDarkTheme
7
- } from "../chunk-MRVFFJEH.js";
8
- import {
9
- parseWhatsAppFormatting
10
- } from "../chunk-CBYEHPX6.js";
8
+ } from "../chunk-PJNR6UMN.js";
11
9
 
12
10
  // src/flows/FlowNodeCard.tsx
13
11
  import { useState } from "react";