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

@xcelsior/ui-chat

v1.0.7

Published

A comprehensive, real-time chat widget component for React applications with WebSocket support, emoji picker, file uploads, and more.

Downloads

584

Readme

@xcelsior/ui-chat

A comprehensive, real-time chat widget component for React applications with WebSocket support, emoji picker, file uploads, and more.

Features

  • 🔄 Real-time Communication: WebSocket-based instant messaging
  • 💬 Rich Messages: Support for text, markdown, images, and files
  • 🤖 AI Assistant: Visual differentiation for AI-generated messages with robot icon
  • 😊 Emoji Support: Built-in emoji picker
  • 📎 File Uploads: Upload and share files with progress tracking
  • ⌨️ Typing Indicators: Show when users are typing
  • ✓✓ Read Receipts: Track message delivery and read status
  • 🎨 Customizable: Fully configurable appearance and behavior
  • 📱 Responsive: Works great on all screen sizes
  • 🌓 Dark Mode: Automatic dark mode support
  • Accessible: Built with accessibility in mind

Installation

pnpm add @xcelsior/ui-chat @xcelsior/design-system @xcelsior/ui-fields

Peer Dependencies

Make sure you have the following peer dependencies installed:

pnpm add react react-dom @tanstack/react-query react-hook-form flowbite-react

Usage

Basic Example

import { ChatWidget } from '@xcelsior/ui-chat';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

function App() {
    const chatConfig = {
        websocketUrl: 'wss://your-api.com/chat',
        userId: 'user-123',
        userType: 'customer',
        conversationId: 'conv-456',
        currentUser: {
            id: 'user-123',
            name: 'John Doe',
            email: '[email protected]',
            type: 'customer',
        },
    };

    return (
        <QueryClientProvider client={queryClient}>
            <ChatWidget config={chatConfig} />
        </QueryClientProvider>
    );
}

Chat Widget States (Bubble Mode)

The chat widget supports three states in popover mode:

  • open: Fully open with chat interface visible
  • minimized: Show bubble button only (default)
  • closed: Fully hidden, no bubble visible

Uncontrolled Mode (Default)

By default, the widget starts in minimized state and manages its own state:

import { ChatWidget } from '@xcelsior/ui-chat';

function App() {
    return (
        <ChatWidget 
            config={chatConfig}
            // Starts minimized by default
        />
    );
}

You can also set a different default state:

<ChatWidget 
    config={chatConfig}
    defaultState="open" // or "minimized" or "closed"
/>

Controlled Mode

Control the widget state externally:

import { ChatWidget, ChatWidgetState } from '@xcelsior/ui-chat';
import { useState } from 'react';

function App() {
    const [chatState, setChatState] = useState<ChatWidgetState>('minimized');

    return (
        <>
            <button onClick={() => setChatState('open')}>
                Open Chat
            </button>
            <button onClick={() => setChatState('minimized')}>
                Minimize Chat
            </button>
            <button onClick={() => setChatState('closed')}>
                Close Chat
            </button>
            
            <ChatWidget 
                config={chatConfig}
                state={chatState}
                onStateChange={setChatState}
            />
        </>
    );
}

Listen to State Changes (Semi-controlled)

You can listen to state changes while keeping the widget uncontrolled:

<ChatWidget 
    config={chatConfig}
    defaultState="minimized"
    onStateChange={(state) => {
        console.log('Chat state changed to:', state);
        // Save to localStorage, analytics, etc.
    }}
/>

With File Upload

The file upload feature uses a presigned URL approach for secure, direct-to-S3 uploads:

import { ChatWidget } from '@xcelsior/ui-chat';

const chatConfig = {
    // ... other config
    fileUpload: {
        uploadUrl: 'https://your-api.com/attachments/upload-url',
        maxFileSize: 10 * 1024 * 1024, // 10MB (default)
        allowedTypes: [
            'image/jpeg',
            'image/png',
            'image/gif',
            'image/webp',
            'application/pdf',
            'application/msword',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'text/plain',
            'text/csv',
        ],
        headers: {
            'Authorization': 'Bearer your-token',
        },
    },
};

<ChatWidget config={chatConfig} />

How it works:

  1. User selects a file
  2. Widget requests a presigned upload URL from your API (POST /attachments/upload-url)
  3. Widget uploads file directly to S3 using the presigned URL
  4. File becomes accessible via CloudFront CDN
  5. The CloudFront URL is inserted into the message as markdown

