Skip to content

Commit 5fb8165

Browse files
committed
fix(app): mobile prompt input buttons overflow on narrow viewports
- Use CSS contents for responsive single-row layout on mobile - Replace thinking variant Button with Select dropdown - Add bulb icon for thinking selector - Fix model selector centering and highlight when open - Add icon/valueClass props to Select component - Fix Select dropdown height constraint bug Fixes #11613
1 parent d957543 commit 5fb8165

File tree

4 files changed

+61
-33
lines changed

4 files changed

+61
-33
lines changed

packages/app/src/components/dialog-select-model.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,17 @@ export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
118118
}
119119
const language = useLanguage()
120120

121+
// Set data-active on trigger when popover is open
122+
createEffect(() => {
123+
const trigger = store.trigger
124+
if (!trigger) return
125+
if (store.open) {
126+
trigger.setAttribute("data-active", "true")
127+
} else {
128+
trigger.removeAttribute("data-active")
129+
}
130+
})
131+
121132
createEffect(() => {
122133
if (!store.open) return
123134

@@ -174,7 +185,8 @@ export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
174185
setStore("open", next)
175186
}}
176187
modal={false}
177-
placement="top-start"
188+
placement="top"
189+
flip
178190
gutter={8}
179191
>
180192
<Kobalte.Trigger

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

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { Select } from "@opencode-ai/ui/select"
4242
import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/util/path"
4343
import { useDialog } from "@opencode-ai/ui/context/dialog"
4444
import { ImagePreview } from "@opencode-ai/ui/image-preview"
45+
4546
import { ModelSelectorPopover } from "@/components/dialog-select-model"
4647
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
4748
import { useProviders } from "@/hooks/use-providers"
@@ -1616,6 +1617,15 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
16161617
})
16171618
}
16181619

