qwen-code/packages/web-shell/client/components/ChatPane.module.css
samuelhsin 26600896d5
feat(web-shell): add split pane header action slot with overflow (#7808)
* feat(web-shell): add split pane header action slot with overflow

Let hosts render per-session actions in each split pane header, collapsing them into a … menu when the pane is too narrow.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(web-shell): add pane header actions PR screenshots

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web-shell): tighten pane header overflow measurement

Drop the per-render children effect dependency that rebuilt ResizeObserver during streaming, and reserve workspace-tag width when computing available header space.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web-shell): address pane header overflow review blockers

Mount host actions in only one tree, and wrap overflow entries as DropdownMenuItems so Radix selection and keyboard navigation work.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web-shell): keep pane header actions alive across overflow

Flatten Fragment host actions before building the overflow menu, and keep the same host instances mounted when collapsing so stateful actions are not reset.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web-shell): address pane header overflow review suggestions (#7808)

* fix(web-shell): proxy overflow clicks via action slots

Wrap host pane actions in stable slots so the overflow menu can activate interactive descendants without requiring opaque custom components to forward internal data attributes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web-shell): address overflow menu review suggestions (#7808)

* fix(web-shell): harden pane header overflow actions (#7808)

Restore the 8px gap between the built-in maximize/close controls, ignore
aria-hidden glyphs when labelling overflow items, omit non-interactive
children from the overflow menu, and document the popover constraint on
renderHeaderActions. Refreshes the design doc to match the mount-once
implementation.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-29 13:06:48 +00:00

192 lines
4.5 KiB
CSS

.pane {
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
height: 100%;
background: var(--background);
border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
}
.header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
/* On a multi-workspace pane, `--ws-accent` is set by a shared accent class
(workspaceAccent.module.css) and colorizes this divider into a per-workspace
strip; it falls back to the neutral border everywhere else, so single-
workspace panes are unchanged. */
border-bottom: 1px solid var(--ws-accent, var(--border));
background: color-mix(in srgb, var(--foreground) 3%, transparent);
flex: 0 0 auto;
}
/* The `--ws-accent` variable that colorizes the divider above and the tag below
is set by a shared accent class from workspaceAccent.module.css (also used by
the composer chip), applied to `.header` when the pane has a workspace. */
/* Which workspace this pane's session lives in — a compact colored chip at the
start of the header, shown only on a multi-workspace daemon. The dot never
shrinks, so panes stay distinguishable by color even when the name and the
session title both ellipsize on a narrow split; the full cwd is in the
native title tooltip. */
.workspaceTag {
display: inline-flex;
align-items: center;
gap: 5px;
flex: 0 1 auto;
min-width: 0;
max-width: 50%;
padding: 2px 8px 2px 6px;
border-radius: 999px;
background: color-mix(in srgb, var(--ws-accent) 14%, transparent);
color: color-mix(in srgb, var(--ws-accent) 70%, var(--foreground));
font-size: 11px;
font-weight: 600;
line-height: 1.4;
}
.workspaceTagDot {
width: 7px;
height: 7px;
flex: 0 0 7px;
border-radius: 999px;
background: var(--ws-accent);
}
.workspaceTagText {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Per-pane connection-loss indicator: each pane has its own daemon connection,
so a drop on one shouldn't silently leave stale messages with no signal. */
.connectionError {
flex: 0 0 auto;
padding: 6px 10px;
background: var(--warning-bg);
border-bottom: 1px solid var(--warning-border);
color: var(--warning-color);
font-size: 12px;
}
.connectionErrorText {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.title {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
font-weight: 600;
color: var(--foreground);
}
.headerActions {
position: relative;
flex: 0 0 auto;
display: inline-flex;
align-items: center;
gap: 4px;
min-width: 0;
}
.headerActionsInline {
display: inline-flex;
align-items: center;
gap: 4px;
min-width: 0;
}
.headerActionSlot {
display: inline-flex;
align-items: center;
}
/* Keep host actions mounted while collapsed so stateful actions survive
resize. Absolutely positioned so they do not affect flex layout. */
.headerActionsHostHidden {
position: absolute;
visibility: hidden;
pointer-events: none;
display: inline-flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
/* The built-in maximize/close controls used to be direct children of `.header`
(gap 8px); keep that spacing now that they are grouped here. */
.headerTrailing {
display: inline-flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
}
.headerOverflowPanel {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
padding: 2px;
}
.headerActionButton,
.closeButton,
.maximizeButton {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--muted-foreground);
cursor: pointer;
}
.headerActionButton:hover,
.closeButton:hover,
.maximizeButton:hover {
color: var(--foreground);
background: color-mix(in srgb, var(--foreground) 8%, transparent);
}
/* The maximized pane keeps its toggle visually "active" so it's clear which
control returns to the tiled layout. */
.maximizeButton[aria-pressed='true'] {
color: var(--foreground);
background: color-mix(in srgb, var(--foreground) 10%, transparent);
}
/* The transcript takes the remaining height and scrolls independently per pane. */
.body {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.footer {
flex: 0 0 auto;
padding: 8px;
position: relative;
}
.approval {
margin-bottom: 8px;
}