Browse Source

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

Vinicius da Motta 1 month ago
parent
commit
b93f33eaa4

+ 42 - 36
packages/opencode/src/cli/cmd/tui/routes/session/header.tsx

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

+ 13 - 7
packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx

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