fix(site/src/pages/WorkspacePage/WorkspaceNotifications): make workspace notification pills keyboard accessible (#24536)

Description:

The workspace notification pills were implemented using Tooltip, which
is hover-only and not reachable via keyboard navigation.

Replace Tooltip/TooltipProvider/TooltipTrigger with
Popover/PopoverContent/PopoverTrigger, and change the trigger element
from a non-interactive div to a button. This makes the notification
pills fully keyboard accessible.


Screenshot:
**Issue**
<img width="640" height="211" alt="not-working"
src="https://github.com/user-attachments/assets/3cb12fdf-704b-41a2-ab9c-c198d03158a7"
/>


**Fix**
<img width="640" height="211" alt="working"
src="https://github.com/user-attachments/assets/25807a6b-7065-4753-b55e-d8db103ba501"
/>
This commit is contained in:
code-qtzl
2026-04-22 14:01:33 -04:00
committed by GitHub
parent 8aa3294f06
commit 075face3cb
2 changed files with 37 additions and 38 deletions
@@ -4,11 +4,10 @@ import type { AlertProps } from "#/components/Alert/Alert";
import { Button, type ButtonProps } from "#/components/Button/Button";
import { Pill } from "#/components/Pill/Pill";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "#/components/Tooltip/Tooltip";
Popover,
PopoverContent,
PopoverTrigger,
} from "#/components/Popover/Popover";
import type { ThemeRole } from "#/theme/roles";
export type NotificationItem = {
@@ -33,55 +32,53 @@ export const Notifications: FC<NotificationsProps> = ({
const theme = useTheme();
return (
<TooltipProvider>
<Tooltip open={isOpen} onOpenChange={setIsOpen} delayDuration={0}>
<TooltipTrigger asChild>
<div
// Adds some spacing from the Tooltip content
className="py-2"
data-testid={`${severity}-notifications`}
>
<NotificationPill
items={items}
severity={severity}
icon={icon}
isTooltipOpen={isOpen}
/>
</div>
</TooltipTrigger>
<TooltipContent
align="end"
collisionPadding={16}
className="max-w-[400px] p-0 bg-surface-secondary border-surface-quaternary text-sm text-content-primary"
style={{
borderColor: theme.roles[severity].outline,
}}
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>
<button
type="button"
className="py-2 bg-transparent border-none cursor-pointer"
data-testid={`${severity}-notifications`}
>
{items.map((n) => (
<NotificationItem notification={n} key={n.title} />
))}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<NotificationPill
items={items}
severity={severity}
icon={icon}
isOpen={isOpen}
/>
</button>
</PopoverTrigger>
<PopoverContent
align="end"
collisionPadding={16}
className="max-w-[400px] p-0 w-auto bg-surface-secondary border-surface-quaternary text-sm text-content-primary"
style={{
borderColor: theme.roles[severity].outline,
}}
>
{items.map((n) => (
<NotificationItem notification={n} key={n.title} />
))}
</PopoverContent>
</Popover>
);
};
type NotificationPillProps = NotificationsProps & {
isTooltipOpen: boolean;
isOpen: boolean;
};
const NotificationPill: FC<NotificationPillProps> = ({
items,
severity,
icon,
isTooltipOpen,
isOpen,
}) => {
return (
<Pill
icon={icon}
css={(theme) => ({
"& svg": { color: theme.roles[severity].outline },
borderColor: isTooltipOpen ? theme.roles[severity].outline : undefined,
borderColor: isOpen ? theme.roles[severity].outline : undefined,
})}
>
{items.length}
@@ -270,7 +270,9 @@ export const WorkspaceNotifications: FC<WorkspaceNotificationsProps> = ({
<Notifications
items={warningNotifications}
severity="warning"
icon={<TriangleAlertIcon className="size-icon-sm" />}
icon={
<TriangleAlertIcon aria-hidden="true" className="size-icon-sm" />
}
/>
)}
</div>