@zyphr-dev/inbox-react
v0.1.2
Published
React components for Zyphr notification inbox
Downloads
273
Maintainers
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.
Installation
npm install @zyphr-dev/inbox-react
# or
yarn add @zyphr-dev/inbox-reactThen 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
