Notification centre
Unread notifications in a popover.
Built with
Northwind People
Good morning, Sara
You have 3 unread notifications. Open the bell to review leave requests and payroll updates.
notification-centre.tsx
"use client";
import { useState, type ReactNode } from "react";
import {
LuBell,
LuCalendarCheck,
LuFileText,
LuMessageSquare,
LuUserPlus,
LuWallet,
} from "react-icons/lu";
import {
Button,
CountBadge,
IconButton,
ListItem,
Popover,
Tab,
TabList,
TabPanel,
Tabs,
} from "@nexera-ui/react";
interface Notice {
id: string;
title: string;
detail: string;
icon: ReactNode;
}
const NOTICES: Notice[] = [
{
id: "n1",
title: "Mia Jensen requested 5 days of leave",
detail: "2 to 6 Nov · 12 min ago",
icon: <LuCalendarCheck />,
},
{
id: "n2",
title: "October payslips are ready",
detail: "Payroll · 1 h ago",
icon: <LuWallet />,
},
{
id: "n3",
title: "Luis Ortega replied to your comment",
detail: "Overtime claim · 3 h ago",
icon: <LuMessageSquare />,
},
{
id: "n4",
title: "Omar Haddad accepted his invite",
detail: "Customer team · Yesterday",
icon: <LuUserPlus />,
},
{
id: "n5",
title: "Updated travel policy published",
detail: "Policies · Mon",
icon: <LuFileText />,
},
];
/** Notification centre: a bell with an unread count opens a popover with All and Unread tabs. */
export default function NotificationCentre() {
const [unread, setUnread] = useState<string[]>(["n1", "n2", "n3"]);
const markRead = (id: string) => {
setUnread((u) => u.filter((x) => x !== id));
};
const list = (items: Notice[]) =>
items.length === 0 ? (
<p className="py-6 text-center text-body-small text-secondary">You are all caught up.</p>
) : (
<ul className="m-0 grid max-h-80 list-none gap-0.5 overflow-y-auto p-0">
{items.map((n) => (
<li key={n.id}>
<ListItem
title={n.title}
subtitle={n.detail}
icon={n.icon}
onPress={() => {
markRead(n.id);
}}
{...(unread.includes(n.id)
? { trailing: <CountBadge type="dot" tone="brand" label="Unread" /> }
: {})}
/>
</li>
))}
</ul>
);
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<div className="mx-auto grid w-full max-w-3xl gap-6">
<header className="flex items-center justify-between gap-3 rounded-xl border border-default bg-surface px-4 py-3 shadow-sm">
<h2 className="text-heading-h4 text-primary">Northwind People</h2>
<div className="relative">
<Popover
variant="with-actions"
title="Notifications"
placement="bottom end"
className="w-[22rem]"
body={
<Tabs defaultSelectedKey="all">
<TabList aria-label="Filter notifications">
<Tab id="all" label="All" size="sm" />
<Tab id="unread" label="Unread" size="sm" count={String(unread.length)} />
</TabList>
<TabPanel id="all" className="pt-2">
{list(NOTICES)}
</TabPanel>
<TabPanel id="unread" className="pt-2">
{list(NOTICES.filter((n) => unread.includes(n.id)))}
</TabPanel>
</Tabs>
}
secondaryAction={
<Button
size="sm"
variant="ghost"
onPress={() => {
setUnread([]);
}}
isDisabled={unread.length === 0}
>
Mark all as read
</Button>
}
primaryAction={
<a
href="#notifications"
className="self-center text-body-small text-brand-text underline underline-offset-4"
>
See all
</a>
}
>
<IconButton
variant="ghost"
size="md"
icon={<LuBell />}
aria-label={
unread.length > 0 ? `Notifications, ${unread.length} unread` : "Notifications"
}
/>
</Popover>
{unread.length > 0 ? (
<span aria-hidden="true" className="pointer-events-none absolute -end-1 -top-1">
<CountBadge count={unread.length} />
</span>
) : null}
</div>
</header>
<section
aria-labelledby="today-title"
className="grid gap-2 rounded-xl border border-default bg-surface p-4 shadow-sm"
>
<h3 id="today-title" className="text-body-strong text-primary">
Good morning, Sara
</h3>
<p className="text-body-default text-secondary">
You have {unread.length === 0 ? "no" : unread.length} unread{" "}
{unread.length === 1 ? "notification" : "notifications"}. Open the bell to review leave
requests and payroll updates.
</p>
</section>
</div>
</div>
);
}
More messaging and collaboration blocks
- ChatOne conversation with bubbles, reactions and a composer.
- InboxConversation list beside the open chat.
- Helpdesk assistantAsk HR or support a question and get answers.
- Comment threadDiscuss a record with replies and reactions.
- AnnouncementA banner for news that affects everyone.
- Document editorWrite and format text with links and lists.
- Day separators and system notesShow who joined and where each day starts.