mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-22 11:03:31 +00:00
refactor(tui): migrate workflow views to V2 themes (#37526)
This commit is contained in:
parent
f2a4011371
commit
c7a7900ff2
15 changed files with 243 additions and 138 deletions
|
|
@ -420,7 +420,7 @@ function App(props: { pair?: DialogPairCredentials; started: number }) {
|
|||
const client = useClient()
|
||||
const toast = useToast()
|
||||
const themeState = useTheme()
|
||||
const { theme, mode, setMode, locked, lock, unlock } = themeState
|
||||
const { themeV2, mode, setMode, locked, lock, unlock } = themeState
|
||||
const data = useData()
|
||||
const location = useLocation()
|
||||
const exit = useExit()
|
||||
|
|
@ -1087,7 +1087,7 @@ function App(props: { pair?: DialogPairCredentials; started: number }) {
|
|||
width={dimensions().width}
|
||||
height={dimensions().height}
|
||||
flexDirection="column"
|
||||
backgroundColor={theme.background}
|
||||
backgroundColor={themeV2.background()}
|
||||
onMouseDown={(evt) => {
|
||||
if (!Flag.OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT) return
|
||||
if (evt.button !== MouseButton.RIGHT) return
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { TextAttributes } from "@opentui/core"
|
||||
import { For } from "solid-js"
|
||||
import { createSignal, For } from "solid-js"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { DevTools } from "../devtools"
|
||||
|
||||
export function DevToolsSidebar() {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const { themeV2, mode, setMode } = useTheme().contextual("elevated")
|
||||
const [modeHovered, setModeHovered] = createSignal(false)
|
||||
|
||||
return (
|
||||
<box
|
||||
|
|
@ -16,6 +17,27 @@ export function DevToolsSidebar() {
|
|||
paddingRight={2}
|
||||
backgroundColor={themeV2.background()}
|
||||
>
|
||||
<box flexShrink={0} marginBottom={1}>
|
||||
<box marginBottom={1}>
|
||||
<text fg={themeV2.text.action()} attributes={TextAttributes.BOLD}>
|
||||
Theme
|
||||
</text>
|
||||
</box>
|
||||
<box flexDirection="row">
|
||||
<text fg={themeV2.text.subdued()}>Mode</text>
|
||||
<box flexGrow={1} />
|
||||
<box
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
backgroundColor={modeHovered() ? themeV2.background.action("hovered") : undefined}
|
||||
onMouseOver={() => setModeHovered(true)}
|
||||
onMouseOut={() => setModeHovered(false)}
|
||||
onMouseUp={() => setMode(mode() === "dark" ? "light" : "dark")}
|
||||
>
|
||||
<text fg={themeV2.text()}>{mode()}</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
<For each={DevTools.data()}>
|
||||
{(group) => (
|
||||
<box flexShrink={0} marginBottom={1}>
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export function connectionSummary(integration: IntegrationInfo) {
|
|||
export function DialogIntegration(props: { onConnected?: OnIntegrationConnected } = {}) {
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const options = createMemo(() =>
|
||||
integrationOptions(data.location.integration.list() ?? []).map((integration) => {
|
||||
const methods = connectMethods(integration)
|
||||
|
|
@ -74,7 +74,7 @@ export function DialogIntegration(props: { onConnected?: OnIntegrationConnected
|
|||
footer: connectionSummary(integration) || undefined,
|
||||
category: integration.id in INTEGRATION_PRIORITY ? "Popular" : "Services",
|
||||
disabled: methods.length === 0,
|
||||
gutter: connected ? () => <text fg={theme.success}>✓</text> : undefined,
|
||||
gutter: connected ? () => <text fg={themeV2.text.feedback.success()}>✓</text> : undefined,
|
||||
onSelect: () =>
|
||||
credentialConnections(integration).length
|
||||
? manageConnections(integration, methods, dialog, props.onConnected)
|
||||
|
|
@ -89,12 +89,12 @@ export function DialogIntegration(props: { onConnected?: OnIntegrationConnected
|
|||
options={options()}
|
||||
emptyView={
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={theme.textMuted}>No integrations available</text>
|
||||
<text fg={themeV2.text.subdued()}>No integrations available</text>
|
||||
</box>
|
||||
}
|
||||
noMatchView={
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={theme.textMuted}>No integrations found</text>
|
||||
<text fg={themeV2.text.subdued()}>No integrations found</text>
|
||||
</box>
|
||||
}
|
||||
/>
|
||||
|
|
@ -289,23 +289,30 @@ function CommandPending(props: {
|
|||
|
||||
function CommandView(props: { title: string; output: string; message: string }) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const { themeV2: overlayTheme } = useTheme().contextual("overlay")
|
||||
onMount(() => dialog.setSize("large"))
|
||||
return (
|
||||
<box gap={1} paddingBottom={1}>
|
||||
<box flexDirection="row" justifyContent="space-between" paddingLeft={2} paddingRight={2}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
|
||||
esc close
|
||||
</text>
|
||||
</box>
|
||||
<box backgroundColor={theme.backgroundElement} paddingLeft={2} paddingRight={2} paddingTop={1} paddingBottom={1}>
|
||||
<text fg={theme.text}>{props.output.trim()}</text>
|
||||
<box
|
||||
backgroundColor={overlayTheme.background()}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
>
|
||||
<text fg={overlayTheme.text()}>{props.output.trim()}</text>
|
||||
</box>
|
||||
<box paddingLeft={2} paddingRight={2}>
|
||||
<text fg={theme.textMuted}>{props.message}</text>
|
||||
<text fg={themeV2.text.subdued()}>{props.message}</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
|
|
@ -320,7 +327,7 @@ function KeyMethod(props: {
|
|||
const dialog = useDialog()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const [error, setError] = createSignal<string>()
|
||||
|
||||
return (
|
||||
|
|
@ -338,7 +345,9 @@ function KeyMethod(props: {
|
|||
.then(() => connected(props.integration, data, dialog, toast, props.onConnected))
|
||||
.catch((cause) => setError(message(cause)))
|
||||
}}
|
||||
description={() => <Show when={error()}>{(value) => <text fg={theme.error}>{value()}</text>}</Show>}
|
||||
description={() => (
|
||||
<Show when={error()}>{(value) => <text fg={themeV2.text.feedback.error()}>{value()}</text>}</Show>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -493,7 +502,7 @@ function OAuthCode(props: {
|
|||
const dialog = useDialog()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const [error, setError] = createSignal<string>()
|
||||
let settled = false
|
||||
|
||||
|
|
@ -527,9 +536,9 @@ function OAuthCode(props: {
|
|||
}}
|
||||
description={() => (
|
||||
<box gap={1}>
|
||||
<text fg={theme.textMuted}>{props.attempt.instructions}</text>
|
||||
<Link href={props.attempt.url} fg={theme.primary} />
|
||||
<Show when={error()}>{(value) => <text fg={theme.error}>{value()}</text>}</Show>
|
||||
<text fg={themeV2.text.subdued()}>{props.attempt.instructions}</text>
|
||||
<Link href={props.attempt.url} fg={themeV2.markdown.link()} />
|
||||
<Show when={error()}>{(value) => <text fg={themeV2.text.feedback.error()}>{value()}</text>}</Show>
|
||||
</box>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -538,31 +547,31 @@ function OAuthCode(props: {
|
|||
|
||||
function OAuthView(props: { title: string; url?: string; instructions?: string; message: string; copy?: boolean }) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<Show when={props.url}>
|
||||
{(url) => (
|
||||
<box gap={1}>
|
||||
<Link href={url()} fg={theme.primary} />
|
||||
<Link href={url()} fg={themeV2.markdown.link()} />
|
||||
<Show when={props.instructions}>
|
||||
{(instructions) => <text fg={theme.textMuted}>{instructions()}</text>}
|
||||
{(instructions) => <text fg={themeV2.text.subdued()}>{instructions()}</text>}
|
||||
</Show>
|
||||
</box>
|
||||
)}
|
||||
</Show>
|
||||
<text fg={theme.textMuted}>{props.message}</text>
|
||||
<text fg={themeV2.text.subdued()}>{props.message}</text>
|
||||
<Show when={props.copy}>
|
||||
<text fg={theme.text}>
|
||||
c <span style={{ fg: theme.textMuted }}>copy</span>
|
||||
<text fg={themeV2.text()}>
|
||||
c <span style={{ fg: themeV2.text.subdued() }}>copy</span>
|
||||
</text>
|
||||
</Show>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Keymap } from "../context/keymap"
|
|||
import { pipe, sortBy } from "remeda"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { useTheme, type Theme } from "../context/theme"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { TextAttributes, type ScrollBoxRenderable } from "@opentui/core"
|
||||
import type { McpServer } from "@opencode-ai/client"
|
||||
import { useClipboard } from "../context/clipboard"
|
||||
|
|
@ -12,30 +12,59 @@ import { useToast } from "../ui/toast"
|
|||
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
|
||||
import { useConfig } from "../config"
|
||||
import { getScrollAcceleration } from "../util/scroll"
|
||||
import type { ComponentTheme } from "../theme/v2/component"
|
||||
|
||||
// Sort by how much attention a server needs: auth prompts first, then failures,
|
||||
// then healthy servers, and intentionally-off servers last.
|
||||
function statusMeta(status: McpServer["status"], theme: Theme) {
|
||||
function statusMeta(status: McpServer["status"], themeV2: ComponentTheme) {
|
||||
switch (status.status) {
|
||||
case "needs_auth":
|
||||
return { rank: 0, icon: "!", label: "Needs authentication", color: theme.warning, error: undefined, bold: false }
|
||||
return {
|
||||
rank: 0,
|
||||
icon: "!",
|
||||
label: "Needs authentication",
|
||||
color: themeV2.text.feedback.warning(),
|
||||
error: undefined,
|
||||
bold: false,
|
||||
}
|
||||
case "needs_client_registration":
|
||||
return { rank: 1, icon: "✗", label: "Needs registration", color: theme.error, error: status.error, bold: false }
|
||||
return {
|
||||
rank: 1,
|
||||
icon: "✗",
|
||||
label: "Needs registration",
|
||||
color: themeV2.text.feedback.error(),
|
||||
error: status.error,
|
||||
bold: false,
|
||||
}
|
||||
case "failed":
|
||||
return { rank: 2, icon: "✗", label: "Failed", color: theme.error, error: status.error, bold: false }
|
||||
return {
|
||||
rank: 2,
|
||||
icon: "✗",
|
||||
label: "Failed",
|
||||
color: themeV2.text.feedback.error(),
|
||||
error: status.error,
|
||||
bold: false,
|
||||
}
|
||||
case "connected":
|
||||
return { rank: 3, icon: "✓", label: "Connected", color: theme.success, error: undefined, bold: true }
|
||||
return {
|
||||
rank: 3,
|
||||
icon: "✓",
|
||||
label: "Connected",
|
||||
color: themeV2.text.feedback.success(),
|
||||
error: undefined,
|
||||
bold: true,
|
||||
}
|
||||
case "pending":
|
||||
return { rank: 4, icon: "◌", label: "Pending", color: theme.textMuted, error: undefined, bold: false }
|
||||
return { rank: 4, icon: "◌", label: "Pending", color: themeV2.text.subdued(), error: undefined, bold: false }
|
||||
default:
|
||||
return { rank: 5, icon: "○", label: "Disabled", color: theme.textMuted, error: undefined, bold: false }
|
||||
return { rank: 5, icon: "○", label: "Disabled", color: themeV2.text.subdued(), error: undefined, bold: false }
|
||||
}
|
||||
}
|
||||
|
||||
export function DialogMcp() {
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const [focused, setFocused] = createSignal<string>()
|
||||
const [detail, setDetail] = createSignal<McpServer>()
|
||||
|
||||
|
|
@ -47,7 +76,7 @@ export function DialogMcp() {
|
|||
pipe(
|
||||
data.location.mcp.server.list() ?? [],
|
||||
sortBy(
|
||||
(server) => statusMeta(server.status, theme).rank,
|
||||
(server) => statusMeta(server.status, themeV2).rank,
|
||||
(server) => server.name,
|
||||
),
|
||||
),
|
||||
|
|
@ -61,7 +90,7 @@ export function DialogMcp() {
|
|||
|
||||
const options = createMemo(() =>
|
||||
servers().map((server) => {
|
||||
const meta = statusMeta(server.status, theme)
|
||||
const meta = statusMeta(server.status, themeV2)
|
||||
return {
|
||||
value: server.name,
|
||||
title: server.name,
|
||||
|
|
@ -77,12 +106,12 @@ export function DialogMcp() {
|
|||
const focusedError = createMemo(() => {
|
||||
const name = focused()
|
||||
const server = servers().find((entry) => entry.name === name)
|
||||
return server ? statusMeta(server.status, theme).error : undefined
|
||||
return server ? statusMeta(server.status, themeV2).error : undefined
|
||||
})
|
||||
|
||||
const open = (name: string | undefined) => {
|
||||
const server = servers().find((entry) => entry.name === name)
|
||||
if (!server || !statusMeta(server.status, theme).error) return
|
||||
if (!server || !statusMeta(server.status, themeV2).error) return
|
||||
setDetail(server)
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +129,7 @@ export function DialogMcp() {
|
|||
onSelect={(option) => open(option.value as string)}
|
||||
footer={
|
||||
<Show when={focusedError()}>
|
||||
<text fg={theme.textMuted}>enter to view error</text>
|
||||
<text fg={themeV2.text.subdued()}>enter to view error</text>
|
||||
</Show>
|
||||
}
|
||||
/>
|
||||
|
|
@ -116,11 +145,12 @@ function DialogMcpError(props: { server: McpServer; onBack: () => void }) {
|
|||
const dialog = useDialog()
|
||||
const clipboard = useClipboard()
|
||||
const toast = useToast()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const { themeV2: overlayTheme } = useTheme().contextual("overlay")
|
||||
const dimensions = useTerminalDimensions()
|
||||
const config = useConfig().data
|
||||
const [copied, setCopied] = createSignal(false)
|
||||
const error = () => statusMeta(props.server.status, theme).error ?? "Unknown MCP connection error"
|
||||
const error = () => statusMeta(props.server.status, themeV2).error ?? "Unknown MCP connection error"
|
||||
const height = createMemo(() => Math.max(3, Math.floor(dimensions().height / 2) - 5))
|
||||
let scroll: ScrollBoxRenderable | undefined
|
||||
|
||||
|
|
@ -152,29 +182,35 @@ function DialogMcpError(props: { server: McpServer; onBack: () => void }) {
|
|||
return (
|
||||
<box paddingLeft={4} paddingRight={4} paddingBottom={1} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
MCP server: {props.server.name}
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={props.onBack}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={props.onBack}>
|
||||
esc back
|
||||
</text>
|
||||
</box>
|
||||
<text fg={theme.error}>✗ Failed</text>
|
||||
<box backgroundColor={theme.backgroundElement} paddingLeft={2} paddingRight={2} paddingTop={1} paddingBottom={1}>
|
||||
<text fg={themeV2.text.feedback.error()}>✗ Failed</text>
|
||||
<box
|
||||
backgroundColor={overlayTheme.background()}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
>
|
||||
<scrollbox
|
||||
ref={(element: ScrollBoxRenderable) => (scroll = element)}
|
||||
height={height()}
|
||||
scrollbarOptions={{ visible: false }}
|
||||
scrollAcceleration={getScrollAcceleration(config)}
|
||||
>
|
||||
<text fg={theme.text} wrapMode="word">
|
||||
<text fg={overlayTheme.text()} wrapMode="word">
|
||||
{error()}
|
||||
</text>
|
||||
</scrollbox>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.textMuted}>↑↓ scroll</text>
|
||||
<text fg={theme.textMuted} onMouseUp={copy}>
|
||||
<text fg={themeV2.text.subdued()}>↑↓ scroll</text>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={copy}>
|
||||
{copied() ? "✓ copied" : "c copy details"}
|
||||
</text>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
|||
const dialog = useDialog()
|
||||
const client = useClient()
|
||||
const dimensions = useTerminalDimensions()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const sessionData = useData()
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
|
|
@ -172,16 +172,18 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
|||
return {
|
||||
title,
|
||||
titleView: isRemoving ? (
|
||||
<span style={{ fg: theme.error }}>Deleting {item.location}</span>
|
||||
<span style={{ fg: themeV2.text.feedback.error() }}>Deleting {item.location}</span>
|
||||
) : deleting ? (
|
||||
<span style={{ fg: theme.text }}>Press {shortcuts.get("dialog.move_session.delete")} again to confirm</span>
|
||||
<span style={{ fg: themeV2.text.action.destructive() }}>
|
||||
Press {shortcuts.get("dialog.move_session.delete")} again to confirm
|
||||
</span>
|
||||
) : suffix ? (
|
||||
<>
|
||||
{visible.slice(0, split)}
|
||||
<span style={{ fg: theme.textMuted }}>{visible.slice(split)}</span>
|
||||
<span style={{ fg: themeV2.text.subdued() }}>{visible.slice(split)}</span>
|
||||
</>
|
||||
) : undefined,
|
||||
bg: deleting ? theme.error : undefined,
|
||||
bg: deleting ? themeV2.background.action.destructive() : undefined,
|
||||
value: {
|
||||
type: "directory",
|
||||
directory: item.location,
|
||||
|
|
@ -314,7 +316,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
|||
title="Move session"
|
||||
titleView={
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
||||
<text fg={themeV2.text()} attributes={TextAttributes.BOLD}>
|
||||
Move session
|
||||
</text>
|
||||
<Show when={working() || directories.loading || loadedProject.loading}>
|
||||
|
|
@ -327,25 +329,25 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
|||
emptyView={
|
||||
showError() ? (
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={theme.error} attributes={TextAttributes.BOLD}>
|
||||
<text fg={themeV2.text.feedback.error()} attributes={TextAttributes.BOLD}>
|
||||
Could not load project directories
|
||||
</text>
|
||||
<text fg={theme.textMuted}>{errorMessage(loadError())}</text>
|
||||
<text fg={theme.textMuted}>Close and reopen Move session to try again.</text>
|
||||
<text fg={themeV2.text.subdued()}>{errorMessage(loadError())}</text>
|
||||
<text fg={themeV2.text.subdued()}>Close and reopen Move session to try again.</text>
|
||||
</box>
|
||||
) : directories.loading || loadedProject.loading ? (
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={theme.textMuted}>Loading project directories…</text>
|
||||
<text fg={themeV2.text.subdued()}>Loading project directories…</text>
|
||||
</box>
|
||||
) : (
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={theme.textMuted}>No project directories available</text>
|
||||
<text fg={themeV2.text.subdued()}>No project directories available</text>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
noMatchView={
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={theme.textMuted}>No project directories found</text>
|
||||
<text fg={themeV2.text.subdued()}>No project directories found</text>
|
||||
</box>
|
||||
}
|
||||
locked={showError() || directories.loading || loadedProject.loading || Boolean(removing())}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { RGBA, TextAttributes } from "@opentui/core"
|
|||
import open from "open"
|
||||
import { createSignal } from "solid-js"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { selectedForeground, useTheme } from "../context/theme"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useDialog, type DialogContext } from "../ui/dialog"
|
||||
import { Link } from "../ui/link"
|
||||
import { BgPulse } from "./bg-pulse"
|
||||
|
|
@ -38,10 +38,9 @@ function panelOverlay(color: RGBA) {
|
|||
|
||||
export function DialogRetryAction(props: DialogRetryActionProps) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const fg = selectedForeground(theme)
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const showGoTreatment = () => props.link === GO_URL
|
||||
const textBg = () => (showGoTreatment() ? panelOverlay(theme.backgroundPanel) : undefined)
|
||||
const textBg = () => (showGoTreatment() ? panelOverlay(themeV2.background()) : undefined)
|
||||
const [selected, setSelected] = createSignal<"dismiss" | "action">("action")
|
||||
|
||||
Keymap.createLayer(() => ({
|
||||
|
|
@ -86,26 +85,26 @@ export function DialogRetryAction(props: DialogRetryActionProps) {
|
|||
) : null}
|
||||
<box zIndex={1} paddingLeft={PAD_X} paddingRight={PAD_X} paddingBottom={1} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text} bg={textBg()}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()} bg={textBg()}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={theme.textMuted} bg={textBg()} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} bg={textBg()} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box gap={0}>
|
||||
<text fg={theme.textMuted} bg={textBg()}>
|
||||
<text fg={themeV2.text.subdued()} bg={textBg()}>
|
||||
{props.message}
|
||||
</text>
|
||||
</box>
|
||||
{props.link ? (
|
||||
showGoTreatment() ? (
|
||||
<box alignItems="center" justifyContent="flex-end" height={7} paddingBottom={1}>
|
||||
<Link href={props.link} fg={theme.primary} bg={textBg()} wrapMode="none" />
|
||||
<Link href={props.link} fg={themeV2.markdown.link()} bg={textBg()} wrapMode="none" />
|
||||
</box>
|
||||
) : (
|
||||
<box width="100%" flexDirection="row" justifyContent="center" paddingBottom={1}>
|
||||
<Link href={props.link} fg={theme.primary} wrapMode="none" />
|
||||
<Link href={props.link} fg={themeV2.markdown.link()} wrapMode="none" />
|
||||
</box>
|
||||
)
|
||||
) : (
|
||||
|
|
@ -115,12 +114,14 @@ export function DialogRetryAction(props: DialogRetryActionProps) {
|
|||
<box
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
backgroundColor={selected() === "dismiss" ? theme.primary : RGBA.fromInts(0, 0, 0, 0)}
|
||||
backgroundColor={
|
||||
selected() === "dismiss" ? themeV2.background.action("focused") : RGBA.fromInts(0, 0, 0, 0)
|
||||
}
|
||||
onMouseOver={() => setSelected("dismiss")}
|
||||
onMouseUp={() => dismiss(props, dialog)}
|
||||
>
|
||||
<text
|
||||
fg={selected() === "dismiss" ? fg : theme.textMuted}
|
||||
fg={selected() === "dismiss" ? themeV2.text.action("focused") : themeV2.text.subdued()}
|
||||
bg={selected() === "dismiss" ? undefined : textBg()}
|
||||
attributes={selected() === "dismiss" ? TextAttributes.BOLD : undefined}
|
||||
>
|
||||
|
|
@ -130,12 +131,12 @@ export function DialogRetryAction(props: DialogRetryActionProps) {
|
|||
<box
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
backgroundColor={selected() === "action" ? theme.primary : RGBA.fromInts(0, 0, 0, 0)}
|
||||
backgroundColor={selected() === "action" ? themeV2.background.action("focused") : RGBA.fromInts(0, 0, 0, 0)}
|
||||
onMouseOver={() => setSelected("action")}
|
||||
onMouseUp={() => runAction(props, dialog)}
|
||||
>
|
||||
<text
|
||||
fg={selected() === "action" ? fg : theme.text}
|
||||
fg={selected() === "action" ? themeV2.text.action("focused") : themeV2.text()}
|
||||
bg={selected() === "action" ? undefined : textBg()}
|
||||
attributes={selected() === "action" ? TextAttributes.BOLD : undefined}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export function DialogSessionDeleteFailed(props: {
|
|||
onDone?: () => void
|
||||
}) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const [store, setStore] = createStore({
|
||||
active: "delete" as "delete" | "restore",
|
||||
})
|
||||
|
|
@ -64,17 +64,17 @@ export function DialogSessionDeleteFailed(props: {
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
Failed to Delete Session
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<text fg={theme.textMuted} wrapMode="word">
|
||||
<text fg={themeV2.text.subdued()} wrapMode="word">
|
||||
{`The session "${props.session}" could not be deleted because the workspace "${props.workspace}" is not available.`}
|
||||
</text>
|
||||
<text fg={theme.textMuted} wrapMode="word">
|
||||
<text fg={themeV2.text.subdued()} wrapMode="word">
|
||||
Choose how you want to recover this broken workspace session.
|
||||
</text>
|
||||
<box flexDirection="column" paddingBottom={1} gap={1}>
|
||||
|
|
@ -86,7 +86,7 @@ export function DialogSessionDeleteFailed(props: {
|
|||
paddingRight={1}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
backgroundColor={item.id === store.active ? theme.primary : undefined}
|
||||
backgroundColor={item.id === store.active ? themeV2.background.action("focused") : undefined}
|
||||
onMouseUp={() => {
|
||||
setStore("active", item.id)
|
||||
void confirm()
|
||||
|
|
@ -94,11 +94,14 @@ export function DialogSessionDeleteFailed(props: {
|
|||
>
|
||||
<text
|
||||
attributes={TextAttributes.BOLD}
|
||||
fg={item.id === store.active ? theme.selectedListItemText : theme.text}
|
||||
fg={item.id === store.active ? themeV2.text.action("focused") : themeV2.text()}
|
||||
>
|
||||
{item.title}
|
||||
</text>
|
||||
<text fg={item.id === store.active ? theme.selectedListItemText : theme.textMuted} wrapMode="word">
|
||||
<text
|
||||
fg={item.id === store.active ? themeV2.text.action("focused") : themeV2.text.subdued()}
|
||||
wrapMode="word"
|
||||
>
|
||||
{item.description}
|
||||
</text>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -31,14 +31,15 @@ export function DialogWorkspaceFileChanges(props: {
|
|||
message?: string
|
||||
}) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const { themeV2: overlayTheme } = useTheme().contextual("overlay")
|
||||
const config = useConfig().data
|
||||
const dimensions = useTerminalDimensions()
|
||||
const scrollAcceleration = createMemo(() => getScrollAcceleration(config))
|
||||
const [store, setStore] = createStore({ active: "yes" as WorkspaceFileChangesChoice })
|
||||
const height = createMemo(() => Math.min(props.files.length, 8))
|
||||
const fileNameWidth = createMemo(
|
||||
() => Math.max(2, Math.min(60, dimensions().width - 2) - 6 - Math.max(7, ...props.files.map(changeCountWidth))),
|
||||
const fileNameWidth = createMemo(() =>
|
||||
Math.max(2, Math.min(60, dimensions().width - 2) - 6 - Math.max(7, ...props.files.map(changeCountWidth))),
|
||||
)
|
||||
|
||||
function confirm() {
|
||||
|
|
@ -71,21 +72,21 @@ export function DialogWorkspaceFileChanges(props: {
|
|||
return (
|
||||
<box gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between" paddingLeft={2} paddingRight={2}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
{props.title ?? "File Changes Found"}
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box paddingLeft={2} paddingRight={2}>
|
||||
<text fg={theme.textMuted} wrapMode="word">
|
||||
<text fg={themeV2.text.subdued()} wrapMode="word">
|
||||
{props.message ?? "Do you want to move these changes with the session?"}
|
||||
</text>
|
||||
</box>
|
||||
<scrollbox
|
||||
height={height()}
|
||||
backgroundColor={theme.backgroundElement}
|
||||
backgroundColor={overlayTheme.background()}
|
||||
scrollbarOptions={{ visible: false }}
|
||||
scrollAcceleration={scrollAcceleration()}
|
||||
>
|
||||
|
|
@ -94,15 +95,19 @@ export function DialogWorkspaceFileChanges(props: {
|
|||
<box flexDirection="row" justifyContent="space-between" paddingLeft={2} paddingRight={2}>
|
||||
<box flexDirection="row" minWidth={0} flexShrink={1}>
|
||||
<box width={2} flexShrink={0}>
|
||||
<text fg={theme.textMuted}>{statusLabel(item.status)}</text>
|
||||
<text fg={overlayTheme.text.subdued()}>{statusLabel(item.status)}</text>
|
||||
</box>
|
||||
<FilePath value={item.file} maxWidth={fileNameWidth()} fg={theme.textMuted} />
|
||||
<FilePath value={item.file} maxWidth={fileNameWidth()} fg={overlayTheme.text.subdued()} />
|
||||
</box>
|
||||
<box flexDirection="row" gap={1} minWidth={7} flexShrink={0} justifyContent="flex-end">
|
||||
<text>
|
||||
{" "}
|
||||
{item.additions ? <span style={{ fg: theme.diffAdded }}>+{item.additions}</span> : null}
|
||||
{item.deletions ? <span style={{ fg: theme.diffRemoved }}> -{item.deletions}</span> : null}
|
||||
{item.additions ? (
|
||||
<span style={{ fg: overlayTheme.diff.text.added() }}>+{item.additions}</span>
|
||||
) : null}
|
||||
{item.deletions ? (
|
||||
<span style={{ fg: overlayTheme.diff.text.removed() }}> -{item.deletions}</span>
|
||||
) : null}
|
||||
</text>
|
||||
</box>
|
||||
</box>
|
||||
|
|
@ -115,14 +120,14 @@ export function DialogWorkspaceFileChanges(props: {
|
|||
<box
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
backgroundColor={item === store.active ? theme.primary : undefined}
|
||||
backgroundColor={item === store.active ? themeV2.background.action("focused") : undefined}
|
||||
onMouseUp={() => {
|
||||
setStore("active", item)
|
||||
props.onSelect(item)
|
||||
dialog.clear()
|
||||
}}
|
||||
>
|
||||
<text fg={item === store.active ? theme.selectedListItemText : theme.textMuted}>{item}</text>
|
||||
<text fg={item === store.active ? themeV2.text.action("focused") : themeV2.text.subdued()}>{item}</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ function Commands(props: { context: Plugin.Context }) {
|
|||
|
||||
function Scrap(props: { context: Plugin.Context }) {
|
||||
const dimensions = useTerminalDimensions()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme()
|
||||
const { themeV2: elevatedTheme } = useTheme().contextual("elevated")
|
||||
|
||||
Keymap.createLayer(() => ({
|
||||
commands: [
|
||||
|
|
@ -42,19 +43,19 @@ function Scrap(props: { context: Plugin.Context }) {
|
|||
}))
|
||||
|
||||
return (
|
||||
<box width={dimensions().width} height={dimensions().height} backgroundColor={theme.background}>
|
||||
<box width={dimensions().width} height={dimensions().height} backgroundColor={themeV2.background()}>
|
||||
<box flexGrow={1} />
|
||||
<box
|
||||
height={1}
|
||||
flexShrink={0}
|
||||
backgroundColor={theme.backgroundPanel}
|
||||
backgroundColor={elevatedTheme.background()}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
flexDirection="row"
|
||||
>
|
||||
<text fg={theme.textMuted}>~/code/anomalyco/opencode</text>
|
||||
<text fg={elevatedTheme.text.subdued()}>~/code/anomalyco/opencode</text>
|
||||
<box flexGrow={1} />
|
||||
<text fg={theme.textMuted}>esc home</text>
|
||||
<text fg={elevatedTheme.text.subdued()}>esc home</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ function neutralAnchors(theme: Theme, mode: "light" | "dark") {
|
|||
const light: { step: HueStep; color: RGBA }[] = [
|
||||
{ step: 100, color: theme.background },
|
||||
{ step: 200, color: theme.backgroundPanel },
|
||||
{ step: 300, color: theme.backgroundMenu },
|
||||
{ step: 300, color: theme.backgroundElement || theme.backgroundMenu },
|
||||
{ step: 700, color: theme.textMuted },
|
||||
{ step: 900, color: theme.text },
|
||||
]
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ type Active = ExportFormat | "debug" | "thinking" | "copy" | "export"
|
|||
|
||||
export function DialogExportOptions(props: DialogExportOptionsProps) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const { themeV2: overlayTheme } = useTheme().contextual("overlay")
|
||||
const [store, setStore] = createStore({
|
||||
format: "markdown" as ExportFormat,
|
||||
debug: false,
|
||||
|
|
@ -75,25 +76,33 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
Export session
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={theme.text}>Export as:</text>
|
||||
<text fg={themeV2.text()}>Export as:</text>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<For each={["markdown", "json"] as const}>
|
||||
{(format) => (
|
||||
<box
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
backgroundColor={store.format === format ? theme.backgroundElement : undefined}
|
||||
backgroundColor={themeV2.background.formfield({
|
||||
focused: store.active === format,
|
||||
selected: store.format === format,
|
||||
})}
|
||||
onMouseUp={() => selectFormat(format)}
|
||||
>
|
||||
<text fg={store.format === format ? theme.text : theme.textMuted}>
|
||||
<text
|
||||
fg={themeV2.text.formfield({
|
||||
focused: store.active === format,
|
||||
selected: store.format === format,
|
||||
})}
|
||||
>
|
||||
{store.format === format ? "◉" : "○"} {format === "markdown" ? "Markdown" : "JSON"}
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -105,43 +114,60 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
|
|||
<box
|
||||
flexDirection="row"
|
||||
gap={1}
|
||||
backgroundColor={store.active === "thinking" ? theme.backgroundElement : undefined}
|
||||
backgroundColor={themeV2.background.formfield({
|
||||
focused: store.active === "thinking",
|
||||
selected: store.thinking,
|
||||
})}
|
||||
onMouseUp={() => {
|
||||
setStore("active", "thinking")
|
||||
setStore("thinking", !store.thinking)
|
||||
}}
|
||||
>
|
||||
<text fg={store.active === "thinking" ? theme.primary : theme.textMuted}>
|
||||
<text fg={themeV2.text.formfield({ focused: store.active === "thinking", selected: store.thinking })}>
|
||||
{store.thinking ? "[x]" : "[ ]"}
|
||||
</text>
|
||||
<text fg={store.active === "thinking" ? theme.primary : theme.text}>Include thinking</text>
|
||||
<text fg={themeV2.text.formfield({ focused: store.active === "thinking", selected: store.thinking })}>
|
||||
Include thinking
|
||||
</text>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={store.format === "json"}>
|
||||
<box
|
||||
flexDirection="row"
|
||||
gap={1}
|
||||
backgroundColor={store.active === "debug" ? theme.backgroundElement : undefined}
|
||||
backgroundColor={themeV2.background.formfield({
|
||||
focused: store.active === "debug",
|
||||
selected: store.debug,
|
||||
})}
|
||||
onMouseUp={() => {
|
||||
setStore("active", "debug")
|
||||
setStore("debug", !store.debug)
|
||||
}}
|
||||
>
|
||||
<text fg={store.active === "debug" ? theme.primary : theme.textMuted}>{store.debug ? "[x]" : "[ ]"}</text>
|
||||
<text fg={store.active === "debug" ? theme.primary : theme.text}>Events (debug)</text>
|
||||
<text fg={themeV2.text.formfield({ focused: store.active === "debug", selected: store.debug })}>
|
||||
{store.debug ? "[x]" : "[ ]"}
|
||||
</text>
|
||||
<text fg={themeV2.text.formfield({ focused: store.active === "debug", selected: store.debug })}>
|
||||
Events (debug)
|
||||
</text>
|
||||
</box>
|
||||
</Show>
|
||||
<box flexDirection="row" justifyContent="flex-end" gap={1} paddingBottom={1}>
|
||||
<box
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
backgroundColor={theme.backgroundElement}
|
||||
backgroundColor={overlayTheme.background()}
|
||||
onMouseUp={() => confirm("copy")}
|
||||
>
|
||||
<text fg={theme.text}>Copy</text>
|
||||
<text fg={overlayTheme.text()}>Copy</text>
|
||||
</box>
|
||||
<box paddingLeft={4} paddingRight={4} backgroundColor={theme.primary} onMouseUp={() => confirm("export")}>
|
||||
<text fg={theme.selectedListItemText}>Export</text>
|
||||
<box
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
backgroundColor={themeV2.background.action({ focused: store.active === "export" })}
|
||||
onMouseUp={() => confirm("export")}
|
||||
>
|
||||
<text fg={themeV2.text.action({ focused: store.active === "export" })}>Export</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export type DialogPromptProps = {
|
|||
|
||||
export function DialogPrompt(props: DialogPromptProps) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
const [textareaTarget, setTextareaTarget] = createSignal<TextareaRenderable>()
|
||||
let textarea: TextareaRenderable
|
||||
|
|
@ -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={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={theme.textMuted} 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={theme.textMuted}
|
||||
textColor={props.busy ? theme.textMuted : theme.text}
|
||||
focusedTextColor={props.busy ? theme.textMuted : theme.text}
|
||||
cursorColor={props.busy ? theme.backgroundElement : theme.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()}
|
||||
/>
|
||||
<Show when={props.busy}>
|
||||
<Spinner color={theme.textMuted}>{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={theme.textMuted}>processing...</text>}>
|
||||
<Show when={!props.busy} fallback={<text fg={themeV2.text.subdued()}>processing...</text>}>
|
||||
<Show when={shortcuts.get("dialog.prompt.submit")}>
|
||||
<text fg={theme.text}>
|
||||
{shortcuts.get("dialog.prompt.submit")} <span style={{ fg: theme.textMuted }}>submit</span>
|
||||
<text fg={themeV2.text()}>
|
||||
{shortcuts.get("dialog.prompt.submit")} <span style={{ fg: themeV2.text.subdued() }}>submit</span>
|
||||
</text>
|
||||
</Show>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export function Dialog(
|
|||
}>,
|
||||
) {
|
||||
const dimensions = useTerminalDimensions()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const renderer = useRenderer()
|
||||
|
||||
let dismiss = false
|
||||
|
|
@ -59,7 +59,7 @@ export function Dialog(
|
|||
}}
|
||||
width={width()}
|
||||
maxWidth={dimensions().width - 2}
|
||||
backgroundColor={theme.backgroundPanel}
|
||||
backgroundColor={themeV2.background()}
|
||||
paddingTop={1}
|
||||
>
|
||||
{props.children}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ type ToastInput = Omit<ToastOptions, "duration"> & { duration?: number }
|
|||
|
||||
export function Toast() {
|
||||
const toast = useToast()
|
||||
const { theme, themeV2 } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("overlay")
|
||||
const dimensions = useTerminalDimensions()
|
||||
|
||||
return (
|
||||
|
|
@ -31,8 +31,8 @@ export function Toast() {
|
|||
paddingRight={2}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
backgroundColor={theme.backgroundPanel}
|
||||
borderColor={theme[current().variant]}
|
||||
backgroundColor={themeV2.background()}
|
||||
borderColor={themeV2.text.feedback[current().variant]()}
|
||||
border={["left", "right"]}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ test("infers chromatic hues, anchors light and dark colors, and aliases ambiguou
|
|||
expect(() => resolveThemeFile(migrated, "dark")).not.toThrow()
|
||||
})
|
||||
|
||||
test("builds gray from V1 surfaces and text without using borders", () => {
|
||||
test("builds gray from V1 surfaces and text without using menus or borders", () => {
|
||||
const source = structuredClone(DEFAULT_THEMES.opencode)
|
||||
source.theme.backgroundMenu = { light: "#ededed", dark: "#252525" }
|
||||
const light = resolveV1(source, "light")
|
||||
|
|
@ -97,12 +97,12 @@ test("builds gray from V1 surfaces and text without using borders", () => {
|
|||
|
||||
expect(lightGray[100]).toBe(hex(light.background))
|
||||
expect(lightGray[200]).toBe(hex(light.backgroundPanel))
|
||||
expect(lightGray[300]).toBe(hex(light.backgroundMenu))
|
||||
expect(lightGray[300]).toBe(hex(light.backgroundElement))
|
||||
expect(lightGray[700]).toBe(hex(light.textMuted))
|
||||
expect(lightGray[900]).toBe(hex(light.text))
|
||||
expect(darkGray[100]).toBe(hex(dark.text))
|
||||
expect(darkGray[300]).toBe(hex(dark.textMuted))
|
||||
expect(darkGray[700]).toBe(hex(dark.backgroundMenu))
|
||||
expect(darkGray[700]).toBe(hex(dark.backgroundElement))
|
||||
expect(darkGray[800]).toBe(hex(dark.backgroundPanel))
|
||||
expect(darkGray[900]).toBe(hex(dark.background))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue