From af1c70d45d42b9bac955d5d6775d1488f8f67501 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Thu, 17 Sep 2026 23:08:06 +0200 Subject: [PATCH] feat(review-center): add repository filter and SelectMenu option meta/icon Add a repository picker to the Review Center and wire it into the request filtering logic. Introduces repositoryFilter state and a derived repositoryOptions list (grouped by owner and carrying counts). If the chosen repository disappears from the options, the filter is cleared automatically. Enhance SelectMenu to support per-option meta text and an optional optionIcon snippet. Render options as [icon] label [meta] with updated markup and CSS to align and style icon/label/meta. Adjust toolbar grid and responsive CSS to make room for the new repository picker and to tweak select popup/option styles. --- src/app.css | 23 ++++++- src/lib/components/ReviewCenter.svelte | 88 ++++++++++++++++++++++---- src/lib/components/SelectMenu.svelte | 10 ++- 3 files changed, 105 insertions(+), 16 deletions(-) diff --git a/src/app.css b/src/app.css index 5f4956e..f9747e8 100644 --- a/src/app.css +++ b/src/app.css @@ -298,9 +298,9 @@ text-align: center; } .select-menu-option { - display: grid; - grid-template-columns: minmax(0, 1fr) 14px; + display: flex; align-items: center; + gap: 8px; justify-content: initial; width: 100%; min-height: 30px; @@ -314,6 +314,25 @@ text-align: left; } .select-menu-option > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .select-menu-option-label { flex: 1 1 auto; min-width: 0; } + .select-menu-option-icon { + display: grid; + flex: 0 0 auto; + place-items: center; + width: 18px; + overflow: visible; + color: var(--color-ink-faint); + } + .select-menu-option.selected .select-menu-option-icon, + .select-menu-option:hover .select-menu-option-icon { color: var(--color-accent); } + .select-menu-option-icon svg { color: inherit; } + .select-menu-option-meta { + flex: 0 0 auto; + color: var(--color-ink-faint); + font-size: 9.5px; + font-weight: 650; + letter-spacing: 0; + } .select-menu-option svg { color: var(--color-primary); } .select-menu-option:hover:not(:disabled), .select-menu-option.active:not(:disabled) { border-color: var(--color-border-subtle); diff --git a/src/lib/components/ReviewCenter.svelte b/src/lib/components/ReviewCenter.svelte index f4f3af5..6553574 100644 --- a/src/lib/components/ReviewCenter.svelte +++ b/src/lib/components/ReviewCenter.svelte @@ -45,6 +45,7 @@ let errors = $state>([]); let query = $state(""); let stateFilter = $state("open"); + let repositoryFilter = $state(""); let selectedSourceId = $state(""); let selectedId = $state(""); let detailOpen = $state(false); @@ -67,8 +68,49 @@ const normalizedQuery = $derived(query.trim().toLocaleLowerCase()); const filtered = $derived(requests.filter((request) => { const haystack = `${request.title} ${request.repositoryName} ${request.author} ${request.number} ${request.sourceBranch} ${request.targetBranch}`.toLocaleLowerCase(); - return request.state === stateFilter && (!normalizedQuery || haystack.includes(normalizedQuery)); + return request.state === stateFilter + && (!repositoryFilter || request.repositoryName === repositoryFilter) + && (!normalizedQuery || haystack.includes(normalizedQuery)); })); + + /** + * Repositories of the requests loaded for the current state tab, grouped by + * owner and carrying the number of requests as the right-hand meta text. + */ + const repositoryOptions = $derived.by(() => { + const counts = new Map(); + for (const request of requests) { + if (request.state !== stateFilter || !request.repositoryName) continue; + counts.set(request.repositoryName, (counts.get(request.repositoryName) ?? 0) + 1); + } + + const entries = [...counts.entries()] + .sort(([left], [right]) => left.localeCompare(right, undefined, { sensitivity: "base" })) + .map(([name, count]) => { + const separator = name.lastIndexOf("/"); + return { + value: name, + label: separator > 0 ? name.slice(separator + 1) : name, + group: separator > 0 ? name.slice(0, separator) : (activeSource?.label ?? ""), + meta: String(count), + }; + }); + + entries.sort((left, right) => left.group.localeCompare(right.group, undefined, { sensitivity: "base" }) + || left.label.localeCompare(right.label, undefined, { sensitivity: "base" })); + + return [ + { value: "", label: de ? "Alle Repositories" : "All repositories", meta: String(counts.size) }, + ...entries, + ]; + }); + + $effect(() => { + // Drop the filter as soon as the chosen repository is no longer in the list. + if (repositoryFilter && !repositoryOptions.some((option) => option.value === repositoryFilter)) { + repositoryFilter = ""; + } + }); const groupedRequests = $derived.by(() => { const groups = new Map(); for (const request of filtered) { @@ -440,6 +482,20 @@ {/each} +
+ 8} + searchPlaceholder={de ? "Repository suchen …" : "Search repository …"} + emptyText={de ? "Kein passendes Repository" : "No matching repository"} + ariaLabel={de ? "Repository filtern" : "Filter repository"} + onChange={(value) => { repositoryFilter = value; selectedId = ""; }} + > + {#snippet optionIcon()} +
({ value: source.id, label: source.label }))} ariaLabel={de ? "Integration auswählen" : "Select integration"} onChange={selectSource} />
@@ -586,7 +642,7 @@ /* Review Center B — compact hierarchy, overlay inspector, semantic actions. */ .review-center{font-size:11px}.review-header{min-height:44px}.review-heading h1{font-size:14px} - .review-toolbar{display:grid;min-height:50px;grid-template-columns:auto minmax(220px,1fr) 190px auto;align-items:center;gap:10px;padding:0 14px;background:color-mix(in srgb,var(--color-surface) 35%,var(--app-bg))} + .review-toolbar{display:grid;min-height:50px;grid-template-columns:auto minmax(200px,1fr) 170px 190px auto;align-items:center;gap:10px;padding:0 14px;background:color-mix(in srgb,var(--color-surface) 35%,var(--app-bg))} .state-tabs{height:100%;min-height:0;gap:3px;padding:0;border:0;background:transparent}.state-tabs button{min-width:58px;justify-content:center;padding:0 9px;font-size:10.5px}.state-tabs button.active{color:var(--color-ink)}.state-tabs button.active:after{right:7px;left:7px}.state-tabs span,.group-header>span{min-width:17px;height:17px;padding:0 4px;border:1px solid var(--color-border-subtle);border-radius:1px;font-size:9px} .review-search{min-width:0;height:30px}.review-search:focus-within{box-shadow:inset 2px 0 0 var(--color-accent)}.group-actions{gap:4px}.group-actions .icon-button{width:30px;margin:0;padding:0;border-color:var(--color-border-subtle)} .table-head,.request-row{grid-template-columns:92px minmax(285px,1.65fr) 132px 126px minmax(220px,1fr) 190px}.table-head{min-height:34px;padding:0 16px;border-top:1px solid var(--color-border-subtle);border-bottom-color:var(--color-border);color:color-mix(in srgb,var(--color-ink-faint) 82%,transparent);background:color-mix(in srgb,var(--color-surface-raised) 80%,var(--app-bg));font-size:8.5px;letter-spacing:.07em}.request-groups{min-width:1080px}.request-group{border-bottom:0}.group-header{height:39px;gap:8px;padding:0 16px;border-bottom-color:var(--color-border);color:var(--color-ink);background:color-mix(in srgb,var(--color-surface) 78%,var(--app-bg))}.group-header:hover{background:color-mix(in srgb,var(--color-surface-hover) 82%,var(--app-bg))}.group-header>:global(svg){color:var(--color-accent)}.group-header strong{font-size:11.5px;font-weight:680;letter-spacing:.005em}.group-header>span{margin-left:4px;color:var(--color-ink-muted);background:color-mix(in srgb,var(--color-surface-raised) 88%,var(--app-bg))} @@ -598,19 +654,19 @@ .detail-main>.description{padding:12px 0}.detail-main>.comments-section{padding:12px 0 14px}.comment-list{gap:8px}.comment-list article{gap:8px}.comment-list article>div{padding:8px 9px;border-radius:0;background:color-mix(in srgb,var(--color-surface) 48%,var(--app-bg))} .detail-sidebar{padding:15px 0 15px 16px;background:color-mix(in srgb,var(--color-surface) 58%,var(--app-bg))}.detail-sidebar .detail-actions{gap:6px;margin-bottom:9px}.detail-action{height:31px}.detail-sidebar section{padding:14px 0}.people{grid-template-columns:23px minmax(0,1fr);gap:8px;margin-top:10px}.people strong,.detail-meta strong,.detail-meta span{overflow:hidden;color:var(--color-ink-muted);font-size:10px;font-weight:500;text-overflow:ellipsis}.detail-meta{display:grid;gap:8px}.detail-actions>.danger-action:first-child{color:#e0aa55;border-color:#b88432;background:color-mix(in srgb,#b88432 8%,var(--color-surface))} .local-resolution{margin:8px 0 0;border-color:color-mix(in srgb,#d6a64f 55%,var(--color-border));background:color-mix(in srgb,#d6a64f 7%,var(--color-surface))}.local-resolution>div>:global(svg){color:#d6a64f}.local-resolution button{color:#e0aa55;border-color:#b88432;background:color-mix(in srgb,#b88432 10%,var(--color-surface))} - @media(max-width:1280px){.review-toolbar{grid-template-columns:auto minmax(180px,1fr) 155px auto}.state-tabs button{min-width:52px;padding:0 6px}.table-head,.request-row{grid-template-columns:80px minmax(250px,1.5fr) 110px minmax(190px,1fr) 180px}.table-head>span:nth-child(4),.collaborators{display:none}.table-head,.request-groups{min-width:980px}.detail-panel{width:58%;min-width:680px}} - @media(max-width:900px){.review-toolbar{grid-template-columns:1fr 150px auto;grid-template-rows:38px 38px;padding:0 10px}.state-tabs{grid-column:1/-1;grid-row:1}.review-search{grid-column:1;grid-row:2}.group-actions{grid-column:3;grid-row:2}.detail-panel{width:76%;min-width:620px}.detail-content{grid-template-columns:minmax(0,1fr) 220px;gap:14px}.table-head,.request-row{grid-template-columns:76px minmax(240px,1.5fr) minmax(180px,1fr) 180px}.table-head>span:nth-child(3),.request-author{display:none}.request-groups{min-width:760px}} + @media(max-width:1280px){.review-toolbar{grid-template-columns:auto minmax(160px,1fr) 150px 155px auto}.state-tabs button{min-width:52px;padding:0 6px}.table-head,.request-row{grid-template-columns:80px minmax(250px,1.5fr) 110px minmax(190px,1fr) 180px}.table-head>span:nth-child(4),.collaborators{display:none}.table-head,.request-groups{min-width:980px}.detail-panel{width:58%;min-width:680px}} + @media(max-width:900px){.review-toolbar{grid-template-columns:minmax(120px,1fr) 150px 150px auto;grid-template-rows:38px 38px;padding:0 10px}.state-tabs{grid-column:1/-1;grid-row:1}.review-search{grid-column:1;grid-row:2}.group-actions{grid-column:3;grid-row:2}.detail-panel{width:76%;min-width:620px}.detail-content{grid-template-columns:minmax(0,1fr) 220px;gap:14px}.table-head,.request-row{grid-template-columns:76px minmax(240px,1.5fr) minmax(180px,1fr) 180px}.table-head>span:nth-child(3),.request-author{display:none}.request-groups{min-width:760px}} @media(max-width:680px){.state-tabs button{min-width:0;flex:1}.group-actions .icon-button:nth-child(-n+2){display:none}.detail-panel{width:100%;min-width:0;max-width:none}.detail-content{display:block;padding:0 13px}.detail-main{height:100%}.detail-sidebar{display:none}} /* Accepted Review Center concept — faithful final layout. */ .review-header{min-height:54px;padding:0 24px;background:color-mix(in srgb,var(--app-bg) 78%,var(--color-surface))}.review-heading{gap:10px}.review-heading h1{font-size:14px;font-weight:700} - .review-toolbar{min-height:55px;grid-template-columns:350px minmax(220px,1fr) 160px 34px;gap:10px;padding:0 16px;border-bottom-color:var(--color-border);background:color-mix(in srgb,var(--app-bg) 88%,var(--color-surface))}.state-tabs{gap:8px}.state-tabs button{min-width:72px;justify-content:flex-start;padding:0 8px;color:var(--color-ink-muted);font-size:11px}.state-tabs button.active{color:var(--color-ink)}.state-tabs button.active:after{right:0;left:0;height:2px}.state-tabs span{min-width:17px;height:17px;margin-left:auto;border:0;background:var(--color-surface-raised)}.review-search,.group-actions .icon-button{height:34px;background:color-mix(in srgb,var(--app-input-bg) 92%,#11171c)}.review-search{padding:0 11px}.group-actions .icon-button{width:34px} + .review-toolbar{min-height:55px;grid-template-columns:350px minmax(200px,1fr) 155px 160px 34px;gap:10px;padding:0 16px;border-bottom-color:var(--color-border);background:color-mix(in srgb,var(--app-bg) 88%,var(--color-surface))}.state-tabs{gap:8px}.state-tabs button{min-width:72px;justify-content:flex-start;padding:0 8px;color:var(--color-ink-muted);font-size:11px}.state-tabs button.active{color:var(--color-ink)}.state-tabs button.active:after{right:0;left:0;height:2px}.state-tabs span{min-width:17px;height:17px;margin-left:auto;border:0;background:var(--color-surface-raised)}.review-search,.group-actions .icon-button{height:34px;background:color-mix(in srgb,var(--app-input-bg) 92%,#11171c)}.review-search{padding:0 11px}.group-actions .icon-button{width:34px} .table-head,.request-row{grid-template-columns:90px minmax(255px,1.65fr) 105px 120px minmax(180px,1fr) 150px}.table-head{min-height:37px;padding:0 16px;background:color-mix(in srgb,var(--color-surface-raised) 88%,var(--app-bg));font-size:8.5px}.table-head,.request-groups{min-width:980px}.group-header{height:45px;padding:0 17px;background:color-mix(in srgb,var(--app-bg) 72%,var(--color-surface))}.group-header strong{font-size:11.5px}.request-row{min-height:78px;padding:0 16px}.request-row.selected{background:linear-gradient(90deg,color-mix(in srgb,var(--color-accent) 7%,var(--color-surface-raised)),color-mix(in srgb,var(--color-surface-raised) 72%,var(--app-bg)))}.request-title{gap:8px}.request-title strong{font-size:12px}.request-status{font-size:10.5px}.request-author i,.avatar,.collaborators i{width:28px;height:28px;background:color-mix(in srgb,var(--color-accent) 58%,#26333a)}.provider-button,.action-toggle,.panel-button{height:34px}.provider-button{min-width:102px}.action-toggle{width:34px}.panel-button{width:34px}.action-menu{top:38px;width:154px}.action-menu button{height:36px} .detail-panel{width:44%;min-width:680px;max-width:none;background:color-mix(in srgb,var(--app-bg) 94%,var(--color-surface))}.detail-header{min-height:54px;padding:0 24px;background:color-mix(in srgb,var(--app-bg) 78%,var(--color-surface))}.detail-provider strong{font-size:13px}.detail-content{grid-template-columns:minmax(0,1fr) 225px;gap:18px;padding:0 0 0 24px}.detail-main{padding-right:0}.detail-title{padding:18px 0 16px}.detail-title-line{display:flex;min-width:0;align-items:baseline;gap:10px}.detail-title-line>span{flex:0 0 auto;color:var(--color-accent);font-size:17px;font-weight:700}.detail-title h2{min-width:0;margin:0;overflow:hidden;font-size:19px;font-weight:700;text-overflow:ellipsis;white-space:nowrap}.detail-title .detail-summary{min-height:33px;gap:7px}.detail-summary .avatar{width:23px;height:23px;margin-left:5px}.detail-summary strong{font-size:10.5px}.summary-separator{color:var(--color-ink-faint)}.detail-branch-route{display:flex;min-width:0;align-items:center;gap:6px;color:var(--color-ink-muted)}.detail-branch-route>:global(svg){color:var(--color-ink-muted)}.detail-branch-route code{max-width:95px;overflow:hidden;color:var(--color-ink-muted);font-size:10.5px;text-overflow:ellipsis;white-space:nowrap}.detail-branch-route>span{color:var(--color-ink-faint)}.state-badge{padding:0;border:0;font-size:10.5px} .detail-main>.merge-summary{min-height:55px;margin:0 0 4px;padding:0 12px;border-color:color-mix(in srgb,#63c783 65%,var(--color-border));background:color-mix(in srgb,#63c783 5%,var(--app-bg))}.detail-main>.description{padding:14px 0 18px}.description h3,.comments-section h3{font-size:11.5px}.description p{font-size:10.5px}.detail-main>.comments-section{padding:13px 0 0}.comments-section>header{min-height:28px;margin:0 0 8px}.comments-section>header>span{border-radius:1px}.comment-list{gap:10px;padding-right:0}.comment-list article{display:block}.comment-card{padding:0!important;border:1px solid var(--color-border)!important;background:color-mix(in srgb,var(--color-surface) 35%,var(--app-bg))!important}.comment-card header{min-height:42px;margin:0!important;padding:0 10px;border-bottom:0}.comment-card header .avatar{width:25px;height:25px;margin-right:2px}.comment-card header strong{font-size:10.5px}.comment-card header time{margin-left:auto}.owner-badge{padding:3px 6px;border:1px solid var(--color-border);color:var(--color-ink-faint);font-size:8.5px}.comment-card p{padding:0 44px 13px!important;color:var(--color-ink)!important} .detail-sidebar{padding:28px 17px 16px;border-left-color:var(--color-border);background:color-mix(in srgb,var(--color-surface) 46%,var(--app-bg))}.detail-sidebar .detail-actions{gap:10px;margin:0 0 12px}.detail-action{height:39px;font-size:11px}.detail-sidebar section{padding:17px 0}.detail-sidebar section h3{font-size:11.5px}.people{margin-top:12px}.detail-meta{gap:11px} - @media(max-width:1280px){.review-toolbar{grid-template-columns:310px minmax(180px,1fr) 145px 34px}.state-tabs{gap:3px}.state-tabs button{min-width:64px}.table-head,.request-row{grid-template-columns:80px minmax(250px,1.5fr) 105px minmax(190px,1fr) 150px}.request-groups{min-width:840px}.detail-panel{width:50%;min-width:650px}} - @media(max-width:900px){.review-toolbar{grid-template-columns:1fr 150px 34px;grid-template-rows:40px 40px}.detail-panel{width:72%;min-width:600px}.detail-content{grid-template-columns:minmax(0,1fr) 205px;gap:14px;padding-left:16px}.table-head,.request-row{grid-template-columns:76px minmax(240px,1.5fr) minmax(180px,1fr) 150px}.request-groups{min-width:720px}} + @media(max-width:1280px){.review-toolbar{grid-template-columns:310px minmax(160px,1fr) 140px 145px 34px}.state-tabs{gap:3px}.state-tabs button{min-width:64px}.table-head,.request-row{grid-template-columns:80px minmax(250px,1.5fr) 105px minmax(190px,1fr) 150px}.request-groups{min-width:840px}.detail-panel{width:50%;min-width:650px}} + @media(max-width:900px){.review-toolbar{grid-template-columns:minmax(120px,1fr) 145px 145px 34px;grid-template-rows:40px 40px}.detail-panel{width:72%;min-width:600px}.detail-content{grid-template-columns:minmax(0,1fr) 205px;gap:14px;padding-left:16px}.table-head,.request-row{grid-template-columns:76px minmax(240px,1.5fr) minmax(180px,1fr) 150px}.request-groups{min-width:720px}} @media(max-width:680px){.detail-panel{width:100%;min-width:0}.detail-content{display:block;padding:0 14px}.detail-sidebar{display:none}.detail-title h2{font-size:16px}.detail-title-line>span{font-size:14px}} /* Match the approved reference at its 1672px desktop width. */ @@ -627,22 +683,28 @@ /* The inspector overlays an unchanged background; only viewport size reflows it. */ - .review-toolbar{box-sizing:border-box;width:100%;height:56px;min-height:56px;grid-template-columns:316px minmax(140px,1fr) 145px 30px;grid-template-rows:1fr;gap:10px;padding:0 14px 0 8px} + .review-toolbar{box-sizing:border-box;width:100%;height:56px;min-height:56px;grid-template-columns:316px minmax(140px,1fr) 150px 145px 30px;grid-template-rows:1fr;gap:10px;padding:0 14px 0 8px} .review-toolbar .state-tabs{grid-column:1;grid-row:1;gap:8px;align-items:stretch} .review-center .state-tabs button{min-width:0;flex:1;gap:6px;padding:0 6px;justify-content:center;font-size:12px;white-space:nowrap} .state-tabs span{min-width:14px;height:16px;margin-left:0;padding:0 3px;font-size:10px} .state-tabs button.active span{background:transparent} .review-search{grid-column:2;grid-row:1;height:32px;order:0} .review-search input{font-size:12px} - .integration-picker{position:relative;grid-column:3;grid-row:1;height:32px;min-width:0} + .repository-picker{position:relative;grid-column:3;grid-row:1;height:32px;min-width:0} + .integration-picker{position:relative;grid-column:4;grid-row:1;height:32px;min-width:0} - .review-toolbar .group-actions{grid-column:4;grid-row:1;order:0} + .review-toolbar .group-actions{grid-column:5;grid-row:1;order:0} .review-toolbar .group-actions .icon-button{width:30px;height:32px} - @media(max-width:720px){.review-toolbar{grid-template-columns:minmax(120px,1fr) 145px 30px;grid-template-rows:36px 38px;height:80px;min-height:80px;gap:0 10px}.review-toolbar .state-tabs{grid-column:1/-1;grid-row:1;max-width:316px}.review-search{grid-column:1;grid-row:2}.integration-picker{grid-column:2;grid-row:2}.review-toolbar .group-actions{grid-column:3;grid-row:2}} + @media(max-width:720px){.review-toolbar{grid-template-columns:minmax(110px,1fr) 130px 130px 30px;grid-template-rows:36px 38px;height:80px;min-height:80px;gap:0 10px}.review-toolbar .state-tabs{grid-column:1/-1;grid-row:1;max-width:316px}.review-search{grid-column:1;grid-row:2}.repository-picker{grid-column:2;grid-row:2}.integration-picker{grid-column:3;grid-row:2}.review-toolbar .group-actions{grid-column:4;grid-row:2}} - .integration-picker :global(.select-menu){width:100%;height:32px} - .integration-picker :global(.select-menu-trigger){height:32px;min-height:32px;font-size:12px} + .integration-picker :global(.select-menu), + .repository-picker :global(.select-menu){width:100%;height:32px} + .integration-picker :global(.select-menu-trigger), + .repository-picker :global(.select-menu-trigger){height:32px;min-height:32px;font-size:12px} + .repository-picker :global(.select-menu-popup){min-width:290px;max-height:420px;padding:4px} + .repository-picker :global(.select-menu-option){min-height:34px;font-weight:650} + .repository-picker :global(.select-menu-group){margin:6px 3px 2px;padding:7px 6px 5px} /* Compact request actions, matching the approved menu reference. */ .review-center .row-actions .provider-button{min-width:54px;height:27px;min-height:27px;padding:0 8px;font-size:11px;font-weight:500} .review-center .row-actions .action-toggle{width:25px;height:27px;min-height:27px;background:transparent} diff --git a/src/lib/components/SelectMenu.svelte b/src/lib/components/SelectMenu.svelte index 5323dc8..8c751cc 100644 --- a/src/lib/components/SelectMenu.svelte +++ b/src/lib/components/SelectMenu.svelte @@ -1,4 +1,5 @@