1620+
const currentModelVariant = createMemo(() => {
1621+
const modelVariant = local.model.variant.current() ?? ""
1622+
return modelVariant === "xhigh"
1623+
? "xHigh"
1624+
: modelVariant.length > 0
1625+
? modelVariant[0].toUpperCase() + modelVariant.slice(1)
1626+
: "Default"
1627+
})
1628+
16191629
return (
16201630
<div class="relative size-full _max-h-[320px] flex flex-col gap-3">
16211631
<Show when={store.popover}>
@@ -1890,8 +1900,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
18901900
</div>
18911901
</Show>
18921902
</div>
1893-
<div class="relative p-3 flex items-center justify-between">
1894-
<div class="flex items-center justify-start gap-0.5">
1903+
<div class="relative p-3 flex items-center gap-2 sm:gap-0 sm:justify-between">
1904+
<div class="contents sm:flex sm:items-center sm:justify-start sm:gap-0.5 sm:min-w-0">
18951905
<Switch>
18961906
<Match when={store.mode === "shell"}>
18971907
<div class="flex items-center gap-2 px-2 h-6">
@@ -1910,7 +1920,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
19101920
options={local.agent.list().map((agent) => agent.name)}
19111921
current={local.agent.current()?.name ?? ""}
19121922
onSelect={local.agent.set}
1913-
class="capitalize"
1923+
class="capitalize shrink-0"
19141924
variant="ghost"
19151925
/>
19161926
</TooltipKeybind>
@@ -1925,7 +1935,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
19251935
<Button
19261936
as="div"
19271937
variant="ghost"
1928-
class="p-2 -m-2 sm:p-0 sm:m-0"
1938+
class="shrink-0"
19291939
onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}
19301940
>
19311941
<Show when={local.model.current()?.provider?.id}>
@@ -1934,7 +1944,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
19341944
<span class="hidden sm:inline">
19351945
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
19361946
</span>
1937-
<Icon name="chevron-down" size="small" class="hidden sm:block" />
1947+
<Icon name="chevron-down" class="size-3.5" />
19381948
</Button>
19391949
</TooltipKeybind>
19401950
}
@@ -1946,15 +1956,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
19461956
>
19471957
<ModelSelectorPopover
19481958
triggerAs={Button}
1949-
triggerProps={{ variant: "ghost", class: "p-2 -m-2 sm:p-0 sm:m-0" }}
1959+
triggerProps={{
1960+
variant: "ghost",
1961+
class: "shrink-0",
1962+
}}
19501963
>
19511964
<Show when={local.model.current()?.provider?.id}>
19521965
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
19531966
</Show>
19541967
<span class="hidden sm:inline">
19551968
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
19561969
</span>
1957-
<Icon name="chevron-down" size="small" class="hidden sm:block" />
1970+
<Icon name="chevron-down" class="size-3.5" />
19581971
</ModelSelectorPopover>
19591972
</TooltipKeybind>
19601973
</Show>
@@ -1964,17 +1977,22 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
19641977
title={language.t("command.model.variant.cycle")}
19651978
keybind={command.keybind("model.variant.cycle")}
19661979
>
1967-
<Button
1968-
data-action="model-variant-cycle"
1980+
<Select
1981+
options={local.model.variant
1982+
.list()
1983+
.map((v) => (v === "" ? "Default" : v === "xhigh" ? "xHigh" : v[0].toUpperCase() + v.slice(1)))}
1984+
current={currentModelVariant()}
1985+
onSelect={(display) => {
1986+
if (!display) return
1987+
const variant =
1988+
display === "Default" ? "" : display === "xHigh" ? "xhigh" : display.toLowerCase()
1989+
local.model.variant.set(variant)
1990+
}}
1991+
class="shrink-0"
19691992
variant="ghost"
1970-
class="p-2 -m-2 sm:p-0 sm:m-0 text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
1971-
onClick={() => local.model.variant.cycle()}
1972-
>
1973-
<Icon name="brain" size="small" class="sm:hidden" />
1974-
<span class="hidden sm:inline">
1975-
{local.model.variant.current() ?? language.t("common.default")}
1976-
</span>
1977-
</Button>
1993+
icon={<Icon name="bulb" class="size-4 shrink-0" />}
1994+
valueClass="hidden sm:inline"
1995+
/>
19781996
</TooltipKeybind>
19791997
</Show>
19801998
<Show when={permission.permissionsEnabled() && params.id}>
@@ -2009,7 +2027,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
20092027
</Match>
20102028
</Switch>
20112029
</div>
2012-
<div class="flex items-center gap-3 absolute right-3 bottom-3">
2030+
<div class="contents sm:flex sm:items-center sm:gap-3 sm:shrink-0">
20132031
<input
20142032
ref={fileInputRef}
20152033
type="file"
@@ -2021,18 +2039,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
20212039
e.currentTarget.value = ""
20222040
}}
20232041
/>
2024-
<div class="flex items-center gap-2">
2042+
<div class="contents sm:flex sm:items-center sm:gap-2">
20252043
<SessionContextUsage />
20262044
<Show when={store.mode === "normal"}>
20272045
<Tooltip placement="top" value={language.t("prompt.action.attachFile")}>
20282046
<Button
20292047
type="button"
20302048
variant="ghost"
2031-
class="size-6 p-2 -m-2 sm:p-0 sm:m-0"
20322049
onClick={() => fileInputRef.click()}
20332050
aria-label={language.t("prompt.action.attachFile")}
20342051
>
2035-
<Icon name="photo" class="size-4.5" />
2052+
<Icon name="photo" />
20362053
</Button>
20372054
</Tooltip>
20382055
</Show>
@@ -2062,7 +2079,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
20622079
disabled={!prompt.dirty() && !working()}
20632080
icon={working() ? "stop" : "arrow-up"}
20642081
variant="primary"
2065-
class="h-6 w-4.5 p-2 -m-2 sm:p-0 sm:m-0"
20662082
aria-label={working() ? language.t("prompt.action.stop") : language.t("prompt.action.send")}
20672083
/>
20682084
</Tooltip>

