feat: add number key shortcuts to variant popover

Add numbered selection (1-9) to the variant dialog so users can quickly
pick a variant by pressing its number key. Also hide the search filter
input since there are typically only a few variants. Up/down navigation
and Enter selection still work as before.

https://claude.ai/code/session_011xS58qyP1BjcuCrJcWPxQB
pull/20058/head
Claude 2026-03-30 12:06:34 +00:00
parent 8e4bab5181
commit 0b6d955560
No known key found for this signature in database
2 changed files with 65 additions and 25 deletions

View File

@ -34,6 +34,8 @@ export function DialogVariant() {
title={"Select variant"}
current={local.model.variant.selected()}
flat={true}
hideFilter={true}
numbered={true}
/>
)
}

View File

@ -21,6 +21,8 @@ export interface DialogSelectProps<T> {
onFilter?: (query: string) => void
onSelect?: (option: DialogSelectOption<T>) => void
skipFilter?: boolean
hideFilter?: boolean
numbered?: boolean
keybind?: {
keybind?: Keybind.Info
title: string
@ -194,6 +196,21 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
if (evt.name === "home") moveTo(0)
if (evt.name === "end") moveTo(flat().length - 1)
if (props.numbered && !evt.ctrl && !evt.alt && !evt.meta) {
const num = parseInt(evt.name, 10)
if (num >= 1 && num <= 9 && num <= flat().length) {
evt.preventDefault()
evt.stopPropagation()
const index = num - 1
const option = flat()[index]
if (option) {
if (option.onSelect) option.onSelect(dialog)
props.onSelect?.(option)
}
return
}
}
if (evt.name === "return") {
const option = selected()
if (option) {
@ -240,29 +257,31 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
esc
</text>
</box>
<box paddingTop={1}>
<input
onInput={(e) => {
batch(() => {
setStore("filter", e)
props.onFilter?.(e)
})
}}
focusedBackgroundColor={theme.backgroundPanel}
cursorColor={theme.primary}
focusedTextColor={theme.textMuted}
ref={(r) => {
input = r
setTimeout(() => {
if (!input) return
if (input.isDestroyed) return
input.focus()
}, 1)
}}
placeholder={props.placeholder ?? "Search"}
placeholderColor={theme.textMuted}
/>
</box>
<Show when={!props.hideFilter}>
<box paddingTop={1}>
<input
onInput={(e) => {
batch(() => {
setStore("filter", e)
props.onFilter?.(e)
})
}}
focusedBackgroundColor={theme.backgroundPanel}
cursorColor={theme.primary}
focusedTextColor={theme.textMuted}
ref={(r) => {
input = r
setTimeout(() => {
if (!input) return
if (input.isDestroyed) return
input.focus()
}, 1)
}}
placeholder={props.placeholder ?? "Search"}
placeholderColor={theme.textMuted}
/>
</box>
</Show>
</box>
<Show
when={grouped().length > 0}
@ -293,6 +312,25 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
{(option) => {
const active = createMemo(() => isDeepEqual(option.value, selected()?.value))
const current = createMemo(() => isDeepEqual(option.value, props.current))
const flatIndex = createMemo(() => flat().findIndex((x) => isDeepEqual(x.value, option.value)))
const numberLabel = createMemo(() => {
if (!props.numbered) return undefined
const idx = flatIndex()
return idx >= 0 && idx < 9 ? `${idx + 1}` : undefined
})
const gutter = createMemo(() => {
if (numberLabel()) {
return (
<text
fg={active() ? selectedForeground(theme) : theme.textMuted}
flexShrink={0}
>
{numberLabel()}
</text>
)
}
return option.gutter
})
return (
<box
id={JSON.stringify(option.value)}
@ -316,7 +354,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
moveTo(index)
}}
backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)}
paddingLeft={current() || option.gutter ? 1 : 3}
paddingLeft={current() || gutter() ? 1 : 3}
paddingRight={3}
gap={1}
>
@ -326,7 +364,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
description={option.description !== category ? option.description : undefined}
active={active()}
current={current()}
gutter={option.gutter}
gutter={gutter()}
/>
</box>
)