fix(tui): responsive layout for narrow screens (#9703)

This commit is contained in:
Vinicius da Motta
2026-01-21 01:22:38 -03:00
committed by GitHub
parent 34d473c0f5
commit b93f33eaa4
2 changed files with 55 additions and 43 deletions

View File

@@ -8,6 +8,7 @@ import type { AssistantMessage, Session } from "@opencode-ai/sdk/v2"
import { useCommandDialog } from "@tui/component/dialog-command" import { useCommandDialog } from "@tui/component/dialog-command"
import { useKeybind } from "../../context/keybind" import { useKeybind } from "../../context/keybind"
import { Installation } from "@/installation" import { Installation } from "@/installation"
import { useTerminalDimensions } from "@opentui/solid"
const Title = (props: { session: Accessor<Session> }) => { const Title = (props: { session: Accessor<Session> }) => {
const { theme } = useTheme() const { theme } = useTheme()
@@ -63,6 +64,8 @@ export function Header() {
const keybind = useKeybind() const keybind = useKeybind()
const command = useCommandDialog() const command = useCommandDialog()
const [hover, setHover] = createSignal<"parent" | "prev" | "next" | null>(null) const [hover, setHover] = createSignal<"parent" | "prev" | "next" | null>(null)
const dimensions = useTerminalDimensions()
const narrow = createMemo(() => dimensions().width < 80)
return ( return (
<box flexShrink={0}> <box flexShrink={0}>
@@ -79,49 +82,52 @@ export function Header() {
> >
<Switch> <Switch>
<Match when={session()?.parentID}> <Match when={session()?.parentID}>
<box flexDirection="row" gap={2}> <box flexDirection="column" gap={1}>
<text fg={theme.text}> <box flexDirection={narrow() ? "column" : "row"} justifyContent="space-between" gap={narrow() ? 1 : 0}>
<b>Subagent session</b>
</text>
<box
onMouseOver={() => setHover("parent")}
onMouseOut={() => setHover(null)}
onMouseUp={() => command.trigger("session.parent")}
backgroundColor={hover() === "parent" ? theme.backgroundElement : theme.backgroundPanel}
>
<text fg={theme.text}> <text fg={theme.text}>
Parent <span style={{ fg: theme.textMuted }}>{keybind.print("session_parent")}</span> <b>Subagent session</b>
</text> </text>
<box flexDirection="row" gap={1} flexShrink={0}>
<ContextInfo context={context} cost={cost} />
<text fg={theme.textMuted}>v{Installation.VERSION}</text>
</box>
</box> </box>
<box <box flexDirection="row" gap={2}>
onMouseOver={() => setHover("prev")} <box
onMouseOut={() => setHover(null)} onMouseOver={() => setHover("parent")}
onMouseUp={() => command.trigger("session.child.previous")} onMouseOut={() => setHover(null)}
backgroundColor={hover() === "prev" ? theme.backgroundElement : theme.backgroundPanel} onMouseUp={() => command.trigger("session.parent")}
> backgroundColor={hover() === "parent" ? theme.backgroundElement : theme.backgroundPanel}
<text fg={theme.text}> >
Prev <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle_reverse")}</span> <text fg={theme.text}>
</text> Parent <span style={{ fg: theme.textMuted }}>{keybind.print("session_parent")}</span>
</box> </text>
<box </box>
onMouseOver={() => setHover("next")} <box
onMouseOut={() => setHover(null)} onMouseOver={() => setHover("prev")}
onMouseUp={() => command.trigger("session.child.next")} onMouseOut={() => setHover(null)}
backgroundColor={hover() === "next" ? theme.backgroundElement : theme.backgroundPanel} onMouseUp={() => command.trigger("session.child.previous")}
> backgroundColor={hover() === "prev" ? theme.backgroundElement : theme.backgroundPanel}
<text fg={theme.text}> >
Next <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle")}</span> <text fg={theme.text}>
</text> Prev <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle_reverse")}</span>
</box> </text>
<box flexGrow={1} flexShrink={1} /> </box>
<box flexDirection="row" gap={1} flexShrink={0}> <box
<ContextInfo context={context} cost={cost} /> onMouseOver={() => setHover("next")}
<text fg={theme.textMuted}>v{Installation.VERSION}</text> onMouseOut={() => setHover(null)}
onMouseUp={() => command.trigger("session.child.next")}
backgroundColor={hover() === "next" ? theme.backgroundElement : theme.backgroundPanel}
>
<text fg={theme.text}>
Next <span style={{ fg: theme.textMuted }}>{keybind.print("session_child_cycle")}</span>
</text>
</box>
</box> </box>
</box> </box>
</Match> </Match>
<Match when={true}> <Match when={true}>
<box flexDirection="row" justifyContent="space-between" gap={1}> <box flexDirection={narrow() ? "column" : "row"} justifyContent="space-between" gap={1}>
<Title session={session} /> <Title session={session} />
<box flexDirection="row" gap={1} flexShrink={0}> <box flexDirection="row" gap={1} flexShrink={0}>
<ContextInfo context={context} cost={cost} /> <ContextInfo context={context} cost={cost} />

View File

@@ -302,6 +302,8 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: (
const { theme } = useTheme() const { theme } = useTheme()
const keybind = useKeybind() const keybind = useKeybind()
const textareaKeybindings = useTextareaKeybindings() const textareaKeybindings = useTextareaKeybindings()
const dimensions = useTerminalDimensions()
const narrow = createMemo(() => dimensions().width < 80)
useKeyboard((evt) => { useKeyboard((evt) => {
if (evt.name === "escape" || keybind.match("app_exit", evt)) { if (evt.name === "escape" || keybind.match("app_exit", evt)) {
@@ -332,14 +334,16 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: (
</box> </box>
</box> </box>
<box <box
flexDirection="row" flexDirection={narrow() ? "column" : "row"}
flexShrink={0} flexShrink={0}
paddingTop={1} paddingTop={1}
paddingLeft={2} paddingLeft={2}
paddingRight={3} paddingRight={3}
paddingBottom={1} paddingBottom={1}
backgroundColor={theme.backgroundElement} backgroundColor={theme.backgroundElement}
justifyContent="space-between" justifyContent={narrow() ? "flex-start" : "space-between"}
alignItems={narrow() ? "flex-start" : "center"}
gap={1}
> >
<textarea <textarea
ref={(val: TextareaRenderable) => (input = val)} ref={(val: TextareaRenderable) => (input = val)}
@@ -349,7 +353,7 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: (
cursorColor={theme.primary} cursorColor={theme.primary}
keyBindings={textareaKeybindings()} keyBindings={textareaKeybindings()}
/> />
<box flexDirection="row" gap={2} flexShrink={0} marginLeft={1}> <box flexDirection="row" gap={2} flexShrink={0}>
<text fg={theme.text}> <text fg={theme.text}>
enter <span style={{ fg: theme.textMuted }}>confirm</span> enter <span style={{ fg: theme.textMuted }}>confirm</span>
</text> </text>
@@ -379,6 +383,7 @@ function Prompt<const T extends Record<string, string>>(props: {
expanded: false, expanded: false,
}) })
const diffKey = Keybind.parse("ctrl+f")[0] const diffKey = Keybind.parse("ctrl+f")[0]
const narrow = createMemo(() => dimensions().width < 80)
useKeyboard((evt) => { useKeyboard((evt) => {
if (evt.name === "left" || evt.name == "h") { if (evt.name === "left" || evt.name == "h") {
@@ -440,7 +445,7 @@ function Prompt<const T extends Record<string, string>>(props: {
{props.body} {props.body}
</box> </box>
<box <box
flexDirection="row" flexDirection={narrow() ? "column" : "row"}
flexShrink={0} flexShrink={0}
gap={1} gap={1}
paddingTop={1} paddingTop={1}
@@ -448,9 +453,10 @@ function Prompt<const T extends Record<string, string>>(props: {
paddingRight={3} paddingRight={3}
paddingBottom={1} paddingBottom={1}
backgroundColor={theme.backgroundElement} backgroundColor={theme.backgroundElement}
justifyContent="space-between" justifyContent={narrow() ? "flex-start" : "space-between"}
alignItems={narrow() ? "flex-start" : "center"}
> >
<box flexDirection="row" gap={1}> <box flexDirection="row" gap={1} flexShrink={0}>
<For each={keys}> <For each={keys}>
{(option) => ( {(option) => (
<box <box
@@ -470,7 +476,7 @@ function Prompt<const T extends Record<string, string>>(props: {
)} )}
</For> </For>
</box> </box>
<box flexDirection="row" gap={2}> <box flexDirection="row" gap={2} flexShrink={0}>
<Show when={props.fullscreen}> <Show when={props.fullscreen}>
<text fg={theme.text}> <text fg={theme.text}>
{"ctrl+f"} <span style={{ fg: theme.textMuted }}>{hint()}</span> {"ctrl+f"} <span style={{ fg: theme.textMuted }}>{hint()}</span>