npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@meerkat-coding/chat-widget

v2.0.18

Published

Biblioteca modular de widgets de chat para React com arquitetura escalável

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.

npm version License: MIT

✨ 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-widget

Via 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.3 para versão específica ou @latest para 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 renderizados

Responsividade

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 mantido

Customizaçã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-breaks ativado - \n simples quebra linha automaticamente

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 corretamente

Resultado 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.js

Tamanho 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/json

Body:

{
  "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:

  1. Quando o usuário envia uma mensagem, o widget gera um ID único: msg-{sessionId}-{timestamp}
  2. Este ID é enviado no campo idempotencyKey do payload POST /api/send
  3. O backend deve usar este idempotencyKey como id da mensagem
  4. 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:

  1. sessionId (prop direta) - se fornecido, usa ele
  2. generateSessionId() (função) - se fornecida, chama ela
  3. 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 widget
  • true: Mantém a sessão entre reloads usando localStorage
  • Útil quando sessionId fixo 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 fundo
  • toggleHover: Cor no hover (com efeito de escala automático)
  • toggleIconStroke: Cor do ícone SVG
  • toggleSize: Tamanho do botão
  • toggleBorderRadius: "50%" para círculo, "12px" para quadrado arredondado
  • widgetIcon: 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 NPM

Princípios de Design

  1. Single Responsibility: Cada módulo tem uma única responsabilidade
  2. Composição: Componentes pequenos e focados que podem ser compostos
  3. Separação de Responsabilidades: Componentes (UI) + Hooks (lógica) + Utils (helpers)
  4. Open/Closed: Aberto para extensão, fechado para modificação
  5. 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 exemplo

Estrutura de Build

  • dist/index.js - CommonJS (NPM)
  • dist/index.esm.js - ES Modules (NPM)
  • dist/chat-widget.js - IIFE standalone com sourcemaps
  • dist/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.tsx para renderização de imagens

v2.0.12 - Icon Padding Control

Melhorias:

  • Nova propriedade iconPadding no tema - Controle o padding dos ícones do header
    • Adicione general.iconPadding ao theme (ex: "4px", "0.5rem")
    • Valor padrão: "0"
    • Adiciona espaçamento interno ao redor do ícone
  • 📚 Documentação atualizada com exemplo de uso

v2.0.11 - Icon Size & Padding Control

Melhorias:

  • Nova propriedade iconSize no tema - Controle o tamanho dos ícones do header via tema
    • Adicione general.iconSize ao theme (ex: "32px", "1.5em")
    • Valor padrão: "24px"
    • Aplica width, height e fontSize automaticamente
  • Nova propriedade iconPadding no tema - Controle o padding dos ícones do header
    • Adicione general.iconPadding ao theme (ex: "4px", "0.5rem")
    • Valor padrão: "0"
    • Adiciona espaçamento interno ao redor do ícone
  • 📚 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,..."
  • 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 isBlocked das dependências de loadMessages, usando functional setState para evitar leitura de estado
    • Polling sem dependências circulares: loadMessagesRef usado 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 i18n e theme movidas para fora do componente, todos os callbacks memoizados com useCallback
  • 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 useMessages e useSendMessage para evitar recriação quando passado inline
    • Polling otimizado: Removidas dependências circulares startPolling/stopPolling do 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
  • 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 useMessages e useSendMessage usando refs para evitar "Maximum update depth exceeded" no Next.js e outros frameworks React
    • Callbacks onMessagesLoaded, onError, onBlockStateChange, onMessageSent, onSuccess agora são gerenciados via refs
    • Removidas dependências problemáticas dos useCallbacks internos

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 showTimestamps para 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 \n simples 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:

  1. Fork o projeto
  2. Crie uma branch para sua feature (git checkout -b feature/MinhaFeature)
  3. Commit suas mudanças (git commit -m 'Adiciona MinhaFeature')
  4. Push para a branch (git push origin feature/MinhaFeature)
  5. Abra um Pull Request

Desenvolvido com ❤️