@adatechnology/conversations-ui 0.1.0-rc.7 → 0.1.0-rc.8

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.
@@ -394,6 +394,7 @@ function Lightbox({ imageUrl, caption, onClose, labels }) {
394
394
  // src/InteractiveMessage.tsx
395
395
  import { useState as useState3 } from "react";
396
396
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
397
+ var FORMATTING_CLASSES = "[&_strong]:font-bold [&_em]:italic [&_del]:line-through";
397
398
  var DEFAULT_INTERACTIVE_MESSAGE_LABELS = {
398
399
  openList: "Ver op\xE7\xF5es"
399
400
  };
@@ -411,9 +412,18 @@ function InteractiveMessage({ payload, onSelect, labels, className }) {
411
412
  const hasRows = collectRows(payload).length > 0;
412
413
  const isInteractable = onSelect !== void 0;
413
414
  return /* @__PURE__ */ jsxs5("div", { className: cn("flex flex-col gap-1", className), children: [
414
- payload.header?.text ? /* @__PURE__ */ jsx7("p", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: payload.header.text }) : null,
415
- payload.body?.text ? /* @__PURE__ */ jsx7("p", { className: "whitespace-pre-wrap break-words text-sm text-gray-900 dark:text-gray-100", children: payload.body.text }) : null,
416
- payload.footer?.text ? /* @__PURE__ */ jsx7("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: payload.footer.text }) : null,
415
+ payload.header?.text ? /* @__PURE__ */ jsx7("p", { className: cn("text-sm font-semibold text-gray-900 dark:text-gray-100", FORMATTING_CLASSES), children: parseWhatsAppFormatting(payload.header.text) }) : null,
416
+ payload.body?.text ? /* @__PURE__ */ jsx7(
417
+ "p",
418
+ {
419
+ className: cn(
420
+ "whitespace-pre-wrap break-words text-sm text-gray-900 dark:text-gray-100",
421
+ FORMATTING_CLASSES
422
+ ),
423
+ children: parseWhatsAppFormatting(payload.body.text)
424
+ }
425
+ ) : null,
426
+ payload.footer?.text ? /* @__PURE__ */ jsx7("p", { className: cn("text-xs text-gray-500 dark:text-gray-400", FORMATTING_CLASSES), children: parseWhatsAppFormatting(payload.footer.text) }) : null,
417
427
  buttons.length > 0 ? /* @__PURE__ */ jsx7("div", { className: "mt-1 flex flex-col gap-1 border-t border-gray-200 pt-1 dark:border-gray-700", children: buttons.map((button) => /* @__PURE__ */ jsx7(
418
428
  "button",
419
429
  {
package/dist/index.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  useConversationDocuments,
44
44
  useConversationLocales,
45
45
  useConversations
46
- } from "./chunk-TGTBMMFC.js";
46
+ } from "./chunk-73MW5HNT.js";
47
47
  import {
48
48
  htmlToWA,
49
49
  parseWhatsAppFormatting,
@@ -6,7 +6,7 @@ import {
6
6
  DocumentsLibrary,
7
7
  MessageBubble,
8
8
  MessageComposer
9
- } from "../chunk-TGTBMMFC.js";
9
+ } from "../chunk-73MW5HNT.js";
10
10
  import "../chunk-OGRRHQQW.js";
11
11
 
12
12
  // src/preview/previewStore.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adatechnology/conversations-ui",
3
- "version": "0.1.0-rc.7",
3
+ "version": "0.1.0-rc.8",
4
4
  "description": "WhatsApp conversation UI components — parametrizável por endpoint, tema e feature flags",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import { renderToStaticMarkup } from 'react-dom/server'
3
+
4
+ import { InteractiveMessage } from './InteractiveMessage'
5
+ import type { InteractivePayload } from './types'
6
+
7
+ function render(payload: InteractivePayload): string {
8
+ return renderToStaticMarkup(<InteractiveMessage payload={payload} />)
9
+ }
10
+
11
+ describe('InteractiveMessage', () => {
12
+ it('formata negrito no corpo, como o aparelho faz', () => {
13
+ const markup = render({ type: 'button', body: { text: 'Qual o *prazo desejado*?' } })
14
+
15
+ expect(markup).toContain('<strong>prazo desejado</strong>')
16
+ expect(markup).not.toContain('*prazo desejado*')
17
+ })
18
+
19
+ it('formata cabeçalho e rodapé também', () => {
20
+ const markup = render({
21
+ type: 'list',
22
+ header: { text: '*Menu*' },
23
+ body: { text: 'corpo' },
24
+ footer: { text: '_Selecione_' },
25
+ })
26
+
27
+ expect(markup).toContain('<strong>Menu</strong>')
28
+ expect(markup).toContain('<em>Selecione</em>')
29
+ })
30
+
31
+ it('deixa título de opção literal, porque o WhatsApp não formata ali', () => {
32
+ const markup = render({
33
+ type: 'button',
34
+ body: { text: 'corpo' },
35
+ action: { buttons: [{ reply: { id: 'sim', title: '*Sim*' } }] },
36
+ })
37
+
38
+ expect(markup).toContain('*Sim*')
39
+ expect(markup).not.toContain('<strong>Sim</strong>')
40
+ })
41
+ })
@@ -13,6 +13,10 @@
13
13
  import { useState } from 'react'
14
14
  import type { InteractiveOption, InteractivePayload, InteractiveSelection } from './types'
15
15
  import { cn } from './lib/cn'
16
+ import { parseWhatsAppFormatting } from './lib/whatsapp-formatting'
17
+
18
+ /** `parseWhatsAppFormatting` emite `<strong>`/`<em>`/`<del>`, que o reset do Tailwind zera. */
19
+ const FORMATTING_CLASSES = '[&_strong]:font-bold [&_em]:italic [&_del]:line-through'
16
20
 
17
21
  export interface InteractiveMessageLabels {
18
22
  /** Fallback do rótulo do botão que abre a lista, quando o payload não traz um. */
@@ -51,18 +55,31 @@ export function InteractiveMessage({ payload, onSelect, labels, className }: Int
51
55
 
52
56
  return (
53
57
  <div className={cn('flex flex-col gap-1', className)}>
58
+ {/* Cabeçalho, corpo e rodapé passam pela formatação do WhatsApp — o aparelho renderiza
59
+ `*negrito*` neles como em qualquer texto. Título e descrição de opção NÃO: ali o WhatsApp
60
+ mostra os asteriscos literais, e formatá-los aqui deixaria o simulador mais bonito que o
61
+ aparelho, que é o tipo de divergência que só aparece com o cliente na frente. */}
54
62
  {payload.header?.text ? (
55
- <p className="text-sm font-semibold text-gray-900 dark:text-gray-100">{payload.header.text}</p>
63
+ <p className={cn('text-sm font-semibold text-gray-900 dark:text-gray-100', FORMATTING_CLASSES)}>
64
+ {parseWhatsAppFormatting(payload.header.text)}
65
+ </p>
56
66
  ) : null}
57
67
 
58
68
  {payload.body?.text ? (
59
- <p className="whitespace-pre-wrap break-words text-sm text-gray-900 dark:text-gray-100">
60
- {payload.body.text}
69
+ <p
70
+ className={cn(
71
+ 'whitespace-pre-wrap break-words text-sm text-gray-900 dark:text-gray-100',
72
+ FORMATTING_CLASSES,
73
+ )}
74
+ >
75
+ {parseWhatsAppFormatting(payload.body.text)}
61
76
  </p>
62
77
  ) : null}
63
78
 
64
79
  {payload.footer?.text ? (
65
- <p className="text-xs text-gray-500 dark:text-gray-400">{payload.footer.text}</p>
80
+ <p className={cn('text-xs text-gray-500 dark:text-gray-400', FORMATTING_CLASSES)}>
81
+ {parseWhatsAppFormatting(payload.footer.text)}
82
+ </p>
66
83
  ) : null}
67
84
 
68
85
  {buttons.length > 0 ? (