diff --git a/components/layout/__tests__/top-bar-plugin-actions.test.tsx b/components/layout/__tests__/top-bar-plugin-actions.test.tsx index e3f09f61e..d4e4c48ae 100644 --- a/components/layout/__tests__/top-bar-plugin-actions.test.tsx +++ b/components/layout/__tests__/top-bar-plugin-actions.test.tsx @@ -56,8 +56,11 @@ describe('phone header', () => { render(); fireEvent.click(screen.getByRole('button', { name: 'more' })); - expect(screen.getAllByRole('menuitem').map((m) => m.textContent)).toEqual(['title']); - fireEvent.click(screen.getByRole('menuitem', { name: 'title' })); + // Notifications and the command palette (both labelled "title" here, as + // the translations are keys in tests); no plugin entries. + const items = screen.getAllByRole('menuitem'); + expect(items.map((m) => m.textContent)).toEqual(['title', 'title']); + fireEvent.click(items[1]); expect(opened).toHaveBeenCalled(); window.removeEventListener('bulwark:command-palette', opened); }); diff --git a/components/layout/mobile-header.tsx b/components/layout/mobile-header.tsx index 9fa7fbf64..3dc6db4cd 100644 --- a/components/layout/mobile-header.tsx +++ b/components/layout/mobile-header.tsx @@ -1,7 +1,7 @@ "use client"; import { useCallback, useEffect, useRef, useState } from "react"; -import { Menu, ArrowLeft, Plus, Search, X, MoreVertical, Terminal } from "@/components/icons"; +import { Menu, ArrowLeft, Plus, Search, X, MoreVertical, Terminal, Bell } from "@/components/icons"; import { Button } from "@/components/ui/button"; import { useUIStore } from "@/stores/ui-store"; import { useSettingsStore } from "@/stores/settings-store"; @@ -13,18 +13,22 @@ import { PluginActions } from "@/components/plugins/plugin-actions"; import { useMenuNavigation } from "@/hooks/use-menu-navigation"; import { ContextMenuItem } from "@/components/ui/context-menu"; import { openCommandPalette } from "@/lib/app-commands"; +import { NotificationSheet, formatBadgeCount, useUnreadNotificationCount } from "@/components/notifications/notification-center"; /** Plugin buttons in the top bar are not about any one item. */ const TOP_BAR_CONTEXT: Record = {}; /** - * The phone header's overflow menu: the command palette (no Ctrl+K on a - * phone) and what the desktop top bar shows as buttons (plugin - * top-bar-actions) and has no room for here. + * The phone header's overflow menu: the notifications (the desktop bell), the + * command palette (no Ctrl+K on a phone) and what the desktop top bar shows + * as buttons (plugin top-bar-actions) and has no room for here. */ function MobileHeaderMenu() { const t = useTranslations('top_bar'); const tPalette = useTranslations('command_palette'); + const tNotifications = useTranslations('notification_center'); + const unread = useUnreadNotificationCount(); + const [showNotifications, setShowNotifications] = useState(false); const [open, setOpen] = useState(false); const triggerRef = useRef(null); const close = useCallback(() => setOpen(false), []); @@ -47,12 +51,15 @@ function MobileHeaderMenu() { variant="ghost" size="icon" onClick={() => setOpen((v) => !v)} - className={cn("h-11 w-11", open && "bg-accent")} - aria-label={t('more')} + className={cn("relative h-11 w-11", open && "bg-accent")} + aria-label={unread > 0 ? `${t('more')} (${tNotifications('unread_count', { count: unread })})` : t('more')} aria-haspopup="menu" aria-expanded={open} > + {unread > 0 && ( +