Skip to content

Commit 4a9cc6a

Browse files
Merge pull request #153 from ThisIs-Developer/feature/global-new-tab-and-overflow
feat(tabs): relocate new tab button and implement overflow handling
2 parents 542472b + e379725 commit 4a9cc6a

6 files changed

Lines changed: 388 additions & 52 deletions

File tree

desktop-app/resources/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ <h2 class="h5 m-0">Menu</h2>
238238
<!-- Tab Bar -->
239239
<div class="tab-bar" id="tab-bar">
240240
<div class="tab-list" id="tab-list" role="tablist" aria-label="Document tabs"></div>
241+
<button class="tab-new-btn" id="tab-new-btn" title="New Tab (Ctrl+T)" aria-label="Open new tab">
242+
<i class="bi bi-plus-lg"></i> New Tab
243+
</button>
241244
<button class="tab-reset-btn" id="tab-reset-btn" title="Reset all files" aria-label="Reset all files">
242245
<i class="bi bi-arrow-counterclockwise"></i> Reset
243246
</button>

desktop-app/resources/js/script.js

Lines changed: 93 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,23 +1151,6 @@ document.addEventListener("DOMContentLoaded", function () {
11511151
if (tabId) switchTab(tabId);
11521152
};
11531153

1154-
// "+ Create" button at end of tab list (placed outside tabList to prevent ARIA child violation)
1155-
let newBtn = tabList.parentElement.querySelector('.tab-new-btn');
1156-
if (!newBtn) {
1157-
newBtn = document.createElement('button');
1158-
newBtn.className = 'tab-new-btn';
1159-
newBtn.title = 'New Tab (Ctrl+T)';
1160-
newBtn.setAttribute('aria-label', 'Open new tab');
1161-
newBtn.innerHTML = '<i class="bi bi-plus-lg"></i>';
1162-
newBtn.addEventListener('click', function() { newTab(); });
1163-
1164-
const resetBtn = document.getElementById('tab-reset-btn');
1165-
if (resetBtn) {
1166-
tabList.parentElement.insertBefore(newBtn, resetBtn);
1167-
} else {
1168-
tabList.parentElement.appendChild(newBtn);
1169-
}
1170-
}
11711154

11721155
// Auto-scroll active tab into view (paint-aligned to prevent forced reflows)
11731156
const activeItem = tabList.querySelector('.tab-item.active');
@@ -1224,6 +1207,91 @@ document.addEventListener("DOMContentLoaded", function () {
12241207
};
12251208

12261209
renderMobileTabList(tabsArr, currentActiveTabId);
1210+
if (typeof tabList.dispatchEvent === 'function') {
1211+
tabList.dispatchEvent(new Event('scroll'));
1212+
}
1213+
}
1214+
1215+
// ========================================
1216+
// TAB OVERFLOW — Scroll Buttons, Wheel, Indicators
1217+
// ========================================
1218+
1219+
var _tabOverflowInitialized = false;
1220+
1221+
function setupTabOverflow() {
1222+
if (_tabOverflowInitialized) return;
1223+
_tabOverflowInitialized = true;
1224+
1225+
var tabBar = document.getElementById('tab-bar');
1226+
var tabList = document.getElementById('tab-list');
1227+
if (!tabBar || !tabList) return;
1228+
1229+
// --- Create scroll arrow buttons ---
1230+
var scrollLeftBtn = document.createElement('button');
1231+
scrollLeftBtn.className = 'tab-scroll-btn tab-scroll-left';
1232+
scrollLeftBtn.setAttribute('aria-label', 'Scroll tabs left');
1233+
scrollLeftBtn.title = 'Scroll left';
1234+
scrollLeftBtn.innerHTML = '<i class="bi bi-chevron-left"></i>';
1235+
scrollLeftBtn.addEventListener('click', function() {
1236+
tabList.scrollBy({ left: -200, behavior: 'smooth' });
1237+
});
1238+
1239+
var scrollRightBtn = document.createElement('button');
1240+
scrollRightBtn.className = 'tab-scroll-btn tab-scroll-right';
1241+
scrollRightBtn.setAttribute('aria-label', 'Scroll tabs right');
1242+
scrollRightBtn.title = 'Scroll right';
1243+
scrollRightBtn.innerHTML = '<i class="bi bi-chevron-right"></i>';
1244+
scrollRightBtn.addEventListener('click', function() {
1245+
tabList.scrollBy({ left: 200, behavior: 'smooth' });
1246+
});
1247+
1248+
// Insert scroll buttons flanking the tab-list
1249+
tabBar.insertBefore(scrollLeftBtn, tabList);
1250+
var newBtn = document.getElementById('tab-new-btn');
1251+
if (newBtn) {
1252+
tabBar.insertBefore(scrollRightBtn, newBtn);
1253+
} else {
1254+
var resetBtn = document.getElementById('tab-reset-btn');
1255+
if (resetBtn) {
1256+
tabBar.insertBefore(scrollRightBtn, resetBtn);
1257+
} else {
1258+
tabBar.appendChild(scrollRightBtn);
1259+
}
1260+
}
1261+
1262+
// --- Overflow detection ---
1263+
var _overflowRafId = null;
1264+
function updateOverflowState() {
1265+
if (_overflowRafId) return;
1266+
_overflowRafId = requestAnimationFrame(function() {
1267+
_overflowRafId = null;
1268+
var hasLeft = tabList.scrollLeft > 1;
1269+
var hasRight = tabList.scrollLeft < (tabList.scrollWidth - tabList.clientWidth - 1);
1270+
tabBar.classList.toggle('has-overflow-left', hasLeft);
1271+
tabBar.classList.toggle('has-overflow-right', hasRight);
1272+
});
1273+
}
1274+
1275+
tabList.addEventListener('scroll', updateOverflowState);
1276+
1277+
// Use ResizeObserver to detect when overflow state changes due to window resize
1278+
if (typeof ResizeObserver !== 'undefined') {
1279+
var resizeObs = new ResizeObserver(updateOverflowState);
1280+
resizeObs.observe(tabList);
1281+
}
1282+
1283+
// Initial check
1284+
updateOverflowState();
1285+
1286+
// --- Mouse wheel scroll: vertical wheel → horizontal scroll ---
1287+
tabList.addEventListener('wheel', function(e) {
1288+
// Only intercept vertical wheel (don't fight native horizontal wheel/trackpad)
1289+
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
1290+
e.preventDefault();
1291+
tabList.scrollLeft += e.deltaY;
1292+
updateOverflowState();
1293+
}
1294+
}, { passive: false });
12271295
}
12281296

