1- import { RaisedPill } from './raised-pill'
1+ import React , { useState } from 'react'
2+ import stringWidth from 'string-width'
23import { useTheme } from '../hooks/use-theme'
34
45import type { AgentMode } from '../utils/constants'
@@ -14,32 +15,171 @@ const getModeConfig = (theme: ChatTheme) =>
1415 MAX : {
1516 frameColor : theme . modeMaxBg ,
1617 textColor : theme . modeMaxText ,
17- label : '💪 MAX' ,
18+ label : 'MAX' ,
1819 } ,
1920 PLAN : {
2021 frameColor : theme . modePlanBg ,
2122 textColor : theme . modePlanText ,
22- label : '📋 PLAN' ,
23+ label : 'PLAN' ,
2324 } ,
2425 } ) as const
2526
27+ const ALL_MODES : AgentMode [ ] = [ 'FAST' , 'MAX' , 'PLAN' ]
28+
2629export const AgentModeToggle = ( {
2730 mode,
2831 onToggle,
32+ onSelectMode,
2933} : {
3034 mode : AgentMode
3135 onToggle : ( ) => void
36+ onSelectMode ?: ( mode : AgentMode ) => void
3237} ) => {
3338 const theme = useTheme ( )
3439 const config = getModeConfig ( theme )
35- const { frameColor, textColor, label } = config [ mode ]
40+ const [ isOpen , setIsOpen ] = useState ( false )
41+
42+ const handlePress = ( selectedMode : AgentMode ) => {
43+ if ( selectedMode === mode ) {
44+ // Toggle collapsed/expanded when clicking current mode
45+ setIsOpen ( ! isOpen )
46+ } else {
47+ // Switch to different mode and close the toggle
48+ if ( onSelectMode ) {
49+ onSelectMode ( selectedMode )
50+ } else {
51+ onToggle ( )
52+ }
53+ setIsOpen ( false )
54+ }
55+ }
56+
57+ if ( ! isOpen ) {
58+ // Collapsed state: show only current mode
59+ const { frameColor, textColor, label } = config [ mode ]
60+ const arrow = ' <'
61+ const contentText = ` ${ label } ${ arrow } `
62+ const contentWidth = stringWidth ( contentText )
63+ const horizontal = '─' . repeat ( contentWidth )
64+
65+ return (
66+ < box
67+ style = { {
68+ flexDirection : 'column' ,
69+ gap : 0 ,
70+ backgroundColor : 'transparent' ,
71+ } }
72+ onMouseDown = { ( ) => handlePress ( mode ) }
73+ >
74+ < text >
75+ < span fg = { frameColor } > { `╭${ horizontal } ╮` } </ span >
76+ </ text >
77+ < text >
78+ < span fg = { frameColor } > │</ span >
79+ < span fg = { textColor } > { contentText } </ span >
80+ < span fg = { frameColor } > │</ span >
81+ </ text >
82+ < text >
83+ < span fg = { frameColor } > { `╰${ horizontal } ╯` } </ span >
84+ </ text >
85+ </ box >
86+ )
87+ }
88+
89+ // Expanded state: show all modes with current mode rightmost
90+ const orderedModes = [
91+ ...ALL_MODES . filter ( ( m ) => m !== mode ) ,
92+ mode ,
93+ ]
94+
95+ // Calculate widths for each segment
96+ const segmentWidths = orderedModes . map ( ( m ) => {
97+ const label = config [ m ] . label
98+ if ( m === mode ) {
99+ // Active mode shows label with collapse arrow
100+ return stringWidth ( ` ${ label } > ` )
101+ }
102+ return stringWidth ( ` ${ label } ` )
103+ } )
104+
105+ const buildSegment = (
106+ modeItem : AgentMode ,
107+ index : number ,
108+ isLast : boolean ,
109+ ) => {
110+ const { frameColor, textColor, label } = config [ modeItem ]
111+ const isActive = modeItem === mode
112+ const width = segmentWidths [ index ]
113+ const content = isActive ? ` ${ label } > ` : ` ${ label } `
114+ const horizontal = '─' . repeat ( width )
115+
116+ return {
117+ topBorder : isLast ? `${ horizontal } ╮` : `${ horizontal } ┬` ,
118+ content,
119+ bottomBorder : isLast ? `${ horizontal } ╯` : `${ horizontal } ┴` ,
120+ frameColor,
121+ textColor,
122+ }
123+ }
124+
125+ const segments = orderedModes . map ( ( m , idx ) =>
126+ buildSegment ( m , idx , idx === orderedModes . length - 1 ) ,
127+ )
36128
37129 return (
38- < RaisedPill
39- segments = { [ { text : label , fg : textColor } ] }
40- frameColor = { frameColor }
41- textColor = { textColor }
42- onPress = { onToggle }
43- />
130+ < box
131+ style = { {
132+ flexDirection : 'column' ,
133+ gap : 0 ,
134+ backgroundColor : 'transparent' ,
135+ } }
136+ >
137+ { /* Top border */ }
138+ < text >
139+ < span fg = { segments [ 0 ] . frameColor } > ╭</ span >
140+ { segments . map ( ( seg , idx ) => (
141+ < span key = { `top-${ idx } ` } fg = { seg . frameColor } >
142+ { seg . topBorder }
143+ </ span >
144+ ) ) }
145+ </ text >
146+
147+ { /* Content row with clickable segments */ }
148+ < box
149+ style = { {
150+ flexDirection : 'row' ,
151+ gap : 0 ,
152+ } }
153+ >
154+ < text >
155+ < span fg = { segments [ 0 ] . frameColor } > │</ span >
156+ </ text >
157+ { segments . map ( ( seg , idx ) => {
158+ const modeItem = orderedModes [ idx ]
159+ return (
160+ < React . Fragment key = { `content-${ idx } ` } >
161+ < box onMouseDown = { ( ) => handlePress ( modeItem ) } >
162+ < text >
163+ < span fg = { seg . textColor } > { seg . content } </ span >
164+ </ text >
165+ </ box >
166+ < text >
167+ < span fg = { seg . frameColor } > │</ span >
168+ </ text >
169+ </ React . Fragment >
170+ )
171+ } ) }
172+ </ box >
173+
174+ { /* Bottom border */ }
175+ < text >
176+ < span fg = { segments [ 0 ] . frameColor } > ╰</ span >
177+ { segments . map ( ( seg , idx ) => (
178+ < span key = { `bottom-${ idx } ` } fg = { seg . frameColor } >
179+ { seg . bottomBorder }
180+ </ span >
181+ ) ) }
182+ </ text >
183+ </ box >
44184 )
45185}
0 commit comments