@@ -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
105115type 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