feat(tui): resolve action state priority (#37373)

This commit is contained in:
James Long 2026-07-16 16:40:34 -04:00 committed by GitHub
commit 88b8883122
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 35 additions and 12 deletions

View file

@ -1,6 +1,6 @@
import { createMemo, For, Show, createEffect, onMount, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import { TextAttributes, RGBA, ScrollBoxRenderable } from "@opentui/core"
import { TextAttributes, ScrollBoxRenderable } from "@opentui/core"
import { useData } from "../../../context/data"
import { useLocation } from "../../../context/location"
import { useClient } from "../../../context/client"
@ -105,11 +105,11 @@ export function ShellTab(props: { sessionID: string }) {
flexDirection="row"
paddingLeft={1}
paddingRight={1}
backgroundColor={active() ? themeV2.background.action.primary("selected") : RGBA.fromInts(0, 0, 0, 0)}
backgroundColor={themeV2.background.action.primary({ focused: active() })}
onMouseOver={() => setStore("selected", index())}
>
<text
fg={themeV2.text.action.primary(active() ? "focused" : "default")}
fg={themeV2.text.action.primary({ focused: active() })}
attributes={active() ? TextAttributes.BOLD : undefined}
wrapMode="none"
>

View file

@ -1,6 +1,6 @@
import { createMemo, For, Show, createEffect, onMount, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import { TextAttributes, RGBA, ScrollBoxRenderable } from "@opentui/core"
import { TextAttributes, ScrollBoxRenderable } from "@opentui/core"
import { useRoute, useRouteData } from "../../../context/route"
import { useData } from "../../../context/data"
import { useClient } from "../../../context/client"
@ -216,7 +216,7 @@ export function SubagentsTab(props: { sessionID: string }) {
flexDirection="row"
paddingLeft={1}
paddingRight={1}
backgroundColor={themeV2.background.action.primary(active() ? "focused" : "default")}
backgroundColor={themeV2.background.action.primary({ focused: active(), selected: entry.current })}
onMouseOver={() => setStore("selected", index())}
onMouseUp={() => {
setStore("selected", index())
@ -225,9 +225,7 @@ export function SubagentsTab(props: { sessionID: string }) {
>
<box flexGrow={1} minWidth={0} flexDirection="row">
<text
fg={themeV2.text.action.primary(
active() ? "focused" : entry.current ? "selected" : "default",
)}
fg={themeV2.text.action.primary({ focused: active(), selected: entry.current })}
attributes={active() ? TextAttributes.BOLD : undefined}
wrapMode="none"
>
@ -235,7 +233,14 @@ export function SubagentsTab(props: { sessionID: string }) {
</text>
</box>
<Show when={status()}>
<text fg={active() ? themeV2.text.action.primary() : themeV2.text.subdued()} wrapMode="none">
<text
fg={
active()
? themeV2.text.action.primary({ focused: active(), selected: entry.current })
: themeV2.text.subdued()
}
wrapMode="none"
>
{status()}
</text>
</Show>

View file

@ -1,7 +1,6 @@
import type { RGBA } from "@opentui/core"
import type { Accessor } from "solid-js"
import type {
ActionState,
ActionVariant,
FormfieldState,
ResolvedActionState,
@ -9,6 +8,9 @@ import type {
ResolvedThemeView,
HueStep,
} from "./index"
import { ActionState } from "./schema"
export type ActionStates = Partial<Record<ActionState, boolean>>
export function createComponentTheme(current: Accessor<ResolvedThemeView>) {
const textAction = actions((variant, state) => current().text.action[variant][state])
@ -121,7 +123,10 @@ export function createComponentTheme(current: Accessor<ResolvedThemeView>) {
}
function actions(get: (variant: ActionVariant, state: ResolvedActionState) => RGBA) {
const action = (variant: ActionVariant) => (state: ActionState | "default" = "default") => get(variant, state)
const action = (variant: ActionVariant) => (states: ActionState | "default" | ActionStates = "default") => {
if (typeof states === "string") return get(variant, states)
return get(variant, ActionState.literals.find((state) => states[state]) ?? "default")
}
const primary = action("primary")
return Object.assign(primary, {
primary,

View file

@ -12,7 +12,7 @@ export type HueAlias = Schema.Schema.Type<typeof HueAlias>
export const ActionVariant = Schema.Literals(["primary", "secondary", "destructive"])
export type ActionVariant = Schema.Schema.Type<typeof ActionVariant>
export const ActionState = Schema.Literals(["focused", "pressed", "selected", "disabled"])
export const ActionState = Schema.Literals(["disabled", "pressed", "focused", "selected"])
export type ActionState = Schema.Schema.Type<typeof ActionState>
export type ActionStateKey = `$${ActionState}`

View file

@ -23,6 +23,19 @@ test("provides reactive property, variant, state, and context accessors", () =>
expect(theme.text.action.primary("pressed")).toBe(resolved().text.action.primary.pressed)
expect(theme.text.action.primary("selected")).toBe(resolved().text.action.primary.selected)
expect(theme.background.action.primary("selected")).toBe(resolved().background.action.primary.selected)
expect(theme.background.action.primary({ selected: true })).toBe(resolved().background.action.primary.selected)
expect(theme.background.action.primary({ focused: true, selected: true })).toBe(
resolved().background.action.primary.focused,
)
expect(theme.background.action.primary({ pressed: true, focused: true, selected: true })).toBe(
resolved().background.action.primary.pressed,
)
expect(theme.background.action.primary({ disabled: true, pressed: true, focused: true, selected: true })).toBe(
resolved().background.action.primary.disabled,
)
expect(theme.background.action.primary({ disabled: false, selected: false })).toBe(
resolved().background.action.primary.default,
)
expect(theme.background.action.secondary("disabled")).toBe(
resolved().background.action.secondary.disabled,
)