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

@bernierllc/messaging-ui

v0.1.1

Published

React components for messaging interfaces with real-time chat features

Downloads

223

Readme

@bernierllc/messaging-ui

React components for messaging interfaces with real-time chat features.

Installation

npm install @bernierllc/messaging-ui

Usage

Basic Chat Interface

import React, { useState } from 'react';
import { 
  ChatInterface, 
  User, 
  ChatRoom, 
  Message, 
  MessageData 
} from '@bernierllc/messaging-ui';

const MyChatApp: React.FC = () => {
  const [messages, setMessages] = useState<Message[]>([]);
  const [rooms, setRooms] = useState<ChatRoom[]>([]);
  const [users, setUsers] = useState<User[]>([]);

  const currentUser: User = {
    id: 'user-1',
    name: 'John Doe',
    status: 'online',
    metadata: {}
  };

  const handleSendMessage = (messageData: MessageData) => {
    const newMessage: Message = {
      id: `msg-${Date.now()}`,
      content: messageData.content,
      type: messageData.type,
      sender: currentUser,
      roomId: messageData.roomId,
      timestamp: new Date(),
      reactions: [],
      status: 'sending',
      metadata: {}
    };
    
    setMessages(prev => [...prev, newMessage]);
    
    // Simulate message being sent
    setTimeout(() => {
      setMessages(prev => 
        prev.map(msg => 
          msg.id === newMessage.id 
            ? { ...msg, status: 'delivered' }
            : msg
        )
      );
    }, 1000);
  };

  const handleJoinRoom = (roomId: string) => {
    console.log(`Joined room: ${roomId}`);
  };

  const handleLeaveRoom = (roomId: string) => {
    console.log(`Left room: ${roomId}`);
  };

  const handleUserSelect = (user: User) => {
    console.log(`Selected user: ${user.name}`);
  };

  return (
    <div style={{ height: '100vh', width: '100%' }}>
      <ChatInterface
        currentUser={currentUser}
        rooms={rooms}
        messages={messages}
        users={users}
        onSendMessage={handleSendMessage}
        onJoinRoom={handleJoinRoom}
        onLeaveRoom={handleLeaveRoom}
        onUserSelect={handleUserSelect}
      />
    </div>
  );
};

export default MyChatApp;

Individual Components

MessageList

import { MessageList } from '@bernierllc/messaging-ui';

const MyMessageList: React.FC = () => {
  const handleMessageReact = (messageId: string, reaction: string) => {
    console.log(`React to ${messageId} with ${reaction}`);
  };

  const handleMessageReply = (message: Message) => {
    console.log('Reply to message:', message);
  };

  const handleMessageEdit = (messageId: string, content: string) => {
    console.log(`Edit message ${messageId}: ${content}`);
  };

  const handleMessageDelete = (messageId: string) => {
    console.log(`Delete message ${messageId}`);
  };

  return (
    <MessageList
      messages={messages}
      currentUser={currentUser}
      onMessageReact={handleMessageReact}
      onMessageReply={handleMessageReply}
      onMessageEdit={handleMessageEdit}
      onMessageDelete={handleMessageDelete}
      hasMore={true}
      onLoadMore={() => console.log('Load more messages')}
    />
  );
};

MessageInput

import { MessageInput } from '@bernierllc/messaging-ui';

const MyMessageInput: React.FC = () => {
  const handleSendMessage = (content: string, attachments?: File[]) => {
    console.log('Send message:', { content, attachments });
  };

  const handleTyping = (isTyping: boolean) => {
    console.log('Typing status:', isTyping);
  };

  return (
    <MessageInput
      onSendMessage={handleSendMessage}
      onTyping={handleTyping}
      placeholder="Type a message..."
      allowAttachments={true}
      allowEmojis={true}
      maxLength={1000}
    />
  );
};

UserList

import { UserList } from '@bernierllc/messaging-ui';

const MyUserList: React.FC = () => {
  const handleUserSelect = (user: User) => {
    console.log('Selected user:', user);
  };

  return (
    <UserList
      users={users}
      currentUser={currentUser}
      onUserSelect={handleUserSelect}
    />
  );
};

