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

@zyphr-dev/inbox-react

v0.1.2

Published

React components for Zyphr notification inbox

Downloads

273

Readme

@zyphr-dev/inbox-react

Drop-in React components for a fully-featured notification inbox. Real-time updates, preference management, and theme support — all out of the box.

npm version license

Installation

npm install @zyphr-dev/inbox-react
# or
yarn add @zyphr-dev/inbox-react

Then import the styles in your app's entry point:

import '@zyphr-dev/inbox-react/styles.css';

Requirements: React 17+ and React DOM 17+

Quick Start

Wrap your app (or a subtree) with ZyphrProvider, then drop in the components:

import { ZyphrProvider, InboxPopover } from '@zyphr-dev/inbox-react';
import '@zyphr-dev/inbox-react/styles.css';

function App() {
  return (
    <ZyphrProvider subscriberToken="your_subscriber_token">
      <header>
        <InboxPopover />
      </header>
    </ZyphrProvider>
  );
}

That's it — you get a bell icon with an unread badge, a popover with your notification feed, and real-time updates via WebSocket.

Provider Configuration

ZyphrProvider wraps your component tree and manages API connections, WebSocket subscriptions, and theme state.

<ZyphrProvider
  subscriberToken="sk_subscriber_xxx"  // Required
  apiUrl="https://api.zyphr.dev"       // Default
  wsUrl="wss://ws.api.zyphr.dev"       // Default
  realtime={true}                      // Enable WebSocket updates (default: true)
  theme="system"                       // 'light' | 'dark' | 'system' (default: 'system')
  locale="en"                          // Locale for date formatting (default: 'en')
  demo={false}                         // Demo mode with mock data (default: false)
>
  {children}
</ZyphrProvider>

| Prop | Type | Default | Description | |------|------|---------|-------------| | subscriberToken | string | — | Required. Subscriber authentication token | | apiUrl | string | https://api.zyphr.dev | API endpoint | | wsUrl | string | wss://ws.api.zyphr.dev | WebSocket endpoint | | realtime | boolean | true | Enable real-time updates via WebSocket | | theme | 'light' \| 'dark' \| 'system' | 'system' | Color scheme | | locale | string | 'en' | Locale code for date formatting | | demo | boolean | false | Use mock data, no API calls |

Access the config from any child component:

import { useZyphr } from '@zyphr-dev/inbox-react';

const { config } = useZyphr();

Components

InboxPopover

A self-contained notification center: bell icon trigger, popover panel, and notification feed. This is the easiest way to add notifications to your app.

import { InboxPopover } from '@zyphr-dev/inbox-react';

<InboxPopover
  position="bottom-right"          // Popover anchor position
  categories={['alerts', 'updates']} // Filter by category
  trigger={<CustomBellButton />}    // Custom trigger element
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | open | boolean | — | Controlled open state | | onOpenChange | (open: boolean) => void | — | Open state change callback | | position | 'bottom-left' \| 'bottom-right' \| 'top-left' \| 'top-right' | 'bottom-right' | Popover position | | categories | string[] | — | Filter notifications by category | | trigger | ReactNode | <InboxBell /> | Custom trigger element | | className | string | — | CSS class for the popover |

Features: Click-outside to close, Escape key support, focus trapping, focus restoration, mobile-friendly backdrop.

InboxBell

A bell icon button with an unread count badge. Use it standalone or as a trigger for custom popovers.

import { InboxBell } from '@zyphr-dev/inbox-react';

<InboxBell
  onClick={() => setOpen(!open)}
  dot={false}         // Show dot instead of count
  maxCount={99}       // Cap displayed count (shows "99+")
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | onClick | () => void | — | Click handler | | dot | boolean | false | Show dot indicator instead of count | | maxCount | number | 99 | Max displayed count (overflows to "N+") | | className | string | — | CSS class |

InboxFeed

The notification list with infinite scroll, tabs, and filtering. Use it inside a popover, a sidebar, or as a full-page view.

import { InboxFeed } from '@zyphr-dev/inbox-react';

<InboxFeed
  categories={['alerts']}
  showArchived={false}
  emptyState={<MyCustomEmptyState />}
  renderItem={(notification, actions) => (
    <MyNotificationCard
      notification={notification}
      onRead={actions.markAsRead}
      onArchive={actions.archive}
    />
  )}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | categories | string[] | — | Filter by category slugs | | showArchived | boolean | false | Include archived notifications | | emptyState | ReactNode | — | Custom empty state component | | renderItem | (notification, actions) => ReactNode | — | Custom notification renderer | | className | string | — | CSS class |