packages/ui/src/components/icon.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const icons = {
99
"bubble-5": `<path d="M18.3327 9.99935C18.3327 5.57227 15.0919 2.91602 9.99935 2.91602C4.90676 2.91602 1.66602 5.57227 1.66602 9.99935C1.66602 11.1487 2.45505 13.1006 2.57637 13.3939C2.58707 13.4197 2.59766 13.4434 2.60729 13.4697C2.69121 13.6987 3.04209 14.9354 1.66602 16.7674C3.51787 17.6528 5.48453 16.1973 5.48453 16.1973C6.84518 16.9193 8.46417 17.0827 9.99935 17.0827C15.0919 17.0827 18.3327 14.4264 18.3327 9.99935Z" stroke="currentColor" stroke-linecap="square"/>`,
1010
brain: `<path d="M13.332 8.7487C11.4911 8.7487 9.9987 7.25631 9.9987 5.41536M6.66536 11.2487C8.50631 11.2487 9.9987 12.7411 9.9987 14.582M9.9987 2.78209L9.9987 17.0658M16.004 15.0475C17.1255 14.5876 17.9154 13.4849 17.9154 12.1978C17.9154 11.3363 17.5615 10.5575 16.9913 9.9987C17.5615 9.43991 17.9154 8.66108 17.9154 7.79962C17.9154 6.21199 16.7136 4.90504 15.1702 4.73878C14.7858 3.21216 13.4039 2.08203 11.758 2.08203C11.1171 2.08203 10.5162 2.25337 9.9987 2.55275C9.48117 2.25337 8.88032 2.08203 8.23944 2.08203C6.59353 2.08203 5.21157 3.21216 4.82722 4.73878C3.28377 4.90504 2.08203 6.21199 2.08203 7.79962C2.08203 8.66108 2.43585 9.43991 3.00609 9.9987C2.43585 10.5575 2.08203 11.3363 2.08203 12.1978C2.08203 13.4849 2.87191 14.5876 3.99339 15.0475C4.46688 16.7033 5.9917 17.9154 7.79962 17.9154C8.61335 17.9154 9.36972 17.6698 9.9987 17.2488C10.6277 17.6698 11.384 17.9154 12.1978 17.9154C14.0057 17.9154 15.5305 16.7033 16.004 15.0475Z" stroke="currentColor"/>`,
1111
"bullet-list": `<path d="M9.58329 13.7497H17.0833M9.58329 6.24967H17.0833M6.24996 6.24967C6.24996 7.17015 5.50377 7.91634 4.58329 7.91634C3.66282 7.91634 2.91663 7.17015 2.91663 6.24967C2.91663 5.3292 3.66282 4.58301 4.58329 4.58301C5.50377 4.58301 6.24996 5.3292 6.24996 6.24967ZM6.24996 13.7497C6.24996 14.6701 5.50377 15.4163 4.58329 15.4163C3.66282 15.4163 2.91663 14.6701 2.91663 13.7497C2.91663 12.8292 3.66282 12.083 4.58329 12.083C5.50377 12.083 6.24996 12.8292 6.24996 13.7497Z" stroke="currentColor" stroke-linecap="square"/>`,
12+
bulb: `<path d="M7.29 12.9V13.96C7.29 14.65 7.85 15.21 8.54 15.21H11.46C12.15 15.21 12.71 14.65 12.71 13.96V12.9M7.29 12.9C6.95 12.73 6.62 12.52 6.32 12.29C4.89 11.18 4 9.45 4 7.5C4 4.18 6.69 1.5 10 1.5C13.31 1.5 16 4.18 16 7.5C16 9.45 15.11 11.18 13.68 12.29C13.38 12.52 13.05 12.73 12.71 12.9M7.29 12.9H12.71M8.125 17.67H11.875" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>`,
1213
"check-small": `<path d="M6.5 11.4412L8.97059 13.5L13.5 6.5" stroke="currentColor" stroke-linecap="square"/>`,
1314
"chevron-down": `<path d="M6.6665 8.33325L9.99984 11.6666L13.3332 8.33325" stroke="currentColor" stroke-linecap="square"/>`,
1415
"chevron-right": `<path d="M8.33301 13.3327L11.6663 9.99935L8.33301 6.66602" stroke="currentColor" stroke-linecap="square"/>`,

packages/ui/src/components/select.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "
1818
children?: (item: T | undefined) => JSX.Element
1919
triggerStyle?: JSX.CSSProperties
2020
triggerVariant?: "settings"
21+
icon?: JSX.Element
22+
valueClass?: string
2123
}
2224

2325
export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">) {
@@ -36,6 +38,8 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
3638
"children",
3739
"triggerStyle",
3840
"triggerVariant",
41+
"icon",
42+
"valueClass",
3943
])
4044

4145
const state = {
@@ -84,7 +88,8 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
8488
{...others}
8589
data-component="select"
8690
data-trigger-style={local.triggerVariant}
87-
placement={local.triggerVariant === "settings" ? "bottom-end" : "bottom-start"}
91+
placement={local.triggerVariant === "settings" ? "bottom-end" : "top-start"}
92+
flip
8893
gutter={4}
8994
value={local.current}
9095
options={grouped()}
@@ -140,7 +145,8 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
140145
[local.class ?? ""]: !!local.class,
141146
}}
142147
>
143-
<Kobalte.Value<T> data-slot="select-select-trigger-value">
148+
{local.icon}
149+
<Kobalte.Value<T> data-slot="select-select-trigger-value" class={local.valueClass}>
144150
{(state) => {
145151
const selected = state.selectedOption() ?? local.current
146152
if (!selected) return local.placeholder || ""
@@ -153,14 +159,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
153159
</Kobalte.Icon>
154160
</Kobalte.Trigger>
155161
<Kobalte.Portal>
156-
<Kobalte.Content
157-
classList={{
158-
...(local.classList ?? {}),
159-
[local.class ?? ""]: !!local.class,
160-
}}
161-
data-component="select-content"
162-
data-trigger-style={local.triggerVariant}
163-
>
162+
<Kobalte.Content data-component="select-content" data-trigger-style={local.triggerVariant}>
164163
<Kobalte.Listbox data-slot="select-select-content-list" />
165164
</Kobalte.Content>
166165
</Kobalte.Portal>

0 commit comments

Comments
 (0)