## 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>
45 lines
1.5 KiB
TypeScript
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)}
|
|
/>
|
|
</>
|
|
);
|
|
}
|