11import { TextAttributes } from '@opentui/core'
2- import { useCallback , useState } from 'react'
2+ import { useCallback , useEffect , useRef , useState } from 'react'
33
44import { defineToolComponent } from './types'
55import { useTheme } from '../../hooks/use-theme'
66import { safeOpen } from '../../utils/open-url'
77import { Button } from '../button'
88
9- import type { ChatTheme } from '../../types/theme-system'
109import type { ToolRenderConfig } from './types'
1110import type { RenderUIButtonWidget } from '@codebuff/common/tools/params/tool/render-ui'
1211
@@ -33,25 +32,10 @@ const isRenderUIButtonWidget = (
3332}
3433
3534const getButtonColors = (
36- theme : ChatTheme ,
35+ theme : ReturnType < typeof useTheme > ,
3736 variant : RenderUIButtonVariant ,
3837 isHovered : boolean ,
39- status : 'idle' | 'opened' | 'failed' ,
4038) => {
41- if ( status === 'failed' ) {
42- return {
43- backgroundColor : theme . surface ,
44- foregroundColor : theme . error ,
45- }
46- }
47-
48- if ( status === 'opened' ) {
49- return {
50- backgroundColor : theme . surface ,
51- foregroundColor : theme . success ,
52- }
53- }
54-
5539 if ( variant === 'secondary' ) {
5640 return {
5741 backgroundColor : isHovered ? theme . surfaceHover : theme . surface ,
@@ -65,36 +49,51 @@ const getButtonColors = (
6549 }
6650}
6751
52+ const CLICK_FLASH_DURATION_MS = 150
53+
6854const RenderUIButton = ( { widget } : { widget : RenderUIButtonWidget } ) => {
6955 const theme = useTheme ( )
7056 const [ isHovered , setIsHovered ] = useState ( false )
71- const [ status , setStatus ] = useState < 'idle' | 'opened' | 'failed' > ( 'idle' )
57+ const [ isClicked , setIsClicked ] = useState ( false )
58+ const clickTimeoutRef = useRef < ReturnType < typeof setTimeout > | null > ( null )
7259 const variant = widget . variant ?? 'primary'
7360 const { backgroundColor, foregroundColor } = getButtonColors (
7461 theme ,
7562 variant ,
7663 isHovered ,
77- status ,
7864 )
7965
80- const handleClick = useCallback ( async ( ) => {
81- const opened = await safeOpen ( widget . link )
82- setStatus ( opened ? 'opened' : 'failed' )
66+ useEffect ( ( ) => {
67+ return ( ) => {
68+ if ( clickTimeoutRef . current ) {
69+ clearTimeout ( clickTimeoutRef . current )
70+ }
71+ }
72+ } , [ ] )
73+
74+ const handleClick = useCallback ( ( ) => {
75+ if ( clickTimeoutRef . current ) {
76+ clearTimeout ( clickTimeoutRef . current )
77+ }
78+ setIsClicked ( true )
79+ safeOpen ( widget . link )
80+ clickTimeoutRef . current = setTimeout (
81+ ( ) => setIsClicked ( false ) ,
82+ CLICK_FLASH_DURATION_MS ,
83+ )
8384 } , [ widget . link ] )
8485
85- const statusText =
86- status === 'opened'
87- ? 'Opened'
88- : status === 'failed'
89- ? `Could not open: ${ widget . link } `
90- : ''
86+ const textAttributes = isClicked
87+ ? TextAttributes . DIM
88+ : isHovered
89+ ? TextAttributes . BOLD
90+ : undefined
9191
9292 return (
9393 < box
9494 style = { {
9595 flexDirection : 'row' ,
9696 alignItems : 'center' ,
97- gap : statusText ? 1 : 0 ,
9897 } }
9998 >
10099 < Button
@@ -108,19 +107,11 @@ const RenderUIButton = ({ widget }: { widget: RenderUIButtonWidget }) => {
108107 } }
109108 >
110109 < text >
111- < span
112- fg = { foregroundColor }
113- attributes = { isHovered ? TextAttributes . BOLD : undefined }
114- >
110+ < span fg = { foregroundColor } attributes = { textAttributes } >
115111 { widget . text }
116112 </ span >
117113 </ text >
118114 </ Button >
119- < text style = { { wrapMode : 'word' } } >
120- < span fg = { status === 'failed' ? theme . error : theme . muted } >
121- { statusText }
122- </ span >
123- </ text >
124115 </ box >
125116 )
126117}
0 commit comments