Skip to content

Commit 5265b2a

Browse files
more
1 parent 22e8663 commit 5265b2a

File tree

1 file changed

+115
-6
lines changed
  • packages/opencode/src/cli/cmd/tui/routes/session

1 file changed

+115
-6
lines changed

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ type PluginUINode =
7979
borderColor?: string
8080
paddingX?: number
8181
paddingY?: number
82+
paddingTop?: number
83+
paddingBottom?: number
8284
justifyContent?: "flex-start" | "flex-end" | "center" | "space-between"
8385
alignSelf?: "flex-start" | "flex-end" | "center"
8486
minWidth?: number
@@ -99,8 +101,16 @@ type PluginUINode =
99101
label: string
100102
fg?: string
101103
bg?: string
104+
fgHover?: string
105+
borderColorHover?: string
102106
onConfirm?: string
103107
}
108+
| {
109+
type: "button-group"
110+
gap?: number
111+
defaultIndex?: number
112+
children: PluginUINode[]
113+
}
104114

105115
type PluginUIComponent = {
106116
name: string
@@ -185,7 +195,7 @@ function PluginUIRenderer(props: { node: PluginUINode; metadata: Record<string,
185195
{(() => {
186196
const node = props.node as { type: "text"; content: string; fg?: string; bold?: boolean }
187197
const content = interpolate(node.content, props.metadata)
188-
return <text content={node.bold ? `**${content}**` : content} fg={getColor(node.fg) ?? theme.text} />
198+
return <text fg={getColor(node.fg) ?? theme.text}>{node.bold ? <strong>{content}</strong> : content}</text>
189199
})()}
190200
</Match>
191201
<Match when={props.node.type === "box"}>
@@ -200,6 +210,8 @@ function PluginUIRenderer(props: { node: PluginUINode; metadata: Record<string,
200210
borderColor?: string
201211
paddingX?: number
202212
paddingY?: number
213+
paddingTop?: number
214+
paddingBottom?: number
203215
justifyContent?: "flex-start" | "flex-end" | "center" | "space-between"
204216
alignSelf?: "flex-start" | "flex-end" | "center"
205217
minWidth?: number
@@ -211,12 +223,12 @@ function PluginUIRenderer(props: { node: PluginUINode; metadata: Record<string,
211223
gap={node.gap ?? 0}
212224
backgroundColor={getColor(node.bg)}
213225
border={node.border}
214-
borderStyle={node.borderStyle}
215-
borderColor={getColor(node.borderColor) ?? theme.border}
226+
borderStyle={node.border ? node.borderStyle : undefined}
227+
borderColor={node.border ? (getColor(node.borderColor) ?? theme.border) : undefined}
216228
paddingLeft={node.paddingX}
217229
paddingRight={node.paddingX}
218-
paddingTop={node.paddingY}
219-
paddingBottom={node.paddingY}
230+
paddingTop={node.paddingTop ?? node.paddingY}
231+
paddingBottom={node.paddingBottom ?? node.paddingY}
220232
justifyContent={node.justifyContent}
221233
alignSelf={node.alignSelf}
222234
minWidth={node.minWidth}
@@ -289,20 +301,117 @@ function PluginUIRenderer(props: { node: PluginUINode; metadata: Record<string,
289301
label: string
290302
fg?: string
291303
bg?: string
304+
fgHover?: string
305+
borderColorHover?: string
292306
onConfirm?: string
293307
}
308+
const [hovered, setHovered] = createSignal(false)
294309
return (
295310
<box
296311
backgroundColor={getColor(node.bg) ?? theme.accent}
297312
paddingLeft={2}
298313
paddingRight={2}
314+
border={hovered() ? ["left"] : undefined}
315+
borderStyle={hovered() ? "heavy" : undefined}
316+
borderColor={hovered() ? (getColor(node.borderColorHover) ?? theme.warning) : undefined}
317+
onMouseOver={() => setHovered(true)}
318+
onMouseOut={() => setHovered(false)}
299319
onMouseDown={() => {
300320
if (node.onConfirm && props.metadata._component) {
301321
PluginRegistry.emit(props.metadata._component, node.onConfirm, {})
302322
}
303323
}}
304324
>
305-
<text content={node.label} fg={getColor(node.fg) ?? theme.background} />
325+
<text
326+
content={node.label}
327+
fg={hovered() ? (getColor(node.fgHover) ?? theme.warning) : (getColor(node.fg) ?? theme.background)}
328+
/>
329+
</box>
330+
)
331+
})()}
332+
</Match>
333+
<Match when={props.node.type === "button-group"}>
334+
{(() => {
335+
const node = props.node as {
336+
type: "button-group"
337+
gap?: number
338+
defaultIndex?: number
339+
children: PluginUINode[]
340+
}
341+
const [focusedIndex, setFocusedIndex] = createSignal(node.defaultIndex ?? node.children.length - 1)
342+
const [clickedIndex, setClickedIndex] = createSignal<number | null>(null)
343+
344+
useKeyboard((evt) => {
345+
if (clickedIndex() !== null) return false
346+
if (evt.name === "left" || evt.name === "h") {
347+
setFocusedIndex((i) => Math.max(0, i - 1))
348+
return true
349+
}
350+
if (evt.name === "right" || evt.name === "l") {
351+
setFocusedIndex((i) => Math.min(node.children.length - 1, i + 1))
352+
return true
353+
}
354+
if (evt.name === "return") {
355+
const child = node.children[focusedIndex()] as any
356+
if (child?.onConfirm && props.metadata._component) {
357+
setClickedIndex(focusedIndex())
358+
PluginRegistry.emit(props.metadata._component, child.onConfirm, {})
359+
}
360+
return true
361+
}
362+
return false
363+
})
364+
365+
return (
366+
<box flexDirection="row" gap={node.gap ?? 2}>
367+
<For each={node.children}>
368+
{(child, index) => {
369+
const btn = child as {
370+
type: "confirm-button"
371+
label: string
372+
fg?: string
373+
bg?: string
374+
fgHover?: string
375+
borderColorHover?: string
376+
onConfirm?: string
377+
}
378+
const isActive = () => {
379+
if (clickedIndex() !== null) return clickedIndex() === index()
380+
return focusedIndex() === index()
381+
}
382+
return (
383+
<box flexDirection="row">
384+
<box minWidth={1}>
385+
<text content={isActive() ? "┃" : " "} fg={getColor(btn.borderColorHover) ?? theme.warning} />
386+
</box>
387+
<box
388+
backgroundColor={getColor(btn.bg) ?? theme.accent}
389+
paddingLeft={1}
390+
paddingRight={2}
391+
onMouseOver={() => {
392+
if (clickedIndex() === null) setFocusedIndex(index())
393+
}}
394+
onMouseDown={() => {
395+
if (clickedIndex() !== null) return
396+
if (btn.onConfirm && props.metadata._component) {
397+
setClickedIndex(index())
398+
PluginRegistry.emit(props.metadata._component, btn.onConfirm, {})
399+
}
400+
}}
401+
>
402+
<text
403+
content={btn.label}
404+
fg={
405+
isActive()
406+
? (getColor(btn.fgHover) ?? theme.warning)
407+
: (getColor(btn.fg) ?? theme.background)
408+
}
409+
/>
410+
</box>
411+
</box>
412+
)
413+
}}
414+
</For>
306415
</box>
307416
)
308417
})()}

0 commit comments

Comments
 (0)