Backend API Contract:

// Request
POST /attachments/upload-url
{
  "fileName": "document.pdf",
  "contentType": "application/pdf",
  "fileSize": 1234567
}

// Response
{
  "data": {
    "uploadUrl": "https://s3.amazonaws.com/...",      // Presigned URL for upload
    "attachmentUrl": "https://cdn.example.com/...",   // Final public URL
    "key": "attachments/uuid.pdf",                     // S3 key
    "expiresIn": 300                                   // URL expiry (seconds)
  }
}

With Event Callbacks

import { ChatWidget } from '@xcelsior/ui-chat';

const chatConfig = {
    // ... other config
    onMessageSent: (message) => {
        console.log('Message sent:', message);
    },
    onMessageReceived: (message) => {
        console.log('Message received:', message);
    },
    onConnectionChange: (connected) => {
        console.log('Connection status:', connected);
    },
    onError: (error) => {
        console.error('Chat error:', error);
    },
    toast: {
        success: (message) => console.log('Success:', message),
        error: (message) => console.error('Error:', message),
        info: (message) => console.log('Info:', message),
    },
};

<ChatWidget config={chatConfig} />

Configuration

IChatConfig

The main configuration object for the chat widget.

| Property | Type | Required | Description | |----------|------|----------|-------------| | websocketUrl | string | ✓ | WebSocket server URL | | userId | string | ✓ | Current user's ID | | userType | 'customer' \| 'agent' | ✓ | Type of user | | currentUser | IUser | ✓ | Current user information | | conversationId | string | | Conversation ID (auto-generated if not provided) | | httpApiUrl | string | | REST API URL for fetching data | | headers | Record<string, string> | | HTTP headers for API requests | | fileUpload | IFileUploadConfig | | File upload configuration | | enableEmoji | boolean | | Enable emoji picker (default: true) | | enableFileUpload | boolean | | Enable file uploads (default: true) | | enableTypingIndicator | boolean | | Enable typing indicators (default: true) | | enableReadReceipts | boolean | | Enable read receipts (default: true) | | onMessageSent | (message: IMessage) => void | | Callback when message is sent | | onMessageReceived | (message: IMessage) => void | | Callback when message is received | | onConnectionChange | (connected: boolean) => void | | Callback when connection status changes | | onError | (error: Error) => void | | Callback when error occurs | | toast | object | | Toast notification handlers |

IFileUploadConfig

Configuration for file uploads.

| Property | Type | Required | Description | |----------|------|----------|-------------| | uploadUrl | string | ✓ | Upload endpoint URL | | maxFileSize | number | | Maximum file size in bytes (default: 5MB) | | allowedTypes | string[] | | Allowed MIME types (default: images and PDFs) | | headers | Record<string, string> | | Additional headers for upload request |

WebSocket Integration

The chat widget connects to your WebSocket API using the following protocol:

Connection

wss://your-api.com/chat?userId=user-123&userType=customer&conversationId=conv-456

Sending Messages

{
  "action": "sendMessage",
  "data": {
    "conversationId": "conv-456",
    "content": "Hello!",
    "messageType": "text"
  }
}

Receiving Messages

{
  "type": "message",
  "data": {
    "id": "msg-789",
    "conversationId": "conv-456",
    "senderId": "agent-123",
    "senderType": "agent",
    "content": "Hi! How can I help?",
    "messageType": "text",
    "createdAt": "2025-10-28T12:00:00Z",
    "status": "delivered"
  }
}

Typing Indicator

{
  "action": "typing",
  "data": {
    "conversationId": "conv-456",
    "isTyping": true
  }
}

REST API Integration

The chat widget can fetch existing messages from your REST API when httpApiUrl is provided in the config.

Fetching Messages

When the widget loads or the conversationId changes, it will automatically fetch existing messages:

GET /messages?conversationId=conv-456&limit=50

Expected response format:

{
  "success": true,
  "data": {
    "items": [...messages],
    "nextPageToken": "optional-pagination-token"
  }
}

or alternatively:

{
  "success": true,
  "data": [...messages],
  "nextToken": "optional-pagination-token"
}

Example with HTTP API

const chatConfig = {
    websocketUrl: 'wss://your-api.com/chat',
    httpApiUrl: 'https://your-api.com',
    conversationId: 'conv-456',
    headers: {
        'Authorization': 'Bearer your-token',
    },
    // ... other config
};

