Skip to content

Commit 51a1be9

Browse files
fix(app): show agent picker descriptions on the full row and keyboard focus
Hovering empty space in a row and arrowing through the list should show the same tooltip, with only one visible when keyboard takes over. Co-authored-by: Cursor <[email protected]>
1 parent 13ebaa4 commit 51a1be9

3 files changed

Lines changed: 63 additions & 16 deletions

File tree

‎packages/app/src/components/prompt-input.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1649,6 +1649,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
16491649
<Show when={!agentsLoading()}>
16501650
<div
16511651
data-component="prompt-agent-control"
1652+
class="[&_[data-slot=select-select-item-label]]:flex [&_[data-slot=select-select-item-label]]:min-w-0 [&_[data-slot=select-select-item-label]]:flex-1"
16521653
classList={{ "animate-in fade-in duration-300": agentsShouldFadeIn() }}
16531654
>
16541655
<TooltipKeybind
@@ -1681,7 +1682,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
16811682
fallback={name}
16821683
>
16831684
{(description) => (
1684-
<Tooltip placement="right" gutter={8} value={description()}>
1685+
<Tooltip class="min-w-0 w-full flex-1" placement="right" gutter={8} value={description()}>
16851686
{name}
16861687
</Tooltip>
16871688
)}

‎packages/session-ui/src/v2/components/prompt-input/index.tsx‎

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createEffect, createMemo, For, Show, type Accessor, type JSX } from "solid-js"
1+
import { createEffect, createMemo, createSignal, For, Show, type Accessor, type JSX } from "solid-js"
22
import { FileIcon } from "@opencode-ai/ui/file-icon"
33
import { Icon } from "@opencode-ai/ui/icon"
44
import { IconButton } from "@opencode-ai/ui/icon-button"
@@ -558,6 +558,10 @@ export function PromptInputV2Select(props: {
558558
onOpenChange?: (open: boolean) => void
559559
onSelect: (id: string) => void
560560
}) {
561+
const [focused, setFocused] = createSignal<string>()
562+
// Menu items also take DOM focus on pointer highlight. Only force the
563+
// description tooltip open for keyboard focus so hover keeps its delay.
564+
let pointer = true
561565
return (
562566
<TooltipV2
563567
placement="top"
@@ -575,6 +579,13 @@ export function PromptInputV2Select(props: {
575579
size="normal"
576580
class={`max-w-[220px] justify-start ![font-weight:440] ${props.class ?? ""}`}
577581
aria-label={props.title}
582+
onPointerDown={() => {
583+
pointer = true
584+
}}
585+
onKeyDown={(event) => {
586+
if (event.key !== "Enter" && event.key !== " " && event.key !== "ArrowDown" && event.key !== "ArrowUp") return
587+
pointer = false
588+
}}
578589
>
579590
{props.currentIcon}
580591
<span class="truncate capitalize leading-5">
@@ -585,24 +596,55 @@ export function PromptInputV2Select(props: {
585596
</span>
586597
</MenuV2.Trigger>
587598
<MenuV2.Portal>
588-
<MenuV2.Content>
599+
<MenuV2.Content
600+
onPointerMove={() => {
601+
pointer = true
602+
setFocused()
603+
}}
604+
onKeyDown={(event) => {
605+
if (event.key !== "ArrowDown" && event.key !== "ArrowUp" && event.key !== "Home" && event.key !== "End")
606+
return
607+
pointer = false
608+
}}
609+
>
589610
<MenuV2.RadioGroup value={props.current} onChange={props.onSelect}>
590611
<For each={props.options}>
591612
{(option) => (
592-
<MenuV2.RadioItem value={option.id} class="capitalize" closeOnSelect>
593-
<Show when={option.description} fallback={option.label}>
594-
{(description) => (
595-
<TooltipV2
596-
placement="right"
597-
gutter={8}
598-
value={description()}
599-
contentStyle={{ "max-width": "280px", "white-space": "pre-wrap" }}
613+
<Show
614+
when={option.description}
615+
fallback={
616+
<MenuV2.RadioItem value={option.id} class="capitalize" closeOnSelect>
617+
{option.label}
618+
</MenuV2.RadioItem>
619+
}
620+
>
621+
{(description) => (
622+
<TooltipV2
623+
class="w-full min-w-0 flex-col"
624+
placement="right"
625+
gutter={8}
626+
disabled={focused() !== undefined && focused() !== option.id}
627+
forceOpen={focused() === option.id}
628+
value={description()}
629+
contentStyle={{ "max-width": "280px", "white-space": "pre-wrap" }}
630+
>
631+
<MenuV2.RadioItem
632+
value={option.id}
633+
class="capitalize w-full"
634+
closeOnSelect
635+
onFocus={() => {
636+
if (pointer) return
637+
setFocused(option.id)
638+
}}
639+
onBlur={() => {
640+
if (focused() === option.id) setFocused()
641+
}}
600642
>
601643
{option.label}
602-
</TooltipV2>
603-
)}
604-
</Show>
605-
</MenuV2.RadioItem>
644+
</MenuV2.RadioItem>
645+
</TooltipV2>
646+
)}
647+
</Show>
606648
)}
607649
</For>
608650
</MenuV2.RadioGroup>

‎packages/ui/src/v2/components/tooltip-v2.tsx‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface TooltipV2Props extends ComponentProps<typeof KobalteTooltip> {
1111
contentStyle?: JSX.CSSProperties
1212
inactive?: boolean
1313
forceOpen?: boolean
14+
disabled?: boolean
1415
}
1516

1617
export function TooltipV2(props: TooltipV2Props) {
@@ -29,6 +30,7 @@ export function TooltipV2(props: TooltipV2Props) {
2930
"forceOpen",
3031
"ignoreSafeArea",
3132
"value",
33+
"disabled",
3234
])
3335

3436
const close = () => setState("open", false)
@@ -93,9 +95,11 @@ export function TooltipV2(props: TooltipV2Props) {
9395
{...others}
9496
closeDelay={0}
9597
ignoreSafeArea={local.ignoreSafeArea ?? true}
96-
open={local.forceOpen || state.open}
98+
disabled={local.disabled}
99+
open={local.forceOpen || (!local.disabled && state.open)}
97100
onOpenChange={(open) => {
98101
if (local.forceOpen) return
102+
if (local.disabled && !open) return
99103
if (state.block && open) return
100104
if (justClickedTrigger) {
101105
justClickedTrigger = false

0 commit comments

Comments
 (0)