The syntaxility-notifications-ui-library package provides a modular, TypeScript-first notification system with these core components:
- NotificationBell: Top-nav bell icon with unread count badge
- NotificationsModal: Primary modal (max 5 items) with "Read All" / "View All" actions
- FullNotificationsModal: Scrollable, paginated modal for complete list
- NotificationItem: Reusable single notification component
- useNotifications: Custom hook for data fetching, pagination, and state management
- NotificationProvider: Context provider for global configuration
- Fully typed with TypeScript
- Tailwind CSS styling (zero dependencies)
- ARIA-compliant accessibility
- Framer Motion animations
- Server/Client component compatible
- Tree-shakable imports
npm install syntaxility-notifications-ui-library
# or
yarn add syntaxility-notifications-ui-library
# or
pnpm add syntaxility-notifications-ui-libraryimport type { Notification, NotificationsConfig } from 'syntaxility-notifications-ui-library';
export interface Notification {
id: string;
title: string;
message: string;
timestamp: Date | string;
read: boolean;
type?: 'info' | 'success' | 'warning' | 'error';
url?: string;
}
export interface NotificationsConfig {
fetchNotifications: (
page: number,
limit: number
) => Promise<{
data: Notification[];
total: number;
totalPages: number;
}>;
maxPreviewItems?: number; // default: 5
onReadAll?: () => void | Promise<void>;
onNotificationClick?: (notification: Notification) => void;
}Example Next.js route: app/api/notifications/route.ts
// app/api/notifications/route.ts
import { NextResponse } from 'next/server';
const MOCK_NOTIFICATIONS = Array.from({ length: 25 }, (_, i) => ({
id: String(i + 1),
title: `Notification ${i + 1}`,
message: `This is notification #${i + 1}`,
timestamp: new Date().toISOString(),
read: i < 3 ? false : true,
type: i % 4 === 0 ? 'warning' : 'info',
}));
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get('page') || '1');
const limit = Number(searchParams.get('limit') || '10');
const start = (page - 1) * limit;
const end = start + limit;
const data = MOCK_NOTIFICATIONS.slice(start, end);
const total = MOCK_NOTIFICATIONS.length;
const totalPages = Math.ceil(total / limit);
return NextResponse.json({ data, total, totalPages });
}'use client';
import { useRef } from 'react';
import {
NotificationBell,
NotificationsModal,
FullNotificationsModal,
useNotifications,
type Notification,
type NotificationsConfig,
} from 'syntaxility-notifications-ui-library';
export default function Page() {
const config: NotificationsConfig = {
async fetchNotifications(page, limit) {
const res = await fetch(
`/api/notifications?page=${page}&limit=${limit}`,
{ cache: 'no-store' }
);
if (!res.ok) throw new Error('Failed to fetch notifications');
// Must return { data, total, totalPages }
return res.json();
},
maxPreviewItems: 5,
onReadAll: async () => {
// Optional: call your backend to mark all as read
console.log('Read all clicked');
},
onNotificationClick: (notification: Notification) => {
console.log('Notification clicked', notification);
// Optional: navigate or mark as read
},
};
const {
notifications, // preview list (maxPreviewItems)
fullNotifications, // full list for big modal
unreadCount,
isPreviewOpen,
isFullModalOpen,
setIsPreviewOpen,
setIsFullModalOpen,
readAll,
currentPage,
totalPages,
totalCount,
isLoading,
loadNextPage,
loadPreviousPage,
} = useNotifications(config);
const bellRef = useRef<HTMLDivElement | null>(null);
const handleNotificationClick = (notification: Notification) => {
config.onNotificationClick?.(notification);
setIsPreviewOpen(false);
};
return (
<section className="flex flex-col h-screen items-end p-8 bg-white">
{/* Top bar with bell */}
<nav className="w-full rounded-3xl p-6 mb-8 flex items-center justify-between border">
<span />
<NotificationBell
ref={bellRef}
unreadCount={unreadCount}
onClick={() => setIsPreviewOpen(true)}
className="flex-shrink-0"
/>
</nav>
{/* Preview modal (max 5 items) */}
<NotificationsModal
isOpen={isPreviewOpen}
onClose={() => setIsPreviewOpen(false)}
notifications={notifications}
unreadCount={unreadCount}
onReadAll={readAll}
onViewAll={() => setIsFullModalOpen(true)}
onNotificationClick={handleNotificationClick}
/>
{/* Full modal with pagination */}
<FullNotificationsModal
isOpen={isFullModalOpen}
onClose={() => setIsFullModalOpen(false)}
notifications={fullNotifications}
currentPage={currentPage}
totalPages={totalPages}
totalCount={totalCount}
isLoading={isLoading}
onNotificationClick={handleNotificationClick}
onLoadNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
/>
</section>
);
}