Skip to content

Commit 49ff54e

Browse files
authored
refactor(ui): unify chat icon buttons on one borderless design and align top chrome rows (#104574)
* refactor(ui): unify chat icon buttons on the compact boxed design * refactor(ui): keep chat icon buttons borderless and align top chrome rows
1 parent 5d0e9ce commit 49ff54e

9 files changed

Lines changed: 52 additions & 57 deletions

File tree

ui/src/e2e/chat-flow.e2e.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,7 @@ describeControlUiE2e("Control UI mocked Gateway E2E", () => {
10451045

10461046
try {
10471047
await page.goto(`${server.baseUrl}chat`);
1048-
await page.locator(".chat-workspace-open").click();
1048+
await page.locator(".chat-workspace-toggle").click();
10491049
await page.getByText("AGENTS.md").waitFor({ timeout: 10_000 });
10501050

10511051
await page.getByRole("button", { name: "Copy path" }).click();
@@ -1116,7 +1116,7 @@ describeControlUiE2e("Control UI mocked Gateway E2E", () => {
11161116
await page.goto(`${server.baseUrl}chat`);
11171117
// Collapsed rails render nothing; the floating opener (with the
11181118
// changed-file badge) is the only pointer affordance.
1119-
const opener = page.locator(".chat-workspace-open");
1119+
const opener = page.locator(".chat-workspace-toggle");
11201120
await opener.waitFor({ timeout: 10_000 });
11211121
expect(await gateway.getRequests("sessions.files.list")).toHaveLength(0);
11221122
expect(await page.locator(".chat-workspace-rail").count()).toBe(0);
@@ -1188,7 +1188,7 @@ describeControlUiE2e("Control UI mocked Gateway E2E", () => {
11881188

11891189
try {
11901190
await page.goto(`${server.baseUrl}chat`);
1191-
await page.locator(".chat-workspace-open").click();
1191+
await page.locator(".chat-workspace-toggle").click();
11921192
await page.locator(".chat-workspace-rail__file-name", { hasText: "file-60.ts" }).waitFor({
11931193
timeout: 10_000,
11941194
});

ui/src/pages/chat/chat-pane.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,14 +1208,14 @@ class ChatPane extends OpenClawLightDomElement {
12081208
drag-and-drop. -->
12091209
<span class="chat-pane__session-title" title=${this.paneTitle}>${this.paneTitle}</span>
12101210
<div class="chat-pane__actions">
1211-
${renderSessionDiffToggle(sessionWorkspace, "pane-header")}
1212-
${renderBackgroundTasksToggle(backgroundTasks, "pane-header")}
1213-
${renderSessionWorkspaceToggle(sessionWorkspace, "pane-header")}
1211+
${renderSessionDiffToggle(sessionWorkspace)}
1212+
${renderBackgroundTasksToggle(backgroundTasks)}
1213+
${renderSessionWorkspaceToggle(sessionWorkspace)}
12141214
${!this.narrow
12151215
? html`
12161216
<openclaw-tooltip .content=${t("chat.splitView.splitDown")}>
12171217
<button
1218-
class="btn btn--ghost btn--icon"
1218+
class="btn btn--ghost btn--icon chat-icon-btn"
12191219
type="button"
12201220
aria-label=${t("chat.splitView.splitDown")}
12211221
@click=${() => this.onSplitDown?.(this.paneId)}
@@ -1225,7 +1225,7 @@ class ChatPane extends OpenClawLightDomElement {
12251225
</openclaw-tooltip>
12261226
<openclaw-tooltip .content=${t("chat.splitView.splitRight")}>
12271227
<button
1228-
class="btn btn--ghost btn--icon"
1228+
class="btn btn--ghost btn--icon chat-icon-btn"
12291229
type="button"
12301230
aria-label=${t("chat.splitView.splitRight")}
12311231
@click=${() => this.onSplitRight?.(this.paneId)}
@@ -1237,7 +1237,7 @@ class ChatPane extends OpenClawLightDomElement {
12371237
: nothing}
12381238
<openclaw-tooltip .content=${t("chat.splitView.closePane")}>
12391239
<button
1240-
class="btn btn--ghost btn--icon"
1240+
class="btn btn--ghost btn--icon chat-icon-btn"
12411241
type="button"
12421242
aria-label=${t("chat.splitView.closePane")}
12431243
@click=${() => this.onClosePane?.(this.paneId)}

ui/src/pages/chat/chat-view.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,7 +1463,7 @@ describe("chat composer workbench", () => {
14631463

14641464
// A collapsed rail renders nothing — no icon strip in the layout.
14651465
expect(container.querySelector(".chat-workspace-rail")).toBeNull();
1466-
const toggle = container.querySelector<HTMLButtonElement>(".chat-workspace-open");
1466+
const toggle = container.querySelector<HTMLButtonElement>(".chat-workspace-toggle");
14671467
expect(toggle?.getAttribute("aria-label")).toBe("Show session files");
14681468
expect(toggle?.getAttribute("aria-expanded")).toBe("false");
14691469
expect(toggle?.getAttribute("aria-keyshortcuts")).toBe("Meta+Shift+B");
@@ -1523,7 +1523,7 @@ describe("chat composer workbench", () => {
15231523
},
15241524
});
15251525

1526-
expect(container.querySelector(".chat-workspace-open")).toBeNull();
1526+
expect(container.querySelector(".chat-workspace-toggle")).toBeNull();
15271527
expect(container.querySelector(".chat-workspace-rail")).toBeNull();
15281528
});
15291529

ui/src/pages/chat/chat-view.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ export function renderChat(props: ChatProps) {
434434
? html`
435435
<openclaw-tooltip .content=${t("chat.splitView.open")}>
436436
<button
437-
class="btn btn--sm btn--icon chat-open-split-view"
437+
class="btn btn--ghost btn--icon chat-icon-btn chat-open-split-view"
438438
type="button"
439439
aria-label=${t("chat.splitView.open")}
440440
@click=${props.onOpenSplitView}
@@ -445,13 +445,13 @@ export function renderChat(props: ChatProps) {
445445
`
446446
: nothing}
447447
${props.sessionWorkspace?.collapsed
448-
? renderSessionDiffToggle(props.sessionWorkspace, "floating")
448+
? renderSessionDiffToggle(props.sessionWorkspace)
449449
: nothing}
450450
${props.backgroundTasks?.collapsed
451-
? renderBackgroundTasksToggle(props.backgroundTasks, "floating")
451+
? renderBackgroundTasksToggle(props.backgroundTasks)
452452
: nothing}
453453
${props.sessionWorkspace?.collapsed
454-
? renderSessionWorkspaceToggle(props.sessionWorkspace, "floating")
454+
? renderSessionWorkspaceToggle(props.sessionWorkspace)
455455
: nothing}
456456
</div>
457457
`

ui/src/pages/chat/components/chat-background-tasks.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,6 @@ export function backgroundTasksActiveCount(props: BackgroundTasksProps | undefin
308308

309309
export function renderBackgroundTasksToggle(
310310
backgroundTasks: BackgroundTasksProps | undefined,
311-
variant: "pane-header" | "floating",
312311
): TemplateResult | typeof nothing {
313312
if (!backgroundTasks) {
314313
return nothing;
@@ -319,9 +318,7 @@ export function renderBackgroundTasksToggle(
319318
return html`
320319
<openclaw-tooltip .content=${label}>
321320
<button
322-
class="${variant === "pane-header"
323-
? "btn btn--ghost btn--icon"
324-
: "btn btn--sm btn--icon chat-tasks-open"} chat-tasks-toggle"
321+
class="btn btn--ghost btn--icon chat-icon-btn chat-tasks-toggle"
325322
type="button"
326323
aria-label=${label}
327324
aria-expanded=${String(expanded)}

ui/src/pages/chat/components/chat-session-workspace.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,6 @@ export function sessionWorkspaceModifiedCount(
706706
* this button is the only pointer affordance (⇧⌘B still works). */
707707
export function renderSessionWorkspaceToggle(
708708
sessionWorkspace: SessionWorkspaceProps | undefined,
709-
variant: "pane-header" | "floating",
710709
): TemplateResult | typeof nothing {
711710
if (!sessionWorkspace) {
712711
return nothing;
@@ -717,9 +716,7 @@ export function renderSessionWorkspaceToggle(
717716
return html`
718717
<openclaw-tooltip .content=${`${label} (⇧⌘B)`}>
719718
<button
720-
class="${variant === "pane-header"
721-
? "btn btn--ghost btn--icon"
722-
: "btn btn--sm btn--icon chat-workspace-open"} chat-workspace-toggle"
719+
class="btn btn--ghost btn--icon chat-icon-btn chat-workspace-toggle"
723720
type="button"
724721
aria-label=${label}
725722
aria-keyshortcuts="Meta+Shift+B"
@@ -741,7 +738,6 @@ export function renderSessionWorkspaceToggle(
741738
* gateway does not advertise sessions.diff. */
742739
export function renderSessionDiffToggle(
743740
sessionWorkspace: SessionWorkspaceProps | undefined,
744-
variant: "pane-header" | "floating",
745741
): TemplateResult | typeof nothing {
746742
if (!sessionWorkspace?.onOpenDiff) {
747743
return nothing;
@@ -750,9 +746,7 @@ export function renderSessionDiffToggle(
750746
return html`
751747
<openclaw-tooltip .content=${label}>
752748
<button
753-
class="${variant === "pane-header"
754-
? "btn btn--ghost btn--icon"
755-
: "btn btn--sm btn--icon chat-diff-open"} chat-session-diff-toggle"
749+
class="btn btn--ghost btn--icon chat-icon-btn chat-session-diff-toggle"
756750
type="button"
757751
aria-label=${label}
758752
@click=${sessionWorkspace.onOpenDiff}

ui/src/styles/chat/sidebar.css

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,29 +55,35 @@
5555
free as a window-drag surface in the native macOS app. */
5656
.chat-floating-toggles {
5757
position: absolute;
58-
top: 10px;
59-
right: 10px;
58+
/* 8px top + 2px row padding keeps the 28px buttons on the shared 24px
59+
chrome centerline (sidebar brand row, pane-header actions). */
60+
top: 8px;
61+
right: 8px;
6062
z-index: 12;
6163
display: flex;
62-
gap: 6px;
64+
/* Same tight spacing as .chat-pane__actions so both rows read identical. */
65+
gap: 2px;
66+
/* Ghost buttons carry no chrome of their own, but scrolled thread content
67+
passes under this row; a frosted wash keeps the icons legible without
68+
reading as boxed buttons. Invisible over the empty titlebar band. */
69+
padding: 2px;
70+
border-radius: var(--radius-md);
71+
background: color-mix(in srgb, var(--bg) 72%, transparent);
72+
backdrop-filter: blur(10px);
73+
-webkit-backdrop-filter: blur(10px);
6374
}
6475

65-
.chat-workspace-open,
66-
.chat-tasks-open,
67-
.chat-diff-open,
68-
.chat-open-split-view {
69-
/* Match the 28px pane-header/split-view openers; .btn--icon's
70-
min-width/height of 36px would win otherwise. */
76+
/* Compact icon button shared by the split-pane header actions and the
77+
floating toggles above: one borderless 28px design so both surfaces match.
78+
Sizing only — chrome stays ghost; .btn--icon's 36px min-width/height would
79+
win otherwise. The floating row sits in the thread's reserved titlebar band
80+
(never over content), so it needs no backdrop of its own. */
81+
.chat-icon-btn {
7182
width: 28px;
7283
min-width: 28px;
7384
height: 28px;
7485
min-height: 28px;
7586
padding: 5px;
76-
border: 1px solid color-mix(in srgb, var(--border) 88%, transparent);
77-
background: color-mix(in srgb, var(--panel) 92%, transparent);
78-
box-shadow: var(--shadow-sm);
79-
backdrop-filter: blur(10px);
80-
-webkit-backdrop-filter: blur(10px);
8187
}
8288

8389
/* The workspace rail adapts to the pane width (side column on wide panes,
@@ -86,8 +92,7 @@
8692
side-dock only, so its toggles still hide on narrow viewports until it
8793
grows a narrow presentation. */
8894
@media (max-width: 1120px) {
89-
.chat-tasks-open,
90-
.chat-pane__actions .chat-tasks-toggle {
95+
.chat-tasks-toggle {
9196
display: none;
9297
}
9398
}
@@ -1745,7 +1750,7 @@
17451750
/* ── Session diff panel (sessions.diff sidebar content) ── */
17461751

17471752
/* The floating diff opener sits in the .chat-floating-toggles row and shares
1748-
the .chat-workspace-open sizing/chrome rules near the top of this file. */
1753+
the .chat-icon-btn sizing/chrome rules near the top of this file. */
17491754

17501755
.session-diff {
17511756
display: flex;

ui/src/styles/chat/split-view.css

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,10 @@ openclaw-chat-pane {
145145
flex: 0 0 auto;
146146
align-items: center;
147147
gap: 8px;
148-
min-height: 36px;
148+
/* 48px centers the 28px actions on the app's shared 24px chrome
149+
centerline (sidebar brand row, floating toggles), so split headers
150+
line up with the sidebar across the top of the shell. */
151+
min-height: 48px;
149152
padding: 0 6px 0 12px;
150153
border-bottom: 1px solid color-mix(in srgb, var(--border) 74%, transparent);
151154
background: color-mix(in srgb, var(--panel) 62%, transparent);
@@ -175,13 +178,3 @@ openclaw-chat-pane {
175178
gap: 2px;
176179
flex: 0 0 auto;
177180
}
178-
179-
.chat-pane__actions .btn--icon {
180-
width: 28px;
181-
/* .btn--icon's min-width: 36px otherwise wins over width and stretches
182-
these into 36x28 pills. */
183-
min-width: 28px;
184-
height: 28px;
185-
min-height: 28px;
186-
padding: 5px;
187-
}

ui/src/styles/layout.css

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,10 @@ html.openclaw-native-nav .shell-nav-expand:focus-visible {
194194

195195
.settings-sidebar__header {
196196
flex-shrink: 0;
197-
padding: 14px 12px 6px;
197+
/* 8px top pad centers the 32px back button on the shared 24px chrome
198+
centerline, mirroring .sidebar-shell; macOS overrides with titlebar
199+
clearance. */
200+
padding: 8px 12px 6px;
198201
}
199202

200203
.settings-sidebar__back {
@@ -717,7 +720,10 @@ html.openclaw-native-nav .shell-nav-expand:focus-visible {
717720
flex-direction: column;
718721
min-height: 0;
719722
flex: 1;
720-
padding: 14px 10px 12px;
723+
/* 9px top pad centers the brand row's 30px icons on the app's shared
724+
24px chrome centerline (pane-header actions, floating toggles). The
725+
macOS app overrides this with its 50px titlebar clearance. */
726+
padding: 9px 10px 12px;
721727
border: none;
722728
border-radius: 0;
723729
background: transparent;

0 commit comments

Comments
 (0)