fix(ui): align menu selection checks to the right (#122158)

Aligns selector menu checks to the trailing rail so icons and labels remain stable across Control UI menus and New Session place selection.

Refs #122156.
Prepared head SHA: b1b60d1e15
Co-authored-by: Vyctor H. Brzezowski <51521767+vyctorbrzezowski@users.noreply.github.com>
Co-authored-by: Shakker <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
This commit is contained in:
Vyctor H. Brzezowski
2026-08-11 14:17:46 -03:00
committed by GitHub
parent 5c81ed5883
commit 24bbb416b5
5 changed files with 100 additions and 20 deletions
@@ -0,0 +1,80 @@
import { afterEach, describe, expect, it } from "vitest";
import "../styles.css";
import "../styles/cron.css";
import "../styles/usage.css";
import "./web-awesome.ts";
const hasBrowserLayout = !navigator.userAgent.toLowerCase().includes("jsdom");
type DropdownItemElement = HTMLElement & {
checked: boolean;
type: "checkbox" | "normal";
updateComplete: Promise<boolean>;
};
afterEach(() => {
document.body.replaceChildren();
});
function createMenuItem(label: string, checkbox: boolean, checked = false) {
const item = document.createElement("wa-dropdown-item") as DropdownItemElement;
item.type = checkbox ? "checkbox" : "normal";
item.checked = checked;
item.setAttribute("checkbox-adjacent", "");
const icon = document.createElement("span");
icon.slot = "icon";
icon.textContent = "#";
item.append(icon, label);
return item;
}
function partBounds(item: DropdownItemElement, part: string): DOMRect {
const element = item.shadowRoot?.querySelector<HTMLElement>(`[part="${part}"]`);
expect(element, `${part} part should exist`).not.toBeNull();
return element!.getBoundingClientRect();
}
describe.skipIf(!hasBrowserLayout)("menu selection check alignment", () => {
it("keeps the icon column flush left and selection checks in the trailing rail", async () => {
const menu = document.createElement("div");
menu.style.width = "240px";
const selected = createMenuItem("Selected", true, true);
const unselected = createMenuItem("Unselected", true);
const command = createMenuItem("Command", false);
const usageSelected = createMenuItem("Usage selected", true, true);
selected.className = "cron-filter-dropdown__option";
unselected.className = "cron-filter-dropdown__option";
command.className = "cron-filter-dropdown__option";
usageSelected.className = "usage-filter-option";
menu.append(selected, unselected, command, usageSelected);
document.body.append(menu);
await Promise.all([
selected.updateComplete,
unselected.updateComplete,
command.updateComplete,
usageSelected.updateComplete,
]);
expect(selected.hasAttribute("checkbox-adjacent")).toBe(true);
expect(unselected.hasAttribute("checkbox-adjacent")).toBe(true);
expect(command.hasAttribute("checkbox-adjacent")).toBe(true);
const selectedCheck = partBounds(selected, "checkmark");
const selectedLabel = partBounds(selected, "label");
expect(selectedCheck.width).toBeGreaterThan(0);
expect(selectedLabel.width).toBeGreaterThan(0);
expect(selectedCheck.left).toBeGreaterThanOrEqual(selectedLabel.right);
expect(Math.abs(selectedCheck.top - selectedLabel.top)).toBeLessThanOrEqual(2);
const unselectedCheck = partBounds(unselected, "checkmark");
const unselectedLabel = partBounds(unselected, "label");
expect(unselectedCheck.left).toBeGreaterThanOrEqual(unselectedLabel.right);
const usageCheck = partBounds(usageSelected, "checkmark");
const usageLabel = partBounds(usageSelected, "label");
expect(usageCheck.left).toBeGreaterThanOrEqual(usageLabel.right);
const iconStarts = [selected, unselected, command].map((item) => partBounds(item, "icon").left);
expect(Math.max(...iconStarts) - Math.min(...iconStarts)).toBeLessThanOrEqual(1);
});
});
+3 -3
View File
@@ -37,14 +37,14 @@ export function renderSessionMenuItem(params: SessionMenuItemOptions, submitting
?disabled=${submitting || (params.disabled ?? false)}
@click=${params.onSelect}
>
<span class="session-menu__check" aria-hidden="true"
>${params.checked ? icons.check : nothing}</span
>
${params.icon
? html`<span class="session-menu__icon" aria-hidden="true">${params.icon}</span>`
: nothing}
<span class="session-menu__text">${params.label}</span>
${params.sub ? html`<span class="session-menu__sub">${params.sub}</span>` : nothing}
<span class="session-menu__check" aria-hidden="true"
>${params.checked ? icons.check : nothing}</span
>
</button>
`;
}
-1
View File
@@ -398,7 +398,6 @@ export function renderPlaceSelect(params: {
?disabled=${params.submitting || params.pendingCloud || !params.browseAvailable}
@click=${() => params.onBrowse(browseTarget)}
>
<span class="session-menu__check" aria-hidden="true"></span>
<span class="session-menu__text">${t("newSession.browse")}</span>
<span class="new-session-page__menu-chevron" aria-hidden="true"
>${icons.chevronRight}</span
+15
View File
@@ -799,6 +799,21 @@ wa-dropdown-item[disabled] {
cursor: not-allowed;
}
/* Keep Web Awesome's checkbox rail trailing so it cannot shift menu icons and labels. */
wa-dropdown-item[checkbox-adjacent] {
padding-inline-start: 1em;
}
wa-dropdown-item[type="checkbox"]::part(checkmark) {
order: 1;
margin-inline: 0.75em 0;
}
wa-dropdown-item[type="checkbox"] {
display: flex;
align-items: center;
}
button,
input,
textarea,
+2 -16
View File
@@ -2512,8 +2512,8 @@ wa-dropdown.sidebar-customize-menu::part(menu) {
}
.sidebar-customize-menu__group-title {
margin: 6px 4px 2px;
padding: 7px 6px 4px;
margin: 6px 0 2px;
padding: 7px 8px 4px;
border-top: 1px solid color-mix(in srgb, var(--border) 72%, transparent);
color: var(--muted);
font-size: calc(10px * var(--control-ui-text-scale));
@@ -2569,20 +2569,6 @@ wa-dropdown-item.sidebar-customize-menu__item {
text-decoration: none;
}
/* Pin-editor rows: wa-dropdown flags every row [checkbox-adjacent] when the menu
holds checkbox items and tucks the shadow checkmark into a 2em start inset via
a -1.5em margin. Our compact `padding: 0 8px` above removes that inset, so
rebuild the check column here or the checkmark lands outside the row edge. */
.sidebar-pin-editor-menu .sidebar-customize-menu__item[checkbox-adjacent] {
padding-inline-start: 30px; /* 8px edge + 14px check column + 8px gutter */
}
.sidebar-pin-editor-menu .sidebar-customize-menu__item::part(checkmark) {
width: 14px;
min-width: 14px;
margin-inline: -22px 8px; /* pull back into the padding; the end margin spaces the icon */
}
/* The agent switcher marks its selection in the trailing details rail
(.session-menu__check), so the native leading checkmark is layout-only: it
reserved a column that pushed avatar rows out of the command rows' gutter. */