@adatechnology/conversations-ui 0.1.0-rc.7 → 0.1.0-rc.9
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/dist/{chunk-OGRRHQQW.js → chunk-2AYDBWNE.js} +55 -1
- package/dist/{chunk-TGTBMMFC.js → chunk-G5BM3VBP.js} +42 -7
- package/dist/flows/index.js +2 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -6
- package/dist/preview/index.js +2 -2
- package/package.json +1 -1
- package/src/InteractiveMessage.test.tsx +41 -0
- package/src/InteractiveMessage.tsx +21 -4
- package/src/Wallpaper.test.tsx +21 -0
- package/src/Wallpaper.tsx +52 -6
- package/dist/chunk-NV2RZ5KT.js +0 -56
|
@@ -151,9 +151,63 @@ function waToHTMLInline(text) {
|
|
|
151
151
|
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>");
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
// src/useDarkMode.ts
|
|
155
|
+
import { useState, useEffect, useCallback } from "react";
|
|
156
|
+
var STORAGE_KEY = "conversations-ui-dark-mode";
|
|
157
|
+
function getInitialDark() {
|
|
158
|
+
if (typeof window === "undefined") return false;
|
|
159
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
160
|
+
if (stored !== null) {
|
|
161
|
+
return stored === "true";
|
|
162
|
+
}
|
|
163
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
164
|
+
}
|
|
165
|
+
function useIsDarkTheme() {
|
|
166
|
+
const [isDark, setIsDark] = useState(
|
|
167
|
+
() => typeof document !== "undefined" && document.documentElement.classList.contains("dark")
|
|
168
|
+
);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
const root = document.documentElement;
|
|
171
|
+
const observer = new MutationObserver(() => setIsDark(root.classList.contains("dark")));
|
|
172
|
+
observer.observe(root, { attributes: true, attributeFilter: ["class"] });
|
|
173
|
+
setIsDark(root.classList.contains("dark"));
|
|
174
|
+
return () => observer.disconnect();
|
|
175
|
+
}, []);
|
|
176
|
+
return isDark;
|
|
177
|
+
}
|
|
178
|
+
function useDarkMode() {
|
|
179
|
+
const [isDark, setIsDark] = useState(getInitialDark);
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
const root = document.documentElement;
|
|
182
|
+
if (isDark) {
|
|
183
|
+
root.classList.add("dark");
|
|
184
|
+
} else {
|
|
185
|
+
root.classList.remove("dark");
|
|
186
|
+
}
|
|
187
|
+
localStorage.setItem(STORAGE_KEY, String(isDark));
|
|
188
|
+
}, [isDark]);
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
191
|
+
const handleChange = (event) => {
|
|
192
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
193
|
+
if (stored === null) {
|
|
194
|
+
setIsDark(event.matches);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
198
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
199
|
+
}, []);
|
|
200
|
+
const toggle = useCallback(() => {
|
|
201
|
+
setIsDark((prev) => !prev);
|
|
202
|
+
}, []);
|
|
203
|
+
return { isDark, toggle };
|
|
204
|
+
}
|
|
205
|
+
|
|
154
206
|
export {
|
|
155
207
|
parseWhatsAppFormatting,
|
|
156
208
|
waToHTML,
|
|
157
209
|
htmlToWA,
|
|
158
|
-
waToHTMLInline
|
|
210
|
+
waToHTMLInline,
|
|
211
|
+
useIsDarkTheme,
|
|
212
|
+
useDarkMode
|
|
159
213
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
parseWhatsAppFormatting
|
|
3
|
-
|
|
2
|
+
parseWhatsAppFormatting,
|
|
3
|
+
useIsDarkTheme
|
|
4
|
+
} from "./chunk-2AYDBWNE.js";
|
|
4
5
|
|
|
5
6
|
// src/ConversationLocalesProvider.tsx
|
|
6
7
|
import { createContext, useContext } from "react";
|
|
@@ -394,6 +395,7 @@ function Lightbox({ imageUrl, caption, onClose, labels }) {
|
|
|
394
395
|
// src/InteractiveMessage.tsx
|
|
395
396
|
import { useState as useState3 } from "react";
|
|
396
397
|
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
398
|
+
var FORMATTING_CLASSES = "[&_strong]:font-bold [&_em]:italic [&_del]:line-through";
|
|
397
399
|
var DEFAULT_INTERACTIVE_MESSAGE_LABELS = {
|
|
398
400
|
openList: "Ver op\xE7\xF5es"
|
|
399
401
|
};
|
|
@@ -411,9 +413,18 @@ function InteractiveMessage({ payload, onSelect, labels, className }) {
|
|
|
411
413
|
const hasRows = collectRows(payload).length > 0;
|
|
412
414
|
const isInteractable = onSelect !== void 0;
|
|
413
415
|
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(
|
|
416
|
-
|
|
416
|
+
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,
|
|
417
|
+
payload.body?.text ? /* @__PURE__ */ jsx7(
|
|
418
|
+
"p",
|
|
419
|
+
{
|
|
420
|
+
className: cn(
|
|
421
|
+
"whitespace-pre-wrap break-words text-sm text-gray-900 dark:text-gray-100",
|
|
422
|
+
FORMATTING_CLASSES
|
|
423
|
+
),
|
|
424
|
+
children: parseWhatsAppFormatting(payload.body.text)
|
|
425
|
+
}
|
|
426
|
+
) : null,
|
|
427
|
+
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
428
|
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
429
|
"button",
|
|
419
430
|
{
|
|
@@ -606,8 +617,32 @@ function MessageBubble({
|
|
|
606
617
|
|
|
607
618
|
// src/Wallpaper.tsx
|
|
608
619
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
609
|
-
function
|
|
610
|
-
|
|
620
|
+
function doodlePattern(strokeColor, opacity) {
|
|
621
|
+
const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><g fill='none' stroke='${strokeColor}' stroke-width='1.2' opacity='${opacity}'><circle cx='15' cy='15' r='3'/><path d='M40 10 q5 8 0 16 q-5 -8 0 -16z'/><circle cx='70' cy='28' r='2'/><path d='M18 55 l6 6 m-6 0 l6 -6'/><circle cx='55' cy='68' r='2.5'/><path d='M85 58 q6 6 0 12 q-6 -6 0 -12z'/><circle cx='8' cy='85' r='2'/><path d='M65 90 l5 5 m-5 0 l5 -5'/></g></svg>`;
|
|
622
|
+
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
|
|
623
|
+
}
|
|
624
|
+
var LIGHT_WALLPAPER = {
|
|
625
|
+
backgroundColor: "#efeae2",
|
|
626
|
+
backgroundImage: doodlePattern("#d7cfc0", 0.7),
|
|
627
|
+
backgroundRepeat: "repeat",
|
|
628
|
+
backgroundSize: "100px 100px"
|
|
629
|
+
};
|
|
630
|
+
var DARK_WALLPAPER = {
|
|
631
|
+
backgroundColor: "#0b141a",
|
|
632
|
+
backgroundImage: doodlePattern("#19232a", 0.9),
|
|
633
|
+
backgroundRepeat: "repeat",
|
|
634
|
+
backgroundSize: "100px 100px"
|
|
635
|
+
};
|
|
636
|
+
function ConversationWallpaper({ children, className, style }) {
|
|
637
|
+
const isDark = useIsDarkTheme();
|
|
638
|
+
return /* @__PURE__ */ jsx10(
|
|
639
|
+
"div",
|
|
640
|
+
{
|
|
641
|
+
className: cn("cv-wallpaper", className),
|
|
642
|
+
style: { ...isDark ? DARK_WALLPAPER : LIGHT_WALLPAPER, ...style },
|
|
643
|
+
children
|
|
644
|
+
}
|
|
645
|
+
);
|
|
611
646
|
}
|
|
612
647
|
|
|
613
648
|
// src/emojiCatalog.ts
|
package/dist/flows/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
parseWhatsAppFormatting,
|
|
2
3
|
useIsDarkTheme
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import {
|
|
5
|
-
parseWhatsAppFormatting
|
|
6
|
-
} from "../chunk-OGRRHQQW.js";
|
|
4
|
+
} from "../chunk-2AYDBWNE.js";
|
|
7
5
|
|
|
8
6
|
// src/flows/FlowNodeCard.tsx
|
|
9
7
|
import { Handle, Position } from "@xyflow/react";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import react__default, { ReactNode, FormEvent } from 'react';
|
|
2
|
+
import react__default, { ReactNode, CSSProperties, FormEvent } from 'react';
|
|
3
3
|
import { y as MessagePayload, A as ResolveMediaUrl, v as InteractiveSelection, t as InteractivePayload, p as ConversationsFeatures, m as ConversationSummary, h as ConversationChannel, L as ListConversationsParams, o as ConversationsApi, S as SSEProvider, w as ListDocumentsParams, i as ConversationDocument, n as ConversationTemplate } from './types-B5C1DLu1.js';
|
|
4
4
|
export { C as CHANNEL_CAPABILITIES, a as CHANNEL_FILTER_ALL, b as CONVERSATION_CHANNEL, c as ChannelCapabilities, d as ChannelFilter, e as ChannelFilterOption, f as CompanyDocument, g as CompanyDocumentPage, j as ConversationDocumentPage, k as ConversationEventSource, l as ConversationPage, q as ConversationsTheme, r as ConversationsUIConfig, D as DEFAULT_CONVERSATION_CHANNEL, F as FormatContactHandleParams, H as HANDLE_KIND, s as HandleKind, I as InteractiveOption, u as InteractiveSection, M as MediaRenderer, x as MediaRendererProps, R as REOPEN_MECHANISM, z as ReopenMechanism, B as capabilitiesOf, E as channelFiltersFor, G as contactFlag, J as formatContactHandle } from './types-B5C1DLu1.js';
|
|
5
5
|
|
|
@@ -41,8 +41,10 @@ declare function InteractiveMessage({ payload, onSelect, labels, className }: In
|
|
|
41
41
|
interface ConversationWallpaperProps {
|
|
42
42
|
children?: ReactNode;
|
|
43
43
|
className?: string;
|
|
44
|
+
/** Ajusta ou substitui o fundo padrão — para produto com identidade visual própria. */
|
|
45
|
+
style?: CSSProperties;
|
|
44
46
|
}
|
|
45
|
-
declare function ConversationWallpaper({ children, className }: ConversationWallpaperProps): react.JSX.Element;
|
|
47
|
+
declare function ConversationWallpaper({ children, className, style }: ConversationWallpaperProps): react.JSX.Element;
|
|
46
48
|
|
|
47
49
|
interface ConversationLocales {
|
|
48
50
|
bubble: {
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useDarkMode,
|
|
3
|
-
useIsDarkTheme
|
|
4
|
-
} from "./chunk-NV2RZ5KT.js";
|
|
5
1
|
import {
|
|
6
2
|
AudioPlayer,
|
|
7
3
|
ConversationDocumentsPanel,
|
|
@@ -43,13 +39,15 @@ import {
|
|
|
43
39
|
useConversationDocuments,
|
|
44
40
|
useConversationLocales,
|
|
45
41
|
useConversations
|
|
46
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-G5BM3VBP.js";
|
|
47
43
|
import {
|
|
48
44
|
htmlToWA,
|
|
49
45
|
parseWhatsAppFormatting,
|
|
46
|
+
useDarkMode,
|
|
47
|
+
useIsDarkTheme,
|
|
50
48
|
waToHTML,
|
|
51
49
|
waToHTMLInline
|
|
52
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-2AYDBWNE.js";
|
|
53
51
|
|
|
54
52
|
// src/WhatsAppMessageEditor.tsx
|
|
55
53
|
import { useRef } from "react";
|
package/dist/preview/index.js
CHANGED
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
DocumentsLibrary,
|
|
7
7
|
MessageBubble,
|
|
8
8
|
MessageComposer
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-G5BM3VBP.js";
|
|
10
|
+
import "../chunk-2AYDBWNE.js";
|
|
11
11
|
|
|
12
12
|
// src/preview/previewStore.ts
|
|
13
13
|
var GLOBAL_CHANNEL = "global";
|
package/package.json
CHANGED
|
@@ -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=
|
|
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
|
|
60
|
-
{
|
|
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=
|
|
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 ? (
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
|
|
4
|
+
import { ConversationWallpaper } from './Wallpaper'
|
|
5
|
+
|
|
6
|
+
describe('ConversationWallpaper', () => {
|
|
7
|
+
it('desenha o fundo sozinho, sem depender do styles.css do host', () => {
|
|
8
|
+
const markup = renderToStaticMarkup(<ConversationWallpaper />)
|
|
9
|
+
|
|
10
|
+
expect(markup).toContain('background-color:#efeae2')
|
|
11
|
+
expect(markup).toContain('background-image:url("data:image/svg+xml,')
|
|
12
|
+
expect(markup).toContain('cv-wallpaper')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('deixa o produto sobrescrever o fundo por style', () => {
|
|
16
|
+
const markup = renderToStaticMarkup(<ConversationWallpaper style={{ backgroundColor: '#123456' }} />)
|
|
17
|
+
|
|
18
|
+
expect(markup).toContain('background-color:#123456')
|
|
19
|
+
expect(markup).not.toContain('background-color:#efeae2')
|
|
20
|
+
})
|
|
21
|
+
})
|
package/src/Wallpaper.tsx
CHANGED
|
@@ -1,15 +1,61 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Fundo da conversa — a textura clara/escura do WhatsApp.
|
|
3
|
+
*
|
|
4
|
+
* O fundo vem embutido, e não da folha de estilo do pacote: `styles.css` é um import opcional que o
|
|
5
|
+
* host precisa lembrar de fazer, e esquecê-lo deixava a conversa sobre branco liso, sem erro nenhum
|
|
6
|
+
* para denunciar o problema. Fundo é identidade do componente, não tema do host.
|
|
7
|
+
*
|
|
8
|
+
* A classe `cv-wallpaper` continua no elemento para quem já sobrescreve por CSS.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { CSSProperties, ReactNode } from 'react'
|
|
2
12
|
|
|
3
13
|
import { cn } from './lib/cn'
|
|
14
|
+
import { useIsDarkTheme } from './useDarkMode'
|
|
15
|
+
|
|
16
|
+
/** Textura do WhatsApp: rabiscos esparsos que se repetem a cada 100px. */
|
|
17
|
+
function doodlePattern(strokeColor: string, opacity: number): string {
|
|
18
|
+
const svg =
|
|
19
|
+
`<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'>` +
|
|
20
|
+
`<g fill='none' stroke='${strokeColor}' stroke-width='1.2' opacity='${opacity}'>` +
|
|
21
|
+
`<circle cx='15' cy='15' r='3'/><path d='M40 10 q5 8 0 16 q-5 -8 0 -16z'/>` +
|
|
22
|
+
`<circle cx='70' cy='28' r='2'/><path d='M18 55 l6 6 m-6 0 l6 -6'/>` +
|
|
23
|
+
`<circle cx='55' cy='68' r='2.5'/><path d='M85 58 q6 6 0 12 q-6 -6 0 -12z'/>` +
|
|
24
|
+
`<circle cx='8' cy='85' r='2'/><path d='M65 90 l5 5 m-5 0 l5 -5'/>` +
|
|
25
|
+
`</g></svg>`
|
|
26
|
+
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const LIGHT_WALLPAPER: CSSProperties = {
|
|
30
|
+
backgroundColor: '#efeae2',
|
|
31
|
+
backgroundImage: doodlePattern('#d7cfc0', 0.7),
|
|
32
|
+
backgroundRepeat: 'repeat',
|
|
33
|
+
backgroundSize: '100px 100px',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DARK_WALLPAPER: CSSProperties = {
|
|
37
|
+
backgroundColor: '#0b141a',
|
|
38
|
+
backgroundImage: doodlePattern('#19232a', 0.9),
|
|
39
|
+
backgroundRepeat: 'repeat',
|
|
40
|
+
backgroundSize: '100px 100px',
|
|
41
|
+
}
|
|
4
42
|
|
|
5
43
|
export interface ConversationWallpaperProps {
|
|
6
44
|
children?: ReactNode
|
|
7
45
|
className?: string
|
|
46
|
+
/** Ajusta ou substitui o fundo padrão — para produto com identidade visual própria. */
|
|
47
|
+
style?: CSSProperties
|
|
8
48
|
}
|
|
9
49
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
50
|
+
export function ConversationWallpaper({ children, className, style }: ConversationWallpaperProps) {
|
|
51
|
+
const isDark = useIsDarkTheme()
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className={cn('cv-wallpaper', className)}
|
|
56
|
+
style={{ ...(isDark ? DARK_WALLPAPER : LIGHT_WALLPAPER), ...style }}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
15
61
|
}
|
package/dist/chunk-NV2RZ5KT.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
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
|
-
export {
|
|
54
|
-
useIsDarkTheme,
|
|
55
|
-
useDarkMode
|
|
56
|
-
};
|