diff --git a/src/features/workbench/components/workbench-top-bar-menus.tsx b/src/features/workbench/components/workbench-top-bar-menus.tsx
index 2957616..85b0b81 100644
--- a/src/features/workbench/components/workbench-top-bar-menus.tsx
+++ b/src/features/workbench/components/workbench-top-bar-menus.tsx
@@ -5,7 +5,6 @@ import {
Bot,
CheckCircle2,
ChevronDown,
- ChevronRight,
Columns2,
FileText,
Keyboard,
@@ -56,6 +55,11 @@ const menuSurfaceClassName = cn(
);
const menuReadableRowClassName =
"surface-well focus:bg-[var(--action-soft)] focus:text-slate-950";
+const alertActionClassName = cn(
+ MAP_COMPACT_RADIUS_CLASS_NAME,
+ "h-10 w-10 shrink-0 justify-center border border-transparent bg-transparent p-0 text-[var(--ink-muted)] transition-colors hover:border-[var(--action-blue)] hover:bg-[var(--action-blue)] hover:text-white focus:border-[var(--action-blue)] focus:bg-[var(--action-blue)] focus:text-white data-[highlighted]:border-[var(--action-blue)] data-[highlighted]:bg-[var(--action-blue)] data-[highlighted]:text-white active:scale-95"
+);
+
export function AnalysisViewMenu({
open,
onOpenChange,
@@ -224,40 +228,42 @@ export function AlertMenu({
transition={{ duration: skipRemovalMotion ? 0 : 0.18, ease: [0.4, 0, 1, 1] }}
className={cn(
tone === "warning" ? "status-tone-warning" : "status-tone-danger",
- "relative my-1 min-h-[88px] border border-transparent py-3 pl-3 pr-[76px] transition-colors hover:border-[var(--status-border)] hover:bg-[var(--action-soft)] focus-within:border-[var(--status-border)] focus-within:bg-[var(--action-soft)]",
+ "relative my-1 min-h-[76px] border border-transparent px-3 py-2 transition-colors hover:border-[var(--action-border-hover)] hover:bg-[var(--action-selection-soft)] focus-within:border-[var(--action-border-hover)] focus-within:bg-[var(--action-selection-soft)]",
MAP_COMPACT_RADIUS_CLASS_NAME,
menuReadableRowClassName
)}
>
-
-
- {alert.time}
-
-
{alert.title}
- {alert.description}
-
- {onRemoveAlert ? (
-
{
- event.preventDefault();
- onRemoveAlert(alert);
- }}
- >
-
-
- ) : null}
-
onSelectAlert(alert)}
+ >
+
+ />
+
+
+
+
+ {alert.time}
+
+
{alert.title}
+
{alert.description}
+ {onRemoveAlert ? (
+
{
+ event.preventDefault();
+ onRemoveAlert(alert);
+ }}
+ >
+
+
+ ) : null}
);
}) : (
diff --git a/tests/browser/alert-removal.e2e.ts b/tests/browser/alert-removal.e2e.ts
index b28ac82..aa5c720 100644
--- a/tests/browser/alert-removal.e2e.ts
+++ b/tests/browser/alert-removal.e2e.ts
@@ -42,13 +42,30 @@ for (const { width, reducedMotion } of [{ width: 1280, reducedMotion: false }, {
await expect(removeTask).toBeVisible();
const openTask = page.getByRole("menuitem", { name: `打开通知:${notification.title}` });
await expect(openTask).toHaveText("");
- const actions = await openTask.evaluate((element) => {
- const open = element.getBoundingClientRect();
- const remove = element.previousElementSibling!.getBoundingClientRect();
- return { centerDifference: Math.abs((open.left + open.width / 2) - (remove.left + remove.width / 2)), gap: open.top - remove.bottom };
+ const notificationRow = page.locator(`[data-alert-id="operational-task-notification:${notification.notification_id}"]`);
+ const titleBounds = await notificationRow.locator("p").first().boundingBox();
+ const rowBounds = await notificationRow.boundingBox();
+ expect(rowBounds!.height).toBeLessThanOrEqual(82);
+ const hit = await page.evaluate(({ x, y }) => document.elementFromPoint(x, y)?.closest("button")?.getAttribute("aria-label"),
+ { x: titleBounds!.x + titleBounds!.width / 2, y: titleBounds!.y + titleBounds!.height / 2 });
+ expect(hit).toBe(`打开通知:${notification.title}`);
+ await openTask.hover();
+ await expect(notificationRow).toHaveCSS("background-color", "rgb(218, 229, 253)");
+ await expect(notificationRow).toHaveCSS("border-color", "rgb(66, 99, 199)");
+ for (const action of [removeTask]) {
+ await action.hover();
+ await expect(action).toHaveCSS("background-color", "rgb(66, 99, 199)");
+ await expect(action).toHaveCSS("color", "rgb(255, 255, 255)");
+ }
+ await testInfo.attach(`alert-hover-${width}`, { body: await page.screenshot({ path: testInfo.outputPath("alert-hover.png"), style: "[data-sonner-toaster] { visibility: hidden !important; }" }), contentType: "image/png" });
+ await page.mouse.move(0, 0);
+ const removePosition = await removeTask.evaluate((element) => {
+ const remove = element.getBoundingClientRect();
+ const row = element.closest("[data-alert-id]")!.getBoundingClientRect();
+ return { top: remove.top - row.top, right: row.right - remove.right };
});
- expect(actions.centerDifference).toBeLessThan(1);
- expect(actions.gap).toBeGreaterThanOrEqual(0);
+ expect(removePosition.top).toBeLessThanOrEqual(6);
+ expect(removePosition.right).toBeLessThanOrEqual(6);
const box = await menu.boundingBox();
expect(box!.x).toBeGreaterThanOrEqual(0);
expect(box!.x + box!.width).toBeLessThanOrEqual(width);
diff --git a/tests/browser/scada-notifications.e2e.ts b/tests/browser/scada-notifications.e2e.ts
index fb713ee..1c8e6f5 100644
--- a/tests/browser/scada-notifications.e2e.ts
+++ b/tests/browser/scada-notifications.e2e.ts
@@ -93,7 +93,12 @@ for (const width of [1280, 375, 320]) {
await expect(page.getByRole("region", { name: "工况任务", exact: true }).getByRole("heading", { name: task.title })).toBeVisible();
await expect(popup).toBeVisible();
await menu.click();
- await page.getByRole("menuitem", { name: `打开通知:${alert.title}` }).click();
+ const alertRow = page.locator(`[data-alert-id="${alert.id}"]`);
+ const titleBox = await alertRow.locator("p").first().boundingBox();
+ const rowBox = await alertRow.boundingBox();
+ await alertRow.click({ position: { x: titleBox!.x - rowBox!.x + titleBox!.width / 2,
+ y: titleBox!.y - rowBox!.y + titleBox!.height / 2 } });
+ await expect(page.getByRole("menu")).toHaveCount(0);
await expect(page.getByRole("region", { name: "工况任务", exact: true }).getByRole("heading", { name: task.title })).toBeVisible();
await emit();
await expect(popup).toBeVisible();