fix(ui): keep console layout usable at mid widths

This commit is contained in:
vince 2026-06-20 02:21:16 +02:00
parent 0c75220adc
commit 8bb5052dd5
6 changed files with 98 additions and 14 deletions

View file

@ -35,7 +35,7 @@ describe("React app dashboard state view", () => {
expect(html).toContain('data-ui-theme-toggle="true"');
expect(html).toContain('data-ui-theme-current="dark"');
expect(html).toContain('aria-label="Switch to light theme"');
expect(html).toContain('aria-label="Light theme"');
expect(html).toContain('aria-pressed="false"');
expect(html).toContain("Theme");
expect(html).toContain("Dark");

View file

@ -9,11 +9,10 @@ export interface ThemeToggleProps {
export function ThemeToggle({ theme, onThemeChange }: ThemeToggleProps) {
const nextTheme = getNextUiTheme(theme);
const label = `Switch to ${nextTheme} theme`;
return (
<button
aria-label={label}
aria-label="Light theme"
aria-pressed={theme === "light"}
className="theme-toggle"
data-ui-theme-current={theme}

View file

@ -190,7 +190,7 @@ describe("dashboard UI components", () => {
expect(body).toContain('class="theme-toggle"');
expect(body).toContain('data-ui-theme-current="light"');
expect(body).toContain('aria-label="Switch to dark theme"');
expect(body).toContain('aria-label="Light theme"');
expect(body).toContain('aria-pressed="true"');
expect(body).toContain('class="theme-toggle__label"');
expect(body).toContain('class="theme-toggle__switch"');

View file

@ -862,13 +862,34 @@
}
}
@media (max-width: 780px) {
@media (max-width: 1180px), (max-height: 840px) {
.dashboard-frame {
grid-template-rows: auto auto auto auto;
height: auto;
min-height: 100vh;
overflow: visible;
}
.dashboard-frame__panels {
overflow: visible;
}
}
@media (max-width: 1180px) {
.dashboard-frame__header {
grid-template-columns: 1fr;
}
.dashboard-frame__panels {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.dashboard-frame__panels .service-panel[data-layout="grid"] .panel__body {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 780px) {
.dashboard-frame__header,
.dashboard-frame__title,
.dashboard-header,

View file

@ -109,6 +109,61 @@ test.describe("dashboard page QA gate", () => {
expect(metrics.clippedItems).toEqual([]);
});
test("keeps intermediate viewports scrollable without horizontal clipping", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
for (const viewport of [
{ width: 900, height: 956 },
{ width: 1024, height: 768 },
]) {
await page.setViewportSize(viewport);
await page.goto("/");
await waitForDashboardReady(page);
const metrics = await page.evaluate(() => {
const footer = document.querySelector("[data-model-id='footer-status']");
const trackedElements = [
document.querySelector(".dashboard-frame__header"),
...document.querySelectorAll(".telemetry-card"),
...document.querySelectorAll(".service-panel"),
document.querySelector("[data-model-id='runtime-health']"),
footer,
].filter((element): element is Element => Boolean(element));
const footerRect = footer?.getBoundingClientRect();
const clippedRight = trackedElements
.map((element) => {
const rect = element.getBoundingClientRect();
return {
id: element.getAttribute("data-model-id") || element.className,
right: rect.right,
width: rect.width,
};
})
.filter((item) => item.right > window.innerWidth + 1 || item.width <= 0);
return {
clippedRight,
footerBottomInDocument: (footerRect?.bottom ?? 0) + window.scrollY,
frameOverflow: window.getComputedStyle(
document.querySelector(".dashboard-frame") as Element,
).overflow,
scrollHeight: document.documentElement.scrollHeight,
scrollWidth: document.documentElement.scrollWidth,
};
});
expect(metrics.scrollWidth).toBeLessThanOrEqual(viewport.width);
expect(metrics.scrollHeight).toBeGreaterThanOrEqual(
Math.ceil(metrics.footerBottomInDocument),
);
expect(metrics.frameOverflow).not.toBe("hidden");
expect(metrics.clippedRight).toEqual([]);
}
});
test("renders bookmark rows as a compact divider list", async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
@ -210,10 +265,9 @@ test.describe("dashboard page QA gate", () => {
await expect(page.getByRole("main")).toHaveCount(1);
await page.keyboard.press("Tab");
const themeToggle = page.getByRole("button", {
name: "Switch to light theme",
});
const themeToggle = page.getByRole("button", { name: "Light theme" });
await expect(themeToggle).toBeFocused();
await expect(themeToggle).toHaveAttribute("aria-pressed", "false");
const themeFocusBoxShadow = await themeToggle.evaluate((element) => {
return window.getComputedStyle(element).boxShadow;
});
@ -247,8 +301,12 @@ test.describe("dashboard page QA gate", () => {
await page.goto("/");
await waitForDashboardReady(page);
await page.getByRole("button", { name: "Switch to light theme" }).click();
await page.getByRole("button", { name: "Light theme" }).click();
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
"aria-pressed",
"true",
);
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
@ -291,15 +349,17 @@ test.describe("dashboard page QA gate", () => {
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "dark");
const switchToLight = page.getByRole("button", {
name: "Switch to light theme",
name: "Light theme",
});
await expect(switchToLight).toBeVisible();
await expect(switchToLight).toHaveAttribute("aria-pressed", "false");
await switchToLight.click();
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
await expect(
page.getByRole("button", { name: "Switch to dark theme" }),
).toBeVisible();
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
"aria-pressed",
"true",
);
await page.reload();
await waitForDashboardReady(page);

View file

@ -21,8 +21,12 @@ test.describe("Storybook theme QA", () => {
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
await expect(
page.getByRole("button", { name: "Switch to dark theme" }),
page.getByRole("button", { name: "Light theme" }),
).toBeVisible();
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
"aria-pressed",
"true",
);
await expect(page.locator("body")).toHaveCSS(
"background-color",
"rgb(238, 242, 231)",