@meerkat-coding/chat-widget
v2.0.18
Published
Biblioteca modular de widgets de chat para React com arquitetura escalável
Maintainers
Readme
🤖 Chat Widget Library
Uma biblioteca React modular, escalável e totalmente personalizável para criar widgets de chat com suporte a Markdown, temas customizáveis e incorporação standalone.
✨ Features
- 🎨 Sistema de Tema Completo - 50+ propriedades customizáveis organizadas em categorias
- 📱 Responsivo - Adapta automaticamente a mobile, tablet e desktop com viewport units
- 🪟 Window Mode - Botão flutuante minimizado com animações suaves e ícone customizável
- ✏️ Suporte a Markdown - Renderização rica com react-markdown + GitHub Flavored Markdown
- 📦 Embed Code - Incorpore em qualquer site com apenas 2 linhas de código
- 🔄 Polling Inteligente - Atualização automática e adaptativa baseada no estado
- 🔒 Bloqueio de Mensagens - Controle de fluxo baseado no backend
- 🎯 Modular - Use componentes e hooks individuais ou o widget completo
- 💪 TypeScript - Totalmente tipado com interfaces claras
- 🎨 Totalmente Customizável - Estilos, comportamento e callbacks
📦 Instalação
Via NPM (React Projects)
npm install @meerkat-coding/chat-widgetVia Standalone Script (Qualquer Site)
<!-- Configuração -->
<script>
window.ChatWidgetConfig = {
loadMessagesUrl: "https://seu-servidor.com/api/messages",
sendMessageUrl: "https://seu-servidor.com/api/send",
// Internacionalização (obrigatório)
i18n: {
pt: {
title: "Chat de Suporte",
subtitle: "Estamos aqui para ajudar!",
footer: "",
getStarted: "Iniciar Chat",
inputPlaceholder: "Digite sua mensagem...",
initialMessages: ["Olá! Como posso ajudar?"],
},
},
defaultLanguage: "pt",
// Recursos
enableMarkdown: true,
enablePolling: true,
refetchInterval: 3000,
// Opcional: Personalização de tema
theme: {
colors: {
headerBackground: "#667eea",
messageUserBackground: "#764ba2",
},
},
// Opcional: Callbacks
onMessageSent: function (message) {
console.log("Mensagem enviada:", message);
},
onError: function (error) {
console.error("Erro no chat:", error);
},
};
</script>
<!-- Script do Widget via CDN -->
<!-- Opção 1: unpkg (recomendado) -->
<script src="https://unpkg.com/@meerkat-coding/chat-widget@latest/dist/chat-widget.min.js"></script>
<!-- Opção 2: jsDelivr -->
<!-- <script src="https://cdn.jsdelivr.net/npm/@meerkat-coding/chat-widget@latest/dist/chat-widget.min.js"></script> -->💡 Dica sobre CDN:
- unpkg e jsDelivr servem automaticamente packages do npm
- Use
@2.0.3para versão específica ou@latestpara sempre pegar a mais recente- O cache do CDN garante carregamento rápido globalmente
- Sem necessidade de hospedar o arquivo no seu próprio servidor!
🚀 Uso Rápido
Opção 1: Widget Completo (React)
import { ChatWidget } from "@meerkat-coding/chat-widget";
function App() {
return (
<ChatWidget
loadMessagesUrl="https://api.exemplo.com/messages"
sendMessageUrl="https://api.exemplo.com/send"
i18n={{
pt: {
title: "Atendimento",
subtitle: "Estamos aqui para ajudar!",
footer: "",
getStarted: "Iniciar Chat",
inputPlaceholder: "Digite sua mensagem...",
initialMessages: ["Olá! Como posso ajudar?"],
},
}}
defaultLanguage="pt"
enableMarkdown={true}
theme={{
colors: {
headerBackground: "#667eea",
messageUserBackground: "#764ba2",
},
}}
/>
);
}Opção 2: Componentes Individuais
import {
MessageList,
MessageInput,
useSessionId,
useMessages,
useSendMessage,
} from "@meerkat-coding/chat-widget";
function CustomChatWidget() {
const { sessionId } = useSessionId();
const { messages, isLoading, isBlocked } = useMessages({
loadMessagesUrl: "https://api.exemplo.com/messages",
sessionId,
enablePolling: true,
});
const { sendMessage, isSending } = useSendMessage({
sendMessageUrl: "https://api.exemplo.com/send",
sessionId,
});
return (
<div className="my-chat">
<MessageList
messages={messages}
isLoading={isLoading}
enableMarkdown={true}
/>
<MessageInput
onSend={sendMessage}
isDisabled={isBlocked}
isSending={isSending}
/>
</div>
);
}🎨 Sistema de Tema
Estrutura do Tema
O tema é organizado em 4 categorias principais:
interface ChatWidgetTheme {
general?: {
title?: string;
subtitle?: string;
placeholder?: string;
initialMessage?: string;
fontFamily?: string;
widgetIcon?: React.ReactNode; // Ícone do botão flutuante (mode: window)
iconSize?: string; // Tamanho do ícone do header (ex: "24px", padrão: "24px")
iconPadding?: string; // Padding do ícone do header (ex: "4px", padrão: "0")
};
colors?: {
// Header (4 propriedades)
headerBackground?: string;
headerText?: string;
headerSubtitleText?: string;
headerIconColor?: string;
// Mensagens (6 propriedades)
messageUserBackground?: string;
messageUserText?: string;
messageBotBackground?: string;
messageBotText?: string;
messageTimestampColor?: string;
// Input
inputBackground?: string;
inputText?: string;
inputBorder?: string;
inputPlaceholder?: string;
sendButtonBackground?: string;
sendButtonText?: string;
// Toggle Button (mode: window)
toggleBackground?: string; // Cor de fundo do botão flutuante
toggleHover?: string; // Cor no hover
toggleIconStroke?: string; // Cor do ícone SVG
// Outros
lightBackground?: string;
disabledText?: string;
};
layout?: {
windowWidth?: string;
windowHeight?: string;
borderRadius?: string;
spacing?: string;
headerHeight?: string;
// Toggle Button (mode: window)
toggleSize?: string; // Tamanho do botão (ex: "60px")
toggleBorderRadius?: string; // Arredondamento (ex: "50%" para círculo, "12px" para quadrado)
messageMaxWidth?: string;
messageBorderRadius?: string;
transitionDuration?: string; // Duração das animações (ex: "0.3s")
};
typography?: {
titleFontSize?: string;
subtitleFontSize?: string;
messageFontSize?: string;
messageLineHeight?: string;
subtitleLineHeight?: string;
};
}Exemplo de Uso
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
// Tema customizado
theme={{
general: {
title: "Suporte Premium",
subtitle: "Online 24/7",
fontFamily: "Inter, sans-serif",
},
colors: {
headerBackground: "#6366f1",
messageUserBackground: "#8b5cf6",
messageBotBackground: "#f3f4f6",
},
layout: {
windowWidth: "450px",
windowHeight: "650px",
borderRadius: "1rem",
},
}}
// Sobrescritas específicas via customStyles (opcional)
customStyles={{
header: {
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
},
}}
/>Hierarquia de Estilos
defaultTheme (base)
↓
theme (suas customizações)
↓
customStyles (sobrescritas específicas)
↓
Estilos finais renderizadosResponsividade
O widget é totalmente responsivo e se adapta automaticamente a diferentes tamanhos de tela:
Dimensões Responsivas:
- Largura:
min(420px, calc(100vw - 40px))- Adapta a telas pequenas com margem de 20px - Altura:
min(600px, calc(100vh - 100px))- Acompanha altura da viewport com margem de 50px topo/base - Mensagens:
min(75%, 280px)- Largura máxima garante legibilidade em mobile
Safe Areas (Notch/Rounded Corners):
- Botão toggle e widget respeitam
env(safe-area-inset-*)em iOS/Android modernos - Posicionamento automático evita sobreposição com elementos do sistema
Mobile-First:
// Em mobile (< 380px viewport):
// - Widget ocupa 100vw - 40px (deixa margem)
// - Altura se ajusta a 100vh - 100px
// Em desktop (> 420px viewport):
// - Widget usa tamanho fixo 420x600px
// - Comportamento padrão mantidoCustomização de Dimensões:
<ChatWidget
theme={{
layout: {
// Sobrescrever dimensões responsivas
windowWidth: "min(500px, 95vw)", // Widget maior
windowHeight: "min(700px, 85vh)", // Mais alto
},
}}
/>Dica para Mobile: Se o widget parecer muito grande em mobile, ajuste as margens:
theme={{
layout: {
windowWidth: "min(420px, calc(100vw - 60px))", // Margem 30px
windowHeight: "min(600px, calc(100vh - 120px))", // Margem 60px
},
}}✏️ Suporte a Markdown
O widget suporta GitHub Flavored Markdown completo:
Recursos Suportados
- Texto:
**negrito**,*itálico*,~~riscado~~ - Links:
[texto](url) - Código:
`inline`ou blocos com``` - Listas: ordenadas (
1.) e não-ordenadas (-) - Citações:
> texto - Tabelas: Sintaxe GFM
- Quebras de linha:
\n(quebra simples) ou\n\n(novo parágrafo)- ✨ Suporte a line breaks: Plugin
remark-breaksativado -\nsimples quebra linha automaticamente
- ✨ Suporte a line breaks: Plugin
Exemplo
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
enableMarkdown={true}
initialMessage={
"Olá! 👋 Eu suporto **Markdown**!\n" +
"Esta é uma nova linha.\n\n" +
"Experimente:\n" +
"- `código inline`\n" +
"- **negrito** e *itálico*\n" +
"- [links clicáveis](https://example.com)"
}
/>⚠️ Importante sobre quebras de linha:
Em JSX, use {...} com concatenação de strings JavaScript:
✅ Correto:
initialMessage={
"Linha 1\n" +
"Linha 2\n\n" +
"Parágrafo 2"
}❌ Incorreto:
initialMessage = "Linha 1\nLinha 2"; // \n pode não ser interpretado corretamenteResultado renderizado:
Olá! 👋 Eu suporto Markdown!
Esta é uma nova linha.
Experimente:
- código inline
- negrito e itálico
- links clicáveis🔗 Embed Code - Incorporação em Qualquer Site
Instalação Rápida
1. Copie e cole este código no final do <body> do seu HTML:
<!-- Configuração do Widget -->
<script>
window.ChatWidgetConfig = {
// Obrigatório
loadMessagesUrl: "https://api.exemplo.com/api/messages",
sendMessageUrl: "https://api.exemplo.com/api/send",
// Internacionalização (obrigatório)
i18n: {
pt: {
title: "Chat de Suporte",
subtitle: "Estamos aqui para ajudar!",
footer: "",
getStarted: "Iniciar Chat",
inputPlaceholder: "Digite sua mensagem...",
initialMessages: ["Olá! Como posso ajudar?"],
},
en: {
title: "Support Chat",
subtitle: "We are here to help!",
footer: "",
getStarted: "Start Chat",
inputPlaceholder: "Type your message...",
initialMessages: ["Hello! How can I help you?"],
},
},
defaultLanguage: "pt",
// Recursos
enableMarkdown: true,
enablePolling: true,
refetchInterval: 3000,
// Welcome Screen & Mode (opcional)
showWelcomeScreen: false,
mode: "window",
// Session ID (opcional - útil para integração com autenticação)
sessionId: "user-session-123", // ou obtenha de cookies/localStorage
// Carregar sessão anterior (opcional - padrão: false)
loadPreviousSession: false, // true = mantém sessão entre reloads, false = sempre nova sessão
// Tema (opcional)
theme: {
colors: {
headerBackground: "#667eea",
messageUserBackground: "#764ba2",
},
},
// Callbacks (opcional)
onMessageSent: function (message) {
console.log("Mensagem enviada:", message);
},
onError: function (error) {
console.error("Erro:", error);
},
};
</script>
<!-- Script do Widget via CDN -->
<!-- Opção 1: unpkg (recomendado) -->
<script src="https://unpkg.com/@meerkat-coding/chat-widget@latest/dist/chat-widget.min.js"></script>
<!-- Opção 2: jsDelivr -->
<!-- <script src="https://cdn.jsdelivr.net/npm/@meerkat-coding/chat-widget@latest/dist/chat-widget.min.js"></script> -->2. Pronto! O widget aparecerá automaticamente no canto inferior direito.
Build do Standalone
Para gerar os arquivos de embed:
npm run build # Gera dist/chat-widget.js e chat-widget.min.jsTamanho do Bundle:
chat-widget.min.js: ~80KB (~25KB gzip)- Inclui React, React-DOM e todas as dependências
Gerador de Embed Code
O projeto inclui um gerador visual de código em http://localhost:5173. Role a página para baixo para ver a seção "🔗 Embed Code Generator" onde você pode:
- Configurar todas as propriedades visualmente
- Ver preview do código em tempo real
- Copiar código pronto com um clique
Exemplo Completo
Veja example/public/embed-demo.html para uma demonstração funcional do widget incorporado em HTML puro.
⚙️ Propriedades Completas
URLs (Obrigatórias)
{
loadMessagesUrl: string; // GET endpoint para carregar mensagens
sendMessageUrl: string; // POST endpoint para enviar mensagens
}Internacionalização (Obrigatória)
{
i18n: I18nConfig; // Configuração de idiomas (obrigatório)
defaultLanguage?: string; // Idioma padrão (padrão: "en")
}
interface I18nConfig {
[language: string]: I18nTranslations;
}
interface I18nTranslations {
title: string; // Título do header
subtitle: string; // Subtítulo do header
footer: string; // Texto do footer
getStarted: string; // Texto do botão "Get Started" (welcome screen)
inputPlaceholder: string; // Placeholder do input
initialMessages?: string[]; // Mensagens iniciais do bot (array)
}Interface & Comportamento
{
// UI
showWelcomeScreen?: boolean; // Mostrar tela de boas-vindas (padrão: false)
mode?: 'window' | 'fullscreen'; // Modo de exibição (padrão: 'window')
// - window: botão flutuante minimizado, clique para expandir
// - fullscreen: chat sempre aberto
icon?: React.ReactNode | string; // Ícone customizado (ReactNode, URL, SVG, emoji ou base64)
// Dados
initialMessages?: Message[]; // Mensagens pré-carregadas (além das do i18n)
// Comportamento
enableMarkdown?: boolean; // Ativa Markdown (padrão: true)
showTimestamps?: boolean; // Exibir timestamps nas mensagens (padrão: true)
enablePolling?: boolean; // Ativa polling automático (padrão: true)
refetchInterval?: number; // Intervalo de polling em ms (padrão: 3000)
authHeader?: AuthConfig; // Configuração de autenticação
sessionId?: string; // Session ID fixo (prioridade sobre generateSessionId)
generateSessionId?: () => string; // Gerador de session ID customizado
loadPreviousSession?: boolean; // Carregar sessão anterior (padrão: false)
}Tema e Estilos
{
theme?: ChatWidgetTheme; // Tema estruturado (50+ propriedades)
customStyles?: ChatWidgetStyles; // Sobrescritas CSS específicas
}Callbacks
{
onMessageSent?: (message: Message) => void;
onMessagesLoaded?: (messages: Message[]) => void;
onError?: (error: Error) => void;
onBlockStateChange?: (isBlocked: boolean) => void;
}📡 Formato das APIs
GET /api/messages
Headers:
X-Session-ID: uuid-da-sessao
Authorization: Bearer token (opcional)Response:
{
"messages": [
{
"id": "msg-1",
"message": "Olá!",
"sender": "customer",
"timestamp": "2025-01-01T12:00:00Z",
"type": "text"
},
{
"id": "msg-2",
"message": "Oi! Como posso ajudar?",
"sender": "system",
"timestamp": "2025-01-01T12:00:01Z",
"type": "text"
}
],
"blockNewMessages": false
}POST /api/send
Headers:
X-Session-ID: uuid-da-sessao
Authorization: Bearer token (opcional)
Content-Type: application/jsonBody:
{
"message": "Preciso de ajuda",
"sessionId": "uuid-da-sessao",
"timestamp": "2025-01-01T12:00:02Z",
"idempotencyKey": "msg-uuid-da-sessao-1234567890"
}Response:
{
"success": true,
"message": {
"id": "msg-uuid-da-sessao-1234567890",
"message": "Preciso de ajuda",
"sender": "customer",
"timestamp": "2025-01-01T12:00:02Z",
"type": "text"
},
"blockNewMessages": true
}Controle de Bloqueio
O campo blockNewMessages controla o estado do input:
true: Input desabilitado (bot processando)false: Input habilitado
Idempotency Keys (Recomendado)
O widget automaticamente gera e envia idempotency keys para prevenir mensagens duplicadas em cenários de race condition.
Como funciona:
- Quando o usuário envia uma mensagem, o widget gera um ID único:
msg-{sessionId}-{timestamp} - Este ID é enviado no campo
idempotencyKeydo payload POST/api/send - O backend deve usar este
idempotencyKeycomoidda mensagem - Se uma mensagem com este ID já existe, retorna a existente (evita duplicata)
Implementação no Backend:
app.post('/api/send', (req, res) => {
const { message, sessionId, idempotencyKey } = req.body;
// Verifica se já existe mensagem com este ID (previne duplicata)
const existingMessage = findMessageById(sessionId, idempotencyKey);
if (existingMessage) {
// Retorna mensagem existente (evita duplicata)
return res.json({
success: true,
message: existingMessage,
blockNewMessages: false
});
}
// Salva nova mensagem usando idempotencyKey como ID
const newMessage = saveMessage({
id: idempotencyKey, // ← Usa idempotency key como ID
message,
sessionId,
timestamp: new Date().toISOString(),
sender: 'customer',
type: 'text'
});
res.json({
success: true,
message: newMessage,
blockNewMessages: false
});
});Benefícios:
- ✅ Previne mensagens duplicadas em race conditions
- ✅ Permite retries seguros (mesmo ID = não duplica)
- ✅ Matching preciso entre mensagens temporárias e reais (compara IDs)
- ✅ Modelo de dados limpo (1 campo ao invés de 2)
- ✅ Padrão da indústria usado por Stripe, Twilio, AWS, etc.
Backward Compatibility:
O campo idempotencyKey é opcional no payload. Se o backend não implementar idempotency, o widget continuará funcionando normalmente (mas sem a proteção contra duplicatas).
🪝 Hooks Personalizados
useSessionId()
Gerencia sessionID com localStorage.
const {
sessionId, // UUID da sessão
resetSession, // Gera novo sessionID
clearSession // Remove da localStorage
} = useSessionId(customGenerator?);useMessages()
Carrega e faz polling de mensagens.
const {
messages, // Array de mensagens
isLoading, // Estado de loading
isBlocked, // Estado de bloqueio
error, // Erro (se houver)
loadMessages, // Função para recarregar
addMessage, // Adiciona mensagem local
updateMessage, // Atualiza mensagem
removeMessage, // Remove mensagem
setBlockState, // Define estado de bloqueio
startPolling, // Inicia polling
stopPolling, // Para polling
} = useMessages({
loadMessagesUrl,
sessionId,
authConfig,
refetchInterval,
enablePolling,
onMessagesLoaded,
onError,
onBlockStateChange,
});useSendMessage()
Envia mensagens para o backend.
const {
sendMessage, // Função de envio
isSending, // Estado de envio
error, // Erro (se houver)
clearError, // Limpa erro
} = useSendMessage({
sendMessageUrl,
sessionId,
authConfig,
onSuccess,
onError,
});useTheme()
Processa tema e gera estilos CSS.
const {
theme, // Tema processado (merged)
styles // Estilos CSS prontos
} = useTheme(theme?, customStyles?);🎯 Componentes
ChatWidget
Componente principal all-in-one.
<ChatWidget {...props} />Header
Header com título, subtítulo e ícone.
<Header
title="Chat"
subtitle="Online"
icon={<Icon />} // Ou URL, emoji, SVG ou base64
containerStyle={...}
titleStyle={...}
subtitleStyle={...}
iconStyle={...}
/>MessageList
Lista de mensagens com scroll automático.
<MessageList
messages={messages}
isLoading={isLoading}
enableMarkdown={true}
customStyles={...}
/>MessageContent
Renderiza conteúdo com Markdown.
<MessageContent
content="**Olá**!"
enableMarkdown={true}
isUser={false}
customStyles={...}
/>MessageInput
Input com validação e estado.
<MessageInput
onSend={handleSend}
isDisabled={isBlocked}
isSending={isSending}
placeholder="Digite..."
customStyles={...}
/>📚 Exemplos
Exemplo 1: Básico
<ChatWidget
loadMessagesUrl="https://api.exemplo.com/messages"
sendMessageUrl="https://api.exemplo.com/send"
i18n={{
en: {
title: "Support Chat",
subtitle: "We're online!",
footer: "",
getStarted: "Start Chat",
inputPlaceholder: "Type your message...",
},
}}
/>Exemplo 2: Com Autenticação
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
en: {
title: "Support Chat",
subtitle: "We're online!",
footer: "",
getStarted: "Start Chat",
inputPlaceholder: "Type your message...",
},
}}
authHeader={{
headerName: "Authorization",
headerValue: "Bearer seu-token-jwt",
}}
/>Exemplo 3: Session ID Personalizado
Opção A: Session ID Fixo (recomendado quando você já tem o ID)
const userSessionId = "user-123-abc-xyz";
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
en: {
title: "Support Chat",
subtitle: "We're online!",
footer: "",
getStarted: "Start Chat",
inputPlaceholder: "Type your message...",
},
}}
sessionId={userSessionId}
/>;Opção B: Função Geradora (quando precisa gerar dinamicamente)
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
en: {
title: "Support Chat",
subtitle: "We're online!",
footer: "",
getStarted: "Start Chat",
inputPlaceholder: "Type your message...",
},
}}
generateSessionId={() => `user-${userId}-${Date.now()}`}
/>Hierarquia de Prioridade:
sessionId(prop direta) - se fornecido, usa elegenerateSessionId()(função) - se fornecida, chama ela- Auto-gerado (padrão) - gera UUID e persiste no localStorage
Comportamento do loadPreviousSession:
false(padrão): Sempre cria uma nova sessão ao carregar o widgettrue: Mantém a sessão entre reloads usando localStorage- Útil quando
sessionIdfixo não é fornecido
Exemplo 4: Tema Customizado
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
pt: {
title: "Suporte Premium",
subtitle: "Atendimento VIP 24/7",
footer: "",
getStarted: "Iniciar",
inputPlaceholder: "Digite...",
},
}}
theme={{
general: {
fontFamily: "Inter, sans-serif",
},
colors: {
headerBackground: "#6366f1",
messageUserBackground: "#8b5cf6",
},
layout: {
windowWidth: "500px",
borderRadius: "16px",
},
}}
/>Exemplo 5: Ícones Customizados
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
pt: {
title: "Suporte",
subtitle: "Estamos aqui!",
footer: "",
getStarted: "Iniciar",
inputPlaceholder: "Digite...",
},
}}
// React Component
icon={<MyCustomIcon />}
// OU URL de imagem
icon="https://exemplo.com/chat-icon.png"
// OU Emoji
icon="💬"
// OU SVG inline
icon={
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2Z" />
</svg>
}
// OU Base64
icon="data:image/png;base64,iVBORw0KG..."
/>Formatos suportados:
- ✅ ReactNode:
<Component />ou<svg>...</svg> - ✅ URL:
"https://..."ou"//cdn.example.com/icon.png" - ✅ Emoji/Texto:
"💬","Chat", etc. - ✅ Base64:
"data:image/png;base64,..."
Controlando o tamanho e padding do ícone:
<ChatWidget
icon="💬"
theme={{
general: {
iconSize: "32px", // Tamanho padrão: "24px"
iconPadding: "4px", // Padding padrão: "0"
},
}}
/>Exemplo 6: Com Callbacks
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
en: {
title: "Support",
subtitle: "We're here!",
footer: "",
getStarted: "Start",
inputPlaceholder: "Type...",
},
}}
onMessageSent={(msg) => {
console.log("Enviada:", msg);
analytics.track("message_sent");
}}
onMessagesLoaded={(msgs) => {
console.log("Carregadas:", msgs.length);
}}
onError={(error) => {
Sentry.captureException(error);
}}
onBlockStateChange={(blocked) => {
console.log("Bloqueado:", blocked);
}}
/>Exemplo 7: Multi-idioma com Initial Messages
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
pt: {
title: "Chat de Suporte",
subtitle: "Online agora!",
footer: "",
getStarted: "Iniciar",
inputPlaceholder: "Digite sua mensagem...",
initialMessages: [
"Olá! 👋",
"Experimente enviar:\n- **negrito**\n- *itálico*\n- `código`\n- [links](https://exemplo.com)",
],
},
en: {
title: "Support Chat",
subtitle: "Online now!",
footer: "",
getStarted: "Start",
inputPlaceholder: "Type your message...",
initialMessages: [
"Hello! 👋",
"Try sending:\n- **bold**\n- *italic*\n- `code`\n- [links](https://example.com)",
],
},
}}
defaultLanguage="pt"
enableMarkdown={true}
/>Exemplo 8: Dark Mode
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
en: {
title: "Support",
subtitle: "24/7 Available",
footer: "",
getStarted: "Start",
inputPlaceholder: "Type...",
},
}}
theme={{
colors: {
headerBackground: "#1f2937",
headerText: "#f9fafb",
lightBackground: "#111827",
darkBackground: "#030712",
messageUserBackground: "#3b82f6",
messageBotBackground: "#374151",
messageBotText: "#e5e7eb",
inputBackground: "#1f2937",
inputText: "#f9fafb",
inputBorder: "#4b5563",
},
}}
/>Exemplo 9: Welcome Screen + Fullscreen Mode
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
pt: {
title: "Bem-vindo!",
subtitle: "Clique para iniciar o chat",
footer: "",
getStarted: "Começar Conversa",
inputPlaceholder: "Digite...",
initialMessages: ["Olá! Estou aqui para ajudar."],
},
}}
showWelcomeScreen={true}
mode="fullscreen"
/>Exemplo 10: Window Mode com Botão Customizado
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
pt: {
title: "Suporte",
subtitle: "Estamos online",
footer: "",
getStarted: "Iniciar",
inputPlaceholder: "Digite sua mensagem...",
initialMessages: ["Olá! Como posso ajudar?"],
},
}}
mode="window"
theme={{
general: {
// Ícone customizado do botão flutuante
widgetIcon: (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
>
<path d="M12 2L2 7l10 5 10-5-10-5z" />
<path d="M2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
),
},
colors: {
toggleBackground: "#10b981", // Verde
toggleHover: "#059669", // Verde escuro no hover
toggleIconStroke: "#ffffff", // Ícone branco
},
layout: {
toggleSize: "64px", // Botão maior
toggleBorderRadius: "16px", // Quadrado arredondado (use "50%" para círculo)
transitionDuration: "0.3s", // Animações suaves
},
}}
/>Customizações disponíveis para o botão flutuante:
toggleBackground: Cor de fundotoggleHover: Cor no hover (com efeito de escala automático)toggleIconStroke: Cor do ícone SVGtoggleSize: Tamanho do botãotoggleBorderRadius:"50%"para círculo,"12px"para quadrado arredondadowidgetIcon: ReactNode customizado para o ícone
Animações automáticas:
- Hover no botão: Escala 1.1x + sombra aumentada
- Abrir: Widget cresce do botão (scale 0 → 1) com efeito bounce
- Fechar: Widget diminui em direção ao botão
- Transform origin: Canto inferior direito (onde está o botão)
Exemplo 11: Layout Totalmente Customizado
function CustomLayout() {
const { sessionId, resetSession } = useSessionId();
const { messages, isLoading, isBlocked, loadMessages } = useMessages({...});
const { sendMessage, isSending } = useSendMessage({...});
return (
<div className="custom-layout">
<header>
<h1>Suporte</h1>
<button onClick={resetSession}>Nova Conversa</button>
<button onClick={loadMessages}>Recarregar</button>
</header>
<aside>
<p>Session: {sessionId}</p>
<p>Mensagens: {messages.length}</p>
<p>Status: {isBlocked ? '🔒' : '✅'}</p>
</aside>
<main>
<MessageList messages={messages} isLoading={isLoading} enableMarkdown />
</main>
<footer>
<MessageInput
onSend={sendMessage}
isDisabled={isBlocked}
isSending={isSending}
/>
</footer>
</div>
);
}🏗️ Arquitetura
Estrutura de Diretórios
src/
├── components/
│ └── ChatWidget/
│ ├── ChatWidget.tsx # Componente principal
│ ├── MessageList.tsx # Lista de mensagens
│ ├── MessageInput.tsx # Input de envio
│ ├── MessageContent.tsx # Renderização de Markdown
│ ├── Header.tsx # Header com título/subtitle
│ └── types.ts # Tipos dos componentes
│
├── hooks/
│ ├── useSessionId.ts # Gerenciamento de sessão
│ ├── useMessages.ts # Carregamento de mensagens
│ ├── useSendMessage.ts # Envio de mensagens
│ └── useTheme.ts # Processamento de tema
│
├── utils/
│ ├── sessionStorage.ts # Utilitários de sessão
│ └── api.ts # Cliente HTTP
│
├── types/
│ └── index.ts # Tipos globais
│
├── themes/
│ └── defaultTheme.ts # Tema padrão
│
├── embed.tsx # Entry point standalone
└── index.ts # Entry point NPMPrincípios de Design
- Single Responsibility: Cada módulo tem uma única responsabilidade
- Composição: Componentes pequenos e focados que podem ser compostos
- Separação de Responsabilidades: Componentes (UI) + Hooks (lógica) + Utils (helpers)
- Open/Closed: Aberto para extensão, fechado para modificação
- Dependency Injection: Props como injeção de dependências
Fluxo de Dados
ChatWidget (Container)
↓
useSessionId → Cria/recupera sessionID
↓
useMessages → Carrega mensagens + polling
↓
useSendMessage → Envia mensagens
↓
MessageList + MessageInput (Presentational)🧪 TypeScript
Totalmente tipado! Importe os tipos:
import type {
// Componentes
ChatWidgetProps,
ChatWidgetTheme,
ChatWidgetStyles,
// Data
Message,
// API
AuthConfig,
LoadMessagesResponse,
SendMessageResponse,
// Hooks
UseMessagesOptions,
UseSendMessageOptions,
} from "@meerkat-coding/chat-widget";🛠️ Desenvolvimento
Setup
# Instalar dependências
npm install
# Build da biblioteca
npm run build # Build completo (NPM + standalone)
npm run build:lib # Apenas NPM
npm run build:embed # Apenas standalone
# Desenvolvimento
npm run dev # Inicia servidor mock + exemplo
npm run dev:server # Apenas servidor mock
npm run dev:example # Apenas app de exemploEstrutura de Build
dist/index.js- CommonJS (NPM)dist/index.esm.js- ES Modules (NPM)dist/chat-widget.js- IIFE standalone com sourcemapsdist/chat-widget.min.js- IIFE standalone minificado
Ambiente de Exemplo
O projeto inclui um ambiente completo de desenvolvimento em example/:
- App de demonstração (Vite + React)
- Servidor mock (Express) em
http://localhost:3001 - Gerador de Embed Code - Interface visual para gerar código
- Console de eventos - Debugging em tempo real
Acesse: http://localhost:5173
📝 Changelog
v2.0.13 - Bug Fix: URL Icon Sizing
Correções:
- 🐛 Fix: Ícones por URL agora respeitam
iconSize- Removidos width/height 100% que faziam imagem ocupar todo o container - ✅ Ícones de URL agora aplicam corretamente o tamanho e padding definidos no theme
- 📚 Fix aplicado em
iconHelper.tsxpara renderização de imagens
v2.0.12 - Icon Padding Control
Melhorias:
- ✨ Nova propriedade
iconPaddingno tema - Controle o padding dos ícones do header- Adicione
general.iconPaddingao theme (ex:"4px","0.5rem") - Valor padrão:
"0" - Adiciona espaçamento interno ao redor do ícone
- Adicione
- 📚 Documentação atualizada com exemplo de uso
v2.0.11 - Icon Size & Padding Control
Melhorias:
- ✨ Nova propriedade
iconSizeno tema - Controle o tamanho dos ícones do header via tema- Adicione
general.iconSizeao theme (ex:"32px","1.5em") - Valor padrão:
"24px" - Aplica width, height e fontSize automaticamente
- Adicione
- ✨ Nova propriedade
iconPaddingno tema - Controle o padding dos ícones do header- Adicione
general.iconPaddingao theme (ex:"4px","0.5rem") - Valor padrão:
"0" - Adiciona espaçamento interno ao redor do ícone
- Adicione
- 📚 Documentação atualizada com exemplos de uso
v2.0.10 - Enhanced Icon Support
Novas Features:
- ✨ Suporte expandido para ícones - Agora aceita múltiplos formatos além de ReactNode
- React Component:
<MyIcon />ou<svg>...</svg> - URL de imagem:
"https://exemplo.com/icon.png" - Emoji/Texto:
"💬","Chat", etc. - Base64:
"data:image/png;base64,..."
- React Component:
- ✨ Detecção automática de formato - Identifica automaticamente o tipo de ícone
- ✨ Ícone padrão - WidgetToggle exibe chat bubble SVG quando ícone não é fornecido
Melhorias:
- 🎯 Nova utility
renderIcon()para renderização inteligente de ícones - 📚 Documentação expandida com exemplos de todos os formatos suportados
- 🔄 Totalmente backward compatible - código existente continua funcionando
v2.0.9 - Critical Fix: Next.js 15 Infinite Loop Resolution
Correções:
- 🐛 CRITICAL: Solução definitiva para infinite loop no Next.js 15 - Eliminados TODOS os casos de "Maximum update depth exceeded"
- useI18n estabilizado: JSON.stringify para comparação profunda do objeto i18n, evitando recriação quando passado inline
- useMessages otimizado: Removido
isBlockeddas dependências deloadMessages, usando functional setState para evitar leitura de estado - Polling sem dependências circulares:
loadMessagesRefusado no polling para quebrar ciclo de dependências - initialMessages estável: JSON.stringify no ChatWidget para evitar re-renders quando array é equivalente
- Exemplo Next.js aprimorado: Constantes
i18nethememovidas para fora do componente, todos os callbacks memoizados comuseCallback
- ⚡ Performance: Prevenção de re-renders desnecessários em aplicações Next.js
Padrões defensivos aplicados:
- Refs para callbacks que atualizam dependências
- Functional setState para evitar dependências circulares
- JSON.stringify para comparação profunda de objetos complexos
- Constantes externas para configurações estáticas
v2.0.8 - Critical Fix: Complete Re-render Loop Solution
Correções:
- 🐛 CRITICAL: Solução completa para loop infinito - Resolvidos TODOS os problemas de "Maximum update depth exceeded" no Next.js
- authConfig estabilizado: Usa refs em ambos
useMessageseuseSendMessagepara evitar recriação quando passado inline - Polling otimizado: Removidas dependências circulares
startPolling/stopPollingdo useEffect - onSuccess memoizado: Callback estável no ChatWidget evita recriações desnecessárias
- theme e customStyles: Comparação via JSON.stringify para evitar re-processamento quando objetos são equivalentes
- authConfig estabilizado: Usa refs em ambos
- ⚡ Performance: Drasticamente reduzido número de re-renders, especialmente em Next.js
v2.0.7 - Critical Bug Fix
Correções:
- 🐛 CRITICAL: Fix infinite re-render loop - Estabilizados callbacks em
useMessageseuseSendMessageusando refs para evitar "Maximum update depth exceeded" no Next.js e outros frameworks React- Callbacks
onMessagesLoaded,onError,onBlockStateChange,onMessageSent,onSuccessagora são gerenciados via refs - Removidas dependências problemáticas dos
useCallbacks internos
- Callbacks
v2.0.6 - Bug Fix
Correções:
- 🐛 Fix: Estilos inline inválidos - Removidos seletores CSS (
& > *:first-child,& > *:last-child) que causavam erros no Next.js e outros frameworks React
v2.0.5 - Window Mode & UI Enhancements
Novas Features:
- 🪟 Window Mode - Botão flutuante minimizado que expande o chat ao clicar
- Animações suaves de scale com easing customizado
- Ícone customizável via tema (
widgetIcon) - Totalmente responsivo com transições CSS
- 🎨 Botão de Enviar Redesenhado - Ícone de enviar (paper plane) ao invés de texto
- Formato circular mais moderno
- Customizável via tema (
buttonBackground,buttonText)
- 🕒 Controle de Timestamps - Nova prop
showTimestampspara mostrar/ocultar horários das mensagens - ✨ Botão de Fechar - Header com botão X para fechar o chat (mode: window)
Melhorias:
- 🎯 Centralização perfeita de ícones em botões circulares
- 🎨 Tema expandido com mais opções de customização para botões
- 📐 Layout mais limpo e profissional
v2.0.1 - Melhorias
Novas Features:
- ✨ Prop
sessionId- Passe session ID fixo diretamente como prop - ✨ Plugin
remark-breaks- Quebras de linha com\nsimples agora funcionam
Melhorias:
- 📚 Documentação expandida com exemplos de session ID
- 🎯 Hierarquia clara de prioridade: sessionId > generateSessionId > auto-gerado
v2.0.0 - Arquitetura Modular
Novas Features:
- ✨ Sistema de tema completo (50+ propriedades)
- ✨ Suporte a Markdown (react-markdown + GFM)
- ✨ Embed code standalone
- ✨ Polling adaptativo inteligente
- ✨ Novos componentes: Header, MessageContent
- ✨ Novo hook: useTheme
- ✨ Props: subtitle, initialMessage, icon, theme, enableMarkdown
Melhorias:
- ⚡ Desbloqueio imediato (500ms ao invés de 3s)
- ⚡ Performance otimizada
- 🎨 Sistema de estilos mais flexível
- 🐛 Fix: Scroll contido no container
- 🐛 Fix: isMountedRef causando mensagens não aparecerem
- 🐛 Fix: Bloqueio/desbloqueio imediato
Breaking Changes:
- Estrutura de estilos expandida (backward compatible)
📄 Licença
MIT
🤝 Contribuindo
Contribuições são bem-vindas! Por favor:
- Fork o projeto
- Crie uma branch para sua feature (
git checkout -b feature/MinhaFeature) - Commit suas mudanças (
git commit -m 'Adiciona MinhaFeature') - Push para a branch (
git push origin feature/MinhaFeature) - Abra um Pull Request
Desenvolvido com ❤️
