Chat customizations: Hover hints for each section (#297139)

* feat(chat): add info icon with hover description to group headers

* fix(chat): reorder count and info icon in group header renderer

* fix(chat): restructure group label and info icon layout in group header renderer

* tweak info
This commit is contained in:
Josh Spicer
2026-02-23 16:05:18 -08:00
committed by GitHub
parent 3e137f4a03
commit 92ca3edc12
2 changed files with 63 additions and 8 deletions

View File

@@ -44,6 +44,7 @@ import { ILogService } from '../../../../../platform/log/common/log.js';
import { Action, Separator } from '../../../../../base/common/actions.js';
import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';
import { ISCMService } from '../../../scm/common/scm.js';
import { IHoverService } from '../../../../../platform/hover/browser/hover.js';
const $ = DOM.$;
@@ -78,6 +79,7 @@ interface IGroupHeaderEntry {
readonly icon: ThemeIcon;
readonly count: number;
readonly isFirst: boolean;
readonly description: string;
collapsed: boolean;
}
@@ -124,7 +126,9 @@ interface IGroupHeaderTemplateData {
readonly icon: HTMLElement;
readonly label: HTMLElement;
readonly count: HTMLElement;
readonly infoIcon: HTMLElement;
readonly disposables: DisposableStore;
readonly elementDisposables: DisposableStore;
}
/**
@@ -134,19 +138,29 @@ interface IGroupHeaderTemplateData {
class GroupHeaderRenderer implements IListRenderer<IGroupHeaderEntry, IGroupHeaderTemplateData> {
readonly templateId = 'groupHeader';
constructor(
private readonly hoverService: IHoverService,
) { }
renderTemplate(container: HTMLElement): IGroupHeaderTemplateData {
const disposables = new DisposableStore();
const elementDisposables = new DisposableStore();
container.classList.add('ai-customization-group-header');
const chevron = DOM.append(container, $('.group-chevron'));
const icon = DOM.append(container, $('.group-icon'));
const label = DOM.append(container, $('.group-label'));
const labelGroup = DOM.append(container, $('.group-label-group'));
const label = DOM.append(labelGroup, $('.group-label'));
const infoIcon = DOM.append(labelGroup, $('.group-info'));
infoIcon.classList.add(...ThemeIcon.asClassNameArray(Codicon.info));
const count = DOM.append(container, $('.group-count'));
return { container, chevron, icon, label, count, disposables };
return { container, chevron, icon, label, count, infoIcon, disposables, elementDisposables };
}
renderElement(element: IGroupHeaderEntry, _index: number, templateData: IGroupHeaderTemplateData): void {
templateData.elementDisposables.clear();
// Chevron
templateData.chevron.className = 'group-chevron';
templateData.chevron.classList.add(...ThemeIcon.asClassNameArray(element.collapsed ? Codicon.chevronRight : Codicon.chevronDown));
@@ -159,12 +173,22 @@ class GroupHeaderRenderer implements IListRenderer<IGroupHeaderEntry, IGroupHead
templateData.label.textContent = element.label;
templateData.count.textContent = `${element.count}`;
// Info icon hover
templateData.elementDisposables.add(this.hoverService.setupDelayedHover(templateData.infoIcon, () => ({
content: element.description,
appearance: {
compact: true,
skipFadeInAnimation: true,
}
})));
// Collapsed state and separator for non-first groups
templateData.container.classList.toggle('collapsed', element.collapsed);
templateData.container.classList.toggle('has-previous-group', !element.isFirst);
}
disposeTemplate(templateData: IGroupHeaderTemplateData): void {
templateData.elementDisposables.dispose();
templateData.disposables.dispose();
}
}
@@ -345,6 +369,7 @@ export class AICustomizationListWidget extends Disposable {
@ILogService private readonly logService: ILogService,
@IClipboardService private readonly clipboardService: IClipboardService,
@ISCMService private readonly scmService: ISCMService,
@IHoverService private readonly hoverService: IHoverService,
) {
super();
this.element = $('.ai-customization-list-widget');
@@ -417,7 +442,7 @@ export class AICustomizationListWidget extends Disposable {
this.listContainer,
new AICustomizationListDelegate(),
[
new GroupHeaderRenderer(),
new GroupHeaderRenderer(this.hoverService),
this.instantiationService.createInstance(AICustomizationItemRenderer),
],
{
@@ -898,10 +923,10 @@ export class AICustomizationListWidget extends Disposable {
this.logService.info(`[AICustomizationListWidget] filterItems: allItems=${this.allItems.length}, matched=${totalBeforeFilter}`);
// Group items by storage
const groups: { storage: PromptsStorage; label: string; icon: ThemeIcon; items: IAICustomizationListItem[] }[] = [
{ storage: PromptsStorage.local, label: localize('workspaceGroup', "Workspace"), icon: workspaceIcon, items: [] },
{ storage: PromptsStorage.user, label: localize('userGroup', "User"), icon: userIcon, items: [] },
{ storage: PromptsStorage.extension, label: localize('extensionGroup', "Extensions"), icon: extensionIcon, items: [] },
const groups: { storage: PromptsStorage; label: string; icon: ThemeIcon; description: string; items: IAICustomizationListItem[] }[] = [
{ storage: PromptsStorage.local, label: localize('workspaceGroup', "Workspace"), icon: workspaceIcon, description: localize('workspaceGroupDescription', "Customizations stored as files in your project folder and shared with your team via version control."), items: [] },
{ storage: PromptsStorage.user, label: localize('userGroup', "User"), icon: userIcon, description: localize('userGroupDescription', "Customizations stored locally on your machine in a central location. Private to you and available across all projects."), items: [] },
{ storage: PromptsStorage.extension, label: localize('extensionGroup', "Extensions"), icon: extensionIcon, description: localize('extensionGroupDescription', "Read-only customizations provided by installed extensions."), items: [] },
];
for (const item of matchedItems) {
@@ -934,6 +959,7 @@ export class AICustomizationListWidget extends Disposable {
icon: group.icon,
count: group.items.length,
isFirst: isFirstGroup,
description: group.description,
collapsed,
});
isFirstGroup = false;

View File

@@ -225,8 +225,16 @@
opacity: 0.8;
}
.ai-customization-group-header .group-label {
.ai-customization-group-header .group-label-group {
flex: 1;
display: flex;
align-items: center;
gap: 4px;
overflow: hidden;
min-width: 0;
}
.ai-customization-group-header .group-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
@@ -250,6 +258,27 @@
line-height: 16px;
}
.ai-customization-group-header .group-info {
flex-shrink: 0;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
opacity: 0;
color: var(--vscode-descriptionForeground);
transition: opacity 0.1s ease;
}
.ai-customization-group-header:hover .group-info {
opacity: 0.7;
}
.ai-customization-group-header .group-info:hover {
opacity: 1;
}
.ai-customization-group-header.collapsed .group-label {
opacity: 0.7;
}