feat(ui): add SelectMenu component and replace native selects

Introduce a reusable SelectMenu component and replace native select
controls across the UI. This centralizes select behavior and styling,
enabling grouped options, placeholders, and a consistent popup
interaction. Add comprehensive CSS and light-theme tweaks, and update
rebase action styling to integrate the new control.

- Add a unified SelectMenu component and wire change handlers.
- Implement .select-menu styles, popup behavior, and theme overrides.
- Replace ad-hoc native selects in dialogs and rebase UI for consistency.
This commit is contained in:
2026-08-15 18:26:32 +02:00
parent 32832f5db7
commit 412e8d19b5
12 changed files with 358 additions and 131 deletions
+8 -56
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import { ArrowRight, GitCompare, LoaderCircle, X } from "@lucide/svelte";
import type { GitBranch, GitCommit } from "../types";
import SelectMenu from "./SelectMenu.svelte";
interface Props {
commits: GitCommit[];
@@ -41,6 +42,11 @@
let localBranches = $derived(branches.filter((branch) => !branch.remote));
let remoteBranches = $derived(branches.filter((branch) => branch.remote));
let targetCount = $derived(branches.length + commits.length);
let compareOptions = $derived([
...localBranches.map((branch) => ({ value: branchValue(branch), label: `${branch.name}${branch.current ? " (current)" : ""}`, group: "Branches - Local" })),
...remoteBranches.map((branch) => ({ value: branchValue(branch), label: branch.name, group: "Branches - Remote" })),
...commits.map((item) => ({ value: item.hash, label: commitOptionLabel(item), group: "Commits" })),
]);
function handleSubmit(event: SubmitEvent) {
event.preventDefault();
@@ -69,68 +75,14 @@
<form class="compare-form" onsubmit={handleSubmit}>
<label class="compare-field">
<span>Base</span>
<select
value={compareFrom}
onchange={(e) => onCompareFromChange((e.target as HTMLSelectElement).value)}
disabled={isBusy}
>
<option value="" disabled>Select a branch or commit</option>
{#if localBranches.length > 0}
<optgroup label="Local branches">
{#each localBranches as branch (branch.name)}
<option value={branchValue(branch)}>{branch.name}{branch.current ? " (current)" : ""}</option>
{/each}
</optgroup>
{/if}
{#if remoteBranches.length > 0}
<optgroup label="Remote branches">
{#each remoteBranches as branch (branch.name)}
<option value={branchValue(branch)}>{branch.name}</option>
{/each}
</optgroup>
{/if}
{#if commits.length > 0}
<optgroup label="Recent commits">
{#each commits as item (item.hash)}
<option value={item.hash}>{commitOptionLabel(item)}</option>
{/each}
</optgroup>
{/if}
</select>
<SelectMenu class="compare-target-select" value={compareFrom} options={compareOptions} placeholder="Select a branch or commit" showSelectedGroup disabled={isBusy} onChange={onCompareFromChange} />
</label>
<ArrowRight class="compare-arrow" size={18} aria-hidden="true" />
<label class="compare-field">
<span>Compare with</span>
<select
value={compareTo}
onchange={(e) => onCompareToChange((e.target as HTMLSelectElement).value)}
disabled={isBusy}
>
<option value="" disabled>Select a branch or commit</option>
{#if localBranches.length > 0}
<optgroup label="Local branches">
{#each localBranches as branch (branch.name)}
<option value={branchValue(branch)}>{branch.name}{branch.current ? " (current)" : ""}</option>
{/each}
</optgroup>
{/if}
{#if remoteBranches.length > 0}
<optgroup label="Remote branches">
{#each remoteBranches as branch (branch.name)}
<option value={branchValue(branch)}>{branch.name}</option>
{/each}
</optgroup>
{/if}
{#if commits.length > 0}
<optgroup label="Recent commits">
{#each commits as item (item.hash)}
<option value={item.hash}>{commitOptionLabel(item)}</option>
{/each}
</optgroup>
{/if}
</select>
<SelectMenu class="compare-target-select" value={compareTo} options={compareOptions} placeholder="Select a branch or commit" showSelectedGroup disabled={isBusy} onChange={onCompareToChange} />
</label>
<button class="btn-primary" type="submit" disabled={!canCompare}>