API Reference

Core Types

User

interface User {
  id: string;
  name: string;
  avatar?: string;
  status: 'online' | 'offline' | 'away' | 'busy';
  lastSeen?: Date;
  isTyping?: boolean;
  metadata: Record<string, unknown>;
}

Message

interface Message {
  id: string;
  content: string;
  type: 'text' | 'image' | 'file' | 'system';
  sender: User;
  roomId: string;
  timestamp: Date;
  editedAt?: Date;
  reactions: MessageReaction[];
  attachments?: MessageAttachment[];
  replyTo?: Message;
  status: 'sending' | 'sent' | 'delivered' | 'read' | 'error';
  metadata: Record<string, unknown>;
}

ChatRoom

interface ChatRoom {
  id: string;
  name: string;
  type: 'direct' | 'group' | 'channel';
  participants: User[];
  lastMessage?: Message;
  unreadCount: number;
  isActive: boolean;
  createdAt: Date;
  updatedAt: Date;
  metadata: Record<string, unknown>;
}

Components

ChatInterface

Complete chat interface with sidebar and message area.

Props:

  • currentUser: User - The current logged-in user
  • rooms: ChatRoom[] - Array of available chat rooms
  • messages: Message[] - Array of messages to display
  • users: User[] - Array of all users
  • onSendMessage: (message: MessageData) => void - Handle message sending
  • onJoinRoom: (roomId: string) => void - Handle room joining
  • onLeaveRoom: (roomId: string) => void - Handle room leaving
  • onUserSelect: (user: User) => void - Handle user selection
  • loading?: boolean - Show loading state
  • error?: string - Show error state
  • className?: string - Additional CSS classes

MessageList

Scrollable list of messages with actions.

Props:

  • messages: Message[] - Messages to display
  • currentUser: User - Current user for message ownership
  • onMessageReact: (messageId: string, reaction: string) => void - Handle reactions
  • onMessageReply: (message: Message) => void - Handle replies
  • onMessageEdit: (messageId: string, content: string) => void - Handle editing
  • onMessageDelete: (messageId: string) => void - Handle deletion
  • loading?: boolean - Show loading indicator
  • hasMore?: boolean - Whether more messages can be loaded
  • onLoadMore?: () => void - Load more messages
  • className?: string - Additional CSS classes

MessageInput

Input field for typing and sending messages.

Props:

  • onSendMessage: (content: string, attachments?: File[]) => void - Handle message sending
  • onTyping: (isTyping: boolean) => void - Handle typing indicator
  • placeholder?: string - Input placeholder text
  • disabled?: boolean - Disable input
  • maxLength?: number - Maximum message length (default: 1000)
  • allowAttachments?: boolean - Enable file attachments (default: true)
  • allowEmojis?: boolean - Enable emoji picker (default: true)
  • className?: string - Additional CSS classes

MessageBubble

Individual message display with reactions and actions.

Props:

  • message: Message - Message to display
  • currentUser: User - Current user for ownership checks
  • showTimestamp?: boolean - Show message timestamp (default: true)
  • showStatus?: boolean - Show message status (default: true)
  • onReact: (reaction: string) => void - Handle reactions
  • onReply: () => void - Handle reply action
  • onEdit: (content: string) => void - Handle edit action
  • onDelete: () => void - Handle delete action
  • className?: string - Additional CSS classes

UserList

List of users with status indicators.

Props:

  • users: User[] - Users to display
  • currentUser: User - Current user (excluded from list)
  • onUserSelect: (user: User) => void - Handle user selection
  • className?: string - Additional CSS classes

RoomList

List of chat rooms with unread indicators.

Props:

  • rooms: ChatRoom[] - Rooms to display
  • currentUser: User - Current user
  • selectedRoom?: ChatRoom | null - Currently selected room
  • onRoomSelect: (room: ChatRoom) => void - Handle room selection
  • onRoomLeave: (roomId: string) => void - Handle room leaving
  • className?: string - Additional CSS classes

