mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-17 15:24:40 +01:00
sessions: refine new chat empty state layout and picker styling (#304529)
* Update chat view layout to bottom-dock controls and adjust styling for improved user experience * Update chat picker styles for improved readability and consistency * Reduce font sizes for chat picker labels to enhance visual consistency * Reduce font sizes for chat picker labels to improve visual consistency * Remove unused header and letterpress elements from chat welcome widget * Center align chat controls and local mode picker for improved layout consistency * Center chat layout and adjust alignment for improved consistency * Enhance workspace picker accessibility with ARIA attributes and improve dropdown interaction * Refactor project picker styles for improved layout and consistency * Remove left margin from workspace picker dropdown label for improved alignment * Simplify dropdown chevron transform for workspace picker * Update new-chat empty state in sessions: remove watermark, center controls, and restyle workspace picker * Refactor chat welcome widget: rename bottom-docked controls to welcome content and update related DOM appends * Update src/vs/sessions/contrib/chat/browser/workspacePicker.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * style: update chat picker styles and add icon rendering for workspace selection * fix: update workspace selection labels for clarity * fix: enhance workspace selection prompt for clarity and render option group pickers on selection * fix: add onDidChangeSelection event to workspace picker for improved selection handling * fix: trigger selection change event when deselecting workspace in picker * fix: hide action widget when clearing workspace selection --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -645,6 +645,7 @@ interface IPartVisibilityState {
|
||||
|------|--------|
|
||||
| 2026-03-26 | Updated the sessions sidebar appear animation so only the body content (`.part.sidebar > .content`) slides/fades in during reveal while the sidebar title/header and footer remain fixed |
|
||||
| 2026-03-25 | Updated Sessions view documentation to reflect the refactored `SessionsView` implementation in `contrib/sessions/browser/views/sessionsView.ts` and documented the left-aligned "+ Session" sidebar action with its inline keybinding hint |
|
||||
| 2026-03-24 | Updated the sessions new-chat empty state: removed the watermark, vertically centered the empty-state controls block, restyled the workspace picker as an inline `New session in {dropdown}` title row aligned to the chat input, and tuned empty-state dropdown icon/chevron and local-mode spacing for the final visual polish. |
|
||||
| 2026-03-02 | Fixed macOS sidebar traffic light spacer to only render with custom titlebar; added `!hasNativeTitlebar()` guard to `SidebarPart.createTitleArea()` so the 70px spacer is not created when using native titlebar (traffic lights are in the OS title bar, not overlapping the sidebar) |
|
||||
| 2026-02-20 | Replaced custom `EditorModal` with standard `ModalEditorPart` via `MODAL_GROUP`; main editor part created but hidden; changed `workbench.editor.useModal` from boolean to enum (`off`/`some`/`all`); sessions config uses `all`; removed `editorModal.ts` and editor modal CSS |
|
||||
| 2026-02-17 | Added `-webkit-app-region: drag` to sidebar title area so it can be used to drag the window; interactive children (actions, composite bar, labels) marked `no-drag`; CSS rules scoped to `.agent-sessions-workbench` in `parts/media/sidebarPart.css` |
|
||||
|
||||
@@ -12,48 +12,15 @@
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
padding: 0 10px 48px 10px;
|
||||
padding: 16px 16px 20px 16px;
|
||||
container-type: size;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chat-full-welcome.revealed {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.chat-full-welcome-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Watermark letterpress */
|
||||
.chat-full-welcome-letterpress {
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
aspect-ratio: 1/1;
|
||||
background-image: url('./letterpress-sessions-dark.svg');
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.vs .chat-full-welcome-letterpress,
|
||||
.hc-light .chat-full-welcome-letterpress {
|
||||
background-image: url('./letterpress-sessions-light.svg');
|
||||
}
|
||||
|
||||
@container (max-height: 350px) {
|
||||
.chat-full-welcome-letterpress {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Input slot */
|
||||
.chat-full-welcome-inputSlot {
|
||||
width: 100%;
|
||||
@@ -73,7 +40,7 @@
|
||||
display: none;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 0 0 24px 0;
|
||||
margin: 0 0 12px 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -100,13 +67,19 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.chat-full-welcome-content {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* Local mode picker (Workspace / Worktree) below input */
|
||||
.chat-full-welcome-local-mode {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin-top: 8px;
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
flex-direction: row;
|
||||
@@ -167,7 +140,8 @@
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
@@ -177,34 +151,46 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Prominent project picker button */
|
||||
.chat-full-welcome-pickers-label {
|
||||
font-size: 18px;
|
||||
line-height: 1.25;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Project picker in inline title row */
|
||||
.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label {
|
||||
height: auto;
|
||||
padding: 8px 20px;
|
||||
font-size: 15px;
|
||||
border: 1px solid var(--vscode-button-border);
|
||||
background-color: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border-radius: 6px;
|
||||
padding: 4px;
|
||||
font-size: 18px;
|
||||
line-height: 1.25;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--vscode-foreground);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label:hover {
|
||||
background-color: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
|
||||
.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .codicon {
|
||||
font-size: 18px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.sessions-chat-picker-slot.sessions-chat-project-picker .action-label .codicon-chevron-down {
|
||||
margin-right: 0;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
background-color: var(--vscode-toolbar-hoverBackground);
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .sessions-chat-dropdown-label {
|
||||
font-size: 15px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label > .codicon:not(.sessions-chat-dropdown-chevron) {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .sessions-chat-dropdown-chevron {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
margin-left: 6px;
|
||||
line-height: 1;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.sessions-chat-dropdown-label {
|
||||
@@ -290,8 +276,13 @@
|
||||
}
|
||||
|
||||
.sessions-chat-picker-slot .action-label .codicon-chevron-down {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
margin-left: 6px;
|
||||
line-height: 1;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.sessions-chat-picker-slot .action-label .chat-session-option-label {
|
||||
|
||||
@@ -138,6 +138,9 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
this._sessionTypePicker = this._register(this.instantiationService.createInstance(SessionTypePicker));
|
||||
|
||||
// When a workspace is selected, create a new session
|
||||
this._register(this._workspacePicker.onDidChangeSelection(() => {
|
||||
this._renderOptionGroupPickers();
|
||||
}));
|
||||
this._register(this._workspacePicker.onDidSelectWorkspace(async (workspace) => {
|
||||
await this._onWorkspaceSelected(workspace);
|
||||
this._focusEditor();
|
||||
@@ -168,15 +171,14 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
|
||||
const welcomeElement = dom.append(wrapper, dom.$('.chat-full-welcome'));
|
||||
|
||||
// Watermark letterpress
|
||||
const header = dom.append(welcomeElement, dom.$('.chat-full-welcome-header'));
|
||||
dom.append(header, dom.$('.chat-full-welcome-letterpress'));
|
||||
// Main empty-state content area (folder picker, input, local mode controls)
|
||||
const welcomeContent = dom.append(welcomeElement, dom.$('.chat-full-welcome-content'));
|
||||
|
||||
// Option group pickers (above the input)
|
||||
this._pickersContainer = dom.append(welcomeElement, dom.$('.chat-full-welcome-pickers-container'));
|
||||
this._pickersContainer = dom.append(welcomeContent, dom.$('.chat-full-welcome-pickers-container'));
|
||||
|
||||
// Input slot
|
||||
this._inputSlot = dom.append(welcomeElement, dom.$('.chat-full-welcome-inputSlot'));
|
||||
this._inputSlot = dom.append(welcomeContent, dom.$('.chat-full-welcome-inputSlot'));
|
||||
|
||||
// Input area inside the input slot
|
||||
const inputArea = dom.$('.sessions-chat-input-area');
|
||||
@@ -193,7 +195,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
this._inputSlot.appendChild(inputArea);
|
||||
|
||||
// Below-input row: session type picker, permission control, spacer, repository config (right)
|
||||
const belowInputRow = dom.append(welcomeElement, dom.$('.chat-full-welcome-local-mode'));
|
||||
const belowInputRow = dom.append(welcomeContent, dom.$('.chat-full-welcome-local-mode'));
|
||||
this._sessionTypePicker.render(belowInputRow);
|
||||
const controlContainer = dom.append(belowInputRow, dom.$('.sessions-chat-control-toolbar'));
|
||||
this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, controlContainer, Menus.NewSessionControl, {
|
||||
@@ -438,6 +440,10 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
dom.clearNode(this._pickersContainer);
|
||||
|
||||
const pickersRow = dom.append(this._pickersContainer, dom.$('.chat-full-welcome-pickers'));
|
||||
const pickersLabel = dom.append(pickersRow, dom.$('.chat-full-welcome-pickers-label'));
|
||||
pickersLabel.textContent = this._workspacePicker.selectedProject
|
||||
? localize('newSessionIn', "New session in")
|
||||
: localize('newSessionChooseWorkspace', "Start by picking");
|
||||
|
||||
// Project picker (unified folder + repo picker)
|
||||
this._workspacePicker.render(pickersRow);
|
||||
|
||||
@@ -64,6 +64,8 @@ export class WorkspacePicker extends Disposable {
|
||||
|
||||
private readonly _onDidSelectWorkspace = this._register(new Emitter<IWorkspaceSelection>());
|
||||
readonly onDidSelectWorkspace: Event<IWorkspaceSelection> = this._onDidSelectWorkspace.event;
|
||||
private readonly _onDidChangeSelection = this._register(new Emitter<void>());
|
||||
readonly onDidChangeSelection: Event<void> = this._onDidChangeSelection.event;
|
||||
|
||||
private _selectedWorkspace: IWorkspaceSelection | undefined;
|
||||
|
||||
@@ -105,6 +107,7 @@ export class WorkspacePicker extends Disposable {
|
||||
if (restored) {
|
||||
this._selectedWorkspace = restored;
|
||||
this._updateTriggerLabel();
|
||||
this._onDidChangeSelection.fire();
|
||||
this._onDidSelectWorkspace.fire(restored);
|
||||
}
|
||||
}
|
||||
@@ -124,6 +127,8 @@ export class WorkspacePicker extends Disposable {
|
||||
const trigger = dom.append(slot, dom.$('a.action-label'));
|
||||
trigger.tabIndex = 0;
|
||||
trigger.role = 'button';
|
||||
trigger.setAttribute('aria-haspopup', 'listbox');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
this._triggerElement = trigger;
|
||||
|
||||
this._updateTriggerLabel();
|
||||
@@ -164,10 +169,16 @@ export class WorkspacePicker extends Disposable {
|
||||
this._selectProject(item.selection);
|
||||
}
|
||||
},
|
||||
onHide: () => { triggerElement.focus(); },
|
||||
onHide: () => {
|
||||
triggerElement.setAttribute('aria-expanded', 'false');
|
||||
triggerElement.focus();
|
||||
},
|
||||
};
|
||||
|
||||
const listOptions = showFilter ? { showFilter: true, filterPlaceholder: localize('workspacePicker.filter', "Search Workspaces..."), reserveSubmenuSpace: false } : { reserveSubmenuSpace: false };
|
||||
const listOptions = showFilter
|
||||
? { showFilter: true, filterPlaceholder: localize('workspacePicker.filter', "Search Workspaces..."), reserveSubmenuSpace: false }
|
||||
: { reserveSubmenuSpace: false };
|
||||
triggerElement.setAttribute('aria-expanded', 'true');
|
||||
|
||||
this.actionWidgetService.show<IWorkspacePickerItem>(
|
||||
'workspacePicker',
|
||||
@@ -197,12 +208,14 @@ export class WorkspacePicker extends Disposable {
|
||||
* Clears the selected project.
|
||||
*/
|
||||
clearSelection(): void {
|
||||
this.actionWidgetService.hide();
|
||||
this._selectedWorkspace = undefined;
|
||||
// Clear checked state from all recents
|
||||
const recents = this._getStoredRecentWorkspaces();
|
||||
const updated = recents.map(p => ({ ...p, checked: false }));
|
||||
this.storageService.store(STORAGE_KEY_RECENT_WORKSPACES, JSON.stringify(updated), StorageScope.PROFILE, StorageTarget.MACHINE);
|
||||
this._updateTriggerLabel();
|
||||
this._onDidChangeSelection.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,6 +231,7 @@ export class WorkspacePicker extends Disposable {
|
||||
this._selectedWorkspace = selection;
|
||||
this._persistSelectedWorkspace(selection);
|
||||
this._updateTriggerLabel();
|
||||
this._onDidChangeSelection.fire();
|
||||
if (fireEvent) {
|
||||
this._onDidSelectWorkspace.fire(selection);
|
||||
}
|
||||
@@ -367,13 +381,13 @@ export class WorkspacePicker extends Disposable {
|
||||
|
||||
dom.clearNode(this._triggerElement);
|
||||
const workspace = this._selectedWorkspace?.workspace;
|
||||
const label = workspace ? workspace.label : localize('pickWorkspace', "Pick a Workspace");
|
||||
const label = workspace ? workspace.label : localize('pickWorkspace', "a workspace");
|
||||
const icon = workspace ? workspace.icon : Codicon.project;
|
||||
|
||||
dom.append(this._triggerElement, renderIcon(icon));
|
||||
const labelSpan = dom.append(this._triggerElement, dom.$('span.sessions-chat-dropdown-label'));
|
||||
labelSpan.textContent = label;
|
||||
dom.append(this._triggerElement, renderIcon(Codicon.chevronDown));
|
||||
dom.append(this._triggerElement, renderIcon(Codicon.chevronDown)).classList.add('sessions-chat-dropdown-chevron');
|
||||
}
|
||||
|
||||
private _isSelectedWorkspace(selection: IWorkspaceSelection): boolean {
|
||||
@@ -521,8 +535,10 @@ export class WorkspacePicker extends Disposable {
|
||||
|
||||
// Clear current selection if it was the removed workspace
|
||||
if (this._isSelectedWorkspace(selection)) {
|
||||
this.actionWidgetService.hide();
|
||||
this._selectedWorkspace = undefined;
|
||||
this._updateTriggerLabel();
|
||||
this._onDidChangeSelection.fire();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
Reference in New Issue
Block a user