mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-01 22:48:16 +00:00
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
This commit is contained in:
@@ -118,6 +118,17 @@ export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
|
||||
}
|
||||
const language = useLanguage()
|
||||
|
||||
// Set data-active on trigger when popover is open
|
||||
createEffect(() => {
|
||||
const trigger = store.trigger
|
||||
if (!trigger) return
|
||||
if (store.open) {
|
||||
trigger.setAttribute("data-active", "true")
|
||||
} else {
|
||||
trigger.removeAttribute("data-active")
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!store.open) return
|
||||
|
||||
@@ -174,7 +185,8 @@ export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
|
||||
setStore("open", next)
|
||||
}}
|
||||
modal={false}
|
||||
placement="top-start"
|
||||
placement="top"
|
||||
flip
|
||||
gutter={8}
|
||||
>
|
||||
<Kobalte.Trigger
|
||||
|
||||
@@ -42,6 +42,7 @@ import { Select } from "@opencode-ai/ui/select"
|
||||
import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/util/path"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
||||
|
||||
import { ModelSelectorPopover } from "@/components/dialog-select-model"
|
||||
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
|
||||
import { useProviders } from "@/hooks/use-providers"
|
||||
@@ -1616,6 +1617,15 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
})
|
||||
}
|
||||
|
||||
const currentModelVariant = createMemo(() => {
|
||||
const modelVariant = local.model.variant.current() ?? ""
|
||||
return modelVariant === "xhigh"
|
||||
? "xHigh"
|
||||
: modelVariant.length > 0
|
||||
? modelVariant[0].toUpperCase() + modelVariant.slice(1)
|
||||
: "Default"
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="relative size-full _max-h-[320px] flex flex-col gap-3">
|
||||
<Show when={store.popover}>
|
||||
@@ -1891,7 +1901,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</Show>
|
||||
</div>
|
||||
<div class="relative p-3 flex items-center justify-between">
|
||||
<div class="flex items-center justify-start gap-0.5">
|
||||
<div class="flex items-center justify-start gap-1 sm:gap-0.5 min-w-0">
|
||||
<Switch>
|
||||
<Match when={store.mode === "shell"}>
|
||||
<div class="flex items-center gap-2 px-2 h-6">
|
||||
@@ -1910,7 +1920,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
options={local.agent.list().map((agent) => agent.name)}
|
||||
current={local.agent.current()?.name ?? ""}
|
||||
onSelect={local.agent.set}
|
||||
class="capitalize"
|
||||
class="capitalize shrink-0"
|
||||
variant="ghost"
|
||||
/>
|
||||
</TooltipKeybind>
|
||||
@@ -1925,7 +1935,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<Button
|
||||
as="div"
|
||||
variant="ghost"
|
||||
class="p-2 -m-2 sm:p-0 sm:m-0"
|
||||
class="shrink-0"
|
||||
onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}
|
||||
>
|
||||
<Show when={local.model.current()?.provider?.id}>
|
||||
@@ -1934,7 +1944,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<span class="hidden sm:inline">
|
||||
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
|
||||
</span>
|
||||
<Icon name="chevron-down" size="small" class="hidden sm:block" />
|
||||
<Icon name="chevron-down" class="size-3.5" />
|
||||
</Button>
|
||||
</TooltipKeybind>
|
||||
}
|
||||
@@ -1946,7 +1956,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
>
|
||||
<ModelSelectorPopover
|
||||
triggerAs={Button}
|
||||
triggerProps={{ variant: "ghost", class: "p-2 -m-2 sm:p-0 sm:m-0" }}
|
||||
triggerProps={{
|
||||
variant: "ghost",
|
||||
class: "shrink-0",
|
||||
}}
|
||||
>
|
||||
<Show when={local.model.current()?.provider?.id}>
|
||||
<ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
|
||||
@@ -1954,7 +1967,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<span class="hidden sm:inline">
|
||||
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
|
||||
</span>
|
||||
<Icon name="chevron-down" size="small" class="hidden sm:block" />
|
||||
<Icon name="chevron-down" class="size-3.5" />
|
||||
</ModelSelectorPopover>
|
||||
</TooltipKeybind>
|
||||
</Show>
|
||||
@@ -1964,17 +1977,22 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
title={language.t("command.model.variant.cycle")}
|
||||
keybind={command.keybind("model.variant.cycle")}
|
||||
>
|
||||
<Button
|
||||
data-action="model-variant-cycle"
|
||||
<Select
|
||||
options={local.model.variant
|
||||
.list()
|
||||
.map((v) => (v === "" ? "Default" : v === "xhigh" ? "xHigh" : v[0].toUpperCase() + v.slice(1)))}
|
||||
current={currentModelVariant()}
|
||||
onSelect={(display) => {
|
||||
if (!display) return
|
||||
const variant =
|
||||
display === "Default" ? "" : display === "xHigh" ? "xhigh" : display.toLowerCase()
|
||||
local.model.variant.set(variant)
|
||||
}}
|
||||
class="shrink-0"
|
||||
variant="ghost"
|
||||
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"
|
||||
onClick={() => local.model.variant.cycle()}
|
||||
>
|
||||
<Icon name="brain" size="small" class="sm:hidden" />
|
||||
<span class="hidden sm:inline">
|
||||
{local.model.variant.current() ?? language.t("common.default")}
|
||||
</span>
|
||||
</Button>
|
||||
icon={<Icon name="bulb" class="size-4 shrink-0" />}
|
||||
valueClass="hidden sm:inline"
|
||||
/>
|
||||
</TooltipKeybind>
|
||||
</Show>
|
||||
<Show when={permission.permissionsEnabled() && params.id}>
|
||||
@@ -2009,7 +2027,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 absolute right-3 bottom-3">
|
||||
<div class="flex items-center gap-1 sm:gap-3 shrink-0">
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
@@ -2021,18 +2039,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
e.currentTarget.value = ""
|
||||
}}
|
||||
/>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-1 sm:gap-2">
|
||||
<SessionContextUsage />
|
||||
<Show when={store.mode === "normal"}>
|
||||
<Tooltip placement="top" value={language.t("prompt.action.attachFile")}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
class="size-6 p-2 -m-2 sm:p-0 sm:m-0"
|
||||
onClick={() => fileInputRef.click()}
|
||||
aria-label={language.t("prompt.action.attachFile")}
|
||||
>
|
||||
<Icon name="photo" class="size-4.5" />
|
||||
<Icon name="photo" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
@@ -2062,7 +2079,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
disabled={!prompt.dirty() && !working()}
|
||||
icon={working() ? "stop" : "arrow-up"}
|
||||
variant="primary"
|
||||
class="h-6 w-4.5 p-2 -m-2 sm:p-0 sm:m-0"
|
||||
aria-label={working() ? language.t("prompt.action.stop") : language.t("prompt.action.send")}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
@@ -9,6 +9,7 @@ const icons = {
|
||||
"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"/>`,
|
||||
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"/>`,
|
||||
"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"/>`,
|
||||
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"/>`,
|
||||
"check-small": `<path d="M6.5 11.4412L8.97059 13.5L13.5 6.5" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
"chevron-down": `<path d="M6.6665 8.33325L9.99984 11.6666L13.3332 8.33325" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
"chevron-right": `<path d="M8.33301 13.3327L11.6663 9.99935L8.33301 6.66602" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
|
||||
@@ -18,6 +18,8 @@ export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "
|
||||
children?: (item: T | undefined) => JSX.Element
|
||||
triggerStyle?: JSX.CSSProperties
|
||||
triggerVariant?: "settings"
|
||||
icon?: JSX.Element
|
||||
valueClass?: string
|
||||
}
|
||||
|
||||
export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">) {
|
||||
@@ -36,6 +38,8 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
|
||||
"children",
|
||||
"triggerStyle",
|
||||
"triggerVariant",
|
||||
"icon",
|
||||
"valueClass",
|
||||
])
|
||||
|
||||
const state = {
|
||||
@@ -84,7 +88,8 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
|
||||
{...others}
|
||||
data-component="select"
|
||||
data-trigger-style={local.triggerVariant}
|
||||
placement={local.triggerVariant === "settings" ? "bottom-end" : "bottom-start"}
|
||||
placement={local.triggerVariant === "settings" ? "bottom-end" : "top-start"}
|
||||
flip
|
||||
gutter={4}
|
||||
value={local.current}
|
||||
options={grouped()}
|
||||
@@ -140,7 +145,8 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
>
|
||||
<Kobalte.Value<T> data-slot="select-select-trigger-value">
|
||||
{local.icon}
|
||||
<Kobalte.Value<T> data-slot="select-select-trigger-value" class={local.valueClass}>
|
||||
{(state) => {
|
||||
const selected = state.selectedOption() ?? local.current
|
||||
if (!selected) return local.placeholder || ""
|
||||
@@ -153,14 +159,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
|
||||
</Kobalte.Icon>
|
||||
</Kobalte.Trigger>
|
||||
<Kobalte.Portal>
|
||||
<Kobalte.Content
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
data-component="select-content"
|
||||
data-trigger-style={local.triggerVariant}
|
||||
>
|
||||
<Kobalte.Content data-component="select-content" data-trigger-style={local.triggerVariant}>
|
||||
<Kobalte.Listbox data-slot="select-select-content-list" />
|
||||
</Kobalte.Content>
|
||||
</Kobalte.Portal>
|
||||
|
||||
Reference in New Issue
Block a user