feat(nav): add cursor-pointer and tooltip to notification bell
Extract bellLabel as $derived to DRY up aria-label and title. Add cursor-pointer globally to button via @layer base so Tailwind preflight reset doesn't override the browser default. Closes #344 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,12 @@ function handleKeydown(event: KeyboardEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bellLabel = $derived(
|
||||||
|
stream.unreadCount > 0
|
||||||
|
? m.notification_bell_unread_label({ count: stream.unreadCount })
|
||||||
|
: m.notification_bell_label()
|
||||||
|
);
|
||||||
|
|
||||||
function attachBellButton(node: HTMLButtonElement) {
|
function attachBellButton(node: HTMLButtonElement) {
|
||||||
bellButtonEl = node;
|
bellButtonEl = node;
|
||||||
return () => {
|
return () => {
|
||||||
@@ -72,12 +78,11 @@ onDestroy(() => {
|
|||||||
{@attach attachBellButton}
|
{@attach attachBellButton}
|
||||||
type="button"
|
type="button"
|
||||||
onclick={toggleDropdown}
|
onclick={toggleDropdown}
|
||||||
aria-label={stream.unreadCount > 0
|
aria-label={bellLabel}
|
||||||
? m.notification_bell_unread_label({ count: stream.unreadCount })
|
title={bellLabel}
|
||||||
: m.notification_bell_label()}
|
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
class="relative rounded-sm p-2 text-white/65 transition-colors hover:bg-white/10 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
class="relative cursor-pointer rounded-sm p-2 text-white/65 transition-colors hover:bg-white/10 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -55,6 +55,34 @@ async function openDropdownAndClickFirstNotification() {
|
|||||||
notifButton.click();
|
notifButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('NotificationBell — cursor and tooltip', () => {
|
||||||
|
it('bell button has cursor-pointer class', async () => {
|
||||||
|
render(NotificationBell);
|
||||||
|
const btn = document.querySelector<HTMLButtonElement>('button[aria-haspopup="true"]')!;
|
||||||
|
expect(btn.classList.contains('cursor-pointer')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bell button title equals aria-label when unreadCount is 0', async () => {
|
||||||
|
mockNotificationList.value = [];
|
||||||
|
render(NotificationBell);
|
||||||
|
const btn = document.querySelector<HTMLButtonElement>('button[aria-haspopup="true"]')!;
|
||||||
|
expect(btn.getAttribute('title')).toBe('Benachrichtigungen');
|
||||||
|
expect(btn.getAttribute('aria-label')).toBe(btn.getAttribute('title'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bell button title equals aria-label when unreadCount is 3', async () => {
|
||||||
|
mockNotificationList.value = [
|
||||||
|
makeNotification({ id: 'n1' }),
|
||||||
|
makeNotification({ id: 'n2' }),
|
||||||
|
makeNotification({ id: 'n3' })
|
||||||
|
];
|
||||||
|
render(NotificationBell);
|
||||||
|
const btn = document.querySelector<HTMLButtonElement>('button[aria-haspopup="true"]')!;
|
||||||
|
expect(btn.getAttribute('title')).toBe('3 ungelesene Benachrichtigungen');
|
||||||
|
expect(btn.getAttribute('aria-label')).toBe(btn.getAttribute('title'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('NotificationBell', () => {
|
describe('NotificationBell', () => {
|
||||||
it('handleMarkRead navigates to URL including annotationId when notification has annotationId', async () => {
|
it('handleMarkRead navigates to URL including annotationId when notification has annotationId', async () => {
|
||||||
mockNotificationList.value = [makeNotification({ annotationId: 'annot-1' })];
|
mockNotificationList.value = [makeNotification({ annotationId: 'annot-1' })];
|
||||||
|
|||||||
@@ -365,6 +365,11 @@
|
|||||||
text-underline-offset: 4px;
|
text-underline-offset: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tailwind preflight resets cursor on *, overriding the browser default for buttons */
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* Fallback focus ring for any interactive element not styled with ring-focus-ring */
|
/* Fallback focus ring for any interactive element not styled with ring-focus-ring */
|
||||||
:focus-visible {
|
:focus-visible {
|
||||||
outline: 2px solid var(--c-focus-ring);
|
outline: 2px solid var(--c-focus-ring);
|
||||||
|
|||||||
Reference in New Issue
Block a user