feat(select-menu): add optionMeta slot and show repo details in create dialog
Add an optional optionMeta snippet to SelectMenu to render richer, right-aligned content per option (falls back to option.meta when not provided). Update SelectMenu markup to render optionMeta if present. Style adjustments for .select-menu-option-meta to align items, allow inline SVGs, and preserve spacing/coloring. Use the new slot in CreateReviewDialog: - supply grouped repository options (owner + name) instead of raw names - show a branch icon for each option and a lock icon + last-updated date in the option meta - add helpers repositoryById and formatUpdatedAt to resolve repository data for the meta snippet No breaking changes: legacy option.meta still works when optionMeta is not supplied.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import SelectMenu from "./SelectMenu.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { GitPullRequest, X, LoaderCircle, ArrowRight, Sparkles } from "@lucide/svelte";
|
||||
import { GitBranch, GitPullRequest, LockKeyhole, X, LoaderCircle, ArrowRight, Sparkles } from "@lucide/svelte";
|
||||
import { credLoad, pullRequestAiGenerate, createIntegrationReviewRequest, listIntegrationRepositories, listIntegrationRepositoryBranches, listRemotes, listBranches } from "../git";
|
||||
import { integrationCredentialKey } from "../integrations";
|
||||
import type { AiSettings, GitIntegrationSource, GitIntegrationRepository, IntegrationReviewRequest, StoredCredential } from "../types";
|
||||
@@ -15,6 +15,26 @@
|
||||
let dialog: HTMLDialogElement;
|
||||
let titleInput: HTMLInputElement;
|
||||
let repositories = $state<GitIntegrationRepository[]>([]);
|
||||
|
||||
/** Repository options grouped by owner, like the clone dialog's list. */
|
||||
const repositoryOptions = $derived(repositories.map((repository) => {
|
||||
const separator = repository.fullName.lastIndexOf("/");
|
||||
return {
|
||||
value: repository.id,
|
||||
label: separator > 0 ? repository.fullName.slice(separator + 1) : repository.fullName,
|
||||
group: separator > 0 ? repository.fullName.slice(0, separator) : source.label,
|
||||
};
|
||||
}));
|
||||
|
||||
function repositoryById(id: string): GitIntegrationRepository | undefined {
|
||||
return repositories.find((repository) => repository.id === id);
|
||||
}
|
||||
|
||||
function formatUpdatedAt(value: string): string {
|
||||
if (!value) return "";
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? "" : new Intl.DateTimeFormat(de ? "de-DE" : "en-US", { dateStyle: "medium" }).format(date);
|
||||
}
|
||||
let repositoryId = $state("");
|
||||
let sourceBranch = $state("");
|
||||
let targetBranch = $state("");
|
||||
@@ -129,7 +149,14 @@
|
||||
<div class="body">
|
||||
{#if error}<div class="error" role="alert">{error}{#if !repositories.length && !loading}<button type="button" onclick={loadRepositories}>{de ? "Erneut laden" : "Retry"}</button>{/if}</div>{/if}
|
||||
<div class="repository-field"><div class="field-heading"><span>Repository</span><small>{loading ? "…" : `${repositories.length} ${de ? "verfügbar" : "available"}`}</small></div>
|
||||
<SelectMenu value={repositoryId} options={repositories.map(repository => ({value:repository.id,label:repository.fullName}))} disabled={loading || busy} ariaLabel="Repository" placeholder={loading ? (de ? "Repositories werden geladen …" : "Loading repositories…") : (de ? "Repository auswählen" : "Select repository")} searchable searchPlaceholder={de ? "Repositories durchsuchen …" : "Search repositories…"} emptyText={de ? "Keine passenden Repositories" : "No matching repositories"} onChange={value => repositoryId = value}/>
|
||||
<SelectMenu value={repositoryId} options={repositoryOptions} disabled={loading || busy} ariaLabel="Repository" placeholder={loading ? (de ? "Repositories werden geladen …" : "Loading repositories…") : (de ? "Repository auswählen" : "Select repository")} searchable searchPlaceholder={de ? "Repositories durchsuchen …" : "Search repositories…"} emptyText={de ? "Keine passenden Repositories" : "No matching repositories"} showSelectedGroup onChange={value => repositoryId = value}>
|
||||
{#snippet optionIcon()}<GitBranch size={14} aria-hidden="true" />{/snippet}
|
||||
{#snippet optionMeta(option)}
|
||||
{@const repository = repositoryById(option.value)}
|
||||
{#if repository?.private}<LockKeyhole size={11} aria-label={de ? "Privat" : "Private"} />{/if}
|
||||
{formatUpdatedAt(repository?.updatedAt ?? "")}
|
||||
{/snippet}
|
||||
</SelectMenu>
|
||||
</div>
|
||||
{#if !loading && !error && !repositories.length}<p>{de ? "Keine Repositories für diese Integration gefunden." : "No repositories found for this integration."}</p>{/if}
|
||||
<div class="branches">
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
emptyText?: string;
|
||||
/** Optional icon in front of every option, e.g. a branch or repository mark. */
|
||||
optionIcon?: Snippet<[SelectMenuOption]>;
|
||||
/** Optional right-aligned content per option, richer than `option.meta`. */
|
||||
optionMeta?: Snippet<[SelectMenuOption]>;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
@@ -40,6 +42,7 @@
|
||||
searchPlaceholder = "Search…",
|
||||
emptyText = "No results",
|
||||
optionIcon = undefined,
|
||||
optionMeta = undefined,
|
||||
onChange,
|
||||
}: Props = $props();
|
||||
|
||||
@@ -195,7 +198,11 @@
|
||||
>
|
||||
{#if optionIcon}<span class="select-menu-option-icon">{@render optionIcon(option)}</span>{/if}
|
||||
<span class="select-menu-option-label">{option.label}</span>
|
||||
{#if option.meta}<small class="select-menu-option-meta">{option.meta}</small>{/if}
|
||||
{#if optionMeta}
|
||||
<small class="select-menu-option-meta">{@render optionMeta(option)}</small>
|
||||
{:else if option.meta}
|
||||
<small class="select-menu-option-meta">{option.meta}</small>
|
||||
{/if}
|
||||
{#if option.value === value}<Check size={14} aria-hidden="true" />{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
Reference in New Issue
Block a user