mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(ui): align settings search with navigation rows (#112172)
* fix(ui): align settings search with navigation * fix(ui): separate scrolled settings navigation
This commit is contained in:
@@ -198,6 +198,15 @@ function renderBlockItem(props: SettingsSidebarProps, block: SettingsSearchBlock
|
||||
`;
|
||||
}
|
||||
|
||||
function syncSettingsSearchScrollShadow(nav: HTMLElement) {
|
||||
// The nav's top padding scrolls away with its rows. Keep the fixed search
|
||||
// region visually separated once content reaches that boundary.
|
||||
nav
|
||||
.closest(".settings-sidebar")
|
||||
?.querySelector(".settings-sidebar__search")
|
||||
?.classList.toggle("settings-sidebar__search--scrolled", nav.scrollTop > 0);
|
||||
}
|
||||
|
||||
export function renderSettingsSidebar(props: SettingsSidebarProps) {
|
||||
const gatewayStatus = t("chat.gatewayStatus", {
|
||||
status: props.connected ? t("common.online") : t("common.offline"),
|
||||
@@ -255,7 +264,12 @@ export function renderSettingsSidebar(props: SettingsSidebarProps) {
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
<nav class="settings-sidebar__nav" aria-label=${t("common.settingsSections")}>
|
||||
<nav
|
||||
class="settings-sidebar__nav"
|
||||
aria-label=${t("common.settingsSections")}
|
||||
@scroll=${(event: Event) =>
|
||||
syncSettingsSearchScrollShadow(event.currentTarget as HTMLElement)}
|
||||
>
|
||||
${navigationGroups.length === 0
|
||||
? html`<p class="settings-sidebar__empty" role="status">
|
||||
${t("nav.settingsSearchNoResults")}
|
||||
|
||||
@@ -121,6 +121,109 @@ describeControlUiE2e("Control UI sidebar customization mocked Gateway E2E", () =
|
||||
await server?.close();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ mode: "standalone", webChrome: false },
|
||||
{ mode: "native web chrome", webChrome: true },
|
||||
])("aligns the settings search with navigation rows in $mode", async ({ mode, webChrome }) => {
|
||||
const context = await browser.newContext({
|
||||
locale: "en-US",
|
||||
serviceWorkers: "block",
|
||||
viewport: { height: 620, width: 1440 },
|
||||
});
|
||||
const page = await context.newPage();
|
||||
if (webChrome) {
|
||||
await page.addInitScript(() => {
|
||||
const nativeWindow = window as Window & {
|
||||
__OPENCLAW_NATIVE_WEB_CHROME__?: boolean;
|
||||
__OPENCLAW_NATIVE_HISTORY__?: { canGoBack: boolean; canGoForward: boolean };
|
||||
};
|
||||
nativeWindow["__OPENCLAW_NATIVE_WEB_CHROME__"] = true;
|
||||
nativeWindow["__OPENCLAW_NATIVE_HISTORY__"] = {
|
||||
canGoBack: false,
|
||||
canGoForward: false,
|
||||
};
|
||||
const stamp = () =>
|
||||
document.documentElement.classList.add(
|
||||
"openclaw-native-macos",
|
||||
"openclaw-native-web-chrome",
|
||||
);
|
||||
if (document.documentElement) {
|
||||
stamp();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", stamp);
|
||||
}
|
||||
});
|
||||
}
|
||||
await installMockGateway(page);
|
||||
|
||||
try {
|
||||
await page.goto(`${server.baseUrl}settings/general`);
|
||||
const settingsSidebar = page.locator(".settings-sidebar");
|
||||
const settingsSearchShell = settingsSidebar.locator(".settings-sidebar__search");
|
||||
const settingsSearchInput = settingsSidebar.locator(".settings-sidebar__search-input");
|
||||
const settingsNav = settingsSidebar.locator(".settings-sidebar__nav");
|
||||
const firstSettingsLink = settingsSidebar.locator(".settings-sidebar__item").first();
|
||||
await settingsSidebar.waitFor();
|
||||
await expect
|
||||
.poll(() =>
|
||||
page
|
||||
.locator("html")
|
||||
.evaluate((element) => element.classList.contains("openclaw-native-web-chrome")),
|
||||
)
|
||||
.toBe(webChrome);
|
||||
await captureSettingsSidebarProof(
|
||||
settingsSidebar,
|
||||
`settings-search-alignment-${mode.replaceAll(" ", "-")}.png`,
|
||||
);
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const [searchBox, firstLinkBox] = await Promise.all([
|
||||
settingsSearchShell.boundingBox(),
|
||||
firstSettingsLink.boundingBox(),
|
||||
]);
|
||||
if (!searchBox || !firstLinkBox) {
|
||||
return null;
|
||||
}
|
||||
return Math.round(searchBox.x - firstLinkBox.x);
|
||||
})
|
||||
.toBe(0);
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const [searchBox, navBox] = await Promise.all([
|
||||
settingsSearchInput.boundingBox(),
|
||||
settingsNav.boundingBox(),
|
||||
]);
|
||||
if (!searchBox || !navBox) {
|
||||
return null;
|
||||
}
|
||||
return Math.round(navBox.y - (searchBox.y + searchBox.height));
|
||||
})
|
||||
.toBe(8);
|
||||
await settingsNav.evaluate((element) => {
|
||||
element.scrollTop = Math.min(48, element.scrollHeight - element.clientHeight);
|
||||
element.dispatchEvent(new Event("scroll"));
|
||||
});
|
||||
await expect
|
||||
.poll(() =>
|
||||
settingsSearchShell.evaluate((element) =>
|
||||
element.classList.contains("settings-sidebar__search--scrolled"),
|
||||
),
|
||||
)
|
||||
.toBe(true);
|
||||
await expect
|
||||
.poll(() =>
|
||||
settingsSearchShell.evaluate((element) => getComputedStyle(element, "::after").opacity),
|
||||
)
|
||||
.toBe("1");
|
||||
await captureSettingsSidebarProof(
|
||||
settingsSidebar,
|
||||
`settings-search-scrolled-${mode.replaceAll(" ", "-")}.png`,
|
||||
);
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("pins routes, restores defaults, and persists navigation state across reloads", async () => {
|
||||
if (captureUiProofEnabled) {
|
||||
await mkdir(uiProofArtifactDir, { recursive: true });
|
||||
|
||||
@@ -326,13 +326,32 @@ html.openclaw-native-web-chrome .shell:not(.shell--mobile-nav) .sidebar-brand .s
|
||||
|
||||
.settings-sidebar__search {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 8px 21px 0;
|
||||
margin: 8px 12px 0;
|
||||
padding-bottom: 8px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.settings-sidebar__search::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset-inline: -12px;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
pointer-events: none;
|
||||
background: color-mix(in srgb, var(--border) 62%, transparent);
|
||||
box-shadow: 0 6px 12px color-mix(in srgb, var(--text) 14%, transparent);
|
||||
opacity: 0;
|
||||
transition: opacity var(--duration-fast) ease;
|
||||
}
|
||||
|
||||
.settings-sidebar__search--scrolled::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.settings-sidebar__search-icon {
|
||||
position: absolute;
|
||||
inset-inline-start: 10px;
|
||||
|
||||
Reference in New Issue
Block a user