refactor(tui): expose theme colors as properties (#37995)

This commit is contained in:
James Long 2026-07-20 16:17:09 -04:00 committed by GitHub
commit c0ba62b972
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 740 additions and 749 deletions

View file

@ -30,27 +30,27 @@ export function DialogAlert(props: DialogAlertProps) {
return (
<box paddingLeft={2} paddingRight={2} gap={1}>
<box flexDirection="row" justifyContent="space-between">
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
{props.title}
</text>
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
esc
</text>
</box>
<box paddingBottom={1}>
<text fg={themeV2.text.subdued()}>{props.message}</text>
<text fg={themeV2.text.subdued}>{props.message}</text>
</box>
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
<box
paddingLeft={3}
paddingRight={3}
backgroundColor={themeV2.background.action("focused")}
backgroundColor={themeV2.background.action.primary.focused}
onMouseUp={() => {
props.onConfirm?.()
dialog.clear()
}}
>
<text fg={themeV2.text.action("focused")}>ok</text>
<text fg={themeV2.text.action.primary.focused}>ok</text>
</box>
</box>
</box>

View file

@ -57,15 +57,15 @@ export function DialogConfirm(props: DialogConfirmProps) {
return (
<box paddingLeft={2} paddingRight={2} gap={1}>
<box flexDirection="row" justifyContent="space-between">
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
{props.title}
</text>
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
esc
</text>
</box>
<box paddingBottom={1}>
<text fg={themeV2.text.subdued()}>{props.message}</text>
<text fg={themeV2.text.subdued}>{props.message}</text>
</box>
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
<For each={["cancel", "confirm"] as const}>
@ -73,14 +73,14 @@ export function DialogConfirm(props: DialogConfirmProps) {
<box
paddingLeft={1}
paddingRight={1}
backgroundColor={key === store.active ? themeV2.background.action("focused") : undefined}
backgroundColor={key === store.active ? themeV2.background.action.primary.focused : undefined}
onMouseUp={() => {
if (key === "confirm") props.onConfirm?.()
if (key === "cancel") props.onCancel?.()
dialog.clear()
}}
>
<text fg={key === store.active ? themeV2.text.action("focused") : themeV2.text.subdued()}>
<text fg={key === store.active ? themeV2.text.action.primary.focused : themeV2.text.subdued}>
{Locale.titlecase(key === "cancel" ? (props.label ?? key) : key)}
</text>
</box>

View file

@ -76,32 +76,38 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
return (
<box paddingLeft={2} paddingRight={2} gap={1}>
<box flexDirection="row" justifyContent="space-between">
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
Export session
</text>
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
esc
</text>
</box>
<box flexDirection="row" gap={1}>
<text fg={themeV2.text()}>Export as:</text>
<text fg={themeV2.text.default}>Export as:</text>
<box flexDirection="row" gap={1}>
<For each={["markdown", "json"] as const}>
{(format) => (
<box
paddingLeft={1}
paddingRight={1}
backgroundColor={themeV2.background.formfield({
focused: store.active === format,
selected: store.format === format,
})}
backgroundColor={
store.active === format
? themeV2.background.formfield.focused
: store.format === format
? themeV2.background.formfield.selected
: themeV2.background.formfield.default
}
onMouseUp={() => selectFormat(format)}
>
<text
fg={themeV2.text.formfield({
focused: store.active === format,
selected: store.format === format,
})}
fg={
store.active === format
? themeV2.text.formfield.focused
: store.format === format
? themeV2.text.formfield.selected
: themeV2.text.formfield.default
}
>
{store.format === format ? "◉" : "○"} {format === "markdown" ? "Markdown" : "JSON"}
</text>
@ -114,19 +120,38 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
<box
flexDirection="row"
gap={1}
backgroundColor={themeV2.background.formfield({
focused: store.active === "thinking",
selected: store.thinking,
})}
backgroundColor={
store.active === "thinking"
? themeV2.background.formfield.focused
: store.thinking
? themeV2.background.formfield.selected
: themeV2.background.formfield.default
}
onMouseUp={() => {
setStore("active", "thinking")
setStore("thinking", !store.thinking)
}}
>
<text fg={themeV2.text.formfield({ focused: store.active === "thinking", selected: store.thinking })}>
<text
fg={
store.active === "thinking"
? themeV2.text.formfield.focused
: store.thinking
? themeV2.text.formfield.selected
: themeV2.text.formfield.default
}
>
{store.thinking ? "[x]" : "[ ]"}
</text>
<text fg={themeV2.text.formfield({ focused: store.active === "thinking", selected: store.thinking })}>
<text
fg={
store.active === "thinking"
? themeV2.text.formfield.focused
: store.thinking
? themeV2.text.formfield.selected
: themeV2.text.formfield.default
}
>
Include thinking
</text>
</box>
@ -135,19 +160,38 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
<box
flexDirection="row"
gap={1}
backgroundColor={themeV2.background.formfield({
focused: store.active === "debug",
selected: store.debug,
})}
backgroundColor={
store.active === "debug"
? themeV2.background.formfield.focused
: store.debug
? themeV2.background.formfield.selected
: themeV2.background.formfield.default
}
onMouseUp={() => {
setStore("active", "debug")
setStore("debug", !store.debug)
}}
>
<text fg={themeV2.text.formfield({ focused: store.active === "debug", selected: store.debug })}>
<text
fg={
store.active === "debug"
? themeV2.text.formfield.focused
: store.debug
? themeV2.text.formfield.selected
: themeV2.text.formfield.default
}
>
{store.debug ? "[x]" : "[ ]"}
</text>
<text fg={themeV2.text.formfield({ focused: store.active === "debug", selected: store.debug })}>
<text
fg={
store.active === "debug"
? themeV2.text.formfield.focused
: store.debug
? themeV2.text.formfield.selected
: themeV2.text.formfield.default
}
>
Events (debug)
</text>
</box>
@ -156,18 +200,26 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
<box
paddingLeft={4}
paddingRight={4}
backgroundColor={overlayTheme.background()}
backgroundColor={overlayTheme.background.default}
onMouseUp={() => confirm("copy")}
>
<text fg={overlayTheme.text()}>Copy</text>
<text fg={overlayTheme.text.default}>Copy</text>
</box>
<box
paddingLeft={4}
paddingRight={4}
backgroundColor={themeV2.background.action({ focused: store.active === "export" })}
backgroundColor={
store.active === "export"
? themeV2.background.action.primary.focused
: themeV2.background.action.primary.default
}
onMouseUp={() => confirm("export")}
>
<text fg={themeV2.text.action({ focused: store.active === "export" })}>Export</text>
<text
fg={store.active === "export" ? themeV2.text.action.primary.focused : themeV2.text.action.primary.default}
>
Export
</text>
</box>
</box>
</box>

View file

@ -27,19 +27,24 @@ export function DialogExportResult(props: { path: string; onClose?: () => void }
return (
<box paddingLeft={2} paddingRight={2} gap={1}>
<box flexDirection="row" justifyContent="space-between">
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
Session exported
</text>
<text fg={themeV2.text.subdued()} onMouseUp={close}>
<text fg={themeV2.text.subdued} onMouseUp={close}>
esc
</text>
</box>
<box>
<text fg={themeV2.text()}>{props.path}</text>
<text fg={themeV2.text.default}>{props.path}</text>
</box>
<box flexDirection="row" justifyContent="flex-end" gap={1} paddingBottom={1}>
<box paddingLeft={3} paddingRight={3} backgroundColor={themeV2.background.action("focused")} onMouseUp={close}>
<text fg={themeV2.text.action("focused")}>Close</text>
<box
paddingLeft={3}
paddingRight={3}
backgroundColor={themeV2.background.action.primary.focused}
onMouseUp={close}
>
<text fg={themeV2.text.action.primary.focused}>Close</text>
</box>
</box>
</box>

View file

@ -19,15 +19,15 @@ export function DialogHelp() {
return (
<box paddingLeft={2} paddingRight={2} gap={1}>
<box flexDirection="row" justifyContent="space-between">
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
Help
</text>
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
esc/enter
</text>
</box>
<box paddingBottom={1}>
<text fg={themeV2.text.subdued()}>
<text fg={themeV2.text.subdued}>
Press {shortcuts.get("command.palette.show")} to see all available actions and commands in any context.
</text>
</box>
@ -35,10 +35,10 @@ export function DialogHelp() {
<box
paddingLeft={3}
paddingRight={3}
backgroundColor={themeV2.background.action("focused")}
backgroundColor={themeV2.background.action.primary.focused}
onMouseUp={() => dialog.clear()}
>
<text fg={themeV2.text.action("focused")}>ok</text>
<text fg={themeV2.text.action.primary.focused}>ok</text>
</box>
</box>
</box>

View file

@ -74,10 +74,10 @@ export function DialogPrompt(props: DialogPromptProps) {
return (
<box paddingLeft={2} paddingRight={2} gap={1}>
<box flexDirection="row" justifyContent="space-between">
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
{props.title}
</text>
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
esc
</text>
</box>
@ -91,20 +91,20 @@ export function DialogPrompt(props: DialogPromptProps) {
}}
initialValue={props.value}
placeholder={props.placeholder ?? "Enter text"}
placeholderColor={themeV2.text.subdued()}
textColor={themeV2.text.formfield({ disabled: props.busy })}
focusedTextColor={themeV2.text.formfield({ disabled: props.busy })}
cursorColor={props.busy ? themeV2.background.formfield("disabled") : themeV2.text()}
placeholderColor={themeV2.text.subdued}
textColor={props.busy ? themeV2.text.formfield.disabled : themeV2.text.formfield.default}
focusedTextColor={props.busy ? themeV2.text.formfield.disabled : themeV2.text.formfield.default}
cursorColor={props.busy ? themeV2.background.formfield.disabled : themeV2.text.default}
/>
<Show when={props.busy}>
<Spinner color={themeV2.text.subdued()}>{props.busyText ?? "Working..."}</Spinner>
<Spinner color={themeV2.text.subdued}>{props.busyText ?? "Working..."}</Spinner>
</Show>
</box>
<box paddingBottom={1} gap={1} flexDirection="row">
<Show when={!props.busy} fallback={<text fg={themeV2.text.subdued()}>processing...</text>}>
<Show when={!props.busy} fallback={<text fg={themeV2.text.subdued}>processing...</text>}>
<Show when={shortcuts.get("dialog.prompt.submit")}>
<text fg={themeV2.text()}>
{shortcuts.get("dialog.prompt.submit")} <span style={{ fg: themeV2.text.subdued() }}>submit</span>
<text fg={themeV2.text.default}>
{shortcuts.get("dialog.prompt.submit")} <span style={{ fg: themeV2.text.subdued }}>submit</span>
</text>
</Show>
</Show>

View file

@ -59,7 +59,7 @@ export function Dialog(
}}
width={width()}
maxWidth={dimensions().width - 2}
backgroundColor={themeV2.background()}
backgroundColor={themeV2.background.default}
paddingTop={1}
>
{props.children}

View file

@ -31,17 +31,17 @@ export function Toast() {
paddingRight={2}
paddingTop={1}
paddingBottom={1}
backgroundColor={themeV2.background()}
borderColor={themeV2.text.feedback[current().variant]()}
backgroundColor={themeV2.background.default}
borderColor={themeV2.text.feedback[current().variant].default}
border={["left", "right"]}
customBorderChars={SplitBorder.customBorderChars}
>
<Show when={current().title}>
<text attributes={TextAttributes.BOLD} marginBottom={1} fg={themeV2.text()}>
<text attributes={TextAttributes.BOLD} marginBottom={1} fg={themeV2.text.default}>
{current().title}
</text>
</Show>
<text fg={themeV2.text()} wrapMode="word" width="100%">
<text fg={themeV2.text.default} wrapMode="word" width="100%">
{current().message}
</text>
</box>