@@ -3,6 +3,7 @@ import { createStore } from "solid-js/store"
33import { Button } from "@opencode-ai/ui/button"
44import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
55import { Icon } from "@opencode-ai/ui/icon"
6+ import { IconButton } from "@opencode-ai/ui/icon-button"
67import { showToast } from "@opencode-ai/ui/toast"
78import type { QuestionAnswer , QuestionRequest } from "@opencode-ai/sdk/v2"
89import { useLanguage } from "@/context/language"
@@ -22,6 +23,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
2223 customOn : [ ] as boolean [ ] ,
2324 editing : false ,
2425 sending : false ,
26+ collapsed : false ,
2527 } )
2628
2729 let root : HTMLDivElement | undefined
@@ -31,6 +33,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
3133 const input = createMemo ( ( ) => store . custom [ store . tab ] ?? "" )
3234 const on = createMemo ( ( ) => store . customOn [ store . tab ] === true )
3335 const multi = createMemo ( ( ) => question ( ) ?. multiple === true )
36+ const picked = createMemo ( ( ) => store . answers [ store . tab ] ?. length ?? 0 )
3437
3538 const summary = createMemo ( ( ) => {
3639 const n = Math . min ( store . tab + 1 , total ( ) )
@@ -39,6 +42,8 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
3942
4043 const last = createMemo ( ( ) => store . tab >= total ( ) - 1 )
4144
45+ const fold = ( ) => setStore ( "collapsed" , ( value ) => ! value )
46+
4247 const customUpdate = ( value : string , selected : boolean = on ( ) ) => {
4348 const prev = input ( ) . trim ( )
4449 const next = value . trim ( )
@@ -228,38 +233,44 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
228233 setStore ( "editing" , false )
229234 }
230235
231- const jump = ( tab : number ) => {
232- if ( store . sending ) return
233- setStore ( "tab" , tab )
234- setStore ( "editing" , false )
235- }
236-
237236 return (
238237 < DockPrompt
239238 kind = "question"
240239 ref = { ( el ) => ( root = el ) }
241240 header = {
242- < >
241+ < div
242+ data-action = "session-question-toggle"
243+ class = "flex flex-1 min-w-0 items-center gap-2 cursor-default select-none"
244+ role = "button"
245+ tabIndex = { 0 }
246+ style = { { margin : "0 -10px" , padding : "0 0 0 10px" } }
247+ onClick = { fold }
248+ onKeyDown = { ( event ) => {
249+ if ( event . key !== "Enter" && event . key !== " " ) return
250+ event . preventDefault ( )
251+ fold ( )
252+ } }
253+ >
243254 < div data-slot = "question-header-title" > { summary ( ) } </ div >
244- < div data-slot = "question-progress ">
245- < For each = { questions ( ) } >
246- { ( _ , i ) => (
247- < button
248- type = "button "
249- data-slot = "question-progress-segment "
250- data-active = { i ( ) === store . tab }
251- data-answered = {
252- ( store . answers [ i ( ) ] ?. length ?? 0 ) > 0 ||
253- ( store . customOn [ i ( ) ] === true && ( store . custom [ i ( ) ] ?? "" ) . trim ( ) . length > 0 )
254- }
255- disabled = { store . sending }
256- onClick = { ( ) => jump ( i ( ) ) }
257- aria-label = { ` ${ language . t ( "ui.tool.questions" ) } ${ i ( ) + 1 } ` }
258- />
259- ) }
260- </ For >
255+ < div class = "ml-auto ">
256+ < IconButton
257+ data-action = "session-question-toggle-button"
258+ icon = "chevron-down"
259+ size = "normal "
260+ variant = "ghost "
261+ classList = { { "rotate-180" : store . collapsed } }
262+ onMouseDown = { ( event ) => {
263+ event . preventDefault ( )
264+ event . stopPropagation ( )
265+ } }
266+ onClick = { ( event ) => {
267+ event . stopPropagation ( )
268+ fold ( )
269+ } }
270+ aria-label = { store . collapsed ? language . t ( "session.todo.expand" ) : language . t ( "session.todo.collapse" ) }
271+ / >
261272 </ div >
262- </ >
273+ </ div >
263274 }
264275 footer = {
265276 < >
@@ -279,56 +290,121 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
279290 </ >
280291 }
281292 >
282- < div data-slot = "question-text" > { question ( ) ?. question } </ div >
283- < Show when = { multi ( ) } fallback = { < div data-slot = "question-hint" > { language . t ( "ui.question.singleHint" ) } </ div > } >
284- < div data-slot = "question-hint" > { language . t ( "ui.question.multiHint" ) } </ div >
293+ < div
294+ data-slot = "question-text"
295+ class = "cursor-default"
296+ classList = { {
297+ "mb-6" : store . collapsed && picked ( ) === 0 ,
298+ } }
299+ role = { store . collapsed ? "button" : undefined }
300+ tabIndex = { store . collapsed ? 0 : undefined }
301+ onClick = { fold }
302+ onKeyDown = { ( event ) => {
303+ if ( ! store . collapsed ) return
304+ if ( event . key !== "Enter" && event . key !== " " ) return
305+ event . preventDefault ( )
306+ fold ( )
307+ } }
308+ >
309+ { question ( ) ?. question }
310+ </ div >
311+ < Show when = { store . collapsed && picked ( ) > 0 } >
312+ < div data-slot = "question-hint" class = "cursor-default mb-6" >
313+ { picked ( ) } answer{ picked ( ) === 1 ? "" : "s" } selected
314+ </ div >
285315 </ Show >
286- < div data-slot = "question-options" >
287- < For each = { options ( ) } >
288- { ( opt , i ) => {
289- const picked = ( ) => store . answers [ store . tab ] ?. includes ( opt . label ) ?? false
290- return (
316+ < div data-slot = "question-answers" hidden = { store . collapsed } aria-hidden = { store . collapsed } >
317+ < Show when = { multi ( ) } fallback = { < div data-slot = "question-hint" > { language . t ( "ui.question.singleHint" ) } </ div > } >
318+ < div data-slot = "question-hint" > { language . t ( "ui.question.multiHint" ) } </ div >
319+ </ Show >
320+ < div data-slot = "question-options" >
321+ < For each = { options ( ) } >
322+ { ( opt , i ) => {
323+ const picked = ( ) => store . answers [ store . tab ] ?. includes ( opt . label ) ?? false
324+ return (
325+ < button
326+ data-slot = "question-option"
327+ data-picked = { picked ( ) }
328+ role = { multi ( ) ? "checkbox" : "radio" }
329+ aria-checked = { picked ( ) }
330+ disabled = { store . sending }
331+ onClick = { ( ) => selectOption ( i ( ) ) }
332+ >
333+ < span data-slot = "question-option-check" aria-hidden = "true" >
334+ < span
335+ data-slot = "question-option-box"
336+ data-type = { multi ( ) ? "checkbox" : "radio" }
337+ data-picked = { picked ( ) }
338+ >
339+ < Show when = { multi ( ) } fallback = { < span data-slot = "question-option-radio-dot" /> } >
340+ < Icon name = "check-small" size = "small" />
341+ </ Show >
342+ </ span >
343+ </ span >
344+ < span data-slot = "question-option-main" >
345+ < span data-slot = "option-label" > { opt . label } </ span >
346+ < Show when = { opt . description } >
347+ < span data-slot = "option-description" > { opt . description } </ span >
348+ </ Show >
349+ </ span >
350+ </ button >
351+ )
352+ } }
353+ </ For >
354+
355+ < Show
356+ when = { store . editing }
357+ fallback = {
291358 < button
292359 data-slot = "question-option"
293- data-picked = { picked ( ) }
360+ data-custom = "true"
361+ data-picked = { on ( ) }
294362 role = { multi ( ) ? "checkbox" : "radio" }
295- aria-checked = { picked ( ) }
363+ aria-checked = { on ( ) }
296364 disabled = { store . sending }
297- onClick = { ( ) => selectOption ( i ( ) ) }
365+ onClick = { customOpen }
298366 >
299- < span data-slot = "question-option-check" aria-hidden = "true" >
300- < span
301- data-slot = "question-option-box"
302- data-type = { multi ( ) ? "checkbox" : "radio" }
303- data-picked = { picked ( ) }
304- >
367+ < span
368+ data-slot = "question-option-check"
369+ aria-hidden = "true"
370+ onClick = { ( e ) => {
371+ e . preventDefault ( )
372+ e . stopPropagation ( )
373+ customToggle ( )
374+ } }
375+ >
376+ < span data-slot = "question-option-box" data-type = { multi ( ) ? "checkbox" : "radio" } data-picked = { on ( ) } >
305377 < Show when = { multi ( ) } fallback = { < span data-slot = "question-option-radio-dot" /> } >
306378 < Icon name = "check-small" size = "small" />
307379 </ Show >
308380 </ span >
309381 </ span >
310382 < span data-slot = "question-option-main" >
311- < span data-slot = "option-label" > { opt . label } </ span >
312- < Show when = { opt . description } >
313- < span data-slot = "option-description" > { opt . description } </ span >
314- </ Show >
383+ < span data-slot = "option-label" > { language . t ( "ui.messagePart.option.typeOwnAnswer" ) } </ span >
384+ < span data-slot = "option-description" > { input ( ) || language . t ( "ui.question.custom.placeholder" ) } </ span >
315385 </ span >
316386 </ button >
317- )
318- } }
319- </ For >
320-
321- < Show
322- when = { store . editing }
323- fallback = {
324- < button
387+ }
388+ >
389+ < form
325390 data-slot = "question-option"
326391 data-custom = "true"
327392 data-picked = { on ( ) }
328393 role = { multi ( ) ? "checkbox" : "radio" }
329394 aria-checked = { on ( ) }
330- disabled = { store . sending }
331- onClick = { customOpen }
395+ onMouseDown = { ( e ) => {
396+ if ( store . sending ) {
397+ e . preventDefault ( )
398+ return
399+ }
400+ if ( e . target instanceof HTMLTextAreaElement ) return
401+ const input = e . currentTarget . querySelector ( '[data-slot="question-custom-input"]' )
402+ if ( input instanceof HTMLTextAreaElement ) input . focus ( )
403+ } }
404+ onSubmit = { ( e ) => {
405+ e . preventDefault ( )
406+ commitCustom ( )
407+ } }
332408 >
333409 < span
334410 data-slot = "question-option-check"
@@ -347,80 +423,39 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
347423 </ span >
348424 < span data-slot = "question-option-main" >
349425 < span data-slot = "option-label" > { language . t ( "ui.messagePart.option.typeOwnAnswer" ) } </ span >
350- < span data-slot = "option-description" > { input ( ) || language . t ( "ui.question.custom.placeholder" ) } </ span >
351- </ span >
352- </ button >
353- }
354- >
355- < form
356- data-slot = "question-option"
357- data-custom = "true"
358- data-picked = { on ( ) }
359- role = { multi ( ) ? "checkbox" : "radio" }
360- aria-checked = { on ( ) }
361- onMouseDown = { ( e ) => {
362- if ( store . sending ) {
363- e . preventDefault ( )
364- return
365- }
366- if ( e . target instanceof HTMLTextAreaElement ) return
367- const input = e . currentTarget . querySelector ( '[data-slot="question-custom-input"]' )
368- if ( input instanceof HTMLTextAreaElement ) input . focus ( )
369- } }
370- onSubmit = { ( e ) => {
371- e . preventDefault ( )
372- commitCustom ( )
373- } }
374- >
375- < span
376- data-slot = "question-option-check"
377- aria-hidden = "true"
378- onClick = { ( e ) => {
379- e . preventDefault ( )
380- e . stopPropagation ( )
381- customToggle ( )
382- } }
383- >
384- < span data-slot = "question-option-box" data-type = { multi ( ) ? "checkbox" : "radio" } data-picked = { on ( ) } >
385- < Show when = { multi ( ) } fallback = { < span data-slot = "question-option-radio-dot" /> } >
386- < Icon name = "check-small" size = "small" />
387- </ Show >
388- </ span >
389- </ span >
390- < span data-slot = "question-option-main" >
391- < span data-slot = "option-label" > { language . t ( "ui.messagePart.option.typeOwnAnswer" ) } </ span >
392- < textarea
393- ref = { ( el ) =>
394- setTimeout ( ( ) => {
395- el . focus ( )
396- el . style . height = "0px"
397- el . style . height = `${ el . scrollHeight } px`
398- } , 0 )
399- }
400- data-slot = "question-custom-input"
401- placeholder = { language . t ( "ui.question.custom.placeholder" ) }
402- value = { input ( ) }
403- rows = { 1 }
404- disabled = { store . sending }
405- onKeyDown = { ( e ) => {
406- if ( e . key === "Escape" ) {
407- e . preventDefault ( )
408- setStore ( "editing" , false )
409- return
426+ < textarea
427+ ref = { ( el ) =>
428+ setTimeout ( ( ) => {
429+ el . focus ( )
430+ el . style . height = "0px"
431+ el . style . height = `${ el . scrollHeight } px`
432+ } , 0 )
410433 }
411- if ( e . key !== "Enter" || e . shiftKey ) return
412- e . preventDefault ( )
413- commitCustom ( )
414- } }
415- onInput = { ( e ) => {
416- customUpdate ( e . currentTarget . value )
417- e . currentTarget . style . height = "0px"
418- e . currentTarget . style . height = `${ e . currentTarget . scrollHeight } px`
419- } }
420- />
421- </ span >
422- </ form >
423- </ Show >
434+ data-slot = "question-custom-input"
435+ placeholder = { language . t ( "ui.question.custom.placeholder" ) }
436+ value = { input ( ) }
437+ rows = { 1 }
438+ disabled = { store . sending }
439+ onKeyDown = { ( e ) => {
440+ if ( e . key === "Escape" ) {
441+ e . preventDefault ( )
442+ setStore ( "editing" , false )
443+ return
444+ }
445+ if ( e . key !== "Enter" || e . shiftKey ) return
446+ e . preventDefault ( )
447+ commitCustom ( )
448+ } }
449+ onInput = { ( e ) => {
450+ customUpdate ( e . currentTarget . value )
451+ e . currentTarget . style . height = "0px"
452+ e . currentTarget . style . height = `${ e . currentTarget . scrollHeight } px`
453+ } }
454+ />
455+ </ span >
456+ </ form >
457+ </ Show >
458+ </ div >
424459 </ div >
425460 </ DockPrompt >
426461 )
0 commit comments