From 9a1e226c629746206d0a0110664ebbdf09aa2e67 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 31 Mar 2026 14:31:46 +0100 Subject: [PATCH] Enhance CI Status Widget: Add collapse/expand functionality and update styles Co-authored-by: Copilot --- .../contrib/changes/browser/changesView.ts | 23 ++++++++- .../contrib/changes/browser/ciStatusWidget.ts | 48 ++++++++++++++++++- .../changes/browser/media/ciStatusWidget.css | 27 +++++++++-- 3 files changed, 89 insertions(+), 9 deletions(-) diff --git a/src/vs/sessions/contrib/changes/browser/changesView.ts b/src/vs/sessions/contrib/changes/browser/changesView.ts index 5d1466878e2..985b8a445db 100644 --- a/src/vs/sessions/contrib/changes/browser/changesView.ts +++ b/src/vs/sessions/contrib/changes/browser/changesView.ts @@ -644,8 +644,8 @@ export class ChangesViewPane extends ViewPane { const ciWidget = this.ciStatusWidget; const ciPane: IView = { element: ciElement, - minimumSize: ciMinHeight, - maximumSize: Number.POSITIVE_INFINITY, + get minimumSize() { return ciWidget.collapsed ? CIStatusWidget.HEADER_HEIGHT : ciMinHeight; }, + get maximumSize() { return ciWidget.collapsed ? CIStatusWidget.HEADER_HEIGHT : Number.POSITIVE_INFINITY; }, onDidChange: Event.map(this.ciStatusWidget.onDidChangeHeight, () => undefined), layout: (height) => { ciElement.style.height = `${height}px`; @@ -668,6 +668,25 @@ export class ChangesViewPane extends ViewPane { // Initially hide CI pane until checks arrive this.splitView.setViewVisible(1, false); + let savedCIPaneHeight = CIStatusWidget.HEADER_HEIGHT + CIStatusWidget.PREFERRED_BODY_HEIGHT; + this._register(this.ciStatusWidget.onDidToggleCollapsed(collapsed => { + if (!this.splitView || !this.ciStatusWidget) { + return; + } + if (collapsed) { + // Save current size before collapsing + const currentSize = this.splitView.getViewSize(1); + if (currentSize > CIStatusWidget.HEADER_HEIGHT) { + savedCIPaneHeight = currentSize; + } + this.splitView.resizeView(1, CIStatusWidget.HEADER_HEIGHT); + } else { + // Restore saved size on expand + this.splitView.resizeView(1, savedCIPaneHeight); + } + this.layoutSplitView(); + })); + this._register(this.ciStatusWidget.onDidChangeHeight(() => { if (!this.splitView || !this.ciStatusWidget) { return; diff --git a/src/vs/sessions/contrib/changes/browser/ciStatusWidget.ts b/src/vs/sessions/contrib/changes/browser/ciStatusWidget.ts index f83c243b5b2..4d21fa58b24 100644 --- a/src/vs/sessions/contrib/changes/browser/ciStatusWidget.ts +++ b/src/vs/sessions/contrib/changes/browser/ciStatusWidget.ts @@ -168,9 +168,14 @@ export class CIStatusWidget extends Disposable { private readonly _onDidChangeHeight = this._register(new Emitter()); readonly onDidChangeHeight = this._onDidChangeHeight.event; + private readonly _onDidToggleCollapsed = this._register(new Emitter()); + readonly onDidToggleCollapsed = this._onDidToggleCollapsed.event; + private _checkCount = 0; + private _collapsed = false; private _model: GitHubPullRequestCIModel | undefined; private _sessionResource: URI | undefined; + private readonly _chevronNode: HTMLElement; get element(): HTMLElement { return this._domNode; @@ -181,6 +186,9 @@ export class CIStatusWidget extends Disposable { if (this._checkCount === 0) { return 0; } + if (this._collapsed) { + return CIStatusWidget.HEADER_HEIGHT; + } return CIStatusWidget.HEADER_HEIGHT + this._checkCount * CICheckListDelegate.ITEM_HEIGHT; } @@ -189,6 +197,11 @@ export class CIStatusWidget extends Disposable { return this._checkCount > 0; } + /** Whether the body is collapsed (header-only). */ + get collapsed(): boolean { + return this._collapsed; + } + constructor( container: HTMLElement, @IOpenerService private readonly _openerService: IOpenerService, @@ -201,18 +214,28 @@ export class CIStatusWidget extends Disposable { this._domNode = dom.append(container, $('.ci-status-widget')); this._domNode.style.display = 'none'; - // Header (always visible) + // Header (always visible, click to collapse/expand) this._headerNode = dom.append(this._domNode, $('.ci-status-widget-header')); this._titleNode = dom.append(this._headerNode, $('.ci-status-widget-title')); this._titleLabelNode = dom.append(this._titleNode, $('.ci-status-widget-title-label')); this._titleLabelNode.textContent = localize('ci.checksLabel', "PR Checks"); - this._countsNode = dom.append(this._titleNode, $('.ci-status-widget-counts')); + this._countsNode = dom.append(this._headerNode, $('.ci-status-widget-counts')); this._headerActionBarContainer = dom.append(this._headerNode, $('.ci-status-widget-header-actions')); this._headerActionBar = this._register(new ActionBar(this._headerActionBarContainer)); this._register(dom.addDisposableListener(this._headerActionBarContainer, dom.EventType.CLICK, e => { e.preventDefault(); e.stopPropagation(); })); + this._chevronNode = dom.append(this._headerNode, $('.group-chevron')); + this._chevronNode.classList.add(...ThemeIcon.asClassNameArray(Codicon.chevronDown)); + + this._register(dom.addDisposableListener(this._headerNode, dom.EventType.CLICK, e => { + // Don't toggle when clicking the action bar + if (dom.isAncestor(e.target as HTMLElement, this._headerActionBarContainer)) { + return; + } + this._toggleCollapsed(); + })); // Body (list of checks) this._bodyNode = dom.append(this._domNode, $('.ci-status-widget-body')); @@ -344,9 +367,30 @@ export class CIStatusWidget extends Disposable { * Called by the parent view after computing available space. */ layout(height: number): void { + if (this._collapsed) { + this._bodyNode.style.display = 'none'; + return; + } + this._bodyNode.style.display = ''; this._list.layout(height); } + private _toggleCollapsed(): void { + this._collapsed = !this._collapsed; + this._updateChevron(); + this._onDidToggleCollapsed.fire(this._collapsed); + this._onDidChangeHeight.fire(); + } + + private _updateChevron(): void { + this._chevronNode.className = 'group-chevron'; + this._chevronNode.classList.add( + ...ThemeIcon.asClassNameArray( + this._collapsed ? Codicon.chevronRight : Codicon.chevronDown + ) + ); + } + private _renderBody(checks: readonly ICICheckListItem[]): void { this._list.splice(0, this._list.length, checks); } diff --git a/src/vs/sessions/contrib/changes/browser/media/ciStatusWidget.css b/src/vs/sessions/contrib/changes/browser/media/ciStatusWidget.css index 199b1b933ed..6a635725289 100644 --- a/src/vs/sessions/contrib/changes/browser/media/ciStatusWidget.css +++ b/src/vs/sessions/contrib/changes/browser/media/ciStatusWidget.css @@ -19,9 +19,28 @@ display: flex; align-items: center; gap: 6px; - padding: 2px 0; + padding: 8px 0; min-height: 22px; font-weight: 500; + cursor: pointer; + user-select: none; +} + +/* Chevron — right-aligned, visible on hover only */ +.ci-status-widget-header .group-chevron { + flex-shrink: 0; + width: 16px; + height: 16px; + display: none; + align-items: center; + justify-content: center; + font-size: 12px; + opacity: 0.7; +} + +.ci-status-widget-header:hover .group-chevron, +.ci-status-widget-header:focus-within .group-chevron { + display: flex; } /* Title - single line, overflow ellipsis */ @@ -47,12 +66,11 @@ gap: 8px; flex-shrink: 0; margin-left: auto; - padding-right: 8px; } .ci-status-widget.has-fix-actions:hover .ci-status-widget-counts, .ci-status-widget.has-fix-actions:focus-within .ci-status-widget-counts { - visibility: hidden; + display: none; } .ci-status-widget-count-badge { @@ -84,8 +102,7 @@ } .ci-status-widget-header-actions { - position: absolute; - right: 0; + flex-shrink: 0; display: none; align-items: center; }