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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@banbox/chat

v1.0.20

Published

Banbox Chat UI components — reusable across all Banbox React/Next.js projects

Readme

@banbox/chat

Current version: 1.0.16

Banbox Chat UI package — plug-and-play chat popup for any React or Next.js project.
Data-agnostic: bring your own adapter (demo, REST API, or WebSocket).


Installation

npm install @banbox/chat

CSS is auto-injected — no manual import needed.


Requirements

| Peer dep | Version | |---|---| | react | ≥ 18 | | react-dom | ≥ 18 | | framer-motion | ≥ 10 | | lottie-react | ≥ 2 |


Quick Start (Vite / React)

1. Create your adapter

// src/components/chat/demoData.ts
import type { ChatAdapter } from "@banbox/chat";

export const createDemoChatAdapter = (): ChatAdapter => ({
  threads: {
    list: () => cache.threads,
    subscribe: (cb) => {
      socket.on("threads:update", cb);
      return () => socket.off("threads:update", cb);
    },
    pin:      (id, pinned) => api.patch(`/threads/${id}`, { pinned }),
    delete:   (id)         => api.delete(`/threads/${id}`),
    markRead: (id)         => api.post(`/threads/${id}/read`),
  },
  messages: {
    list: (tid)       => cache.messages[tid] ?? [],
    subscribe: (tid, cb) => {
      socket.on(`messages:${tid}`, cb);
      return () => socket.off(`messages:${tid}`, cb);
    },
    send: (tid, payload) => api.post(`/threads/${tid}/messages`, payload),
  },
});

2. Mount ChatRoot once at the app root

// src/main.tsx
import { ChatUIProvider, ChatRoot } from "@banbox/chat";
import { createDemoChatAdapter } from "./components/chat/demoData";
import { showToast } from "./utils/toast";

// Create adapter once — outside render
const adapter = createDemoChatAdapter();

createRoot(document.getElementById("root")!).render(
  <ChatUIProvider>
    <App />
    <ChatRoot
      adapter={adapter}
      theme="marketplace"

      // ── Footer toolbar: allow-list per popup ──────────────────────────
      // Default (when omitted): ["attachment", "emoji", "translate"]
      // Available keys: "attachment" | "emoji" | "businessCard" | "addressCard" | "translate"
      inboxFooterActions={["attachment", "emoji", "translate"]}
      singleFooterActions={["attachment", "emoji", "translate"]}
      // ─────────────────────────────────────────────────────────────────

      uiCallbacks={{
        showToast,
        onNavigate: ({ type, id }) => navigate(`/${type}s/${id}`),
      }}
    />
  </ChatUIProvider>
);

3. Open the chat from anywhere in the app

import { useChatUI } from "@banbox/chat";

function OrderPage({ orderId }: { orderId: string }) {
  const { openSingle, openInbox } = useChatUI();

  return (
    <>
      {/* Open inbox (all conversations) */}
      <button onClick={() => openInbox()}>Messages</button>

      {/* Open single chat linked to this order */}
      <button
        onClick={() =>
          openSingle({
            reference: { kind: "order", id: orderId, title: `Order #${orderId}` },
          })
        }
      >
        Chat with Buyer
      </button>
    </>
  );
}

Quick Start (Next.js App Router)

1. Create a client wrapper

// components/ChatWrapper.tsx
"use client";
import { ChatUIProvider, ChatRoot } from "@banbox/chat";
import { createApiChatAdapter } from "@/lib/chat/apiAdapter";

const adapter = createApiChatAdapter();

export function ChatWrapper({ children }: { children: React.ReactNode }) {
  return (
    <ChatUIProvider>
      {children}
      <ChatRoot
        adapter={adapter}
        theme="marketplace"
        inboxFooterActions={["attachment", "emoji", "translate"]}
        singleFooterActions={["attachment", "emoji", "translate"]}
      />
    </ChatUIProvider>
  );
}

2. Add to root layout