12291297
function renderMobileTabList(tabsArr, currentActiveTabId) {
@@ -1510,6 +1578,14 @@ document.addEventListener("DOMContentLoaded", function () {
15101578
markdownEditor.scrollTop = activeTab.scrollPos || 0;
15111579
});
15121580
renderTabBar(tabs, activeTabId);
1581+
setupTabOverflow();
1582+
1583+
const staticNewBtn = document.getElementById('tab-new-btn');
1584+
if (staticNewBtn) {
1585+
staticNewBtn.onclick = function() {
1586+
newTab();
1587+
};
1588+
}
15131589
}
15141590

15151591
// Late-load callback hook for Neutralino command-line files

desktop-app/resources/styles.css

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1831,23 +1831,29 @@ a:focus {
18311831
.tab-new-btn {
18321832
display: flex;
18331833
align-items: center;
1834-
justify-content: center;
1835-
width: 24px;
1834+
gap: 4px;
18361835
height: 24px;
1836+
padding: 0 8px;
18371837
border-radius: 5px;
18381838
background: none;
1839-
border: 1px solid transparent;
1839+
border: 1px solid var(--border-color);
18401840
color: var(--text-color);
18411841
cursor: pointer;
1842-
font-size: 16px;
1842+
font-size: 12px;
18431843
flex-shrink: 0;
1844-
margin-left: 4px;
1845-
transition: background-color 0.15s ease, border-color 0.15s ease;
1844+
margin-left: 6px;
1845+
align-self: center;
1846+
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
18461847
}
18471848

18481849
.tab-new-btn:hover {
1849-
background-color: var(--button-hover);
1850-
border-color: var(--border-color);
1850+
background-color: rgba(46, 160, 67, 0.1);
1851+
border-color: var(--accent-color, #2ea043);
1852+
color: var(--accent-color, #2ea043);
1853+
}
1854+
1855+
.tab-new-btn:active {
1856+
background-color: rgba(46, 160, 67, 0.2);
18511857
}
18521858

18531859
/* Drag-and-drop visual feedback */
@@ -1880,6 +1886,83 @@ a:focus {
18801886
}
18811887
}
18821888

1889+
/* ========================================
1890+
TAB OVERFLOW — Scroll Buttons & Fade Indicators
1891+
======================================== */
1892+
1893+
.tab-scroll-btn {
1894+
display: none;
1895+
align-items: center;
1896+
justify-content: center;
1897+
width: 24px;
1898+
height: 24px;
1899+
border-radius: 4px;
1900+
background: none;
1901+
border: 1px solid transparent;
1902+
color: var(--text-color);
1903+
cursor: pointer;
1904+
font-size: 14px;
1905+
flex-shrink: 0;
1906+
padding: 0;
1907+
transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
1908+
z-index: 2;
1909+
opacity: 0.6;
1910+
}
1911+
1912+
.tab-scroll-btn:hover {
1913+
background-color: var(--button-hover);
1914+
border-color: var(--border-color);
1915+
opacity: 1;
1916+
}
1917+
1918+
.tab-scroll-btn:active {
1919+
background-color: var(--button-active);
1920+
}
1921+
1922+
/* Show scroll buttons only when overflow exists */
1923+
.tab-bar.has-overflow-left .tab-scroll-left,
1924+
.tab-bar.has-overflow-right .tab-scroll-right {
1925+
display: flex;
1926+
}
1927+
1928+
/* Overflow fade indicators — subtle gradient at clipped edges */
1929+
.tab-list::before,
1930+
.tab-list::after {
1931+
content: '';
1932+
position: sticky;
1933+
top: 0;
1934+
bottom: 0;
1935+
width: 0;
1936+
flex-shrink: 0;
1937+
pointer-events: none;
1938+
z-index: 3;
1939+
transition: box-shadow 0.2s ease;
1940+
}
1941+
1942+
.tab-list::before {
1943+
left: 0;
1944+
}
1945+
1946+
.tab-list::after {
1947+
right: 0;
1948+
}
1949+
1950+
.tab-bar.has-overflow-left .tab-list::before {
1951+
box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.12);
1952+
}
1953+
1954+
.tab-bar.has-overflow-right .tab-list::after {
1955+
box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.12);
1956+
}
1957+
1958+
[data-theme="dark"] .tab-bar.has-overflow-left .tab-list::before {
1959+
box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.35);
1960+
}
1961+
1962+
[data-theme="dark"] .tab-bar.has-overflow-right .tab-list::after {
1963+
box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.35);
1964+
}
1965+
18831966
/* ========================================
18841967
THREE-DOT TAB MENU
18851968
======================================== */
@@ -1927,7 +2010,13 @@ a:focus {
19272010
padding: 0 10px 0 12px !important;
19282011
gap: 8px !important;
19292012
}
1930-
.tab-new-btn {
2013+
.tab-new-btn,
2014+
.tab-reset-btn {
2015+
height: 32px !important;
2016+
font-size: 14px !important;
2017+
padding: 0 12px !important;
2018+
}
2019+
.tab-scroll-btn {
19312020
width: 32px !important;
19322021
height: 32px !important;
19332022
font-size: 18px !important;

index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ <h2 class="h5 m-0">Menu</h2>
297297
<!-- Tab Bar -->
298298
<div class="tab-bar" id="tab-bar">
299299
<div class="tab-list" id="tab-list" role="tablist" aria-label="Document tabs"></div>
300+
<button class="tab-new-btn" id="tab-new-btn" title="New Tab (Ctrl+T)" aria-label="Open new tab">
301+
<i class="bi bi-plus-lg"></i> New Tab
302+
</button>
300303
<button class="tab-reset-btn" id="tab-reset-btn" title="Reset all files" aria-label="Reset all files">
301304
<i class="bi bi-arrow-counterclockwise"></i> Reset
302305
</button>

0 commit comments

Comments
 (0)