Skip to content

Commit 109adc4

Browse files
authoredAug 31, 2023
tabbar: support icon-less items (#12804)
VS Code renders action buttons in the tab bar for commands that do not have icons using their title text, instead. This commit does the same in Theia. Additionally, two related minor issues are fixed: - the $(icon) specifiers for icons show in the tooltip of an action, which is confusing - the roll-over highlight shows only on action buttons for commands that use the "icon" property, not those that embed icon specifiers in their titles Fixes #12686 Signed-off-by: Christian W. Damus <cdamus.ext@eclipsesource.com>
1 parent 5d7a655 commit 109adc4

File tree

4 files changed

+83
-6
lines changed

4 files changed

+83
-6
lines changed
 

‎packages/core/src/browser/label-parser.spec.ts

+38
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,42 @@ describe('StatusBarEntryUtility', () => {
124124
expect((iconArr[3] as LabelIcon).name).equals('icon3');
125125
});
126126

127+
it('should strip nothing from an empty string', () => {
128+
text = '';
129+
const stripped: string = statusBarEntryUtility.stripIcons(text);
130+
expect(stripped).to.be.equal(text);
131+
});
132+
133+
it('should strip nothing from an string containing no icons', () => {
134+
// Deliberate double space to verify not concatenating these words
135+
text = 'foo bar';
136+
const stripped: string = statusBarEntryUtility.stripIcons(text);
137+
expect(stripped).to.be.equal(text);
138+
});
139+
140+
it("should strip a medial '$(icon)' from a string", () => {
141+
text = 'foo $(icon) bar';
142+
const stripped: string = statusBarEntryUtility.stripIcons(text);
143+
expect(stripped).to.be.equal('foo bar');
144+
});
145+
146+
it("should strip a terminal '$(icon)' from a string", () => {
147+
// Deliberate double space to verify not concatenating these words
148+
text = 'foo bar $(icon)';
149+
const stripped: string = statusBarEntryUtility.stripIcons(text);
150+
expect(stripped).to.be.equal('foo bar');
151+
});
152+
153+
it("should strip an initial '$(icon)' from a string", () => {
154+
// Deliberate double space to verify not concatenating these words
155+
text = '$(icon) foo bar';
156+
const stripped: string = statusBarEntryUtility.stripIcons(text);
157+
expect(stripped).to.be.equal('foo bar');
158+
});
159+
160+
it("should strip multiple '$(icon)' specifiers from a string", () => {
161+
text = '$(icon1) foo $(icon2)$(icon3) bar $(icon4) $(icon5)';
162+
const stripped: string = statusBarEntryUtility.stripIcons(text);
163+
expect(stripped).to.be.equal('foo bar');
164+
});
127165
});

‎packages/core/src/browser/label-parser.ts

+15
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,19 @@ export class LabelParser {
9090
return parserArray;
9191
}
9292

93+
/**
94+
* Strips icon specifiers from the given `text`, leaving only a
95+
* space-separated concatenation of the non-icon segments.
96+
*
97+
* @param text text to be stripped of icon specifiers
98+
* @returns the `text` with icon specifiers stripped out
99+
*/
100+
stripIcons(text: string): string {
101+
return this.parse(text)
102+
.filter(item => !LabelIcon.is(item))
103+
.map(s => (s as string).trim())
104+
.filter(s => s.length)
105+
.join(' ');
106+
}
107+
93108
}

‎packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx

+25-6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export interface TabBarToolbarFactory {
3333
(): TabBarToolbar;
3434
}
3535

36+
/**
37+
* Class name indicating rendering of a toolbar item without an icon but instead with a text label.
38+
*/
39+
const NO_ICON_CLASS = 'no-icon';
40+
3641
/**
3742
* Tab-bar toolbar widget representing the active [tab-bar toolbar items](TabBarToolbarItem).
3843
*/
@@ -178,8 +183,12 @@ export class TabBarToolbar extends ReactWidget {
178183
protected renderItem(item: AnyToolbarItem): React.ReactNode {
179184
let innerText = '';
180185
const classNames = [];
181-
if (item.text) {
182-
for (const labelPart of this.labelParser.parse(item.text)) {
186+
const command = item.command ? this.commands.getCommand(item.command) : undefined;
187+
// Fall back to the item ID in extremis so there is _something_ to render in the
188+
// case that there is neither an icon nor a title
189+
const itemText = item.text || command?.label || command?.id || item.id;
190+
if (itemText) {
191+
for (const labelPart of this.labelParser.parse(itemText)) {
183192
if (LabelIcon.is(labelPart)) {
184193
const className = `fa fa-${labelPart.name}${labelPart.animation ? ' fa-' + labelPart.animation : ''}`;
185194
classNames.push(...className.split(' '));
@@ -188,13 +197,23 @@ export class TabBarToolbar extends ReactWidget {
188197
}
189198
}
190199
}
191-
const command = item.command ? this.commands.getCommand(item.command) : undefined;
192-
let iconClass = (typeof item.icon === 'function' && item.icon()) || item.icon as string || (command && command.iconClass);
200+
const iconClass = (typeof item.icon === 'function' && item.icon()) || item.icon as string || (command && command.iconClass);
193201
if (iconClass) {
194-
iconClass += ` ${ACTION_ITEM}`;
195202
classNames.push(iconClass);
196203
}
197-
const tooltip = `${item.tooltip || (command && command.label) || ''}${this.resolveKeybindingForCommand(command?.id)}`;
204+
const tooltipText = item.tooltip || (command && command.label) || '';
205+
const tooltip = `${this.labelParser.stripIcons(tooltipText)}${this.resolveKeybindingForCommand(command?.id)}`;
206+
207+
// Only present text if there is no icon
208+
if (classNames.length) {
209+
innerText = '';
210+
} else if (innerText) {
211+
// Make room for the label text
212+
classNames.push(NO_ICON_CLASS);
213+
}
214+
215+
// In any case, this is an action item, with or without icon.
216+
classNames.push(ACTION_ITEM);
198217

199218
const toolbarItemClassNames = this.getToolbarItemClassNames(item);
200219
return <div key={item.id}

‎packages/core/src/browser/style/tabs.css

+5
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,11 @@
480480
line-height: 18px;
481481
}
482482

483+
.p-TabBar-toolbar .item > div.no-icon {
484+
/* Make room for a text label instead of an icon. */
485+
width: 100%;
486+
}
487+
483488
.p-TabBar-toolbar .item .collapse-all {
484489
background: var(--theia-icon-collapse-all) no-repeat;
485490
}

0 commit comments

Comments
 (0)
Please sign in to comment.