perfect hover-open-focus state colors
Some checks failed
CI / check (push) Has been cancelled

This commit is contained in:
2026-09-03 08:16:43 +02:00
parent fa4d1d7db5
commit 74cffe2253
2 changed files with 25 additions and 15 deletions

View File

@@ -261,9 +261,9 @@ export function App(): React.ReactElement {
const gitSelPath = gitSelRow?.path ?? null const gitSelPath = gitSelRow?.path ?? null
const treeSelItem = treeNav[treeSel] ?? null const treeSelItem = treeNav[treeSel] ?? null
// Clicking a row moves the keyboard cursor onto it. Without this the cursor // Clicking or hovering a row moves the keyboard cursor onto it. Hover and focus
// stays at index 0, so the top row of the list keeps its highlight next to // are the same state, so the cursor follows the pointer and no second row keeps
// whichever row the click actually selected. // a stale ring next to the one under the mouse.
const syncGitSel = (target: EventTarget): void => { const syncGitSel = (target: EventTarget): void => {
const row = (target as HTMLElement).closest?.('.git-row') as HTMLElement | null const row = (target as HTMLElement).closest?.('.git-row') as HTMLElement | null
const id = row?.dataset.rowId const id = row?.dataset.rowId
@@ -1023,7 +1023,8 @@ export function App(): React.ReactElement {
<div className="workbench"> <div className="workbench">
{showGit && (<> {showGit && (<>
<div className={'col' + (activePanel === 'git' ? ' panel-active' : '') + flashClass} style={{ width: gitW, flex: '0 0 ' + gitW + 'px' }} <div className={'col' + (activePanel === 'git' ? ' panel-active' : '') + flashClass} style={{ width: gitW, flex: '0 0 ' + gitW + 'px' }}
onMouseDownCapture={(e) => { setActivePanel('git'); syncGitSel(e.target) }}> onMouseDownCapture={(e) => { setActivePanel('git'); syncGitSel(e.target) }}
onMouseOver={(e) => syncGitSel(e.target)}>
<GitPanel branch={proj.branch} changes={proj.changes} committed={NO_COMMITTED} <GitPanel branch={proj.branch} changes={proj.changes} committed={NO_COMMITTED}
commitMsg={commitMsg} setCommitMsg={setCommitMsg} commitMsg={commitMsg} setCommitMsg={setCommitMsg}
onStage={stageGuarded} onUnstage={unstageGuarded} onStageAll={actions.stageAll} onUnstageAll={actions.unstageAll} onCommit={commit} onPush={push} onStage={stageGuarded} onUnstage={unstageGuarded} onStageAll={actions.stageAll} onUnstageAll={actions.unstageAll} onCommit={commit} onPush={push}
@@ -1034,7 +1035,8 @@ export function App(): React.ReactElement {
</>)} </>)}
<div className={'col' + (activePanel === 'tree' ? ' panel-active' : '') + flashClass} style={{ width: treeW, flex: '0 0 ' + treeW + 'px' }} <div className={'col' + (activePanel === 'tree' ? ' panel-active' : '') + flashClass} style={{ width: treeW, flex: '0 0 ' + treeW + 'px' }}
onMouseDownCapture={(e) => { setActivePanel('tree'); syncTreeSel(e.target) }}> onMouseDownCapture={(e) => { setActivePanel('tree'); syncTreeSel(e.target) }}
onMouseOver={(e) => syncTreeSel(e.target)}>
{proj.tree ? ( {proj.tree ? (
<FileTree tree={proj.tree} openDirs={openDirs} toggleDir={toggleDir} onOpen={openFile} <FileTree tree={proj.tree} openDirs={openDirs} toggleDir={toggleDir} onOpen={openFile}
onContext={openMenu} activePath={active} ctxPath={menu?.path ?? null} onContext={openMenu} activePath={active} ctxPath={menu?.path ?? null}

View File

@@ -18,6 +18,10 @@
--accent-soft:rgba(241,159,63,0.16); --accent-soft:rgba(241,159,63,0.16);
/* row states: the open row is --sel + a solid accent bar, focus/hover repeat
both at 30%, so the weaker pair reads as an echo of the open row. */
--row-soft:rgba(73,82,95,0.30);
--accent-dim:rgba(241,159,63,0.30);
--accent-line:rgba(241,159,63,0.55); --accent-line:rgba(241,159,63,0.55);
--add:#5cbd6b; --add:#5cbd6b;
--del:#e0696a; --del:#e0696a;
@@ -187,14 +191,16 @@ kbd { flex:0 0 auto; font-family:var(--mono); font-size:11.5px; color:var(--fg-3
.git-row { .git-row {
display:flex; align-items:center; gap:8px; padding:3px 12px 3px 14px; cursor:pointer; position:relative; display:flex; align-items:center; gap:8px; padding:3px 12px 3px 14px; cursor:pointer; position:relative;
} }
.git-row:hover, .git-row.ctx { background:var(--hover); } /* Row states, one language for the Git list and the Explorer.
.git-row.ctx .git-act { visibility:visible; } open — the file the viewer shows: lighter tint, 3px accent bar.
focus — "↵ acts here": the same pair at 30%.
hover — the same as focus, because the pointer moves the cursor onto the row.
Order matters: open is listed last, so it wins on a row that is also focused. */
.git-row:hover, .git-row.ctx, .git-row.kbd { background:var(--row-soft); }
.git-row:hover::before, .git-row.ctx::before, .git-row.kbd::before { content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--accent-dim); }
.git-row.ctx .git-act, .git-row.kbd .git-act { visibility:visible; }
.git-row.active { background:var(--sel); } .git-row.active { background:var(--sel); }
.git-row.active::before { content:""; position:absolute; left:0; top:0; bottom:0; width:2px; background:var(--accent); } .git-row.active::before { content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--accent); }
/* Three states must stay apart: hover is background only, keyboard focus adds a
1px orange rule top and bottom, and the open file keeps the left accent bar. */
.git-row.kbd { background:var(--hover); box-shadow:inset 0 1px 0 var(--accent), inset 0 -1px 0 var(--accent); }
.git-row.kbd .git-act { visibility:visible; }
.git-stat { width:13px; text-align:center; font-family:var(--mono); font-size:11px; font-weight:600; flex:0 0 13px; } .git-stat { width:13px; text-align:center; font-family:var(--mono); font-size:11px; font-weight:600; flex:0 0 13px; }
.git-stat.M{color:var(--mod);} .git-stat.A{color:var(--add);} .git-stat.D{color:var(--del);} .git-stat.R{color:var(--ren);} .git-stat.U{color:var(--add);} .git-stat.M{color:var(--mod);} .git-stat.A{color:var(--add);} .git-stat.D{color:var(--del);} .git-stat.R{color:var(--ren);} .git-stat.U{color:var(--add);}
.git-name { font-size:12.5px; color:var(--fg-1); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .git-name { font-size:12.5px; color:var(--fg-1); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
@@ -214,10 +220,12 @@ kbd { flex:0 0 auto; font-family:var(--mono); font-size:11.5px; color:var(--fg-3
/* ============ file tree ============ */ /* ============ file tree ============ */
.tree-body { overflow:auto; flex:1; padding:4px 0 14px; } .tree-body { overflow:auto; flex:1; padding:4px 0 14px; }
.tree-row { display:flex; align-items:center; gap:6px; padding:2px 10px 2px 0; cursor:pointer; white-space:nowrap; position:relative; height:23px; } .tree-row { display:flex; align-items:center; gap:6px; padding:2px 10px 2px 0; cursor:pointer; white-space:nowrap; position:relative; height:23px; }
.tree-row:hover, .tree-row.ctx { background:var(--hover); } /* Same three states as the git list. A folder never reaches `active` — it opens
in the tree, not in the viewer — but it takes focus and hover like a file. */
.tree-row:hover, .tree-row.ctx, .tree-row.kbd { background:var(--row-soft); }
.tree-row:hover::before, .tree-row.ctx::before, .tree-row.kbd::before { content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--accent-dim); }
.tree-row.active { background:var(--sel); } .tree-row.active { background:var(--sel); }
.tree-row.active::before { content:""; position:absolute; left:0; top:0; bottom:0; width:2px; background:var(--accent); } .tree-row.active::before { content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--accent); }
.tree-row.kbd { background:var(--hover); box-shadow:inset 0 1px 0 var(--accent), inset 0 -1px 0 var(--accent); }
.tw { display:inline-flex; align-items:center; justify-content:center; width:14px; flex:0 0 14px; color:var(--fg-3); font-size:10px; } .tw { display:inline-flex; align-items:center; justify-content:center; width:14px; flex:0 0 14px; color:var(--fg-3); font-size:10px; }
.tree-label { font-size:12.5px; color:var(--fg-1); overflow:hidden; text-overflow:ellipsis; } .tree-label { font-size:12.5px; color:var(--fg-1); overflow:hidden; text-overflow:ellipsis; }
.tree-row.active .tree-label { color:var(--fg-0); } .tree-row.active .tree-label { color:var(--fg-0); }