feat(history): make branch filter groups collapsible #28
+149
@@ -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)); }
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,7 +1206,19 @@
|
|||||||
|
|
||||||
<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">
|
||||||
|
<button
|
||||||
|
class="branch-filter-group-toggle"
|
||||||
|
type="button"
|
||||||
|
aria-expanded={localBranchGroupOpen}
|
||||||
|
onclick={() => { localBranchGroupOpen = !localBranchGroupOpen; }}
|
||||||
|
>
|
||||||
|
{#if localBranchGroupOpen}<ChevronDown size={14} aria-hidden="true" />{:else}<ChevronRight size={14} aria-hidden="true" />{/if}
|
||||||
|
<span>Local</span>
|
||||||
|
<em>{localBranchNames.filter(branchIsVisible).length}/{localBranchNames.length}</em>
|
||||||
|
</button>
|
||||||
|
{#if localBranchGroupOpen}
|
||||||
|
<div class="branch-filter-group-list">
|
||||||
{#each localBranchNames as branch}
|
{#each localBranchNames as branch}
|
||||||
<label class:muted={!branchIsVisible(branch)} class="branch-filter-option" title={branch}>
|
<label class:muted={!branchIsVisible(branch)} class="branch-filter-option" title={branch}>
|
||||||
<input
|
<input
|
||||||
@@ -1214,11 +1230,26 @@
|
|||||||
<span>{branch}</span>
|
<span>{branch}</span>
|
||||||
</label>
|
</label>
|
||||||
{/each}
|
{/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">
|
||||||
|
<button
|
||||||
|
class="branch-filter-group-toggle"
|
||||||
|
type="button"
|
||||||
|
aria-expanded={remoteBranchGroupOpen}
|
||||||
|
onclick={() => { remoteBranchGroupOpen = !remoteBranchGroupOpen; }}
|
||||||
|
>
|
||||||
|
{#if remoteBranchGroupOpen}<ChevronDown size={14} aria-hidden="true" />{:else}<ChevronRight size={14} aria-hidden="true" />{/if}
|
||||||
|
<span>Remote</span>
|
||||||
|
<em>{remoteBranchNames.filter(branchIsVisible).length}/{remoteBranchNames.length}</em>
|
||||||
|
</button>
|
||||||
|
{#if remoteBranchGroupOpen}
|
||||||
|
<div class="branch-filter-group-list">
|
||||||
{#each remoteBranchNames as branch}
|
{#each remoteBranchNames as branch}
|
||||||
<label class:muted={!branchIsVisible(branch)} class="branch-filter-option remote" title={branch}>
|
<label class:muted={!branchIsVisible(branch)} class="branch-filter-option" title={branch}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={branchIsVisible(branch)}
|
checked={branchIsVisible(branch)}
|
||||||
@@ -1228,6 +1259,9 @@
|
|||||||
<span>{branch}</span>
|
<span>{branch}</span>
|
||||||
</label>
|
</label>
|
||||||
{/each}
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user