Kaynağa Gözat

fix(tui): improve selected spinner contrast (#42141)

Kit Langton 4 gün önce
ebeveyn
işleme
f6aa1a67f0

+ 2 - 1
packages/tui/src/component/dialog-open.tsx

@@ -1,6 +1,7 @@
 import { createMemo, createResource, createSignal } from "solid-js"
 import type { SessionInfo } from "@opencode-ai/client"
 import { useTerminalDimensions } from "@opentui/solid"
+import type { RGBA } from "@opentui/core"
 import { dialogWidth, useDialog } from "../ui/dialog"
 import { DialogSelect, dialogSelectContentWidth } from "../ui/dialog-select"
 import { useRoute } from "../context/route"
@@ -105,7 +106,7 @@ export function DialogOpen(props: { sessions: SessionInfo[] }) {
         footer: `${name ? `${Locale.truncate(name, 20)} · ` : ""}${timeAgo(session.time.updated)}`,
         onSelect: () => location.set(session.location),
         gutter: running
-          ? () => <Spinner />
+          ? (color: RGBA) => <Spinner color={color} />
           : tabs.has(session.id)
             ? () => <text fg={theme.hue.accent[mode() === "light" ? 800 : 200]}>▪</text>
             : undefined,

+ 2 - 1
packages/tui/src/component/dialog-session-list.tsx

@@ -2,6 +2,7 @@ import { createMemo, createResource, createSignal, onMount, Show } from "solid-j
 import path from "path"
 import type { SessionInfo } from "@opencode-ai/client"
 import { TextAttributes } from "@opentui/core"
+import type { RGBA } from "@opentui/core"
 import { useDialog } from "../ui/dialog"
 import { DialogSelect } from "../ui/dialog-select"
 import { useRoute } from "../context/route"
@@ -161,7 +162,7 @@ export function DialogSessionList() {
         gutter:
           data.session.status(session.id) === "running" ||
           data.session.family(session.id).some((id) => data.session.status(id) === "running")
-            ? () => <Spinner />
+            ? (color: RGBA) => <Spinner color={color} />
             : slot === undefined
               ? undefined
               : () => <text fg={theme.hue.accent[mode() === "light" ? 800 : 200]}>{slot}</text>,

+ 3 - 3
packages/tui/src/ui/dialog-select.tsx

@@ -80,7 +80,7 @@ export interface DialogSelectOption<T = any> {
   disabled?: boolean
   bg?: RGBA
   fg?: RGBA
-  gutter?: () => JSX.Element
+  gutter?: (color: RGBA) => JSX.Element
   margin?: JSX.Element
   onSelect?: (ctx: DialogContext) => void
 }
@@ -816,7 +816,7 @@ function Option(props: {
   footerColor?: RGBA
   titleWidth?: number
   truncateTitle?: boolean | "left"
-  gutter?: () => JSX.Element
+  gutter?: (color: RGBA) => JSX.Element
   activeColor?: RGBA
   onMouseOver?: () => void
 }) {
@@ -837,7 +837,7 @@ function Option(props: {
       </Show>
       <Show when={props.gutter}>
         <box flexShrink={0} marginRight={0}>
-          {props.gutter?.()}
+          {props.gutter?.(text())}
         </box>
       </Show>
       <text

+ 27 - 1
packages/tui/test/cli/tui/dialog-select.test.tsx

@@ -1,5 +1,5 @@
 /** @jsxImportSource @opentui/solid */
-import { InputRenderable } from "@opentui/core"
+import { InputRenderable, type RGBA } from "@opentui/core"
 import { testRender } from "@opentui/solid"
 import { expect, test } from "bun:test"
 import { mkdir } from "node:fs/promises"
@@ -197,6 +197,32 @@ test("renders actions with a current selection", async () => {
   }
 })
 
+test("passes the row foreground color to gutters", async () => {
+  await using tmp = await tmpdir()
+  const colors = new Map<string, RGBA>()
+  const gutter = (item: string) => (color: RGBA) => {
+    colors.set(item, color)
+    return <text fg={color}>*</text>
+  }
+  const select = await mountSelect(tmp.path, [
+    { title: "Alpha", value: "alpha", gutter: gutter("alpha") },
+    { title: "Beta", value: "beta", gutter: gutter("beta") },
+  ])
+
+  try {
+    await select.app.waitFor(() => colors.size === 2)
+    const selected = colors.get("alpha")!.toInts()
+    const idle = colors.get("beta")!.toInts()
+    expect(selected).not.toEqual(idle)
+
+    select.app.mockInput.pressArrow("down")
+    await select.app.waitFor(() => colors.get("alpha")!.toInts().every((value, index) => value === idle[index]))
+    expect(colors.get("beta")!.toInts()).toEqual(selected)
+  } finally {
+    select.app.renderer.destroy()
+  }
+})
+
 test("dialog actions run without options while row actions still require a selection", async () => {
   await using tmp = await tmpdir()
   let global = 0