<ChatWidget config={chatConfig} />

Performance Optimizations

Debounced Typing Indicator

The typing indicator is now debounced to reduce unnecessary WebSocket messages:

  • Start typing: Sent after 300ms of typing activity
  • Stop typing: Sent after 1.5s of inactivity
  • This reduces WebSocket traffic while maintaining responsive user feedback

Components

ChatWidget

The main chat widget component that includes all features.

import { ChatWidget } from '@xcelsior/ui-chat';

<ChatWidget config={chatConfig} className="custom-class" />

Individual Components

For custom implementations, you can use individual components:

MessageList

import { MessageList } from '@xcelsior/ui-chat';

<MessageList
    messages={messages}
    currentUser={currentUser}
    isTyping={false}
    autoScroll={true}
/>

MessageItem

Displays individual messages with automatic icon detection:

  • 🤖 Robot icon for AI-generated messages (when metadata.isAI === true)
  • 🎧 Headset icon for human agent messages
  • 👤 User icon for customer messages
import { MessageItem } from '@xcelsior/ui-chat';

// Regular agent message
<MessageItem
    message={message}
    currentUser={currentUser}
    showAvatar={true}
    showTimestamp={true}
/>

// AI-generated message (automatically shows robot icon)
<MessageItem
    message={{
        ...message,
        senderType: 'agent',
        metadata: { isAI: true }
    }}
    currentUser={currentUser}
    showAvatar={true}
    showTimestamp={true}
/>

ChatInput

import { ChatInput } from '@xcelsior/ui-chat';

<ChatInput
    onSend={handleSend}
    onTyping={handleTyping}
    config={chatConfig}
    fileUpload={fileUploadHook}
/>

ChatHeader

import { ChatHeader } from '@xcelsior/ui-chat';

<ChatHeader
    agent={agent}
    onClose={handleClose}
    onMinimize={handleMinimize}
/>

Hooks

useWebSocket

Custom hook for WebSocket connection management.

import { useWebSocket } from '@xcelsior/ui-chat';

const { isConnected, sendMessage, lastMessage, error, reconnect } = useWebSocket(config);

useMessages

Custom hook for message management.

import { useMessages } from '@xcelsior/ui-chat';

const { messages, addMessage, updateMessageStatus, clearMessages } = useMessages(websocket, config);

useFileUpload

Custom hook for file uploads.

import { useFileUpload } from '@xcelsior/ui-chat';

const { uploadFile, isUploading, uploadProgress, error, canUpload } = useFileUpload(config);

useTypingIndicator

Custom hook for typing indicators.

import { useTypingIndicator } from '@xcelsior/ui-chat';

const { isTyping, typingUsers } = useTypingIndicator(websocket);

Styling

The component uses Tailwind CSS for styling and supports dark mode automatically. You can customize the appearance by:

  1. Using the className prop: Pass custom classes to the ChatWidget component
  2. Overriding CSS variables: Define custom CSS variables for colors and sizes
  3. Using Tailwind configuration: Extend your Tailwind config to customize the design system

Backend Integration

This package is designed to work with the @xcelsior/chat-api backend service. For more information on setting up the backend, see the chat-api documentation.

Required Backend Endpoints

  • WebSocket: For real-time communication

    • $connect: Handle new connections
    • $disconnect: Handle disconnections
    • sendMessage: Handle message sending
    • typing: Handle typing indicators
  • REST API (optional): For fetching historical data

    • GET /messages?conversationId=xxx: Fetch message history
    • GET /conversations/:id: Fetch conversation details
  • File Upload API (optional): For file attachment support

    • POST /attachments/upload-url: Generate presigned S3 upload URL
      • Request: { fileName, contentType, fileSize }
      • Response: { uploadUrl, attachmentUrl, key, expiresIn }

Examples

Check out the Storybook for live examples and interactive demos:

pnpm storybook

Development

Build

pnpm build

Development Mode

pnpm dev

Storybook

pnpm storybook

Lint

pnpm lint

TypeScript

This package is written in TypeScript and includes full type definitions. All types are exported from the main package:

import type {
    IChatConfig,
    IMessage,
    IUser,
    IConversation,
    MessageType,
    MessageStatus,
} from '@xcelsior/ui-chat';

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

WebSocket support is required.

License

MIT

Support

For issues and questions, please contact the development team or create an issue in the repository.