Supporting Components

  • UserAvatar - User avatar with status indicator
  • MessageStatus - Message delivery status indicator
  • MessageTimestamp - Formatted message timestamp
  • UnreadBadge - Unread message count badge
  • TypingIndicator - Shows when users are typing
  • MessageReactions - Display and interact with message reactions
  • FileUpload - File attachment upload button
  • EmojiPicker - Emoji selection interface
  • SearchBar - Search input with clear functionality

Features

Real-time Messaging

  • Live message updates
  • Typing indicators
  • User presence status
  • Message delivery status

Rich Message Features

  • Text messages with mentions and URLs
  • File attachments (images, videos, audio, documents)
  • Message reactions with emoji
  • Reply to messages
  • Edit and delete messages
  • Message timestamps

User Interface

  • Responsive design (mobile-friendly)
  • Dark/light theme support
  • Keyboard shortcuts
  • Accessibility support
  • Virtual scrolling for performance

Chat Management

  • Multiple room types (direct, group, channel)
  • Room creation and management
  • User lists with online status
  • Search functionality
  • Message history with pagination

Styling

The components use CSS classes that can be styled with your preferred styling solution:

/* Main chat interface */
.chat-interface {
  display: flex;
  height: 100%;
}

.chat-sidebar {
  width: 250px;
  background: #f5f5f5;
}

.chat-main {
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* Message components */
.message-bubble {
  padding: 8px 12px;
  margin: 4px 0;
  border-radius: 12px;
}

.message-bubble--own {
  background: #007bff;
  color: white;
  margin-left: auto;
}

.message-bubble--other {
  background: #e9ecef;
  color: #333;
}

/* Input components */
.message-input {
  padding: 16px;
  border-top: 1px solid #dee2e6;
}

.message-textarea {
  width: 100%;
  border: none;
  outline: none;
  resize: none;
}

/* User components */
.user-avatar {
  position: relative;
  display: inline-block;
}

.user-status {
  position: absolute;
  bottom: 0;
  right: 0;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid white;
}

Integration Examples

With Socket.IO

import io from 'socket.io-client';

const socket = io('ws://localhost:3001');

const ChatApp: React.FC = () => {
  // ... state management

  useEffect(() => {
    socket.on('message', (message: Message) => {
      setMessages(prev => [...prev, message]);
    });

    socket.on('typing', (data: { userId: string; isTyping: boolean }) => {
      // Update typing state
    });

    return () => {
      socket.off('message');
      socket.off('typing');
    };
  }, []);

  const handleSendMessage = (messageData: MessageData) => {
    socket.emit('sendMessage', messageData);
  };

  const handleTyping = (isTyping: boolean) => {
    socket.emit('typing', { isTyping });
  };

  // ... render ChatInterface
};

With Redux/Context

import { useContext } from 'react';
import { ChatContext } from './ChatContext';

const ChatApp: React.FC = () => {
  const {
    currentUser,
    rooms,
    messages,
    users,
    sendMessage,
    joinRoom,
    leaveRoom,
    selectUser
  } = useContext(ChatContext);

  return (
    <ChatInterface
      currentUser={currentUser}
      rooms={rooms}
      messages={messages}
      users={users}
      onSendMessage={sendMessage}
      onJoinRoom={joinRoom}
      onLeaveRoom={leaveRoom}
      onUserSelect={selectUser}
    />
  );
};

Accessibility

The components include ARIA labels, keyboard navigation, and screen reader support:

  • Keyboard navigation with Tab, Enter, and Escape
  • ARIA labels for interactive elements
  • Screen reader announcements for new messages
  • High contrast mode support
  • Focus management

Performance

  • Virtual scrolling for large message lists
  • Debounced typing indicators
  • Optimized re-renders with React.memo
  • Efficient message grouping algorithms
  • Lazy loading of message history

Browser Support

  • Modern browsers (Chrome, Firefox, Safari, Edge)
  • ES2020+ JavaScript features
  • React 18+ required
  • TypeScript support included

Contributing

This package is part of the BernierLLC tools ecosystem. For development guidelines, see the main repository documentation.

License

Copyright (c) 2025 Bernier LLC. All rights reserved.

This file is licensed to the client under a limited-use license. The client may use and modify this code only within the scope of the project it was delivered for. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.