feat(ui): add collapsible Worktree and Tags panels to left sidebar
Add two new sidebar components (WorktreePanel, TagsPanel) and integrate them into the compact left navigation layout: - Wire up imports and rendering in App.svelte, including collapse toggles and resize handles for both panels. Persisted heights (localStorage) and keyboard resizing are supported; heights are clamped between 80 and 420px with a 140px default. - Extend buildLeftSidebarRows and left-sidebar grid/template styles to include the new panels and reduce panel-handle thickness. Add extensive CSS for the compact accordion navigation, worktree and tags lists. - Move tag and worktree management UI out of BranchPanel (remove tag props), and add dedicated handlers in App.svelte for the new panels. - Refactor worktree loading: introduce a worktreeLoadId to guard async refreshWorktrees() calls and avoid race conditions. refreshWorktrees(path?) now takes an optional path and updates worktree state only when the request is still relevant. - Small behavioral tweaks: stash panel default collapsed state changed to true and the explorer resize-handle visibility condition adjusted. This commit only adds the UI/UX integration and local persistence for the new panels and their resizing/refresh behavior.
This commit is contained in:
+80
@@ -9173,3 +9173,83 @@ section > header.page-header.page-header {
|
||||
border-color: var(--dialog-close-hover-ink);
|
||||
background: color-mix(in srgb, var(--dialog-close-hover-ink) 12%, var(--app-dialog-chrome));
|
||||
}
|
||||
|
||||
/* Compact accordion navigation (layout A). */
|
||||
.left-sidebar { overflow-y: auto; overflow-x: hidden; }
|
||||
.left-sidebar > .panel { min-width: 0; }
|
||||
.left-sidebar .section-head {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 6px;
|
||||
min-height: 42px; padding: 6px 10px;
|
||||
background: var(--color-surface-solid);
|
||||
}
|
||||
.left-sidebar .sidebar-section-title {
|
||||
display: flex; align-items: center; gap: 9px; min-width: 0; margin: 0;
|
||||
color: var(--color-ink-muted); font-size: 11px; font-weight: 750;
|
||||
line-height: 20px; letter-spacing: .055em; text-transform: uppercase;
|
||||
}
|
||||
.left-sidebar .sidebar-section-title svg { flex-shrink: 0; }
|
||||
.left-sidebar .sidebar-section-title svg { width: 16px; height: 16px; stroke-width: 1.75; color: var(--color-ink-muted); }
|
||||
.left-sidebar :is(.branch-head-actions, .stash-head-actions, .explorer-head-actions) { gap: 3px; }
|
||||
.left-sidebar :is(.branch-create-toggle, .stash-toggle, .explorer-bulk-button) {
|
||||
width: 24px; min-width: 24px; height: 24px; min-height: 24px; flex-basis: 24px;
|
||||
border-color: transparent; background: transparent; border-radius: 3px;
|
||||
}
|
||||
.left-sidebar :is(.branch-create-toggle, .stash-toggle, .explorer-bulk-button):hover:not(:disabled) {
|
||||
background: var(--color-surface-hover); color: var(--color-ink);
|
||||
}
|
||||
.left-sidebar .left-panel-resize-handle { min-height: 6px; }
|
||||
.left-sidebar .branch-list, .left-sidebar .explorer-list { padding: 4px 0; }
|
||||
.left-sidebar .branch-group-toggle {
|
||||
min-height: 30px; padding: 5px 10px; border-radius: 0;
|
||||
font-size: 12px; font-weight: 500; letter-spacing: normal; text-transform: none;
|
||||
}
|
||||
.left-sidebar .branch-row {
|
||||
min-height: 34px; padding: 5px 10px; padding-left: calc(14px + var(--branch-indent, 0px));
|
||||
border-radius: 0; margin-top: 0;
|
||||
}
|
||||
.left-sidebar .branch-row.current { border-color: transparent; box-shadow: inset 3px 0 0 var(--color-primary); }
|
||||
.left-sidebar .branch-info strong { font-size: 12px; font-weight: 550; }
|
||||
.left-sidebar .branch-row.current strong { font-weight: 700; }
|
||||
.left-sidebar .branch-folder-row { min-height: 32px; border-radius: 0; margin-top: 0; }
|
||||
.left-sidebar .stash-panel { display: flex; flex-direction: column; min-height: 0; }
|
||||
.left-sidebar .stash-panel .section-head, .left-sidebar .stash-create { flex-shrink: 0; }
|
||||
.left-sidebar .stash-list { flex: 1; min-height: 0; overflow: auto; }
|
||||
.left-sidebar .stash-empty { flex: 1; padding: 16px 10px; }
|
||||
.left-sidebar .stash-create { grid-template-columns: minmax(0, 1fr) auto; }
|
||||
.left-sidebar .stash-input { grid-column: 1 / -1; }
|
||||
.left-sidebar .explorer-tool-action, .left-sidebar .explorer-action-divider { display: none; }
|
||||
/* File actions remain available through the file context menu. */
|
||||
.left-sidebar .worktree-panel { display: grid; grid-template-rows: 42px minmax(0, 1fr); min-height: 0; overflow: hidden; }
|
||||
.left-sidebar .worktree-panel.collapsed { grid-template-rows: 42px; }
|
||||
.sidebar-worktree-list { min-height: 0; overflow: auto; padding: 4px 0; }
|
||||
.sidebar-worktree-row {
|
||||
display: flex; align-items: center; gap: 8px; width: 100%; min-height: 46px;
|
||||
padding: 6px 10px; border: 0; border-radius: 0; background: transparent;
|
||||
color: var(--color-ink-muted); text-align: left;
|
||||
}
|
||||
.sidebar-worktree-row.current {
|
||||
background: color-mix(in srgb, var(--color-primary) 11%, var(--color-surface-solid));
|
||||
box-shadow: inset 3px 0 0 var(--color-primary);
|
||||
}
|
||||
.sidebar-worktree-row:hover:not(:disabled) { background: var(--color-surface-hover); }
|
||||
.sidebar-worktree-row > svg { flex-shrink: 0; }
|
||||
.sidebar-worktree-info { display: grid; gap: 2px; flex: 1; min-width: 0; }
|
||||
.sidebar-worktree-info strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--color-ink); }
|
||||
.sidebar-worktree-info > span { display: flex; align-items: center; gap: 4px; overflow: hidden; white-space: nowrap; font-size: 10px; color: var(--color-ink-dim); }
|
||||
.sidebar-worktree-info > span svg { flex-shrink: 0; }
|
||||
.sidebar-worktree-error { display: grid; gap: 8px; padding: 10px; font-size: 11px; overflow-wrap: anywhere; color: var(--color-ink-muted); }
|
||||
.sidebar-worktree-error button { justify-self: start; }
|
||||
|
||||
/* All collapsed sections share exactly the same header and icon geometry. */
|
||||
.left-sidebar > .panel { border: 0; }
|
||||
.left-sidebar > .panel + .panel { border-top: 0; }
|
||||
.left-sidebar .section-head { height: 42px; min-height: 42px; box-sizing: border-box; }
|
||||
.left-sidebar > .panel.collapsed { height: 42px; min-height: 42px; }
|
||||
.left-sidebar .section-head button svg { width: 14px; height: 14px; stroke-width: 1.75; }
|
||||
.left-sidebar .section-head .pill-count { min-width: 24px; justify-content: center; }
|
||||
|
||||
.left-sidebar .tags-panel { display: grid; grid-template-rows: 42px minmax(0, 1fr); min-height: 0; overflow: hidden; }
|
||||
.left-sidebar .tags-panel.collapsed { grid-template-rows: 42px; }
|
||||
.sidebar-tags-list { min-height: 0; overflow: auto; padding: 4px 0; }
|
||||
.left-sidebar .tags-panel .tag-create-form { grid-template-columns: auto minmax(0, 1fr) auto auto; margin: 4px 8px; }
|
||||
.left-sidebar .tags-panel .tag-create-form input[aria-label="Tag message"] { grid-column: 2 / -1; grid-row: 2; }
|
||||
|
||||
Reference in New Issue
Block a user