// app/layout.tsx
import { ChatWrapper } from "@/components/ChatWrapper";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ChatWrapper>{children}</ChatWrapper>
      </body>
    </html>
  );
}

Tailwind CSS Setup

// vite.config.ts (or tailwind.config.ts)
export default {
  content: [
    "./src/**/*.{ts,tsx}",
    // Include @banbox/chat source for Tailwind class scanning
    "./node_modules/@banbox/chat/src/**/*.{ts,tsx}",
  ],
};

Local Development (Hybrid Mode)

The seller / host app automatically detects the local banbox-chat folder and uses it during development, while using the published npm package in production builds.

// vite.config.ts — hybrid alias (already set up in banbox-seller-react)
import fs from "fs";
import path from "path";

const localChatPath = path.resolve(__dirname, "../banbox-chat");
const useLocalChat = fs.existsSync(localChatPath);

export default defineConfig({
  resolve: {
    alias: useLocalChat
      ? { "@banbox/chat": path.join(localChatPath, "dist/index.js") }
      : {},
    dedupe: ["react", "react-dom", "framer-motion", "lottie-react"],
  },
});

| Mode | Source | |---|---| | npm run dev (local folder exists) | ../banbox-chat/dist/ | | Production build / CI | node_modules/@banbox/chat |


ChatRoot Props

<ChatRoot
  adapter={adapter}           // Required — your ChatAdapter implementation
  theme="marketplace"         // Optional — "marketplace" | "admin" | custom object
  uiCallbacks={...}           // Optional — toast, navigate, kebab menu
  inboxFooterActions={[...]}  // Optional — allow-list for InboxPopup toolbar
  singleFooterActions={[...]} // Optional — allow-list for SinglePopup toolbar
/>

theme prop

// Named themes
<ChatRoot theme="marketplace" />   // orange primary (#ff5300)
<ChatRoot theme="admin" />         // black primary (#1a1a1a)

// Custom theme object
<ChatRoot theme={{ primary: "#7C3AED", primaryActive: "#6D28D9" }} />

inboxFooterActions / singleFooterActions

Controls which toolbar buttons appear in the footer of each popup variant.

| Key | Button | Default | |---|---|---| | "attachment" | 📎 Attach file / image | ✅ Yes | | "emoji" | 😊 Emoji picker | ✅ Yes | | "translate" | 🌐 Translation settings | ✅ Yes | | "businessCard" | 👤 Share business card | ❌ Opt-in | | "addressCard" | 📍 Share delivery address | ❌ Opt-in |

// Default (omit the prop — shows attachment, emoji, translate only)
<ChatRoot adapter={adapter} />

// Add location sharing to SinglePopup only
<ChatRoot
  inboxFooterActions={["attachment", "emoji", "translate"]}
  singleFooterActions={["attachment", "emoji", "translate", "addressCard"]}
/>

// Full toolbar (all buttons)
<ChatRoot
  inboxFooterActions={["attachment", "emoji", "businessCard", "addressCard", "translate"]}
  singleFooterActions={["attachment", "emoji", "businessCard", "addressCard", "translate"]}
/>

uiCallbacks prop

uiCallbacks={{
  // Show a toast from your app's existing toast system
  showToast: ({ type, title, message }) => toast[type](title),

  // Navigate when user clicks "View Order" / "View Inquiry"
  onNavigate: ({ type, id }) => navigate(`/${type}s/${id}`),

  // (Optional) Replace the default ⋮ kebab menu with your own component
  renderKebabMenu: ({ pinned, onPinToggle, onDelete }) => (
    <MyDropdownMenu pinned={pinned} onPin={onPinToggle} onDelete={onDelete} />
  ),
}}

useChatUI() Hook

const {
  openInbox,        // () => void — open inbox (all threads)
  openSingle,       // (opts?) => void — open single chat
  close,            // () => void — close popup
  selectThread,     // (id: string | null) => void
  isOpen,           // boolean
  variant,          // "inbox" | "single"
  reference,        // Reference | undefined
  selectedThreadId, // string | null
} = useChatUI();

