refine workspace panel header layout

This commit is contained in:
dobby-d-elf
2026-05-15 08:58:24 -06:00
parent 6f22fe567c
commit 0e9017a665
7 changed files with 25 additions and 65 deletions
+1 -4
View File
@@ -202,10 +202,7 @@ function syncWorkspacePanelUI(){
if(clearBtn){
clearBtn.disabled=!isOpen;
_setButtonTooltip(clearBtn, hasPreview?'Close preview':'Hide workspace panel');
// On desktop, only show the X button when a file preview is open.
// In browse mode the chevron (btnCollapseWorkspacePanel) already serves
// as the close control, so showing both produces a duplicate X.
if(!isCompact) clearBtn.style.display=hasPreview?'':'none';
if(!isCompact) clearBtn.style.display='';
}
}
+1 -2
View File
@@ -1181,10 +1181,9 @@
<aside class="rightpanel">
<div class="resize-handle" id="rightpanelResize"></div>
<div class="panel-header">
<span id="workspacePanelHeading" class="workspace-panel-heading" title="Workspace">Workspace</span><span id="workspaceHiddenIndicator" class="workspace-hidden-indicator" data-i18n-title="workspace_hidden_files_visible_title" title="Hidden files are visible — click for options" hidden onclick="toggleWorkspacePrefsMenu(event)"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg><span data-i18n="workspace_hidden_files_visible">hidden visible</span></span>
<div class="workspace-panel-title-group"><span id="workspacePanelHeading" class="workspace-panel-heading" title="Workspace">Workspace</span><span id="workspaceHiddenIndicator" class="workspace-hidden-indicator" data-i18n-title="workspace_hidden_files_visible_title" title="Hidden files are visible — click for options" hidden onclick="toggleWorkspacePrefsMenu(event)"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg><span data-i18n="workspace_hidden_files_visible">hidden visible</span></span></div>
<span class="git-badge" id="gitBadge" style="display:none"></span>
<div class="panel-actions">
<button class="panel-icon-btn has-tooltip has-tooltip--bottom" id="btnCollapseWorkspacePanel" data-tooltip="Hide workspace panel" onclick="toggleWorkspacePanel(false)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg></button>
<button class="panel-icon-btn has-tooltip has-tooltip--bottom" id="btnUpDir" data-tooltip="Parent directory" onclick="navigateUp()" style="display:none"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg></button>
<button class="panel-icon-btn has-tooltip has-tooltip--bottom" id="btnNewFile" data-tooltip="New file" onclick="promptNewFile()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button>
<button class="panel-icon-btn has-tooltip has-tooltip--bottom" id="btnNewFolder" data-tooltip="New folder" onclick="promptNewFolder()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></button>
+8 -13
View File
@@ -1279,19 +1279,17 @@
panel's width so the header can collapse less-essential elements as the
user resizes the panel narrower. */
.rightpanel{width:300px;background:var(--sidebar);border-left:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;flex-shrink:0;min-width:0;opacity:1;transform:translateX(0);transform-origin:right center;transition:width .24s cubic-bezier(.22,1,.36,1),opacity .18s ease,transform .24s cubic-bezier(.22,1,.36,1),border-color .24s ease;container-type:inline-size;container-name:rightpanel;}
/* Collapse priority as the panel narrows: git-badge first, then "Workspace"
label, never the icon buttons. flex-shrink ratios give graceful ellipsis;
@container queries below cut to display:none at hard breakpoints. */
.panel-header{padding:12px 16px;border-bottom:1px solid var(--border);font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.1em;display:flex;align-items:center;gap:6px;overflow:visible;}
.panel-header > span:first-child{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;flex-shrink:2;}
.panel-header{padding:12px 16px;border-bottom:1px solid var(--border);font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.1em;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:6px;overflow:visible;}
.workspace-panel-title-group{grid-column:1;grid-row:1;display:flex;align-items:center;min-width:0;}
.workspace-panel-title-group > span:first-child{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;}
.workspace-panel-heading{cursor:default;border-radius:6px;padding:2px 4px;margin:-2px -4px;}
.workspace-panel-heading.workspace-panel-heading--enabled{cursor:pointer;}
.workspace-panel-heading.workspace-panel-heading--enabled:hover,
.workspace-panel-heading.workspace-panel-heading--enabled:focus-visible{color:var(--text);background:var(--hover-bg);outline:none;}
.git-badge{font-size:9px;font-weight:600;color:var(--muted);background:var(--hover-bg);padding:2px 7px;border-radius:4px;letter-spacing:.02em;white-space:nowrap;font-family:'SF Mono',ui-monospace,monospace;flex-shrink:3;overflow:hidden;min-width:0;}
.git-badge{grid-column:1 / -1;grid-row:2;justify-self:start;font-size:9px;font-weight:600;color:var(--muted);background:var(--hover-bg);padding:2px 7px;border-radius:4px;letter-spacing:.02em;white-space:nowrap;font-family:'SF Mono',ui-monospace,monospace;overflow:hidden;min-width:0;max-width:100%;}
.topbar-source-badge{display:inline-flex;align-items:center;margin-left:6px;padding:2px 7px;border-radius:999px;background:var(--accent-bg);color:var(--accent-text);font-size:10px;font-weight:700;letter-spacing:.02em;vertical-align:middle;}
.git-badge.dirty{color:var(--accent-text);background:var(--accent-bg);}
.panel-actions{display:flex;gap:4px;flex-shrink:0;margin-left:auto;}
.panel-actions{grid-column:2;grid-row:1;display:flex;gap:4px;flex-shrink:0;margin-left:auto;}
/* Crisp display:none at narrow widths so the header doesn't show a sliver
of an ellipsised label or git badge icons survive longest. */
@@ -1299,7 +1297,7 @@
.git-badge{display:none !important;}
}
@container rightpanel (max-width: 160px){
.panel-header > span:first-child{display:none;}
.workspace-panel-title-group{display:none;}
}
.mobile-close-btn{display:none;}
.panel-icon-btn{width:24px;height:24px;background:none;border:none;color:var(--muted);cursor:pointer;border-radius:5px;font-size:13px;display:flex;align-items:center;justify-content:center;transition:all .15s;}
@@ -1439,7 +1437,6 @@
.workspace-toggle-btn,.mobile-files-btn{display:inline-flex!important;}
.mobile-close-btn{display:flex;}
.close-preview{display:none;}
#btnCollapseWorkspacePanel{display:none;}
}
@container composer-footer (max-width: 700px){
@@ -1539,10 +1536,8 @@
box-shadow:none!important;}
.rightpanel.mobile-open{right:0!important;box-shadow:-4px 0 24px rgba(0,0,0,.4)!important;}
.rightpanel .resize-handle{display:none;}
.rightpanel .panel-header{display:grid;grid-template-columns:minmax(0,1fr) auto;row-gap:8px;}
.rightpanel .panel-header > span:first-child{grid-column:1;min-width:0;}
.rightpanel .git-badge{grid-column:2;justify-self:end;}
.rightpanel .panel-actions{grid-column:1 / -1;width:100%;align-items:center;gap:8px;justify-content:flex-start;margin-left:0;}
.rightpanel .panel-header{row-gap:8px;}
.rightpanel .panel-actions{align-items:center;gap:8px;}
.rightpanel .mobile-close-btn{margin-left:auto;min-width:36px;min-height:36px;}
/* Topbar adjustments */
.topbar{padding:8px 12px;gap:8px;}
+2 -8
View File
@@ -205,14 +205,8 @@ def test_rightpanel_mobile_slide_over_css():
assert re.search(r'\.rightpanel\.mobile-open\{[^}]*box-shadow:\s*-4px 0 24px rgba\(0,\s*0,\s*0,\s*\.?4\)',
rightpanel_block, re.DOTALL), \
"open mobile rightpanel should keep the edge shadow"
assert re.search(r'\.rightpanel\s+\.panel-header\{[^}]*display:\s*grid[^}]*grid-template-columns:\s*minmax\(0,1fr\)\s+auto', rightpanel_block), \
"mobile workspace header should use a compact two-row grid"
assert re.search(r'\.rightpanel\s+\.panel-header\s*>\s*span:first-child\{[^}]*grid-column:\s*1', rightpanel_block), \
"mobile workspace heading should stay on the first row"
assert re.search(r'\.rightpanel\s+\.git-badge\{[^}]*grid-column:\s*2[^}]*justify-self:\s*end', rightpanel_block), \
"mobile git badge should share the first row with the heading"
assert re.search(r'\.rightpanel\s+\.panel-actions\{[^}]*grid-column:\s*1 / -1[^}]*width:\s*100%', rightpanel_block), \
"mobile workspace actions should span the full second row"
assert re.search(r'\.rightpanel\s+\.panel-header\{[^}]*row-gap:\s*8px', rightpanel_block), \
"mobile workspace header should keep comfortable row spacing"
assert re.search(r'\.rightpanel\s+\.mobile-close-btn\{[^}]*margin-left:\s*auto', rightpanel_block), \
"mobile workspace close button should align to the far right"
+1 -15
View File
@@ -85,16 +85,6 @@ class TestMobileCloseButtonCSS(unittest.TestCase):
block,
".mobile-close-btn must be display:flex inside the 900px media query")
def test_desktop_collapse_btn_hidden_in_900px_query(self):
"""Inside max-width:900px media query, #btnCollapseWorkspacePanel must be display:none."""
m = re.search(r'@media\s*\(max-width\s*:\s*900px\)\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}',
CSS)
self.assertIsNotNone(m, "@media(max-width:900px) block not found in style.css")
block = m.group(1).replace(" ", "")
self.assertIn("#btnCollapseWorkspacePanel{display:none;}",
block,
"#btnCollapseWorkspacePanel must be display:none in 900px media query")
def test_900px_query_retains_existing_rules(self):
"""Ensure the PR didn't accidentally drop existing rules from the 900px block."""
m = re.search(r'@media\s*\(max-width\s*:\s*900px\)\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}',
@@ -108,11 +98,7 @@ class TestMobileCloseButtonCSS(unittest.TestCase):
# ── index.html: button presence ───────────────────────────────────────────
class TestWorkspacePanelButtons(unittest.TestCase):
"""Verify both panel buttons are present in the HTML so CSS rules have targets."""
def test_desktop_collapse_button_exists(self):
self.assertIn("btnCollapseWorkspacePanel", HTML,
"#btnCollapseWorkspacePanel button must exist in index.html")
"""Verify the workspace panel close control remains present."""
def test_mobile_close_button_exists(self):
self.assertIn("mobile-close-btn", HTML,
-8
View File
@@ -121,14 +121,6 @@ class TestDesktopNoDuplicateXButton(unittest.TestCase):
"#btnClearPreview must remain in index.html",
)
def test_btncollapseWorkspacepanel_exists_in_html(self):
"""#btnCollapseWorkspacePanel (chevron) must still exist in the HTML."""
self.assertIn(
'id="btnCollapseWorkspacePanel"',
HTML,
"#btnCollapseWorkspacePanel must remain in index.html",
)
if __name__ == "__main__":
unittest.main()
+12 -15
View File
@@ -74,20 +74,20 @@ class TestWorkspacePanelCollapsePriority:
# in #1775 so its tooltip pseudo-elements can escape the header bar
# (otherwise the workspace-panel header tooltips like "New file" get
# clipped). The title-text ellipsis is preserved by the inner span
# `.panel-header > span:first-child` which has its own
# `.workspace-panel-title-group > span:first-child` which has its own
# overflow:hidden + text-overflow:ellipsis. So we check that EITHER
# the parent uses overflow:hidden (legacy) or that the inner span
# handles its own ellipsis (current).
if "overflow:hidden" not in rule:
inner_span_idx = STYLE_CSS.find(".panel-header > span:first-child{")
inner_span_idx = STYLE_CSS.find(".workspace-panel-title-group > span:first-child{")
assert inner_span_idx != -1, (
".panel-header lost overflow:hidden but no inner span "
"rule (.panel-header > span:first-child) handles the "
"rule (.workspace-panel-title-group > span:first-child) handles the "
"title-text ellipsis as a fallback."
)
inner_rule = STYLE_CSS[inner_span_idx: STYLE_CSS.find("}", inner_span_idx) + 1]
assert "overflow:hidden" in inner_rule and "text-overflow:ellipsis" in inner_rule, (
".panel-header > span:first-child must own the ellipsis "
".workspace-panel-title-group > span:first-child must own the ellipsis "
"behaviour now that the parent is overflow:visible."
)
@@ -106,26 +106,23 @@ class TestWorkspacePanelCollapsePriority:
)
def test_workspace_label_shrinks_with_ellipsis(self):
"""The "Workspace" label (`panel-header > span:first-child`) must
"""The "Workspace" label must shrink with ellipsis truncation."""
shrink with ellipsis truncation rather than overflow uncontrollably."""
# Find the rule
sel = ".panel-header > span:first-child"
sel = ".workspace-panel-title-group > span:first-child"
idx = STYLE_CSS.find(sel)
assert idx >= 0, f"Selector {sel!r} not found in style.css"
rule = STYLE_CSS[idx: STYLE_CSS.find("}", idx)]
assert "text-overflow:ellipsis" in rule
assert "min-width:0" in rule
assert "flex-shrink:2" in rule # shrinks before icons (icons are 0)
assert "min-width:0" in rule
def test_git_badge_shrinks_first(self):
"""`.git-badge` must shrink faster than the label so it disappears
first as the panel narrows."""
def test_git_badge_uses_second_row(self):
"""`.git-badge` should sit beneath the title/action row."""
idx = STYLE_CSS.find(".git-badge{")
rule = STYLE_CSS[idx: STYLE_CSS.find("}", idx)]
assert "flex-shrink:3" in rule, (
".git-badge must have flex-shrink:3 so it shrinks before the "
"label (flex-shrink:2) and the icons (flex-shrink:0)."
)
assert "grid-column:1 / -1" in rule
assert "grid-row:2" in rule
def test_container_query_hides_git_badge_first(self):
"""At narrow widths the git badge gets `display:none` BEFORE the
@@ -145,7 +142,7 @@ class TestWorkspacePanelCollapsePriority:
assert "@container rightpanel (max-width: 160px)" in STYLE_CSS
idx = STYLE_CSS.find("@container rightpanel (max-width: 160px)")
block = STYLE_CSS[idx: idx + 200]
assert ".panel-header > span:first-child{display:none" in block
assert ".workspace-panel-title-group{display:none" in block
def test_breakpoints_in_correct_order(self):
"""Sanity: the git-badge breakpoint (220px) must be wider than the