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

This commit is contained in:
Kit Langton 2026-08-12 16:27:52 -04:00 committed by GitHub
parent 0859f77153
commit f6aa1a67f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 34 additions and 6 deletions

View file

@ -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,

View file

@ -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>,

View file

@ -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

View file

@ -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