feat(status-panel): overhaul status lanes and icons

The status panel now uses a grid-based layout with a flow divider
between unstaged and staged sections and clearer headers. Icons
were swapped to arrows for stage/unstage actions, and labels were
updated to show Working tree and Next commit for clarity.

- Replace action icons with arrows to indicate staging
- Redesign lanes and headers for a compact, responsive UI
- Add a divider and contextual labels for unstaged and staged sections
This commit is contained in:
Christoph Brandau
2026-08-17 21:30:53 +02:00
parent cd01df0108
commit fac22ff173
2 changed files with 128 additions and 17 deletions
+22 -12
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { Check, FileDiff, RotateCcw, Undo2 } from "@lucide/svelte";
import { ArrowLeft, ArrowRight, FileDiff, RotateCcw } from "@lucide/svelte";
import iconUrl from "../../../src-tauri/icons/icon.png";
import type { FileStatusKind, GitFileStatus, GitStatus } from "../types";
@@ -181,19 +181,22 @@
{:else if changedFiles.length === 0}
<div class="blank-state">No file changes returned.</div>
{:else}
<div class="status-lanes overflow-auto">
<section class="status-lane" aria-label="Unstaged changes">
<div class="status-lanes">
<section class="status-lane unstaged-lane" aria-label="Unstaged changes">
<header class="status-lane-head">
<div class="status-lane-title"><strong>Unstaged</strong><span>{unstagedCount}</span></div>
<div class="status-lane-title">
<div class="status-lane-copy"><strong>Unstaged</strong><small>Working tree</small></div>
<span class="status-lane-count">{unstagedCount}</span>
</div>
<div class="status-lane-actions">
{#if selectedUnstagedCount > 1}
<span class="status-selection-count">{selectedUnstagedCount} selected</span>
<button class="btn-sm" type="button" onclick={() => onStage(changedFiles.filter((file) => selectedStatusPaths.has(fileKey(file)) && file.unstaged !== null))} disabled={isBusy} title={`Stage ${selectedUnstagedCount} selected files`}>
<Check size={13} aria-hidden="true" /> Stage {selectedUnstagedCount}
<ArrowRight size={13} aria-hidden="true" /> Stage {selectedUnstagedCount}
</button>
{/if}
<button class="btn-sm" type="button" onclick={onStageAll} disabled={isBusy || !hasUnstaged} title="Stage all unstaged files">
<Check size={13} aria-hidden="true" /> Stage all
<ArrowRight size={13} aria-hidden="true" /> Stage all
</button>
{#if selectedUnstagedCount > 1}
<button class="btn-sm danger" type="button" onclick={() => onDiscard(changedFiles.filter((file) => selectedStatusPaths.has(fileKey(file)) && file.unstaged !== null), false)} disabled={isBusy} title={`Discard unstaged changes in ${selectedUnstagedCount} selected files`}>
@@ -212,7 +215,7 @@
</button>
<span class={`status-badge ${file.unstaged ?? "none"}`}>{statusLabel(file.unstaged)}</span>
<div class="status-file-actions">
<button type="button" onclick={() => stageFromFile(file)} disabled={isBusy || stageTargets.length === 0} title="Stage file"><Check size={14} aria-hidden="true" /></button>
<button type="button" onclick={() => stageFromFile(file)} disabled={isBusy || stageTargets.length === 0} title="Stage file"><ArrowRight size={14} aria-hidden="true" /></button>
<button type="button" onclick={() => onPatch(file, false)} disabled={isBusy || !canPatch(file.unstaged)} title="Show unstaged details"><FileDiff size={14} aria-hidden="true" /></button>
<button class="danger" type="button" onclick={() => discardUnstagedFromFile(file)} disabled={isBusy || stageTargets.length === 0} title="Discard unstaged changes"><RotateCcw size={14} aria-hidden="true" /></button>
</div>
@@ -222,18 +225,25 @@
</div>
</section>
<section class="status-lane" aria-label="Staged changes">
<div class="status-flow-divider" aria-hidden="true">
<span><ArrowRight size={12} /></span>
</div>
<section class="status-lane staged-lane" aria-label="Staged changes">
<header class="status-lane-head">
<div class="status-lane-title"><strong>Staged</strong><span>{stagedCount}</span></div>
<div class="status-lane-title">
<div class="status-lane-copy"><strong>Staged</strong><small>Next commit</small></div>
<span class="status-lane-count">{stagedCount}</span>
</div>
<div class="status-lane-actions">
{#if selectedStagedCount > 1}
<span class="status-selection-count">{selectedStagedCount} selected</span>
<button class="btn-sm" type="button" onclick={() => onUnstage(changedFiles.filter((file) => selectedStatusPaths.has(fileKey(file)) && file.staged !== null))} disabled={isBusy} title={`Unstage ${selectedStagedCount} selected files`}>
<Undo2 size={13} aria-hidden="true" /> Unstage {selectedStagedCount}
<ArrowLeft size={13} aria-hidden="true" /> Unstage {selectedStagedCount}
</button>
{/if}
<button class="btn-sm" type="button" onclick={onUnstageAll} disabled={isBusy || !hasStaged} title="Unstage all staged files">
<Undo2 size={13} aria-hidden="true" /> Unstage all
<ArrowLeft size={13} aria-hidden="true" /> Unstage all
</button>
{#if selectedStagedCount > 1}
<button class="btn-sm danger" type="button" onclick={() => onDiscard(changedFiles.filter((file) => selectedStatusPaths.has(fileKey(file)) && file.staged !== null), true)} disabled={isBusy} title={`Discard staged changes in ${selectedStagedCount} selected files`}>
@@ -252,7 +262,7 @@
</button>
<span class={`status-badge ${file.staged ?? "none"}`}>{statusLabel(file.staged)}</span>
<div class="status-file-actions">
<button type="button" onclick={() => unstageFromFile(file)} disabled={isBusy || unstageTargets.length === 0} title="Unstage file"><Undo2 size={14} aria-hidden="true" /></button>
<button type="button" onclick={() => unstageFromFile(file)} disabled={isBusy || unstageTargets.length === 0} title="Unstage file"><ArrowLeft size={14} aria-hidden="true" /></button>
<button type="button" onclick={() => onPatch(file, true)} disabled={isBusy || !canPatch(file.staged)} title="Show staged details"><FileDiff size={14} aria-hidden="true" /></button>
<button class="danger" type="button" onclick={() => discardStagedFromFile(file)} disabled={isBusy || unstageTargets.length === 0} title="Discard staged changes"><RotateCcw size={14} aria-hidden="true" /></button>
</div>