feat(history): make branch filter groups collapsible

Add collapsible local and remote branch groups to the branch
visibility dialog, including toggle buttons and selection counts.
Introduce comprehensive dialog styling and responsive rules to match
the app UI. Opening the dialog now defaults to local open and remote
closed for faster access.

- Add CSS for branch-filter dialog layout, theming, and responsiveness
- Implement group toggles with chevrons and visible selected counts
- Default to local group open and remote group closed on dialog open
This commit is contained in:
2026-08-23 23:20:13 +02:00
parent 58027f5e22
commit 79f4ec21e4
2 changed files with 207 additions and 24 deletions
+149
View File
@@ -8523,3 +8523,152 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
:root[data-appearance="custom"] .graph-gutter { :root[data-appearance="custom"] .graph-gutter {
background: var(--color-surface-dim); background: var(--color-surface-dim);
} }
/* Branch visibility dialog ---------------------------------------------
Local and remote branches share one visual and interaction language. */
.branch-filter-backdrop,
:root[data-theme="light"] .branch-filter-backdrop {
padding: 24px;
background: var(--app-dialog-backdrop);
backdrop-filter: blur(5px);
}
.branch-filter-dialog,
:root[data-theme="light"] .branch-filter-dialog {
width: min(560px, 100%);
max-height: min(680px, calc(100vh - 48px));
border-color: var(--color-border);
background: var(--app-dialog-bg);
box-shadow: var(--app-dialog-shadow);
}
.branch-filter-dialog-head {
min-height: 64px;
padding: 12px 14px;
border-bottom-color: var(--color-border);
background: var(--app-dialog-chrome);
}
.branch-filter-dialog-head h3 {
color: var(--color-ink);
font-size: 15px;
}
.branch-filter-summary {
min-height: 48px;
padding: 8px 14px;
border-bottom-color: var(--color-border);
color: var(--color-ink-muted);
background: var(--color-surface-dim);
}
.branch-filter-actions button {
min-height: 28px;
border-color: var(--color-border);
color: var(--color-ink-muted);
background: var(--color-surface-raised);
}
.branch-filter-actions button:hover:not(:disabled) {
border-color: var(--color-border-input);
color: var(--color-ink);
background: var(--color-surface-hover);
}
.branch-filter-dialog-list {
gap: 8px;
padding: 10px;
background: var(--color-surface-alt);
}
.branch-filter-group {
display: grid;
min-width: 0;
overflow: hidden;
border: 1px solid var(--color-border);
background: var(--color-surface);
}
.branch-filter-group-toggle {
display: grid;
grid-template-columns: 16px minmax(0, 1fr) auto;
align-items: center;
gap: 8px;
width: 100%;
min-height: 36px;
padding: 0 10px;
border: 0;
color: var(--color-ink-muted);
background: var(--color-surface-dim);
text-align: left;
}
.branch-filter-group-toggle:hover:not(:disabled) {
color: var(--color-ink);
background: var(--color-surface-hover);
}
.branch-filter-group-toggle > svg {
color: var(--color-accent);
}
.branch-filter-group-toggle > span {
font-size: 10px;
font-weight: 850;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.branch-filter-group-toggle > em {
min-width: 34px;
padding: 2px 5px;
border: 1px solid var(--color-border-subtle);
color: var(--color-ink-faint);
background: var(--color-surface-raised);
font-size: 9px;
font-style: normal;
font-weight: 800;
text-align: center;
}
.branch-filter-group-list {
display: grid;
gap: 4px;
padding: 6px;
border-top: 1px solid var(--color-border-subtle);
}
.branch-filter-option,
:root[data-theme="light"] .branch-filter-option,
:root[data-theme="light"] .branch-filter-option.remote {
min-height: 34px;
border-color: color-mix(in srgb, var(--color-accent) 32%, var(--color-border));
color: var(--color-ink);
background: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface-raised));
opacity: 1;
}
.branch-filter-option:hover,
:root[data-theme="light"] .branch-filter-option:hover {
border-color: color-mix(in srgb, var(--color-accent) 52%, var(--color-border));
color: var(--color-ink);
background: color-mix(in srgb, var(--color-accent) 13%, var(--color-surface-hover));
}
.branch-filter-option.muted,
:root[data-theme="light"] .branch-filter-option.muted {
border-color: var(--color-border-subtle);
color: var(--color-ink-faint);
background: var(--color-surface-raised);
opacity: 0.72;
}
.branch-filter-option input {
accent-color: var(--color-accent);
}
@media (max-width: 560px) {
.branch-filter-backdrop { padding: 8px; }
.branch-filter-summary { align-items: stretch; flex-direction: column; }
.branch-filter-actions { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
+58 -24
View File
@@ -117,6 +117,8 @@
let customVisibleBranches = $state<Set<string>>(new Set()); let customVisibleBranches = $state<Set<string>>(new Set());
let loadedVisibilityRepository = $state(""); let loadedVisibilityRepository = $state("");
let branchDialogOpen = $state(false); let branchDialogOpen = $state(false);
let localBranchGroupOpen = $state(true);
let remoteBranchGroupOpen = $state(false);
let expandedRefsCommitHash = $state(""); let expandedRefsCommitHash = $state("");
let panelElement = $state<HTMLElement | null>(null); let panelElement = $state<HTMLElement | null>(null);
let contextCommit = $state<GitCommit | null>(null); let contextCommit = $state<GitCommit | null>(null);
@@ -428,6 +430,8 @@
} }
function openBranchDialog() { function openBranchDialog() {
localBranchGroupOpen = true;
remoteBranchGroupOpen = false;
branchDialogOpen = true; branchDialogOpen = true;
} }
@@ -1202,32 +1206,62 @@
<div class="branch-filter-dialog-list"> <div class="branch-filter-dialog-list">
{#if localBranchNames.length > 0} {#if localBranchNames.length > 0}
<span class="branch-filter-group-label">Local</span> <section class="branch-filter-group">
{#each localBranchNames as branch} <button
<label class:muted={!branchIsVisible(branch)} class="branch-filter-option" title={branch}> class="branch-filter-group-toggle"
<input type="button"
type="checkbox" aria-expanded={localBranchGroupOpen}
checked={branchIsVisible(branch)} onclick={() => { localBranchGroupOpen = !localBranchGroupOpen; }}
onchange={() => toggleGraphBranch(branch)} >
/> {#if localBranchGroupOpen}<ChevronDown size={14} aria-hidden="true" />{:else}<ChevronRight size={14} aria-hidden="true" />{/if}
<GitBranch size={14} aria-hidden="true" /> <span>Local</span>
<span>{branch}</span> <em>{localBranchNames.filter(branchIsVisible).length}/{localBranchNames.length}</em>
</label> </button>
{/each} {#if localBranchGroupOpen}
<div class="branch-filter-group-list">
{#each localBranchNames as branch}
<label class:muted={!branchIsVisible(branch)} class="branch-filter-option" title={branch}>
<input
type="checkbox"
checked={branchIsVisible(branch)}
onchange={() => toggleGraphBranch(branch)}
/>
<GitBranch size={14} aria-hidden="true" />
<span>{branch}</span>
</label>
{/each}
</div>
{/if}
</section>
{/if} {/if}
{#if remoteBranchNames.length > 0} {#if remoteBranchNames.length > 0}
<span class="branch-filter-group-label">Remote</span> <section class="branch-filter-group">
{#each remoteBranchNames as branch} <button
<label class:muted={!branchIsVisible(branch)} class="branch-filter-option remote" title={branch}> class="branch-filter-group-toggle"
<input type="button"
type="checkbox" aria-expanded={remoteBranchGroupOpen}
checked={branchIsVisible(branch)} onclick={() => { remoteBranchGroupOpen = !remoteBranchGroupOpen; }}
onchange={() => toggleGraphBranch(branch)} >
/> {#if remoteBranchGroupOpen}<ChevronDown size={14} aria-hidden="true" />{:else}<ChevronRight size={14} aria-hidden="true" />{/if}
<GitBranch size={14} aria-hidden="true" /> <span>Remote</span>
<span>{branch}</span> <em>{remoteBranchNames.filter(branchIsVisible).length}/{remoteBranchNames.length}</em>
</label> </button>
{/each} {#if remoteBranchGroupOpen}
<div class="branch-filter-group-list">
{#each remoteBranchNames as branch}
<label class:muted={!branchIsVisible(branch)} class="branch-filter-option" title={branch}>
<input
type="checkbox"
checked={branchIsVisible(branch)}
onchange={() => toggleGraphBranch(branch)}
/>
<GitBranch size={14} aria-hidden="true" />
<span>{branch}</span>
</label>
{/each}
</div>
{/if}
</section>
{/if} {/if}
</div> </div>
</div> </div>