template-saas-frontend-v2/src/components/notifications/NotificationBell.tsx
Adrian Flores Cortes 9bd1aba33d [SPRINT-3] feat: Add WCAG improvements and 160 unit tests
## ST-3.1 WCAG Accessibility (5 SP)
- Replace div onClick with semantic buttons
- Add aria-label to interactive icons
- Add aria-hidden to decorative icons
- Add focus:ring-2 for visible focus states
- Add role attributes to modals, trees, progressbars
- Add proper form labels with htmlFor

Files modified: NotificationDrawer, DashboardLayout, PermissionsMatrix,
NetworkTree, GoalProgressBar, AuditFilters, NotificationBell,
NotificationItem, RoleCard, RoleForm

## ST-3.2 Unit Tests (5 SP)
- Add 160 new unit tests (target was 42)
- Hooks: useAuth (18), useRbac (18), useGoals (15), useMlm (19), usePortfolio (24)
- Components: RoleForm (15), GoalProgressBar (28), RankBadge (23)
- All tests use AAA pattern with proper mocking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:27:34 -06:00

45 lines
1.5 KiB
TypeScript

import { useState } from 'react';
import { Bell } from 'lucide-react';
import clsx from 'clsx';
import { useUnreadNotificationsCount } from '@/hooks/useData';
import { NotificationDrawer } from './NotificationDrawer';
export function NotificationBell() {
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const { data } = useUnreadNotificationsCount();
const unreadCount = data?.count ?? 0;
const hasUnread = unreadCount > 0;
return (
<>
<button
onClick={() => setIsDrawerOpen(true)}
className="relative p-2 rounded-lg hover:bg-secondary-100 dark:hover:bg-secondary-700 transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500"
aria-label={`Notifications${hasUnread ? ` (${unreadCount} unread)` : ''}`}
aria-haspopup="dialog"
>
<Bell
className={clsx(
'w-5 h-5 transition-colors',
hasUnread
? 'text-primary-600 dark:text-primary-400'
: 'text-secondary-600 dark:text-secondary-400'
)}
aria-hidden="true"
/>
{hasUnread && (
<span className="absolute top-1 right-1 flex items-center justify-center min-w-[18px] h-[18px] px-1 text-[10px] font-bold text-white bg-red-500 rounded-full" aria-hidden="true">
{unreadCount > 99 ? '99+' : unreadCount}
</span>
)}
</button>
<NotificationDrawer
isOpen={isDrawerOpen}
onClose={() => setIsDrawerOpen(false)}
/>
</>
);
}