refactor(tui): remove secondary action styles (#37378)

This commit is contained in:
James Long
2026-07-16 17:44:11 -04:00
committed by GitHub
parent b4a4ef0b3c
commit 309860558d
16 changed files with 81 additions and 112 deletions
@@ -20,7 +20,7 @@ export function DevToolsSidebar() {
{(group) => ( {(group) => (
<box flexShrink={0} marginBottom={1}> <box flexShrink={0} marginBottom={1}>
<box marginBottom={1}> <box marginBottom={1}>
<text fg={themeV2.background.action.primary()} attributes={TextAttributes.BOLD}> <text fg={themeV2.background.action()} attributes={TextAttributes.BOLD}>
{group.title} {group.title}
</text> </text>
</box> </box>
+20 -35
View File
@@ -187,7 +187,7 @@ export function Prompt(props: PromptProps) {
const renderer = useRenderer() const renderer = useRenderer()
const exit = useExit() const exit = useExit()
const dimensions = useTerminalDimensions() const dimensions = useTerminalDimensions()
const { themeV2, syntax } = useTheme() const { themeV2, syntax, mode } = useTheme()
const animationsEnabled = createMemo(() => config.animations ?? true) const animationsEnabled = createMemo(() => config.animations ?? true)
const list = createMemo(() => props.placeholders?.normal ?? []) const list = createMemo(() => props.placeholders?.normal ?? [])
const shell = createMemo(() => props.placeholders?.shell ?? []) const shell = createMemo(() => props.placeholders?.shell ?? [])
@@ -1319,7 +1319,7 @@ export function Prompt(props: PromptProps) {
const highlight = createMemo(() => { const highlight = createMemo(() => {
if (leader()) return themeV2.border() if (leader()) return themeV2.border()
if (store.mode === "shell") return themeV2.background.action.primary() if (store.mode === "shell") return themeV2.background.action()
const agent = local.agent.current() const agent = local.agent.current()
if (!agent) return themeV2.border() if (!agent) return themeV2.border()
return local.agent.color(agent.id) return local.agent.color(agent.id)
@@ -1378,6 +1378,12 @@ export function Prompt(props: PromptProps) {
}) })
const maxHeight = createMemo(() => Math.max(6, Math.floor(dimensions().height / 3))) const maxHeight = createMemo(() => Math.max(6, Math.floor(dimensions().height / 3)))
const promptBg = createMemo(() =>
mode() === "light"
? themeV2.increase(themeV2.background.surface.offset(), 1)
: themeV2.decrease(themeV2.background.surface.offset(), 1),
)
return ( return (
<> <>
<box ref={(r: BoxRenderable) => (anchor = r)} visible={props.visible !== false} width="100%"> <box ref={(r: BoxRenderable) => (anchor = r)} visible={props.visible !== false} width="100%">
@@ -1395,7 +1401,7 @@ export function Prompt(props: PromptProps) {
paddingRight={2} paddingRight={2}
paddingTop={1} paddingTop={1}
flexShrink={0} flexShrink={0}
backgroundColor={themeV2.background.action.secondary("focused")} backgroundColor={promptBg()}
flexGrow={1} flexGrow={1}
width="100%" width="100%"
> >
@@ -1471,10 +1477,8 @@ export function Prompt(props: PromptProps) {
if (props.disabled) return if (props.disabled) return
r.target?.focus() r.target?.focus()
}} }}
focusedBackgroundColor={themeV2.background.action.secondary("focused")} focusedBackgroundColor="transparent"
cursorColor={ cursorColor={props.disabled ? themeV2.background.surface.offset() : themeV2.text()}
props.disabled ? themeV2.background.surface.offset() : themeV2.text()
}
syntaxStyle={syntax()} syntaxStyle={syntax()}
/> />
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1} justifyContent="space-between"> <box flexDirection="row" flexShrink={0} paddingTop={1} gap={1} justifyContent="space-between">
@@ -1493,16 +1497,11 @@ export function Prompt(props: PromptProps) {
<text fg={fadeColor(themeV2.text.subdued(), modelMetaAlpha())}>·</text> <text fg={fadeColor(themeV2.text.subdued(), modelMetaAlpha())}>·</text>
<text <text
flexShrink={0} flexShrink={0}
fg={fadeColor( fg={fadeColor(leader() ? themeV2.text.subdued() : themeV2.text(), modelMetaAlpha())}
leader() ? themeV2.text.subdued() : themeV2.text(),
modelMetaAlpha(),
)}
> >
{local.model.parsed().model} {local.model.parsed().model}
</text> </text>
<text fg={fadeColor(themeV2.text.subdued(), modelMetaAlpha())}> <text fg={fadeColor(themeV2.text.subdued(), modelMetaAlpha())}>{currentProviderLabel()}</text>
{currentProviderLabel()}
</text>
<Show when={showVariant()}> <Show when={showVariant()}>
<text fg={fadeColor(themeV2.text.subdued(), variantMetaAlpha())}>·</text> <text fg={fadeColor(themeV2.text.subdued(), variantMetaAlpha())}>·</text>
<text> <text>
@@ -1536,15 +1535,15 @@ export function Prompt(props: PromptProps) {
borderColor={borderHighlight()} borderColor={borderHighlight()}
customBorderChars={{ customBorderChars={{
...EmptyBorder, ...EmptyBorder,
vertical: themeV2.background.action.secondary("focused").a !== 0 ? "╹" : " ", vertical: promptBg().a !== 0 ? "╹" : " ",
}} }}
> >
<box <box
height={1} height={1}
border={["bottom"]} border={["bottom"]}
borderColor={themeV2.background.action.secondary("focused")} borderColor={promptBg()}
customBorderChars={ customBorderChars={
themeV2.background.action.secondary("focused").a !== 0 promptBg().a !== 0
? { ? {
...EmptyBorder, ...EmptyBorder,
horizontal: "▀", horizontal: "▀",
@@ -1561,23 +1560,15 @@ export function Prompt(props: PromptProps) {
<Match when={status() === "running"}> <Match when={status() === "running"}>
<box flexDirection="row" gap={1} flexGrow={1} justifyContent="flex-start"> <box flexDirection="row" gap={1} flexGrow={1} justifyContent="flex-start">
<box marginLeft={1}> <box marginLeft={1}>
<Show <Show when={config.animations ?? true} fallback={<text fg={themeV2.text.subdued()}>[]</text>}>
when={config.animations ?? true}
fallback={<text fg={themeV2.text.subdued()}>[]</text>}
>
<spinner color={spinnerDef().color} frames={spinnerDef().frames} interval={40} /> <spinner color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
</Show> </Show>
</box> </box>
<text <text fg={store.interrupt > 0 ? themeV2.background.action() : themeV2.text()}>
fg={store.interrupt > 0 ? themeV2.background.action.primary() : themeV2.text()}
>
esc{" "} esc{" "}
<span <span
style={{ style={{
fg: fg: store.interrupt > 0 ? themeV2.background.action() : themeV2.text.subdued(),
store.interrupt > 0
? themeV2.background.action.primary()
: themeV2.text.subdued(),
}} }}
> >
{store.interrupt > 0 ? "again to interrupt" : "interrupt"} {store.interrupt > 0 ? "again to interrupt" : "interrupt"}
@@ -1613,13 +1604,7 @@ export function Prompt(props: PromptProps) {
<box gap={2} flexDirection="row"> <box gap={2} flexDirection="row">
<Show when={editorContextLabelState() !== "none" ? editorFileLabelDisplay() : undefined}> <Show when={editorContextLabelState() !== "none" ? editorFileLabelDisplay() : undefined}>
{(file) => ( {(file) => (
<text <text fg={editorContextLabelState() === "pending" ? themeV2.hue.accent(500) : themeV2.text.subdued()}>
fg={
editorContextLabelState() === "pending"
? themeV2.hue.accent(500)
: themeV2.text.subdued()
}
>
{file()} {file()}
</text> </text>
)} )}
@@ -105,11 +105,11 @@ export function ShellTab(props: { sessionID: string }) {
flexDirection="row" flexDirection="row"
paddingLeft={1} paddingLeft={1}
paddingRight={1} paddingRight={1}
backgroundColor={themeV2.background.action.primary({ focused: active() })} backgroundColor={themeV2.background.action({ focused: active() })}
onMouseOver={() => setStore("selected", index())} onMouseOver={() => setStore("selected", index())}
> >
<text <text
fg={themeV2.text.action.primary({ focused: active() })} fg={themeV2.text.action({ focused: active() })}
attributes={active() ? TextAttributes.BOLD : undefined} attributes={active() ? TextAttributes.BOLD : undefined}
wrapMode="none" wrapMode="none"
> >
@@ -216,7 +216,7 @@ export function SubagentsTab(props: { sessionID: string }) {
flexDirection="row" flexDirection="row"
paddingLeft={1} paddingLeft={1}
paddingRight={1} paddingRight={1}
backgroundColor={themeV2.background.action.primary({ focused: active(), selected: entry.current })} backgroundColor={themeV2.background.action({ focused: active(), selected: entry.current })}
onMouseOver={() => setStore("selected", index())} onMouseOver={() => setStore("selected", index())}
onMouseUp={() => { onMouseUp={() => {
setStore("selected", index()) setStore("selected", index())
@@ -225,7 +225,7 @@ export function SubagentsTab(props: { sessionID: string }) {
> >
<box flexGrow={1} minWidth={0} flexDirection="row"> <box flexGrow={1} minWidth={0} flexDirection="row">
<text <text
fg={themeV2.text.action.primary({ focused: active(), selected: entry.current })} fg={themeV2.text.action({ focused: active(), selected: entry.current })}
attributes={active() ? TextAttributes.BOLD : undefined} attributes={active() ? TextAttributes.BOLD : undefined}
wrapMode="none" wrapMode="none"
> >
@@ -236,7 +236,7 @@ export function SubagentsTab(props: { sessionID: string }) {
<text <text
fg={ fg={
active() active()
? themeV2.text.action.primary({ focused: active(), selected: entry.current }) ? themeV2.text.action({ focused: active(), selected: entry.current })
: themeV2.text.subdued() : themeV2.text.subdued()
} }
wrapMode="none" wrapMode="none"
+1 -1
View File
@@ -848,7 +848,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
<text fg={themeV2.text.subdued()}>{external().description}</text> <text fg={themeV2.text.subdued()}>{external().description}</text>
</Show> </Show>
<text <text
fg={themeV2.background.action.primary()} fg={themeV2.background.action()}
onMouseUp={() => { onMouseUp={() => {
if (renderer.getSelection()?.getSelectedText()) return if (renderer.getSelection()?.getSelectedText()) return
openExternal() openExternal()
+11 -11
View File
@@ -891,7 +891,7 @@ export function Session() {
paddingLeft: 1, paddingLeft: 1,
visible: showScrollbar(), visible: showScrollbar(),
trackOptions: { trackOptions: {
backgroundColor: themeV2.background.action.secondary("focused"), backgroundColor: themeV2.background.action("focused"),
foregroundColor: themeV2.border(), foregroundColor: themeV2.border(),
}, },
}} }}
@@ -1222,7 +1222,7 @@ function SessionReasoningGroupView(props: {
<box <box
border={["left"]} border={["left"]}
customBorderChars={SplitBorder.customBorderChars} customBorderChars={SplitBorder.customBorderChars}
borderColor={themeV2.background.action.secondary("focused")} borderColor={themeV2.background.action("focused")}
paddingLeft={1} paddingLeft={1}
> >
<code <code
@@ -1538,7 +1538,7 @@ function RevertMessage(props: {
paddingTop={1} paddingTop={1}
paddingBottom={1} paddingBottom={1}
paddingLeft={2} paddingLeft={2}
backgroundColor={hover() ? themeV2.background.action.secondary("focused") : themeV2.background()} backgroundColor={hover() ? themeV2.background.action("focused") : themeV2.background()}
> >
<text fg={themeV2.text.subdued()}> <text fg={themeV2.text.subdued()}>
{props.count} message{props.count === 1 ? "" : "s"} reverted {props.count} message{props.count === 1 ? "" : "s"} reverted
@@ -1644,7 +1644,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
paddingTop={1} paddingTop={1}
paddingBottom={1} paddingBottom={1}
paddingLeft={2} paddingLeft={2}
backgroundColor={hover() ? themeV2.background.action.secondary("focused") : themeV2.background()} backgroundColor={hover() ? themeV2.background.action("focused") : themeV2.background()}
flexShrink={0} flexShrink={0}
> >
<text fg={themeV2.text()}>{props.message.text}</text> <text fg={themeV2.text()}>{props.message.text}</text>
@@ -1664,7 +1664,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
> >
{` ${label} `} {` ${label} `}
</span> </span>
<span style={{ bg: themeV2.background.action.secondary("focused"), fg: themeV2.text.subdued() }}> <span style={{ bg: themeV2.background.action("focused"), fg: themeV2.text.subdued() }}>
{" "} {" "}
{file.name ?? (file.source.type === "uri" ? file.source.uri : "attachment")}{" "} {file.name ?? (file.source.type === "uri" ? file.source.uri : "attachment")}{" "}
</span> </span>
@@ -1880,7 +1880,7 @@ function ReasoningPart(props: {
<box <box
border={!inMinimal() || expanded() ? ["left"] : undefined} border={!inMinimal() || expanded() ? ["left"] : undefined}
customBorderChars={SplitBorder.customBorderChars} customBorderChars={SplitBorder.customBorderChars}
borderColor={themeV2.background.action.secondary("focused")} borderColor={themeV2.background.action("focused")}
paddingLeft={!inMinimal() || expanded() ? 1 : 0} paddingLeft={!inMinimal() || expanded() ? 1 : 0}
> >
<box onMouseUp={toggle}> <box onMouseUp={toggle}>
@@ -1898,7 +1898,7 @@ function ReasoningPart(props: {
<box <box
border={["left"]} border={["left"]}
customBorderChars={SplitBorder.customBorderChars} customBorderChars={SplitBorder.customBorderChars}
borderColor={themeV2.background.action.secondary("focused")} borderColor={themeV2.background.action("focused")}
paddingLeft={inMinimal() ? 3 : 1} paddingLeft={inMinimal() ? 3 : 1}
> >
<code <code
@@ -2093,7 +2093,7 @@ function GenericTool(props: ToolProps) {
<Show when={Object.keys(props.input).length > 0}> <Show when={Object.keys(props.input).length > 0}>
<box gap={1}> <box gap={1}>
<text> <text>
<span style={{ bg: themeV2.background.action.secondary("focused"), fg: themeV2.text.subdued() }}> Input </span> <span style={{ bg: themeV2.background.action("focused"), fg: themeV2.text.subdued() }}> Input </span>
</text> </text>
<box paddingLeft={1}> <box paddingLeft={1}>
<code <code
@@ -2111,7 +2111,7 @@ function GenericTool(props: ToolProps) {
{(value) => ( {(value) => (
<box gap={1}> <box gap={1}>
<text> <text>
<span style={{ bg: themeV2.background.action.secondary("focused"), fg: themeV2.text.subdued() }}> Output </span> <span style={{ bg: themeV2.background.action("focused"), fg: themeV2.text.subdued() }}> Output </span>
</text> </text>
<box paddingLeft={1}> <box paddingLeft={1}>
<text fg={themeV2.text()} wrapMode="word"> <text fg={themeV2.text()} wrapMode="word">
@@ -2301,7 +2301,7 @@ function InlineToolLabel(props: { color?: RGBA; denied?: boolean; status: JSX.El
function StatusBadge(props: { children: string }) { function StatusBadge(props: { children: string }) {
const { themeV2 } = useTheme() const { themeV2 } = useTheme()
return ( return (
<text flexShrink={0} bg={themeV2.background.action.secondary("focused")} fg={themeV2.text.subdued()}> <text flexShrink={0} bg={themeV2.background.action("focused")} fg={themeV2.text.subdued()}>
{" "} {" "}
{props.children}{" "} {props.children}{" "}
</text> </text>
@@ -2335,7 +2335,7 @@ function BlockTool(props: {
paddingLeft={2} paddingLeft={2}
gap={1} gap={1}
backgroundColor={ backgroundColor={
hover() ? themeV2.background.action.secondary("focused") : themeV2.background() hover() ? themeV2.background.action("focused") : themeV2.background()
} }
customBorderChars={SplitBorder.customBorderChars} customBorderChars={SplitBorder.customBorderChars}
borderColor={themeV2.background()} borderColor={themeV2.background()}
@@ -516,7 +516,7 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: (
paddingLeft={2} paddingLeft={2}
paddingRight={3} paddingRight={3}
paddingBottom={1} paddingBottom={1}
backgroundColor={themeV2.background.action.secondary("focused")} backgroundColor={themeV2.background.action("focused")}
justifyContent={narrow() ? "flex-start" : "space-between"} justifyContent={narrow() ? "flex-start" : "space-between"}
alignItems={narrow() ? "flex-start" : "center"} alignItems={narrow() ? "flex-start" : "center"}
gap={1} gap={1}
@@ -656,7 +656,7 @@ function Prompt<const T extends Record<string, string>>(props: {
<box <box
backgroundColor={themeV2.background()} backgroundColor={themeV2.background()}
border={["left"]} border={["left"]}
borderColor={themeV2.background.action.primary("focused")} borderColor={themeV2.background.action("focused")}
customBorderChars={SplitBorder.customBorderChars} customBorderChars={SplitBorder.customBorderChars}
{...(store.expanded {...(store.expanded
? { top: dimensions().height * -1 + 1, bottom: 1, left: 2, right: 2, position: "absolute" } ? { top: dimensions().height * -1 + 1, bottom: 1, left: 2, right: 2, position: "absolute" }
@@ -693,7 +693,7 @@ function Prompt<const T extends Record<string, string>>(props: {
paddingLeft={2} paddingLeft={2}
paddingRight={3} paddingRight={3}
paddingBottom={1} paddingBottom={1}
backgroundColor={themeV2.background.action.secondary("focused")} backgroundColor={themeV2.background.action("focused")}
justifyContent={narrow() ? "flex-start" : "space-between"} justifyContent={narrow() ? "flex-start" : "space-between"}
alignItems={narrow() ? "flex-start" : "center"} alignItems={narrow() ? "flex-start" : "center"}
> >
@@ -703,7 +703,7 @@ function Prompt<const T extends Record<string, string>>(props: {
<box <box
paddingLeft={1} paddingLeft={1}
paddingRight={1} paddingRight={1}
backgroundColor={themeV2.background.action.primary( backgroundColor={themeV2.background.action(
option === store.selected ? "focused" : "default", option === store.selected ? "focused" : "default",
)} )}
onMouseOver={() => setStore("selected", option)} onMouseOver={() => setStore("selected", option)}
@@ -713,7 +713,7 @@ function Prompt<const T extends Record<string, string>>(props: {
}} }}
> >
<text <text
fg={themeV2.text.action.primary( fg={themeV2.text.action(
option === store.selected ? "focused" : "default", option === store.selected ? "focused" : "default",
)} )}
> >
@@ -83,7 +83,7 @@ export function SubagentFooter() {
onMouseOver={() => setHover("parent")} onMouseOver={() => setHover("parent")}
onMouseOut={() => setHover(null)} onMouseOut={() => setHover(null)}
onMouseUp={() => keymap.dispatch("session.parent")} onMouseUp={() => keymap.dispatch("session.parent")}
backgroundColor={hover() === "parent" ? themeV2.background.action.secondary("focused") : themeV2.background()} backgroundColor={hover() === "parent" ? themeV2.background.action("focused") : themeV2.background()}
> >
<text fg={themeV2.text()}> <text fg={themeV2.text()}>
Parent <span style={{ fg: themeV2.text.subdued() }}>{shortcuts.get("session.parent")}</span> Parent <span style={{ fg: themeV2.text.subdued() }}>{shortcuts.get("session.parent")}</span>
@@ -93,7 +93,7 @@ export function SubagentFooter() {
onMouseOver={() => setHover("prev")} onMouseOver={() => setHover("prev")}
onMouseOut={() => setHover(null)} onMouseOut={() => setHover(null)}
onMouseUp={() => keymap.dispatch("session.child.previous")} onMouseUp={() => keymap.dispatch("session.child.previous")}
backgroundColor={hover() === "prev" ? themeV2.background.action.secondary("focused") : themeV2.background()} backgroundColor={hover() === "prev" ? themeV2.background.action("focused") : themeV2.background()}
> >
<text fg={themeV2.text()}> <text fg={themeV2.text()}>
Prev <span style={{ fg: themeV2.text.subdued() }}>{shortcuts.get("session.child.previous")}</span> Prev <span style={{ fg: themeV2.text.subdued() }}>{shortcuts.get("session.child.previous")}</span>
@@ -103,7 +103,7 @@ export function SubagentFooter() {
onMouseOver={() => setHover("next")} onMouseOver={() => setHover("next")}
onMouseOut={() => setHover(null)} onMouseOut={() => setHover(null)}
onMouseUp={() => keymap.dispatch("session.child.next")} onMouseUp={() => keymap.dispatch("session.child.next")}
backgroundColor={hover() === "next" ? themeV2.background.action.secondary("focused") : themeV2.background()} backgroundColor={hover() === "next" ? themeV2.background.action("focused") : themeV2.background()}
> >
<text fg={themeV2.text()}> <text fg={themeV2.text()}>
Next <span style={{ fg: themeV2.text.subdued() }}>{shortcuts.get("session.child.next")}</span> Next <span style={{ fg: themeV2.text.subdued() }}>{shortcuts.get("session.child.next")}</span>
+14 -4
View File
@@ -6,9 +6,8 @@ import type {
ResolvedActionState, ResolvedActionState,
ResolvedFormfieldState, ResolvedFormfieldState,
ResolvedThemeView, ResolvedThemeView,
HueStep,
} from "./index" } from "./index"
import { ActionState } from "./schema" import { ActionState, HueStep } from "./schema"
export type ActionStates = Partial<Record<ActionState, boolean>> export type ActionStates = Partial<Record<ActionState, boolean>>
@@ -79,6 +78,8 @@ export function createComponentTheme(current: Accessor<ResolvedThemeView>) {
return { return {
hue, hue,
increase: (color: RGBA, amount = 1) => shiftHue(current(), color, amount),
decrease: (color: RGBA, amount = 1) => shiftHue(current(), color, -amount),
text, text,
background, background,
border: () => current().border.default, border: () => current().border.default,
@@ -122,6 +123,17 @@ export function createComponentTheme(current: Accessor<ResolvedThemeView>) {
} }
} }
function shiftHue(theme: ResolvedThemeView, color: RGBA, amount: number) {
const colors = Object.values(theme.hue).flatMap((scale) =>
HueStep.literals.map((step, index) => ({ color: scale[step], index, scale })),
)
const match = colors.find((entry) => entry.color === color) ?? colors.find((entry) => entry.color.equals(color))
if (!match) return color
const offset = Number.isFinite(amount) ? Math.trunc(amount) : 0
const index = Math.max(0, Math.min(HueStep.literals.length - 1, match.index + offset))
return match.scale[HueStep.literals[index]]
}
function actions(get: (variant: ActionVariant, state: ResolvedActionState) => RGBA) { function actions(get: (variant: ActionVariant, state: ResolvedActionState) => RGBA) {
const action = (variant: ActionVariant) => (states: ActionState | "default" | ActionStates = "default") => { const action = (variant: ActionVariant) => (states: ActionState | "default" | ActionStates = "default") => {
if (typeof states === "string") return get(variant, states) if (typeof states === "string") return get(variant, states)
@@ -129,8 +141,6 @@ function actions(get: (variant: ActionVariant, state: ResolvedActionState) => RG
} }
const primary = action("primary") const primary = action("primary")
return Object.assign(primary, { return Object.assign(primary, {
primary,
secondary: action("secondary"),
destructive: action("destructive"), destructive: action("destructive"),
}) })
} }
-16
View File
@@ -101,7 +101,6 @@ export const DEFAULT_THEME = {
subdued: "$hue.neutral.600", subdued: "$hue.neutral.600",
action: { action: {
primary: { default: "$hue.neutral.100", $disabled: "$hue.neutral.500" }, primary: { default: "$hue.neutral.100", $disabled: "$hue.neutral.500" },
secondary: { default: "$hue.neutral.900", $disabled: "$hue.neutral.500" },
destructive: { default: "$hue.red.100", $disabled: "$hue.neutral.500" }, destructive: { default: "$hue.red.100", $disabled: "$hue.neutral.500" },
}, },
formfield: { formfield: {
@@ -132,13 +131,6 @@ export const DEFAULT_THEME = {
$selected: "$hue.interactive.700", $selected: "$hue.interactive.700",
$disabled: "$hue.neutral.300", $disabled: "$hue.neutral.300",
}, },
secondary: {
default: "$hue.neutral.200",
$focused: "$hue.neutral.300",
$pressed: "$hue.neutral.400",
$selected: "$hue.neutral.300",
$disabled: "$hue.neutral.200",
},
destructive: { destructive: {
default: "$hue.red.600", default: "$hue.red.600",
$focused: "$hue.red.700", $focused: "$hue.red.700",
@@ -318,7 +310,6 @@ export const DEFAULT_THEME = {
subdued: "$hue.neutral.400", subdued: "$hue.neutral.400",
action: { action: {
primary: { default: "$hue.neutral.100", $disabled: "$hue.neutral.500" }, primary: { default: "$hue.neutral.100", $disabled: "$hue.neutral.500" },
secondary: { default: "$hue.neutral.100", $disabled: "$hue.neutral.500" },
destructive: { default: "$hue.red.100", $disabled: "$hue.neutral.500" }, destructive: { default: "$hue.red.100", $disabled: "$hue.neutral.500" },
}, },
formfield: { formfield: {
@@ -349,13 +340,6 @@ export const DEFAULT_THEME = {
$selected: "$hue.interactive.600", $selected: "$hue.interactive.600",
$disabled: "$hue.neutral.800", $disabled: "$hue.neutral.800",
}, },
secondary: {
default: "$hue.neutral.800",
$focused: "$hue.neutral.700",
$pressed: "$hue.neutral.900",
$selected: "$hue.neutral.700",
$disabled: "$hue.neutral.900",
},
destructive: { destructive: {
default: "$hue.red.600", default: "$hue.red.600",
$focused: "$hue.red.700", $focused: "$hue.red.700",
+1 -2
View File
@@ -9,7 +9,7 @@ export type BaseHue = Schema.Schema.Type<typeof BaseHue>
export const HueAlias = Schema.Literals(["accent", "interactive", "neutral"]) export const HueAlias = Schema.Literals(["accent", "interactive", "neutral"])
export type HueAlias = Schema.Schema.Type<typeof HueAlias> export type HueAlias = Schema.Schema.Type<typeof HueAlias>
export const ActionVariant = Schema.Literals(["primary", "secondary", "destructive"]) export const ActionVariant = Schema.Literals(["primary", "destructive"])
export type ActionVariant = Schema.Schema.Type<typeof ActionVariant> export type ActionVariant = Schema.Schema.Type<typeof ActionVariant>
export const ActionState = Schema.Literals(["disabled", "pressed", "focused", "selected"]) export const ActionState = Schema.Literals(["disabled", "pressed", "focused", "selected"])
@@ -93,7 +93,6 @@ export type FormfieldColorDefinition = Schema.Schema.Type<typeof FormfieldColorD
const ActionColorDefinition = Schema.Struct({ const ActionColorDefinition = Schema.Struct({
primary: Schema.optional(StatefulColorDefinition), primary: Schema.optional(StatefulColorDefinition),
secondary: Schema.optional(StatefulColorDefinition),
destructive: Schema.optional(StatefulColorDefinition), destructive: Schema.optional(StatefulColorDefinition),
}) })
-9
View File
@@ -56,10 +56,6 @@ function migrateMode(theme: Theme, mode: "light" | "dark"): ThemeFile["light"] {
$focused: selected, $focused: selected,
$selected: primary, $selected: primary,
}, },
secondary: {
default: "$text.default",
$disabled: textMuted,
},
destructive: { default: destructive, $disabled: textMuted }, destructive: { default: destructive, $disabled: textMuted },
}, },
formfield: { formfield: {
@@ -84,11 +80,6 @@ function migrateMode(theme: Theme, mode: "light" | "dark"): ThemeFile["light"] {
}, },
action: { action: {
primary: { default: "transparent", $focused: primary, $selected: primary }, primary: { default: "transparent", $focused: primary, $selected: primary },
secondary: {
default: "$background.default",
$focused: color("backgroundElement"),
$pressed: color("backgroundElement"),
},
destructive: { default: color("error") }, destructive: { default: color("error") },
}, },
formfield: { formfield: {
+20 -11
View File
@@ -1,5 +1,6 @@
import { expect, test } from "bun:test" import { expect, test } from "bun:test"
import { createSignal } from "solid-js" import { createSignal } from "solid-js"
import { RGBA } from "@opentui/core"
import { createComponentTheme } from "../../../src/theme/v2/component" import { createComponentTheme } from "../../../src/theme/v2/component"
import { DEFAULT_THEME } from "../../../src/theme/v2/defaults" import { DEFAULT_THEME } from "../../../src/theme/v2/defaults"
import { resolveTheme } from "../../../src/theme/v2/resolve" import { resolveTheme } from "../../../src/theme/v2/resolve"
@@ -18,26 +19,34 @@ test("provides reactive property, variant, state, and context accessors", () =>
expect(theme.hue.accent(500)).toBe(resolved().hue.accent[500]) expect(theme.hue.accent(500)).toBe(resolved().hue.accent[500])
expect(theme.hue.interactive(500)).toBe(resolved().hue.interactive[500]) expect(theme.hue.interactive(500)).toBe(resolved().hue.interactive[500])
expect(theme.hue.gray(200)).toBe(resolved().hue.gray[200]) expect(theme.hue.gray(200)).toBe(resolved().hue.gray[200])
expect(theme.increase(theme.background.surface.offset(), 1)).toBe(resolved().hue.neutral[300])
expect(theme.decrease(theme.hue.red(300), 2)).toBe(resolved().hue.red[100])
expect(theme.increase(theme.hue.red(900), 3)).toBe(resolved().hue.red[900])
expect(theme.decrease(theme.hue.red(100), 3)).toBe(resolved().hue.red[100])
const equivalent = RGBA.fromInts(...resolved().hue.green[500].toInts())
expect(theme.increase(equivalent, 1)).toBe(resolved().hue.green[600])
const unmatched = RGBA.fromInts(1, 2, 3)
expect(theme.increase(unmatched, 1)).toBe(unmatched)
expect(theme.text.subdued()).toBe(resolved().text.subdued) expect(theme.text.subdued()).toBe(resolved().text.subdued)
expect(theme.text.action()).toBe(resolved().text.action.primary.default) expect(theme.text.action()).toBe(resolved().text.action.primary.default)
expect(theme.text.action.primary("pressed")).toBe(resolved().text.action.primary.pressed) expect(theme.text.action("pressed")).toBe(resolved().text.action.primary.pressed)
expect(theme.text.action.primary("selected")).toBe(resolved().text.action.primary.selected) expect(theme.text.action("selected")).toBe(resolved().text.action.primary.selected)
expect(theme.background.action.primary("selected")).toBe(resolved().background.action.primary.selected) expect(theme.background.action("selected")).toBe(resolved().background.action.primary.selected)
expect(theme.background.action.primary({ selected: true })).toBe(resolved().background.action.primary.selected) expect(theme.background.action({ selected: true })).toBe(resolved().background.action.primary.selected)
expect(theme.background.action.primary({ focused: true, selected: true })).toBe( expect(theme.background.action({ focused: true, selected: true })).toBe(
resolved().background.action.primary.focused, resolved().background.action.primary.focused,
) )
expect(theme.background.action.primary({ pressed: true, focused: true, selected: true })).toBe( expect(theme.background.action({ pressed: true, focused: true, selected: true })).toBe(
resolved().background.action.primary.pressed, resolved().background.action.primary.pressed,
) )
expect(theme.background.action.primary({ disabled: true, pressed: true, focused: true, selected: true })).toBe( expect(theme.background.action({ disabled: true, pressed: true, focused: true, selected: true })).toBe(
resolved().background.action.primary.disabled, resolved().background.action.primary.disabled,
) )
expect(theme.background.action.primary({ disabled: false, selected: false })).toBe( expect(theme.background.action({ disabled: false, selected: false })).toBe(
resolved().background.action.primary.default, resolved().background.action.primary.default,
) )
expect(theme.background.action.secondary("disabled")).toBe( expect(theme.background.action.destructive("disabled")).toBe(
resolved().background.action.secondary.disabled, resolved().background.action.destructive.disabled,
) )
expect(theme.background.surface.offset()).toBe(resolved().background.surface.offset) expect(theme.background.surface.offset()).toBe(resolved().background.surface.offset)
expect(theme.background.surface.overlay()).toBe(resolved().background.surface.overlay) expect(theme.background.surface.overlay()).toBe(resolved().background.surface.overlay)
@@ -46,7 +55,7 @@ test("provides reactive property, variant, state, and context accessors", () =>
setContext("@context:elevated") setContext("@context:elevated")
expect(theme.text()).toBe(resolved().contexts["@context:elevated"]!.text.default) expect(theme.text()).toBe(resolved().contexts["@context:elevated"]!.text.default)
expect(theme.background.action.primary("focused")).toBe( expect(theme.background.action("focused")).toBe(
resolved().contexts["@context:elevated"]!.background.action.primary.focused, resolved().contexts["@context:elevated"]!.background.action.primary.focused,
) )
expect(theme.background.formfield("selected")).toBe( expect(theme.background.formfield("selected")).toBe(
@@ -142,7 +142,6 @@ test("resolves matched action variants and states", () => {
expect(theme.text.action.primary.selected).toBeInstanceOf(RGBA) expect(theme.text.action.primary.selected).toBeInstanceOf(RGBA)
expect(theme.background.action.primary.pressed).toBeInstanceOf(RGBA) expect(theme.background.action.primary.pressed).toBeInstanceOf(RGBA)
expect(theme.background.action.primary.selected).toBeInstanceOf(RGBA) expect(theme.background.action.primary.selected).toBeInstanceOf(RGBA)
expect(theme.text.action.secondary.default).toBeInstanceOf(RGBA)
expect(theme.background.action.destructive.disabled).toBeInstanceOf(RGBA) expect(theme.background.action.destructive.disabled).toBeInstanceOf(RGBA)
}) })
@@ -175,7 +174,6 @@ test("context overrides rewire semantic references and apply state precedence",
default: "#111111", default: "#111111",
action: { action: {
primary: { default: "$text.default", $pressed: "#222222" }, primary: { default: "$text.default", $pressed: "#222222" },
secondary: { default: "$text.default" },
}, },
}, },
"@context:elevated": { "@context:elevated": {
@@ -189,7 +187,6 @@ test("context overrides rewire semantic references and apply state precedence",
const overlay = theme.contexts["@context:elevated"]! const overlay = theme.contexts["@context:elevated"]!
expect(overlay.text.default.toInts()).toEqual([51, 51, 51, 255]) expect(overlay.text.default.toInts()).toEqual([51, 51, 51, 255])
expect(overlay.text.action.secondary.default.toInts()).toEqual([51, 51, 51, 255])
expect(overlay.text.action.primary.pressed.toInts()).toEqual([68, 68, 68, 255]) expect(overlay.text.action.primary.pressed.toInts()).toEqual([68, 68, 68, 255])
expect(overlay.text.action.primary.focused.toInts()).toEqual([85, 85, 85, 255]) expect(overlay.text.action.primary.focused.toInts()).toEqual([85, 85, 85, 255])
}) })
-2
View File
@@ -6,7 +6,6 @@ const text = {
subdued: "$hue.neutral.600", subdued: "$hue.neutral.600",
action: { action: {
primary: { default: "$hue.neutral.100", $pressed: "$hue.neutral.200" }, primary: { default: "$hue.neutral.100", $pressed: "$hue.neutral.200" },
secondary: { default: "$hue.neutral.900" },
destructive: { default: "$hue.red.100", $disabled: "$hue.neutral.500" }, destructive: { default: "$hue.red.100", $disabled: "$hue.neutral.500" },
}, },
formfield: { default: "$hue.neutral.600", $selected: "$hue.neutral.100" }, formfield: { default: "$hue.neutral.600", $selected: "$hue.neutral.100" },
@@ -24,7 +23,6 @@ const background = {
$pressed: "$hue.interactive.800", $pressed: "$hue.interactive.800",
$selected: "$hue.interactive.700", $selected: "$hue.interactive.700",
}, },
secondary: { default: "$hue.neutral.200" },
destructive: { default: "$hue.red.600" }, destructive: { default: "$hue.red.600" },
}, },
formfield: { default: "$hue.neutral.100", $selected: "$hue.interactive.600" }, formfield: { default: "$hue.neutral.100", $selected: "$hue.interactive.600" },
@@ -30,7 +30,6 @@ test("migrates resolved V1 modes into literal V2 tokens", () => {
expect(migrated.light.scrollbar?.default).toBe(hex(legacy.borderActive)) expect(migrated.light.scrollbar?.default).toBe(hex(legacy.borderActive))
expect(migrated.light.diff?.lineNumber?.background?.removed).toBe(hex(legacy.diffRemovedLineNumberBg)) expect(migrated.light.diff?.lineNumber?.background?.removed).toBe(hex(legacy.diffRemovedLineNumberBg))
expect(migrated.light.markdown?.emphasis).toBe(hex(legacy.markdownEmph)) expect(migrated.light.markdown?.emphasis).toBe(hex(legacy.markdownEmph))
expect(resolved.background.action.secondary.focused.toInts()).toEqual(legacy.backgroundElement.toInts())
expect(resolved.background.surface.offset.toInts()).toEqual(legacy.backgroundPanel.toInts()) expect(resolved.background.surface.offset.toInts()).toEqual(legacy.backgroundPanel.toInts())
expect(resolved.background.surface.overlay.toInts()).toEqual(legacy.backgroundMenu.toInts()) expect(resolved.background.surface.overlay.toInts()).toEqual(legacy.backgroundMenu.toInts())
expect(resolved.background.formfield.selected.toInts()).toEqual(legacy.background.toInts()) expect(resolved.background.formfield.selected.toInts()).toEqual(legacy.background.toInts())
@@ -46,9 +45,6 @@ test("migrates resolved V1 modes into literal V2 tokens", () => {
expect(resolved.contexts["@context:elevated"]?.background.default.toInts()).toEqual( expect(resolved.contexts["@context:elevated"]?.background.default.toInts()).toEqual(
legacy.backgroundPanel.toInts(), legacy.backgroundPanel.toInts(),
) )
expect(resolved.contexts["@context:elevated"]?.background.action.secondary.default.toInts()).toEqual(
legacy.backgroundPanel.toInts(),
)
expect(resolved.contexts["@context:elevated"]?.background.action.primary.default.toInts()).toEqual([0, 0, 0, 0]) expect(resolved.contexts["@context:elevated"]?.background.action.primary.default.toInts()).toEqual([0, 0, 0, 0])
expect(resolved.contexts["@context:elevated"]?.text.action.primary.default.toInts()).toEqual( expect(resolved.contexts["@context:elevated"]?.text.action.primary.default.toInts()).toEqual(
legacy.text.toInts(), legacy.text.toInts(),