Features: All/Unread tabs, mark all as read, refresh button, infinite scroll via Intersection Observer, skeleton loading, error state with retry.

The renderItem callback receives a NotificationItemActions object:

interface NotificationItemActions {
  markAsRead: () => Promise<void>;
  archive: () => Promise<void>;
  delete: () => Promise<void>;
}

NotificationItem

Renders a single notification with action buttons, priority styling, and relative timestamps.

import { NotificationItem } from '@zyphr-dev/inbox-react';

<NotificationItem
  notification={notification}
  onClick={() => handleNotificationClick(notification)}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | notification | Notification | — | Required. Notification object | | onClick | () => void | — | Click handler | | className | string | — | CSS class |

Features: Image/icon display, relative timestamps ("2 hours ago", "Yesterday"), action URL with arrow indicator, category badge, priority emphasis for urgent/high, automatic mark-as-read on click.

EmptyState

Shown when no notifications match the current filter.

import { EmptyState } from '@zyphr-dev/inbox-react';

<EmptyState
  title="Nothing here"
  description="Check back later for updates."
  icon={<MyIcon />}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | title | string | 'All caught up!' | Heading text | | description | string | 'No new notifications right now...' | Body text | | icon | ReactNode | Bell with checkmark | Custom icon | | className | string | — | CSS class |

PreferencesPanel

A settings panel for subscribers to manage their notification preferences per channel and category.

import { PreferencesPanel } from '@zyphr-dev/inbox-react';

<PreferencesPanel
  showGlobalPreferences={true}
  channels={['email', 'push', 'in_app']}  // Omit channels you don't offer
  channelLabels={{ push: 'Mobile Push' }}  // Override display names
  saveButtonText="Update Preferences"
  onSave={() => toast.success('Preferences saved!')}
  onError={(err) => toast.error(err.message)}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | showGlobalPreferences | boolean | true | Show the "All notifications" global section | | channels | NotificationChannel[] | All channels | Limit which channels are shown | | channelLabels | Partial<Record<NotificationChannel, string>> | Built-in labels | Override channel display names | | saveButtonText | string | 'Save Preferences' | Custom save button text | | onSave | () => void | — | Callback on successful save | | onError | (error: Error) => void | — | Callback on save error | | emptyState | ReactNode | — | Custom empty state when no categories | | className | string | — | CSS class |

