feat(tui): highlight esc label on hover in dialog (#12383)

This commit is contained in:
Akshar Patel
2026-02-05 21:11:08 -05:00
committed by GitHub
parent 229cdafcc4
commit 683d234d80
8 changed files with 95 additions and 29 deletions

View File

@@ -124,6 +124,7 @@ function AutoMethod(props: AutoMethodProps) {
const dialog = useDialog() const dialog = useDialog()
const sync = useSync() const sync = useSync()
const toast = useToast() const toast = useToast()
const [hover, setHover] = createSignal(false)
useKeyboard((evt) => { useKeyboard((evt) => {
if (evt.name === "c" && !evt.ctrl && !evt.meta) { if (evt.name === "c" && !evt.ctrl && !evt.meta) {
@@ -154,9 +155,16 @@ function AutoMethod(props: AutoMethodProps) {
<text attributes={TextAttributes.BOLD} fg={theme.text}> <text attributes={TextAttributes.BOLD} fg={theme.text}>
{props.title} {props.title}
</text> </text>
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}> <box
esc paddingLeft={1}
</text> paddingRight={1}
backgroundColor={hover() ? theme.primary : undefined}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => dialog.clear()}
>
<text fg={hover() ? theme.selectedListItemText : theme.textMuted}>esc</text>
</box>
</box> </box>
<box gap={1}> <box gap={1}>
<Link href={props.authorization.url} fg={theme.primary} /> <Link href={props.authorization.url} fg={theme.primary} />

View File

@@ -2,7 +2,7 @@ import { TextAttributes } from "@opentui/core"
import { useTheme } from "../context/theme" import { useTheme } from "../context/theme"
import { useDialog } from "@tui/ui/dialog" import { useDialog } from "@tui/ui/dialog"
import { useSync } from "@tui/context/sync" import { useSync } from "@tui/context/sync"
import { For, Match, Switch, Show, createMemo } from "solid-js" import { For, Match, Switch, Show, createMemo, createSignal } from "solid-js"
import { Installation } from "@/installation" import { Installation } from "@/installation"
export type DialogStatusProps = {} export type DialogStatusProps = {}
@@ -11,6 +11,7 @@ export function DialogStatus() {
const sync = useSync() const sync = useSync()
const { theme } = useTheme() const { theme } = useTheme()
const dialog = useDialog() const dialog = useDialog()
const [hover, setHover] = createSignal(false)
const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled)) const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
@@ -45,9 +46,16 @@ export function DialogStatus() {
<text fg={theme.text} attributes={TextAttributes.BOLD}> <text fg={theme.text} attributes={TextAttributes.BOLD}>
Status Status
</text> </text>
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}> <box
esc paddingLeft={1}
</text> paddingRight={1}
backgroundColor={hover() ? theme.primary : undefined}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => dialog.clear()}
>
<text fg={hover() ? theme.selectedListItemText : theme.textMuted}>esc</text>
</box>
</box> </box>
<text fg={theme.textMuted}>OpenCode v{Installation.VERSION}</text> <text fg={theme.textMuted}>OpenCode v{Installation.VERSION}</text>
<Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>No MCP Servers</text>}> <Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>No MCP Servers</text>}>

View File

@@ -2,6 +2,7 @@ import { TextAttributes } from "@opentui/core"
import { useTheme } from "../context/theme" import { useTheme } from "../context/theme"
import { useDialog, type DialogContext } from "./dialog" import { useDialog, type DialogContext } from "./dialog"
import { useKeyboard } from "@opentui/solid" import { useKeyboard } from "@opentui/solid"
import { createSignal } from "solid-js"
export type DialogAlertProps = { export type DialogAlertProps = {
title: string title: string
@@ -12,6 +13,7 @@ export type DialogAlertProps = {
export function DialogAlert(props: DialogAlertProps) { export function DialogAlert(props: DialogAlertProps) {
const dialog = useDialog() const dialog = useDialog()
const { theme } = useTheme() const { theme } = useTheme()
const [hover, setHover] = createSignal(false)
useKeyboard((evt) => { useKeyboard((evt) => {
if (evt.name === "return") { if (evt.name === "return") {
@@ -25,9 +27,16 @@ export function DialogAlert(props: DialogAlertProps) {
<text attributes={TextAttributes.BOLD} fg={theme.text}> <text attributes={TextAttributes.BOLD} fg={theme.text}>
{props.title} {props.title}
</text> </text>
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}> <box
esc paddingLeft={1}
</text> paddingRight={1}
backgroundColor={hover() ? theme.primary : undefined}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => dialog.clear()}
>
<text fg={hover() ? theme.selectedListItemText : theme.textMuted}>esc</text>
</box>
</box> </box>
<box paddingBottom={1}> <box paddingBottom={1}>
<text fg={theme.textMuted}>{props.message}</text> <text fg={theme.textMuted}>{props.message}</text>

View File

@@ -2,7 +2,7 @@ import { TextAttributes } from "@opentui/core"
import { useTheme } from "../context/theme" import { useTheme } from "../context/theme"
import { useDialog, type DialogContext } from "./dialog" import { useDialog, type DialogContext } from "./dialog"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { For } from "solid-js" import { createSignal, For } from "solid-js"
import { useKeyboard } from "@opentui/solid" import { useKeyboard } from "@opentui/solid"
import { Locale } from "@/util/locale" import { Locale } from "@/util/locale"
@@ -16,6 +16,7 @@ export type DialogConfirmProps = {
export function DialogConfirm(props: DialogConfirmProps) { export function DialogConfirm(props: DialogConfirmProps) {
const dialog = useDialog() const dialog = useDialog()
const { theme } = useTheme() const { theme } = useTheme()
const [hover, setHover] = createSignal(false)
const [store, setStore] = createStore({ const [store, setStore] = createStore({
active: "confirm" as "confirm" | "cancel", active: "confirm" as "confirm" | "cancel",
}) })
@@ -37,9 +38,16 @@ export function DialogConfirm(props: DialogConfirmProps) {
<text attributes={TextAttributes.BOLD} fg={theme.text}> <text attributes={TextAttributes.BOLD} fg={theme.text}>
{props.title} {props.title}
</text> </text>
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}> <box
esc paddingLeft={1}
</text> paddingRight={1}
backgroundColor={hover() ? theme.primary : undefined}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => dialog.clear()}
>
<text fg={hover() ? theme.selectedListItemText : theme.textMuted}>esc</text>
</box>
</box> </box>
<box paddingBottom={1}> <box paddingBottom={1}>
<text fg={theme.textMuted}>{props.message}</text> <text fg={theme.textMuted}>{props.message}</text>

View File

@@ -2,7 +2,7 @@ import { TextareaRenderable, TextAttributes } from "@opentui/core"
import { useTheme } from "../context/theme" import { useTheme } from "../context/theme"
import { useDialog, type DialogContext } from "./dialog" import { useDialog, type DialogContext } from "./dialog"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { onMount, Show, type JSX } from "solid-js" import { createSignal, onMount, Show, type JSX } from "solid-js"
import { useKeyboard } from "@opentui/solid" import { useKeyboard } from "@opentui/solid"
export type DialogExportOptionsProps = { export type DialogExportOptionsProps = {
@@ -25,6 +25,7 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
const dialog = useDialog() const dialog = useDialog()
const { theme } = useTheme() const { theme } = useTheme()
let textarea: TextareaRenderable let textarea: TextareaRenderable
const [hover, setHover] = createSignal(false)
const [store, setStore] = createStore({ const [store, setStore] = createStore({
thinking: props.defaultThinking, thinking: props.defaultThinking,
toolDetails: props.defaultToolDetails, toolDetails: props.defaultToolDetails,
@@ -80,9 +81,16 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
<text attributes={TextAttributes.BOLD} fg={theme.text}> <text attributes={TextAttributes.BOLD} fg={theme.text}>
Export Options Export Options
</text> </text>
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}> <box
esc paddingLeft={1}
</text> paddingRight={1}
backgroundColor={hover() ? theme.primary : undefined}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => dialog.clear()}
>
<text fg={hover() ? theme.selectedListItemText : theme.textMuted}>esc</text>
</box>
</box> </box>
<box gap={1}> <box gap={1}>
<box> <box>

View File

@@ -3,11 +3,13 @@ import { useTheme } from "@tui/context/theme"
import { useDialog } from "./dialog" import { useDialog } from "./dialog"
import { useKeyboard } from "@opentui/solid" import { useKeyboard } from "@opentui/solid"
import { useKeybind } from "@tui/context/keybind" import { useKeybind } from "@tui/context/keybind"
import { createSignal } from "solid-js"
export function DialogHelp() { export function DialogHelp() {
const dialog = useDialog() const dialog = useDialog()
const { theme } = useTheme() const { theme } = useTheme()
const keybind = useKeybind() const keybind = useKeybind()
const [hover, setHover] = createSignal(false)
useKeyboard((evt) => { useKeyboard((evt) => {
if (evt.name === "return" || evt.name === "escape") { if (evt.name === "return" || evt.name === "escape") {
@@ -21,9 +23,16 @@ export function DialogHelp() {
<text attributes={TextAttributes.BOLD} fg={theme.text}> <text attributes={TextAttributes.BOLD} fg={theme.text}>
Help Help
</text> </text>
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}> <box
esc/enter paddingLeft={1}
</text> paddingRight={1}
backgroundColor={hover() ? theme.primary : undefined}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => dialog.clear()}
>
<text fg={hover() ? theme.selectedListItemText : theme.textMuted}>esc/enter</text>
</box>
</box> </box>
<box paddingBottom={1}> <box paddingBottom={1}>
<text fg={theme.textMuted}> <text fg={theme.textMuted}>

View File

@@ -1,7 +1,7 @@
import { TextareaRenderable, TextAttributes } from "@opentui/core" import { TextareaRenderable, TextAttributes } from "@opentui/core"
import { useTheme } from "../context/theme" import { useTheme } from "../context/theme"
import { useDialog, type DialogContext } from "./dialog" import { useDialog, type DialogContext } from "./dialog"
import { onMount, type JSX } from "solid-js" import { createSignal, onMount, type JSX } from "solid-js"
import { useKeyboard } from "@opentui/solid" import { useKeyboard } from "@opentui/solid"
export type DialogPromptProps = { export type DialogPromptProps = {
@@ -17,6 +17,7 @@ export function DialogPrompt(props: DialogPromptProps) {
const dialog = useDialog() const dialog = useDialog()
const { theme } = useTheme() const { theme } = useTheme()
let textarea: TextareaRenderable let textarea: TextareaRenderable
const [hover, setHover] = createSignal(false)
useKeyboard((evt) => { useKeyboard((evt) => {
if (evt.name === "return") { if (evt.name === "return") {
@@ -39,9 +40,16 @@ export function DialogPrompt(props: DialogPromptProps) {
<text attributes={TextAttributes.BOLD} fg={theme.text}> <text attributes={TextAttributes.BOLD} fg={theme.text}>
{props.title} {props.title}
</text> </text>
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}> <box
esc paddingLeft={1}
</text> paddingRight={1}
backgroundColor={hover() ? theme.primary : undefined}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => dialog.clear()}
>
<text fg={hover() ? theme.selectedListItemText : theme.textMuted}>esc</text>
</box>
</box> </box>
<box gap={1}> <box gap={1}>
{props.description} {props.description}

View File

@@ -1,7 +1,7 @@
import { InputRenderable, RGBA, ScrollBoxRenderable, TextAttributes } from "@opentui/core" import { InputRenderable, RGBA, ScrollBoxRenderable, TextAttributes } from "@opentui/core"
import { useTheme, selectedForeground } from "@tui/context/theme" import { useTheme, selectedForeground } from "@tui/context/theme"
import { entries, filter, flatMap, groupBy, pipe, take } from "remeda" import { entries, filter, flatMap, groupBy, pipe, take } from "remeda"
import { batch, createEffect, createMemo, For, Show, type JSX, on } from "solid-js" import { batch, createEffect, createMemo, createSignal, For, Show, type JSX, on } from "solid-js"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { useKeyboard, useTerminalDimensions } from "@opentui/solid" import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
import * as fuzzysort from "fuzzysort" import * as fuzzysort from "fuzzysort"
@@ -49,6 +49,7 @@ export type DialogSelectRef<T> = {
export function DialogSelect<T>(props: DialogSelectProps<T>) { export function DialogSelect<T>(props: DialogSelectProps<T>) {
const dialog = useDialog() const dialog = useDialog()
const { theme } = useTheme() const { theme } = useTheme()
const [hover, setHover] = createSignal(false)
const [store, setStore] = createStore({ const [store, setStore] = createStore({
selected: 0, selected: 0,
filter: "", filter: "",
@@ -226,9 +227,16 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
<text fg={theme.text} attributes={TextAttributes.BOLD}> <text fg={theme.text} attributes={TextAttributes.BOLD}>
{props.title} {props.title}
</text> </text>
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}> <box
esc paddingLeft={1}
</text> paddingRight={1}
backgroundColor={hover() ? theme.primary : undefined}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => dialog.clear()}
>
<text fg={hover() ? theme.selectedListItemText : theme.textMuted}>esc</text>
</box>
</box> </box>
<box paddingTop={1}> <box paddingTop={1}>
<input <input