mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-19 01:51:21 +08:00
fix(agent-manager): use Popover portal for base branch dropdown in advanced options (#6388)
The branch selector dropdown in the New Worktree dialog's Advanced section was clipped by the dialog's overflow. Replace the hand-rolled absolute-positioned dropdown with the Popover component (portal-based), matching the pattern used by the Import tab's branch selector.
This commit is contained in:
@@ -2135,94 +2135,110 @@ const NewWorktreeDialog: Component<{ onClose: () => void }> = (props) => {
|
||||
</div>
|
||||
<div class="am-advanced-field">
|
||||
<span class="am-nv-config-label">{t("agentManager.dialog.baseBranch")}</span>
|
||||
<div class="am-branch-selector-wrapper">
|
||||
<button
|
||||
class="am-branch-selector-trigger"
|
||||
onClick={() => setBaseBranchOpen(!baseBranchOpen())}
|
||||
type="button"
|
||||
<div class="am-selector-wrapper">
|
||||
<Popover
|
||||
open={baseBranchOpen()}
|
||||
onOpenChange={(open) => {
|
||||
setBaseBranchOpen(open)
|
||||
if (!open) {
|
||||
setBranchSearch("")
|
||||
setHighlightedIndex(0)
|
||||
}
|
||||
}}
|
||||
placement="bottom-start"
|
||||
sameWidth
|
||||
class="am-dropdown"
|
||||
trigger={
|
||||
<button class="am-selector-trigger" type="button">
|
||||
<span class="am-selector-left">
|
||||
<Icon name="branch" size="small" />
|
||||
<span class="am-selector-value" style={{ color: "var(--text-base)" }}>
|
||||
{effectiveBaseBranch()}
|
||||
</span>
|
||||
<Show when={!baseBranch()}>
|
||||
<span class="am-branch-badge">{t("agentManager.dialog.branchBadge.default")}</span>
|
||||
</Show>
|
||||
</span>
|
||||
<span class="am-selector-right">
|
||||
<Icon name="selector" size="small" />
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<Icon name="branch" size="small" />
|
||||
<span class="am-branch-selector-value">{effectiveBaseBranch()}</span>
|
||||
<Show when={!baseBranch()}>
|
||||
<span class="am-branch-badge">{t("agentManager.dialog.branchBadge.default")}</span>
|
||||
</Show>
|
||||
<Icon name="selector" size="small" />
|
||||
</button>
|
||||
<Show when={baseBranchOpen()}>
|
||||
<div class="am-branch-dropdown" onWheel={(e) => e.stopPropagation()}>
|
||||
<div class="am-branch-search">
|
||||
<Icon name="magnifying-glass" size="small" />
|
||||
<input
|
||||
class="am-branch-search-input"
|
||||
type="text"
|
||||
placeholder={t("agentManager.dialog.searchBranches")}
|
||||
value={branchSearch()}
|
||||
ref={(el) => requestAnimationFrame(() => el.focus())}
|
||||
onInput={(e) => {
|
||||
setBranchSearch(e.currentTarget.value)
|
||||
setHighlightedIndex(0)
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
const items = filteredBranches()
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const next = Math.min(highlightedIndex() + 1, items.length - 1)
|
||||
setHighlightedIndex(next)
|
||||
requestAnimationFrame(() => {
|
||||
document
|
||||
.querySelector(`.am-branch-item[data-index="${next}"]`)
|
||||
?.scrollIntoView({ block: "nearest" })
|
||||
})
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const prev = Math.max(highlightedIndex() - 1, 0)
|
||||
setHighlightedIndex(prev)
|
||||
requestAnimationFrame(() => {
|
||||
document
|
||||
.querySelector(`.am-branch-item[data-index="${prev}"]`)
|
||||
?.scrollIntoView({ block: "nearest" })
|
||||
})
|
||||
} else if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const selected = items[highlightedIndex()]
|
||||
if (selected) {
|
||||
setBaseBranch(selected.name)
|
||||
setBaseBranchOpen(false)
|
||||
setBranchSearch("")
|
||||
setHighlightedIndex(0)
|
||||
}
|
||||
} else if (e.key === "Escape") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
<div class="am-dropdown-search">
|
||||
<Icon name="magnifying-glass" size="small" />
|
||||
<input
|
||||
class="am-dropdown-search-input"
|
||||
type="text"
|
||||
placeholder={t("agentManager.dialog.searchBranches")}
|
||||
value={branchSearch()}
|
||||
autofocus
|
||||
onInput={(e) => {
|
||||
setBranchSearch(e.currentTarget.value)
|
||||
setHighlightedIndex(0)
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
const items = filteredBranches()
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const next = Math.min(highlightedIndex() + 1, items.length - 1)
|
||||
setHighlightedIndex(next)
|
||||
requestAnimationFrame(() => {
|
||||
document
|
||||
.querySelector(`.am-branch-item[data-index="${next}"]`)
|
||||
?.scrollIntoView({ block: "nearest" })
|
||||
})
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const prev = Math.max(highlightedIndex() - 1, 0)
|
||||
setHighlightedIndex(prev)
|
||||
requestAnimationFrame(() => {
|
||||
document
|
||||
.querySelector(`.am-branch-item[data-index="${prev}"]`)
|
||||
?.scrollIntoView({ block: "nearest" })
|
||||
})
|
||||
} else if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const selected = items[highlightedIndex()]
|
||||
if (selected) {
|
||||
setBaseBranch(selected.name)
|
||||
setBaseBranchOpen(false)
|
||||
setBranchSearch("")
|
||||
setHighlightedIndex(0)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="am-branch-list">
|
||||
<For each={filteredBranches()}>
|
||||
{(branch, index) => (
|
||||
<button
|
||||
class="am-branch-item"
|
||||
classList={{
|
||||
"am-branch-item-active": effectiveBaseBranch() === branch.name,
|
||||
"am-branch-item-highlighted": highlightedIndex() === index(),
|
||||
}}
|
||||
data-index={index()}
|
||||
onClick={() => {
|
||||
setBaseBranch(branch.name)
|
||||
setBaseBranchOpen(false)
|
||||
setBranchSearch("")
|
||||
setHighlightedIndex(0)
|
||||
}}
|
||||
onMouseEnter={() => setHighlightedIndex(index())}
|
||||
type="button"
|
||||
>
|
||||
} else if (e.key === "Escape") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setBaseBranchOpen(false)
|
||||
setBranchSearch("")
|
||||
setHighlightedIndex(0)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="am-dropdown-list">
|
||||
<For each={filteredBranches()}>
|
||||
{(branch, index) => (
|
||||
<button
|
||||
class="am-branch-item"
|
||||
classList={{
|
||||
"am-branch-item-active": effectiveBaseBranch() === branch.name,
|
||||
"am-branch-item-highlighted": highlightedIndex() === index(),
|
||||
}}
|
||||
data-index={index()}
|
||||
onClick={() => {
|
||||
setBaseBranch(branch.name)
|
||||
setBaseBranchOpen(false)
|
||||
setBranchSearch("")
|
||||
setHighlightedIndex(0)
|
||||
}}
|
||||
onMouseEnter={() => setHighlightedIndex(index())}
|
||||
type="button"
|
||||
>
|
||||
<span class="am-branch-item-left">
|
||||
<Icon name="branch" size="small" />
|
||||
<span class="am-branch-item-name">{branch.name}</span>
|
||||
<Show when={branch.isDefault}>
|
||||
@@ -2233,15 +2249,15 @@ const NewWorktreeDialog: Component<{ onClose: () => void }> = (props) => {
|
||||
{t("agentManager.dialog.branchBadge.remote")}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={branch.lastCommitDate}>
|
||||
<span class="am-branch-item-time">{formatRelativeDate(branch.lastCommitDate!)}</span>
|
||||
</Show>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</span>
|
||||
<Show when={branch.lastCommitDate}>
|
||||
<span class="am-branch-item-time">{formatRelativeDate(branch.lastCommitDate!)}</span>
|
||||
</Show>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1578,38 +1578,7 @@ button.am-section-toggle:hover .am-section-label {
|
||||
color: var(--text-weaker);
|
||||
}
|
||||
|
||||
/* Branch selector trigger + dropdown */
|
||||
|
||||
.am-branch-selector-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.am-branch-selector-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--border-base);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface-base);
|
||||
color: var(--text-base);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: border-color 120ms;
|
||||
}
|
||||
|
||||
.am-branch-selector-trigger:hover {
|
||||
border-color: var(--border-hover);
|
||||
}
|
||||
|
||||
.am-branch-selector-value {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* Branch selector */
|
||||
|
||||
.am-branch-badge {
|
||||
font-size: 10px;
|
||||
@@ -1626,50 +1595,6 @@ button.am-section-toggle:hover .am-section-label {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.am-branch-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
border: 1px solid var(--border-base);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface-float-base, var(--surface-raised-base));
|
||||
box-shadow: var(--shadow-lg-border-base, 0 8px 24px rgba(0, 0, 0, 0.3));
|
||||
max-height: 280px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.am-branch-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--border-base);
|
||||
}
|
||||
|
||||
.am-branch-search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--text-base);
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.am-branch-search-input::placeholder {
|
||||
color: var(--text-weaker);
|
||||
}
|
||||
|
||||
.am-branch-list {
|
||||
overflow-y: auto;
|
||||
max-height: 220px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.am-branch-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user