Features: Global and per-category toggles, required category badges (categories users can't unsubscribe from), optimistic updates, unsaved changes indicator, loading and error states.

Hooks

Use the hooks directly when you need full control over the notification experience.

useInbox

The primary hook for managing the notification inbox — provides state and actions.

import { useInbox } from '@zyphr-dev/inbox-react';

function MyInbox() {
  const {
    notifications,
    unreadCount,
    isLoading,
    hasMore,
    error,
    loadMore,
    refresh,
    markAsRead,
    markAllAsRead,
    archive,
    delete: deleteNotification,
  } = useInbox();

  return (
    <div>
      <p>{unreadCount} unread</p>
      {notifications.map(n => (
        <div key={n.id} onClick={() => markAsRead(n.id)}>
          {n.title}
        </div>
      ))}
      {hasMore && <button onClick={loadMore}>Load more</button>}
    </div>
  );
}

Returns:

| Property | Type | Description | |----------|------|-------------| | notifications | Notification[] | Array of notifications | | unreadCount | number | Unread count | | isLoading | boolean | Currently fetching | | hasMore | boolean | More pages available | | error | Error \| null | Last error | | loadMore | () => Promise<void> | Load next page | | refresh | () => Promise<void> | Refresh the list | | markAsRead | (id: string) => Promise<void> | Mark one as read | | markAllAsRead | (category?: string) => Promise<void> | Mark all (or by category) as read | | archive | (id: string) => Promise<void> | Archive a notification | | delete | (id: string) => Promise<void> | Permanently delete |

useUnreadCount

A lightweight hook when you only need the badge count — avoids fetching the full notification list.

import { useUnreadCount } from '@zyphr-dev/inbox-react';

function NavBadge() {
  const { count, isLoading } = useUnreadCount();
  return <span className="badge">{count}</span>;
}

Returns: { count: number, isLoading: boolean, error: Error | null, refresh: () => Promise<void> }

useRealTime

Manage WebSocket connection and category subscriptions.

import { useRealTime } from '@zyphr-dev/inbox-react';

function ConnectionStatus() {
  const { connected, subscribe, unsubscribe } = useRealTime();

  useEffect(() => {
    subscribe(['alerts', 'deployments']);
    return () => unsubscribe(['alerts', 'deployments']);
  }, []);

  return <span>{connected ? 'Live' : 'Offline'}</span>;
}

Returns: { connected: boolean, subscribe: (categories: string[]) => void, unsubscribe: (categories: string[]) => void }

usePreferences

Manage subscriber notification preferences with optimistic updates.

import { usePreferences } from '@zyphr-dev/inbox-react';

function Settings() {
  const {
    preferences,
    isLoading,
    isSaving,
    hasChanges,
    updatePreference,
    savePreferences,
  } = usePreferences();

  // Toggle a category's email channel
  const handleToggle = (categoryId: string, enabled: boolean) => {
    updatePreference(categoryId, 'email', enabled);
  };

  // Pass null for categoryId to update global preferences
  const handleGlobalToggle = (enabled: boolean) => {
    updatePreference(null, 'email', enabled);
  };

  return (
    <div>
      {/* Render preference toggles */}
      <button onClick={savePreferences} disabled={!hasChanges || isSaving}>
        Save
      </button>
    </div>
  );
}

Returns:

| Property | Type | Description | |----------|------|-------------| | preferences | PreferencesData \| null | Loaded preferences | | isLoading | boolean | Initial fetch in progress | | isSaving | boolean | Save in progress | | error | Error \| null | Last error | | hasChanges | boolean | Unsaved changes exist | | refresh | () => Promise<void> | Reload from server | | updatePreference | (categoryId: string \| null, channel: NotificationChannel, enabled: boolean) => void | Update local state | | savePreferences | () => Promise<void> | Persist changes to server |

Types

All types are exported for TypeScript consumers:

import type {
  Notification,
  NotificationPriority,       // 'low' | 'normal' | 'high' | 'urgent'
  ZyphrConfig,
  InboxState,
  InboxActions,
  UseInboxReturn,
  UseUnreadCountReturn,
  UseRealTimeReturn,
  InboxBellProps,
  InboxPopoverProps,
  InboxFeedProps,
  NotificationItemProps,
  NotificationItemActions,
  EmptyStateProps,
  NotificationChannel,         // 'email' | 'push' | 'sms' | 'in_app'
  ChannelPreference,
  CategoryInfo,
  CategoryPreference,
  PreferencesData,
  PreferencesPanelProps,
  UsePreferencesReturn,
} from '@zyphr-dev/inbox-react';

Notification

interface Notification {
  id: string;
  title: string;
  body: string | null;
  action_url: string | null;
  action_label: string | null;
  image_url: string | null;
  icon: string | null;
  category: string | null;
  priority: NotificationPriority;
  data: Record<string, unknown>;
  tags: string[];
  read_at: string | null;
  archived_at: string | null;
  created_at: string;
}

Theming

The library supports three theme modes: light, dark, and system (auto-detects from prefers-color-scheme).

All components use CSS custom properties scoped under the .zyphr- prefix, so they won't conflict with your app's styles. To customize colors or spacing, override the CSS variables:

/* Override accent color */
.zyphr-inbox-feed {
  --zyphr-accent: #8b5cf6;
}

CSS Class Convention

All classes follow BEM naming with a zyphr- prefix:

  • .zyphr-inbox-bell — block
  • .zyphr-inbox-bell--has-unread — modifier
  • .zyphr-inbox-bell-badge — element
  • .zyphr-notification-item--priority-urgent — priority modifier

Demo Mode

Pass demo={true} to ZyphrProvider to render the inbox with mock data and no API calls. Useful for prototyping, Storybook, or documentation.

<ZyphrProvider subscriberToken="demo" demo={true}>
  <InboxPopover />
</ZyphrProvider>

Demo mode includes 5 sample notifications across different categories, priorities, and read states. All actions (mark as read, archive, delete) work locally.

Accessibility

  • ARIA live regions for status announcements
  • Focus trapping in popovers with focus restoration on close
  • Full keyboard navigation (Escape to close, Enter/Space to activate)
  • Screen reader support for badges, priorities, and unread states
  • Semantic HTML roles (dialog, list, listitem, alert)
  • Relative time formatting via Intl.RelativeTimeFormat

Bundle

Dual-format build — works with both ESM and CommonJS bundlers:

  • ESM: dist/index.js
  • CommonJS: dist/index.cjs
  • Types: dist/index.d.ts
  • Styles: dist/styles.css

License

MIT