feat(repo-tabs): replace tab bar with workspace navigation
publish / Build and publish Windows installer (release) Successful in 8m44s
publish / Build and publish Ubuntu AppImage (release) Successful in 9m14s
publish / Build and publish AUR packages (release) Successful in 18m56s

Replace the legacy repository tab bar with a compact workspace
navigation that groups primary views and exposes a dedicated
"Repositories" action. The repository list is shown only when the
repository view is active and supports per-repo selection and closing.
Also add a Repositories open handler in the app to select the active
repo or prompt to choose a repository folder when none is available.

- Introduce top-level navigation (Dashboard, PRs, Issues, Repositories)
- Add onOpenRepositories hook and app logic to select or choose repo
- Update icons, styles, and accessibility attributes for the nav
This commit is contained in:
2026-09-08 22:10:16 +02:00
parent 200f01185a
commit 8c652b5df8
2 changed files with 51 additions and 67 deletions
+5
View File
@@ -5396,6 +5396,11 @@
{isBusy}
language={appLanguage}
onOpenManagement={openRepoManagement}
onOpenRepositories={() => {
const path = repoTabs.find(tab => sameRepoPath(tab.path, activeRepoPath))?.path ?? repoTabs[0]?.path;
if (path) return selectRepoTab(path);
return chooseRepositoryFolder();
}}
onOpenReviewCenter={() => openReviewCenter()}
onOpenIssues={() => { activeView = "issues"; }}
isActive={(path) => activeView === "repository" && sameRepoPath(activeRepoPath, path)}
+43 -64
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { CircleDot, Folder, GitBranch, GitPullRequest, Plus, X } from "@lucide/svelte";
import { Columns3, Database, Folder, House, GitPullRequest, Plus, X } from "@lucide/svelte";
interface RepositoryTabItem {
path: string;
@@ -12,6 +12,7 @@
export let isBusy: boolean = false;
export let language: "en" | "de" = "en";
export let onOpenManagement: () => void = () => {};
export let onOpenRepositories: () => void | Promise<void> = () => {};
export let onOpenIssues: () => void = () => {};
export let onOpenReviewCenter: () => void = () => {};
export let isActive: (path: string) => boolean = () => false;
@@ -21,70 +22,48 @@
export let onAdd: () => void | Promise<void> = () => {};
</script>
<header class="repo-tabbar" aria-label={language === "de" ? "Repository-Reiter" : "Repository tabs"}>
<button
class="repo-tab management"
class:active={activeView === "management"}
type="button"
onclick={onOpenManagement}
disabled={isBusy}
title={language === "de" ? "Repository-Verwaltung" : "Repository Management"}
aria-label={language === "de" ? "Repository-Verwaltung" : "Repository Management"}
>
<Folder size={15} aria-hidden="true" />
</button>
<div class="repo-tabs-scroll">
<div class="repo-tab-wrap review-center-tab" class:active={activeView === "review-center"} role="presentation">
<button class="repo-tab" type="button" onclick={onOpenReviewCenter} disabled={isBusy} title="Review Center" aria-label="Review Center">
<GitPullRequest size={13} aria-hidden="true" />
<span>PR</span>
</button>
</div>
<div class="repo-tab-wrap" class:active={activeView === "issues"} role="presentation">
<button class="repo-tab" type="button" onclick={onOpenIssues} disabled={isBusy} title="Issues" aria-label="Issues">
<CircleDot size={13} aria-hidden="true" /><span>Issues</span>
</button>
</div>
<header class="workspace-navigation">
<nav class="global-navigation" aria-label={language === "de" ? "Hauptnavigation" : "Main navigation"}>
<button type="button" class:active={activeView === "management"} aria-current={activeView === "management" ? "page" : undefined} disabled={isBusy} onclick={onOpenManagement}><House size={17}/><span>Dashboard</span></button>
<button type="button" class:active={activeView === "repository"} aria-current={activeView === "repository" ? "page" : undefined} disabled={isBusy} onclick={onOpenRepositories}><Database size={17}/><span>Repositories</span></button>
<button type="button" class:active={activeView === "review-center"} aria-current={activeView === "review-center" ? "page" : undefined} disabled={isBusy} onclick={onOpenReviewCenter}><GitPullRequest size={17}/><span>Pull Requests</span></button>
<button type="button" class:active={activeView === "issues"} aria-current={activeView === "issues" ? "page" : undefined} disabled={isBusy} onclick={onOpenIssues}><Columns3 size={17}/><span>Issues &amp; Boards</span></button>
</nav>
{#if activeView === "repository"}
<nav class="repository-navigation" aria-label={language === "de" ? "Geöffnete Repositories" : "Open repositories"}>
{#each repoTabs as repo (repo.path)}
<div
class="repo-tab-wrap"
class:active={isActive(repo.path)}
role="presentation"
oncontextmenu={(event) => onContextMenu(repo.path, event)}
>
<button
class="repo-tab"
type="button"
onclick={() => onSelect(repo.path)}
disabled={isBusy}
title={repo.path}
>
<GitBranch size={13} aria-hidden="true" />
<span>{repo.name}</span>
</button>
<button
class="repo-tab-close"
type="button"
onclick={(event) => onClose(repo.path, event)}
disabled={isBusy}
aria-label={language === "de" ? `${repo.name} schließen` : `Close ${repo.name}`}
title={language === "de" ? "Repository-Tab schließen" : "Close repository tab"}
>
<X size={11} aria-hidden="true" />
</button>
<div class="repository-tab" class:active={isActive(repo.path)} role="presentation" oncontextmenu={(event) => onContextMenu(repo.path, event)}>
<button class="repository-select" type="button" onclick={() => onSelect(repo.path)} disabled={isBusy} title={repo.path} aria-current={isActive(repo.path) ? "page" : undefined}><Folder size={16}/><span>{repo.name}</span></button>
<button class="repository-close" type="button" onclick={(event) => onClose(repo.path, event)} disabled={isBusy} aria-label={language === "de" ? `${repo.name} schließen` : `Close ${repo.name}`}><X size={13}/></button>
</div>
{/each}
</div>
<button
class="repo-tab-add"
type="button"
onclick={onAdd}
disabled={isBusy}
title={language === "de" ? "Repository-Ordner öffnen" : "Open repository folder"}
aria-label={language === "de" ? "Repository-Ordner öffnen" : "Open repository folder"}
>
<Plus size={15} aria-hidden="true" />
</button>
<button class="repository-add" type="button" onclick={onAdd} disabled={isBusy} title={language === "de" ? "Repository-Ordner öffnen" : "Open repository folder"} aria-label={language === "de" ? "Repository-Ordner öffnen" : "Open repository folder"}><Plus size={18}/></button>
</nav>
{/if}
</header>
<style>
.workspace-navigation{flex:0 0 auto;min-width:0;background:var(--app-bg);color:var(--color-ink-muted);font-family:var(--font-sans);border-bottom:1px solid var(--color-border-subtle)}
.global-navigation{display:flex;align-items:stretch;gap:2px;min-height:38px;padding:0 5px;overflow-x:auto;scrollbar-width:thin;border-bottom:1px solid var(--color-border-subtle)}
button{font:inherit;cursor:pointer;color:inherit;background:transparent;border:0;box-shadow:none}
button:disabled{opacity:.45;cursor:default}
button:focus-visible{outline:2px solid var(--color-accent);outline-offset:-3px}
.global-navigation button{position:relative;display:flex;flex:0 0 auto;align-items:center;gap:7px;padding:0 10px;min-height:38px;font-size:13px;white-space:nowrap}
.global-navigation button:hover:not(:disabled){background:var(--color-surface-hover);color:var(--color-ink)}
.global-navigation button.active{color:var(--color-ink);font-weight:600}
.global-navigation button.active::after{position:absolute;content:"";height:2px;bottom:0;left:12px;right:12px;background:var(--color-accent)}
.global-navigation button.active>:global(svg){color:var(--color-accent)}
.repository-navigation{display:flex;align-items:stretch;gap:4px;min-height:34px;padding:4px 5px 0;overflow-x:auto;scrollbar-width:thin;background:color-mix(in srgb,var(--color-surface) 45%,var(--app-bg))}
.repository-tab{display:flex;flex:0 0 auto;align-items:center;min-width:100px;max-width:250px;border:1px solid var(--color-border);border-bottom:0;border-radius:5px 5px 0 0;background:var(--app-bg)}
.repository-tab.active{background:var(--color-surface-raised);border-color:var(--color-border-input);color:var(--color-ink)}
.repository-tab:hover{background:var(--color-surface-hover)}
.repository-select{display:flex;flex:1;min-width:0;align-items:center;gap:7px;min-height:29px;padding:0 9px;font-size:12px;text-align:left}
.repository-select span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.repository-select>:global(svg){flex-shrink:0}
.repository-tab.active .repository-select{font-weight:600}
.repository-close{display:grid;place-items:center;flex:0 0 24px;height:24px;margin-right:2px;color:var(--color-ink-dim)}
.repository-close:hover:not(:disabled){color:var(--color-ink);background:var(--color-surface-hover)}
.repository-add{display:grid;place-items:center;flex:0 0 32px;min-height:29px;border:1px solid var(--color-border);border-bottom:0;border-radius:5px 5px 0 0}
.repository-add:hover:not(:disabled){color:var(--color-accent);background:var(--color-surface-hover)}
@media(max-width:640px){.global-navigation{gap:0;padding:0 4px}.global-navigation button{gap:7px;padding:0 10px;font-size:12px;min-height:38px}.global-navigation{min-height:38px}}
</style>