mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 19:20:16 +00:00
refine workspace panel header layout
This commit is contained in:
+1
-4
@@ -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
@@ -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
@@ -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;}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user