Skip to content

Notification Center

A notification bell that opens a popover with tabbed categories, a notification list, and toast integration for new arrivals.

A notification bell that opens a popover with tabbed categories, a Notification list, and a toast integration for new arrivals.

import {
Badge,
Button,
Heading,
Notification,
Popover,
PopoverContent,
PopoverTrigger,
Stack,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
toast,
} from '@arshad-shah/cynosure-react';
type Item = {
id: string;
title: string;
body: string;
unread: boolean;
timestamp: string;
category: 'all' | 'mentions' | 'system';
};
export function NotificationCenter({ items }: { items: Item[] }) {
const unreadCount = items.filter((i) => i.unread).length;
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="ghost" aria-label="Notifications">
Bell
{unreadCount > 0 && (
<Badge colorScheme="danger" size="sm">
{unreadCount}
</Badge>
)}
</Button>
</PopoverTrigger>
<PopoverContent width={{ base: '100vw', sm: '400px' }}>
<Stack gap="3" padding="3">
<Stack direction="row" align="center" justify="space-between">
<Heading level={2} size="sm">Notifications</Heading>
<Button
variant="link"
onClick={() => toast.success('All notifications marked as read')}
>
Mark all read
</Button>
</Stack>
<Tabs defaultValue="all">
<TabsList>
<TabsTrigger value="all">All</TabsTrigger>
<TabsTrigger value="mentions">Mentions</TabsTrigger>
<TabsTrigger value="system">System</TabsTrigger>
</TabsList>
{(['all', 'mentions', 'system'] as const).map((cat) => (
<TabsContent key={cat} value={cat}>
<Stack gap="2">
{items
.filter((i) => cat === 'all' || i.category === cat)
.map((i) => (
<Notification
key={i.id}
unread={i.unread}
title={i.title}
timestamp={i.timestamp}
>
{i.body}
</Notification>
))}
</Stack>
</TabsContent>
))}
</Tabs>
</Stack>
</PopoverContent>
</Popover>
);
}

When your websocket pushes a new notification, call toast() for the immediate in-page announcement and update the list behind the popover:

toast('Sarah commented on your pull request', {
description: 'Clicking opens the thread in a new tab',
action: { label: 'Open', onClick: () => window.open(url) },
});