Skip to content

Commit ce20f26

Browse files
committed
feat: collapse bottom panel tabs to icons on overflow
Add icon support to bottom panel tabs via options object in createBottomPanel(). Tabs collapse to icons when the tab bar overflows. Supports both FA icon classes and SVG image paths. Panels without icons get a generic fallback icon. Uses panel-titlebar-icon class to override FA specificity issues.
1 parent 744c0b2 commit ce20f26

10 files changed

Lines changed: 117 additions & 43 deletions

File tree

src/extensions/default/Git/src/Panel.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,7 @@ define(function (require, exports) {
12401240
var $panelHtml = $(panelHtml);
12411241
$panelHtml.find(".git-available, .git-not-available").hide();
12421242

1243-
gitPanel = WorkspaceManager.createBottomPanel("main-git.panel", $panelHtml, 100, Strings.GIT_PANEL_TITLE);
1243+
gitPanel = WorkspaceManager.createBottomPanel("main-git.panel", $panelHtml, 100, Strings.GIT_PANEL_TITLE, {iconClass: "fa-brands fa-git-alt"});
12441244
$gitPanel = gitPanel.$panel;
12451245
const resizeObserver = new ResizeObserver(_panelResized);
12461246
resizeObserver.observe($gitPanel[0]);

src/extensionsIntegrated/CustomSnippets/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ define(function (require, exports, module) {
5959
*/
6060
function _createPanel() {
6161
customSnippetsPanel = WorkspaceManager.createBottomPanel(PANEL_ID, $snippetsPanel, PANEL_MIN_SIZE,
62-
Strings.CUSTOM_SNIPPETS_PANEL_TITLE);
62+
Strings.CUSTOM_SNIPPETS_PANEL_TITLE, {iconClass: "fa-solid fa-code"});
6363
UIHelper.init(customSnippetsPanel);
6464
customSnippetsPanel.show();
6565

src/extensionsIntegrated/DisplayShortcuts/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ define(function (require, exports, module) {
479479
// AppInit.htmlReady() has already executed before extensions are loaded
480480
// so, for now, we need to call this ourself
481481
panel = WorkspaceManager.createBottomPanel(TOGGLE_SHORTCUTS_ID, $(s), 300,
482-
Strings.KEYBOARD_SHORTCUT_PANEL_TITLE);
482+
Strings.KEYBOARD_SHORTCUT_PANEL_TITLE, {iconClass: "fa-solid fa-keyboard"});
483483
panel.hide();
484484

485485
$shortcutsPanel = $("#shortcuts-panel");

src/extensionsIntegrated/Terminal/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ define(function (require, exports, module) {
115115
};
116116

117117
$panel = $(Mustache.render(panelHTML, templateVars));
118-
panel = WorkspaceManager.createBottomPanel(PANEL_ID, $panel, PANEL_MIN_SIZE);
118+
panel = WorkspaceManager.createBottomPanel(PANEL_ID, $panel, PANEL_MIN_SIZE, undefined, {iconClass: "fa-solid fa-terminal"});
119119

120120
// Override focus() so Shift+Escape can transfer focus to the terminal
121121
panel.focus = function () {

src/language/CodeInspection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,7 @@ define(function (require, exports, module) {
12401240
Editor.registerGutter(CODE_INSPECTION_GUTTER, CODE_INSPECTION_GUTTER_PRIORITY);
12411241
// Create bottom panel to list error details
12421242
var panelHtml = Mustache.render(PanelTemplate, Strings);
1243-
problemsPanel = WorkspaceManager.createBottomPanel("errors", $(panelHtml), 100, Strings.CMD_VIEW_TOGGLE_PROBLEMS);
1243+
problemsPanel = WorkspaceManager.createBottomPanel("errors", $(panelHtml), 100, Strings.CMD_VIEW_TOGGLE_PROBLEMS, {iconClass: "fa-solid fa-triangle-exclamation"});
12441244
$problemsPanel = $("#problems-panel");
12451245
$fixAllBtn = $problemsPanel.find(".problems-fix-all-btn");
12461246
$fixAllBtn.click(()=>{

src/search/SearchResultsView.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ define(function (require, exports, module) {
8282
const self = this;
8383
let panelHtml = Mustache.render(searchPanelTemplate, {panelID: panelID});
8484

85-
this._panel = WorkspaceManager.createBottomPanel(panelName, $(panelHtml), 100, title);
85+
this._panel = WorkspaceManager.createBottomPanel(panelName, $(panelHtml), 100, title, {iconClass: "fa-solid fa-magnifying-glass"});
8686
this._$summary = this._panel.$panel.find(".title");
8787
this._$table = this._panel.$panel.find(".table-container");
8888
this._$previewEditor = this._panel.$panel.find(".search-editor-preview");

src/styles/Extn-BottomPanelTabs.less

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,52 @@
144144
}
145145
}
146146

147+
/* Tab icon: hidden by default, shown when tabs are collapsed */
148+
.bottom-panel-tab-icon {
149+
display: none;
150+
font-size: 1rem;
151+
width: 1rem;
152+
text-align: center;
153+
pointer-events: none;
154+
}
155+
156+
/* Override any FA class specificity (e.g. fa-brands sets font-size: 1.2em) */
157+
i.panel-titlebar-icon.panel-titlebar-icon {
158+
font-size: 1rem;
159+
}
160+
161+
img.panel-titlebar-icon {
162+
width: 1rem;
163+
height: 1rem;
164+
vertical-align: middle;
165+
}
166+
167+
.default-panel-btn i.panel-titlebar-icon {
168+
font-size: 20px;
169+
}
170+
171+
.default-panel-btn img.panel-titlebar-icon {
172+
width: 20px;
173+
height: 20px;
174+
}
175+
176+
147177
.bottom-panel-tab-title {
148178
display: inline-flex;
149179
align-items: center;
150180
pointer-events: none;
151181
}
152182

183+
/* Collapsed tab bar: show icons, hide titles for tabs that have icons */
184+
.bottom-panel-tabs-collapsed {
185+
.bottom-panel-tab-icon {
186+
display: inline-flex;
187+
}
188+
.bottom-panel-tab-icon ~ .bottom-panel-tab-title {
189+
display: none;
190+
}
191+
}
192+
153193
.bottom-panel-tab-close-btn {
154194
margin-left: 0.55rem;
155195
border-radius: 3px;

src/view/DefaultPanelView.js

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ define(function (require, exports, module) {
4545
},
4646
{
4747
id: "git",
48-
icon: "fa-solid fa-code-branch",
48+
icon: "fa-brands fa-git-alt",
4949
label: Strings.GIT_PANEL_TITLE || "Git",
5050
commandID: Commands.CMD_GIT_TOGGLE_PANEL,
5151
nativeOnly: true
@@ -99,7 +99,7 @@ define(function (require, exports, module) {
9999
.attr("data-command", btn.commandID)
100100
.attr("data-btn-id", btn.id)
101101
.attr("title", btn.label);
102-
let $icon = $('<i></i>').addClass(btn.icon);
102+
let $icon = $('<i class="panel-titlebar-icon"></i>').addClass(btn.icon);
103103
let $label = $('<span class="default-panel-btn-label"></span>').text(btn.label);
104104
$button.append($icon).append($label);
105105
$buttonsRow.append($button);
@@ -161,7 +161,8 @@ define(function (require, exports, module) {
161161
WorkspaceManager.DEFAULT_PANEL_ID,
162162
_$panel,
163163
undefined,
164-
Strings.BOTTOM_PANEL_DEFAULT_TITLE
164+
Strings.BOTTOM_PANEL_DEFAULT_TITLE,
165+
{iconSvg: "styles/images/app-drawer.svg"}
165166
);
166167

167168
// Button click handler: execute the command to open the target panel.
@@ -173,21 +174,6 @@ define(function (require, exports, module) {
173174
}
174175
});
175176

176-
const iconHTML = '<img class="app-drawer-tab-icon" src="styles/images/app-drawer.svg"'
177-
+ ' style="width:12px;height:12px;vertical-align:middle;margin-right:4px">';
178-
179-
/**
180-
* Inject the app-drawer icon into the Quick Access tab title.
181-
* Called each time the panel is shown because the tab DOM is rebuilt.
182-
*/
183-
function _addTabIcon() {
184-
const $tabTitle = $('#bottom-panel-tab-bar .bottom-panel-tab[data-panel-id="'
185-
+ WorkspaceManager.DEFAULT_PANEL_ID + '"] .bottom-panel-tab-title');
186-
if ($tabTitle.length && !$tabTitle.find(".app-drawer-tab-icon").length) {
187-
$tabTitle.prepend(iconHTML);
188-
}
189-
}
190-
191177
// The app-drawer button is defined in index.html; set its title here.
192178
const $drawerBtn = $("#app-drawer-button")
193179
.attr("title", Strings.BOTTOM_PANEL_DEFAULT_TITLE);
@@ -206,7 +192,6 @@ define(function (require, exports, module) {
206192
_panel.hide();
207193
} else {
208194
_updateButtonVisibility();
209-
_addTabIcon();
210195
}
211196
$drawerBtn.toggleClass("selected-button", panelID === WorkspaceManager.DEFAULT_PANEL_ID);
212197
});

src/view/PanelView.js

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,34 @@ define(function (require, exports, module) {
139139
* Call this when tabs are added, removed, or renamed.
140140
* @private
141141
*/
142+
/**
143+
* Build a tab element for a panel.
144+
* @param {Panel} panel
145+
* @param {boolean} isActive
146+
* @return {jQueryObject}
147+
* @private
148+
*/
149+
function _buildTab(panel, isActive) {
150+
let title = panel._tabTitle || _getPanelTitle(panel.panelID, panel.$panel);
151+
let $tab = $('<div class="bottom-panel-tab"></div>')
152+
.toggleClass('active', isActive)
153+
.attr('data-panel-id', panel.panelID);
154+
const opts = panel._options;
155+
if (opts.iconClass) {
156+
$tab.append($('<i class="bottom-panel-tab-icon panel-titlebar-icon"></i>')
157+
.addClass(opts.iconClass));
158+
} else if (opts.iconSvg) {
159+
$tab.append($('<img class="bottom-panel-tab-icon panel-titlebar-icon">')
160+
.attr("src", opts.iconSvg));
161+
} else {
162+
// Fallback generic icon for panels without a custom icon
163+
$tab.append($('<i class="bottom-panel-tab-icon panel-titlebar-icon fa-solid fa-window-maximize"></i>'));
164+
}
165+
$tab.append($('<span class="bottom-panel-tab-title"></span>').text(title));
166+
$tab.append($('<span class="bottom-panel-tab-close-btn">&times;</span>').attr('title', Strings.CLOSE));
167+
return $tab;
168+
}
169+
142170
function _updateBottomPanelTabBar() {
143171
if (!_$tabsOverflow) {
144172
return;
@@ -150,21 +178,15 @@ define(function (require, exports, module) {
150178
if (!panel) {
151179
return;
152180
}
153-
let title = panel._tabTitle || _getPanelTitle(panelId, panel.$panel);
154-
let isActive = (panelId === _activeId);
155-
let $tab = $('<div class="bottom-panel-tab"></div>')
156-
.toggleClass('active', isActive)
157-
.attr('data-panel-id', panelId);
158-
$tab.append($('<span class="bottom-panel-tab-title"></span>').text(title));
159-
$tab.append($('<span class="bottom-panel-tab-close-btn">&times;</span>').attr('title', Strings.CLOSE));
160-
_$tabsOverflow.append($tab);
181+
_$tabsOverflow.append(_buildTab(panel, panelId === _activeId));
161182
});
162183

163184
// Re-append the "+" button at the end (after all tabs)
164185
if (_$addBtn) {
165186
_$tabsOverflow.append(_$addBtn);
166187
_updateAddButtonVisibility();
167188
}
189+
_checkTabOverflow();
168190
}
169191

170192
/**
@@ -199,13 +221,7 @@ define(function (require, exports, module) {
199221
if (!panel) {
200222
return;
201223
}
202-
let title = panel._tabTitle || _getPanelTitle(panelId, panel.$panel);
203-
let isActive = (panelId === _activeId);
204-
let $tab = $('<div class="bottom-panel-tab"></div>')
205-
.toggleClass('active', isActive)
206-
.attr('data-panel-id', panelId);
207-
$tab.append($('<span class="bottom-panel-tab-title"></span>').text(title));
208-
$tab.append($('<span class="bottom-panel-tab-close-btn">&times;</span>').attr('title', Strings.CLOSE));
224+
let $tab = _buildTab(panel, panelId === _activeId);
209225

210226
// Insert before the "+" button so it stays at the end
211227
if (_$addBtn && _$addBtn.parent().length) {
@@ -214,6 +230,7 @@ define(function (require, exports, module) {
214230
_$tabsOverflow.append($tab);
215231
}
216232
_updateAddButtonVisibility();
233+
_checkTabOverflow();
217234
}
218235

219236
/**
@@ -228,6 +245,22 @@ define(function (require, exports, module) {
228245
}
229246
_$tabsOverflow.find('.bottom-panel-tab[data-panel-id="' + panelId + '"]').remove();
230247
_updateAddButtonVisibility();
248+
_checkTabOverflow();
249+
}
250+
251+
/**
252+
* Check if the tab bar is overflowing and collapse tabs to icons if so.
253+
* Only collapses tabs that have an icon available.
254+
* @private
255+
*/
256+
function _checkTabOverflow() {
257+
if (!_$tabBar) {
258+
return;
259+
}
260+
// Remove collapsed state first to measure true width
261+
_$tabBar.removeClass("bottom-panel-tabs-collapsed");
262+
const isOverflowing = _$tabsOverflow[0].scrollWidth > _$tabsOverflow[0].clientWidth;
263+
_$tabBar.toggleClass("bottom-panel-tabs-collapsed", isOverflowing);
231264
}
232265

233266
/**
@@ -281,10 +314,19 @@ define(function (require, exports, module) {
281314
* @param {string} id Unique panel identifier.
282315
* @param {string=} title Optional display title for the tab bar.
283316
*/
284-
function Panel($panel, id, title) {
317+
/**
318+
* @param {jQueryObject} $panel
319+
* @param {string} id
320+
* @param {string=} title
321+
* @param {Object=} options
322+
* @param {string=} options.iconClass FontAwesome class string (e.g. "fa-solid fa-terminal").
323+
* @param {string=} options.iconSvg Path to an SVG icon (e.g. "styles/images/icon.svg").
324+
*/
325+
function Panel($panel, id, title, options) {
285326
this.$panel = $panel;
286327
this.panelID = id;
287328
this._tabTitle = _getPanelTitle(id, $panel, title);
329+
this._options = options || {};
288330
_panelMap[id] = this;
289331
}
290332

@@ -573,6 +615,10 @@ define(function (require, exports, module) {
573615
_toggleMaximize();
574616
});
575617

618+
// Re-check tab overflow when the tab bar resizes (e.g. window resize)
619+
const tabBarResizeObserver = new ResizeObserver(_checkTabOverflow);
620+
tabBarResizeObserver.observe(_$tabsOverflow[0]);
621+
576622
// Restore maximize state from preferences (survives reload).
577623
_isMaximized = PreferencesManager.getViewState(PREF_BOTTOM_PANEL_MAXIMIZED) === true;
578624

src/view/WorkspaceManager.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,13 +265,16 @@ define(function (require, exports, module) {
265265
* attribute, for use as a preferences key.
266266
* @param {number=} minSize @deprecated No longer used. Pass `undefined`.
267267
* @param {string=} title Display title shown in the bottom panel tab bar.
268+
* @param {Object=} options Optional settings:
269+
* - {string} iconClass FontAwesome class string (e.g. "fa-solid fa-terminal").
270+
* - {string} iconSvg Path to an SVG icon (e.g. "styles/images/icon.svg").
268271
* @return {!Panel}
269272
*/
270-
function createBottomPanel(id, $panel, minSize, title) {
273+
function createBottomPanel(id, $panel, minSize, title, options) {
271274
$bottomPanelContainer.append($panel);
272275
$panel.hide();
273276
updateResizeLimits();
274-
let bottomPanel = new PanelView.Panel($panel, id, title);
277+
let bottomPanel = new PanelView.Panel($panel, id, title, options);
275278
panelIDMap[id] = bottomPanel;
276279
return bottomPanel;
277280
}

0 commit comments

Comments
 (0)