openInbox(opts?)

// Open inbox with all threads
openInbox();

// Open inbox pre-filtered to a reference kind
openInbox({ reference: { kind: "order" } });

// Open inbox with a specific thread pre-selected
openInbox({ threadId: "t4" });

openSingle(opts?)

// Plain single chat
openSingle();

// Linked to an order — shows "View Order" bar in header
openSingle({ reference: { kind: "order", id: "ORD-123", title: "Order #123" } });

// Linked to an inquiry
openSingle({ reference: { kind: "inquiry", id: "INQ-456" } });

// Linked to a quotation
openSingle({ reference: { kind: "quotation", id: "QUOT-789" } });

// Product inquiry
openSingle({ reference: { kind: "productInquiry", id: "PI-101" } });

// With metadata for the header (title, subtitle, online status)
openSingle({
  reference: {
    kind: "order",
    id: "ORD-123",
    meta: { title: "Emon Hasan", subtitle: "Customer", online: true },
  },
});

ChatAdapter Interface

Full interface contract:

import type { ChatAdapter } from "@banbox/chat";

const adapter: ChatAdapter = {
  threads: {
    // Returns current thread list (sync — keep a local cache)
    list: (reference?) => Thread[],

    // Subscribe to thread changes — returns unsubscribe function
    subscribe: (cb: () => void) => () => void,

    // Pin / unpin a thread
    pin: (id: string, pinned: boolean) => Promise<void> | void,

    // Delete a thread
    delete: (id: string) => Promise<void> | void,

    // Mark thread as read (optional)
    markRead?: (id: string) => Promise<void> | void,
  },

  messages: {
    // Returns messages for a thread (sync — keep a local cache)
    list: (threadId: string) => Message[],

    // Subscribe to new messages for a thread (optional)
    subscribe?: (threadId: string, cb: () => void) => () => void,

    // Send a message — handles all payload types
    send: (threadId: string, payload: SendPayload) => Promise<void> | void,
  },
};

SendPayload — all message types

// Text message
{ type: "text"; text: string; replyTo?: MessageRef }

// Voice/audio message
{ type: "voice"; src?: string; durationSec: number; durationText: string; replyTo?: MessageRef }

// Images and/or files only
{ type: "attachments"; images: string[]; files: MessageFile[]; replyTo?: MessageRef }

// Text + images/files combined
{ type: "combined"; text: string; images: string[]; files: MessageFile[]; replyTo?: MessageRef }

// Business card
{ type: "businessCard"; card: BusinessCard; replyTo?: MessageRef }

// Address / delivery card
{ type: "addressCard"; card: AddressCard; replyTo?: MessageRef }

Adding a new message type

  1. Add a new variant to SendPayload in banbox-chat/src/types/index.ts
  2. Handle it in your adapter's messages.send() implementation
  3. Optionally add a new UI component in banbox-chat/src/ui/message-items/

Domain Types

import type {
  Thread,         // Conversation thread
  Message,        // A single chat message
  SendPayload,    // Discriminated union of all sendable message types
  MessageFile,    // File attachment
  MessageAudio,   // Audio clip
  MessageRef,     // Reply-to reference
  BusinessCard,   // Business card data
  AddressCard,    // Delivery address data
  ThreadStatus,   // "seen" | "delivered" | { kind: "new", count: number }
  Reference,      // Context link: order | inquiry | quotation | productInquiry
} from "@banbox/chat";

Individual UI Components (Advanced)

Export individual components for custom layouts:

import {
  ChatFooter,
  ChatHeader,
  ChatIdentity,
  ChatMessageItem,
  ChatScroll,
  ChatThreadItem,
  ChatListHeader,
  ChatInquiryBar,
  TypingIndicator,
  ReplyCard,
  ChatSpinner,
  ChatKebabMenu,
} from "@banbox/chat";

ChatFooter standalone

import { ChatFooter, DEFAULT_FOOTER_ACTIONS } from "@banbox/chat";

<ChatFooter
  onSend={(payload) => adapter.messages.send(threadId, payload)}
  onAfterSend={() => setRev(v => v + 1)}

  // Allow-list — only these buttons appear
  enabledActions={["attachment", "emoji", "translate"]}

  replyTo={replyTo}
  clearReply={() => setReplyTo(undefined)}
/>

Translation Support

The translate button (🌐) in the footer opens a Translation Settings modal where users can configure language preferences. To integrate your own translation API, pass onTranslate to ChatMessageItem:

<ChatMessageItem
  // ...
  onTranslate={(originalText) => {
    // Return translated string or undefined (will keep original if undefined)
    return myTranslationService.translate(originalText, "bn");
  }}
/>

When onTranslate is omitted, the translate button is still visible but acts as a no-op toggle (good for demo mode).


Exported Constants

import { DEFAULT_FOOTER_ACTIONS } from "@banbox/chat";
// → ["attachment", "emoji", "translate"]

Build

npm run build        # compile to dist/
npm run build:watch  # watch mode
npm run typecheck    # tsc --noEmit

Changelog

v1.0.16

  • 🧹 Fixed all Tailwind CSS class syntax warnings (IDE linter clean)
    • z-[N]z-N for all numeric z-index values
    • hover:text-[var(--x)]hover:text-(--x) (CSS variable shorthand)
    • bg-[var(--x,fallback)]bg-(--x,fallback)
    • -ml-[5px]ml-[-5px], -z-[1]z-[-1], -top-[6px]top-[-6px]
    • !bg-[#hex]bg-[#hex]! (Tailwind v4 important suffix)

v1.0.15

  • 🚫 Removed ChatKebabMenu (⋮ three-dot menu) from SinglePopup header
    • SinglePopup header now shows only the ✕ close button
    • ChatKebabMenu is still present in InboxPopup (thread list pin/delete)
  • 🖼️ Demo data: Emon Hasan (t2) now has a real generated profile photo

v1.0.14

  • 🐛 Critical: Removed hardcoded /shop/ban-box.png fallback from SinglePopup
    • SinglePopup now conditionally renders variant="avatar" (when avatarSrc exists) or variant="initial" (letter avatar with deterministic background colour)
    • Same behavior as InboxPopup — no broken images when thread has no photo
  • reference.meta.avatarSrc now supported in SinglePopup (for host-app-supplied per-chat avatars)
  • 🖼️ Demo data: all threads now have avatarSrc using images from public/chat/img/

v1.0.13

  • 📖 Full README rewrite — documents all props, hooks, adapter interface, SendPayload types, local dev hybrid mode, individual UI components, DEFAULT_FOOTER_ACTIONS constant

v1.0.12

  • ♻️ hiddenActionKeysenabledActions allow-list in ChatFooter (internal rename)
  • inboxFooterActions and singleFooterActions props on ChatRoot — per-popup toolbar control
  • businessCard and addressCard are now opt-in (not shown by default)

v1.0.11

  • Added hiddenActionKeys prop threading through ChatRootInboxPopup/SinglePopupChatFooter

v1.0.10

  • 🐛 Critical: SinglePopup now subscribes to thread list (real-time updates were missing)
  • 🐛 coalesceThreadId no longer has hardcoded "t4" demo dependency
  • 🐛 isOnline = true hardcoded removed from ChatMessageItem avatar
  • onTranslate prop on ChatMessageItem — replaces internal hardcoded translator
  • 🐛 markRead now fires on initial popup open, not only on thread switch
  • ♻️ Shared utils/theme.tsGRADIENT_BORDER, getThemeAttr, getThemeVars extracted

v1.0.9

  • Initial public release
  • Full adapter pattern, pub/sub threads/messages
  • InboxPopup + SinglePopup
  • All message types: text, voice, images, files, businessCard, addressCard