@@ -4,6 +4,7 @@ import { useMutation } from "@tanstack/solid-query"
44import { Button } from "@opencode-ai/ui/button"
55import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
66import { Icon } from "@opencode-ai/ui/icon"
7+ import { IconButton } from "@opencode-ai/ui/icon-button"
78import { showToast } from "@opencode-ai/ui/toast"
89import type { QuestionAnswer , QuestionRequest } from "@opencode-ai/sdk/v2"
910import { useLanguage } from "@/context/language"
@@ -73,6 +74,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
7374 customOn : cached ?. customOn ?? ( [ ] as boolean [ ] ) ,
7475 editing : false ,
7576 focus : 0 ,
77+ collapsed : false ,
7678 } )
7779
7880 let root : HTMLDivElement | undefined
@@ -87,6 +89,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
8789 const on = createMemo ( ( ) => store . customOn [ store . tab ] === true )
8890 const multi = createMemo ( ( ) => question ( ) ?. multiple === true )
8991 const count = createMemo ( ( ) => options ( ) . length + 1 )
92+ const pickedCount = createMemo ( ( ) => store . answers [ store . tab ] ?. length ?? 0 )
9093
9194 const summary = createMemo ( ( ) => {
9295 const n = Math . min ( store . tab + 1 , total ( ) )
@@ -98,6 +101,8 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
98101
99102 const last = createMemo ( ( ) => store . tab >= total ( ) - 1 )
100103
104+ const fold = ( ) => setStore ( "collapsed" , ( value ) => ! value )
105+
101106 const customUpdate = ( value : string , selected : boolean = on ( ) ) => {
102107 const prev = input ( ) . trim ( )
103108 const next = value . trim ( )
@@ -426,9 +431,21 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
426431 ref = { ( el ) => ( root = el ) }
427432 onKeyDown = { nav }
428433 header = {
429- < >
434+ < div
435+ data-action = "session-question-toggle"
436+ class = "flex flex-1 min-w-0 items-center gap-2 cursor-default select-none"
437+ role = "button"
438+ tabIndex = { 0 }
439+ style = { { margin : "0 -10px" , padding : "0 0 0 10px" } }
440+ onClick = { fold }
441+ onKeyDown = { ( event ) => {
442+ if ( event . key !== "Enter" && event . key !== " " ) return
443+ event . preventDefault ( )
444+ fold ( )
445+ } }
446+ >
430447 < div data-slot = "question-header-title" > { summary ( ) } </ div >
431- < div data-slot = "question-progress" >
448+ < div data-slot = "question-progress" class = "ml-auto mr-1" >
432449 < For each = { questions ( ) } >
433450 { ( _ , i ) => (
434451 < button
@@ -437,13 +454,38 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
437454 data-active = { i ( ) === store . tab }
438455 data-answered = { answered ( i ( ) ) }
439456 disabled = { sending ( ) }
440- onClick = { ( ) => jump ( i ( ) ) }
457+ onMouseDown = { ( event ) => {
458+ event . preventDefault ( )
459+ event . stopPropagation ( )
460+ } }
461+ onClick = { ( event ) => {
462+ event . stopPropagation ( )
463+ jump ( i ( ) )
464+ } }
441465 aria-label = { `${ language . t ( "ui.tool.questions" ) } ${ i ( ) + 1 } ` }
442466 />
443467 ) }
444468 </ For >
445469 </ div >
446- </ >
470+ < div >
471+ < IconButton
472+ data-action = "session-question-toggle-button"
473+ icon = "chevron-down"
474+ size = "normal"
475+ variant = "ghost"
476+ classList = { { "rotate-180" : store . collapsed } }
477+ onMouseDown = { ( event ) => {
478+ event . preventDefault ( )
479+ event . stopPropagation ( )
480+ } }
481+ onClick = { ( event ) => {
482+ event . stopPropagation ( )
483+ fold ( )
484+ } }
485+ aria-label = { store . collapsed ? language . t ( "session.todo.expand" ) : language . t ( "session.todo.collapse" ) }
486+ />
487+ </ div >
488+ </ div >
447489 }
448490 footer = {
449491 < >
@@ -469,72 +511,109 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
469511 </ >
470512 }
471513 >
472- < div data-slot = "question-text" > { question ( ) ?. question } </ div >
473- < Show when = { multi ( ) } fallback = { < div data-slot = "question-hint" > { language . t ( "ui.question.singleHint" ) } </ div > } >
474- < div data-slot = "question-hint" > { language . t ( "ui.question.multiHint" ) } </ div >
514+ < div
515+ data-slot = "question-text"
516+ class = "cursor-default"
517+ classList = { {
518+ "mb-6" : store . collapsed && pickedCount ( ) === 0 ,
519+ } }
520+ role = { store . collapsed ? "button" : undefined }
521+ tabIndex = { store . collapsed ? 0 : undefined }
522+ onClick = { fold }
523+ onKeyDown = { ( event ) => {
524+ if ( ! store . collapsed ) return
525+ if ( event . key !== "Enter" && event . key !== " " ) return
526+ event . preventDefault ( )
527+ fold ( )
528+ } }
529+ >
530+ { question ( ) ?. question }
531+ </ div >
532+ < Show when = { store . collapsed && pickedCount ( ) > 0 } >
533+ < div data-slot = "question-hint" class = "cursor-default mb-6" >
534+ { pickedCount ( ) } answer{ pickedCount ( ) === 1 ? "" : "s" } selected
535+ </ div >
475536 </ Show >
476- < div data-slot = "question-options" >
477- < For each = { options ( ) } >
478- { ( opt , i ) => (
479- < Option
480- multi = { multi ( ) }
481- picked = { picked ( opt . label ) }
482- label = { opt . label }
483- description = { opt . description }
484- disabled = { sending ( ) }
485- ref = { ( el ) => ( optsRef [ i ( ) ] = el ) }
486- onFocus = { ( ) => setStore ( "focus" , i ( ) ) }
487- onClick = { ( ) => selectOption ( i ( ) ) }
488- />
489- ) }
490- </ For >
491-
492- < Show
493- when = { store . editing }
494- fallback = {
495- < button
496- type = "button"
497- ref = { customRef }
537+ < div data-slot = "question-answers" hidden = { store . collapsed } aria-hidden = { store . collapsed } >
538+ < Show when = { multi ( ) } fallback = { < div data-slot = "question-hint" > { language . t ( "ui.question.singleHint" ) } </ div > } >
539+ < div data-slot = "question-hint" > { language . t ( "ui.question.multiHint" ) } </ div >
540+ </ Show >
541+ < div data-slot = "question-options" >
542+ < For each = { options ( ) } >
543+ { ( opt , i ) => (
544+ < Option
545+ multi = { multi ( ) }
546+ picked = { picked ( opt . label ) }
547+ label = { opt . label }
548+ description = { opt . description }
549+ disabled = { sending ( ) }
550+ ref = { ( el ) => ( optsRef [ i ( ) ] = el ) }
551+ onFocus = { ( ) => setStore ( "focus" , i ( ) ) }
552+ onClick = { ( ) => selectOption ( i ( ) ) }
553+ />
554+ ) }
555+ </ For >
556+
557+ < Show
558+ when = { store . editing }
559+ fallback = {
560+ < button
561+ type = "button"
562+ ref = { customRef }
563+ data-slot = "question-option"
564+ data-custom = "true"
565+ data-picked = { on ( ) }
566+ role = { multi ( ) ? "checkbox" : "radio" }
567+ aria-checked = { on ( ) }
568+ disabled = { sending ( ) }
569+ onFocus = { ( ) => setStore ( "focus" , options ( ) . length ) }
570+ onClick = { customOpen }
571+ >
572+ < span
573+ data-slot = "question-option-check"
574+ aria-hidden = "true"
575+ onClick = { ( e ) => {
576+ e . preventDefault ( )
577+ e . stopPropagation ( )
578+ customToggle ( )
579+ } }
580+ >
581+ < span data-slot = "question-option-box" data-type = { multi ( ) ? "checkbox" : "radio" } data-picked = { on ( ) } >
582+ < Show when = { multi ( ) } fallback = { < span data-slot = "question-option-radio-dot" /> } >
583+ < Icon name = "check-small" size = "small" />
584+ </ Show >
585+ </ span >
586+ </ span >
587+ < span data-slot = "question-option-main" >
588+ < span data-slot = "option-label" > { customLabel ( ) } </ span >
589+ < span data-slot = "option-description" > { input ( ) || customPlaceholder ( ) } </ span >
590+ </ span >
591+ </ button >
592+ }
593+ >
594+ < form
498595 data-slot = "question-option"
499596 data-custom = "true"
500597 data-picked = { on ( ) }
501598 role = { multi ( ) ? "checkbox" : "radio" }
502599 aria-checked = { on ( ) }
503- disabled = { sending ( ) }
504- onFocus = { ( ) => setStore ( "focus" , options ( ) . length ) }
505- onClick = { customOpen }
600+ onMouseDown = { ( e ) => {
601+ if ( sending ( ) ) {
602+ e . preventDefault ( )
603+ return
604+ }
605+ if ( e . target instanceof HTMLTextAreaElement ) return
606+ const input = e . currentTarget . querySelector ( '[data-slot="question-custom-input"]' )
607+ if ( input instanceof HTMLTextAreaElement ) input . focus ( )
608+ } }
609+ onSubmit = { ( e ) => {
610+ e . preventDefault ( )
611+ commitCustom ( )
612+ } }
506613 >
507614 < Mark multi = { multi ( ) } picked = { on ( ) } onClick = { toggleCustomMark } />
508615 < span data-slot = "question-option-main" >
509616 < span data-slot = "option-label" > { customLabel ( ) } </ span >
510- < span data-slot = "option-description" > { input ( ) || customPlaceholder ( ) } </ span >
511- </ span >
512- </ button >
513- }
514- >
515- < form
516- data-slot = "question-option"
517- data-custom = "true"
518- data-picked = { on ( ) }
519- role = { multi ( ) ? "checkbox" : "radio" }
520- aria-checked = { on ( ) }
521- onMouseDown = { ( e ) => {
522- if ( sending ( ) ) {
523- e . preventDefault ( )
524- return
525- }
526- if ( e . target instanceof HTMLTextAreaElement ) return
527- const input = e . currentTarget . querySelector ( '[data-slot="question-custom-input"]' )
528- if ( input instanceof HTMLTextAreaElement ) input . focus ( )
529- } }
530- onSubmit = { ( e ) => {
531- e . preventDefault ( )
532- commitCustom ( )
533- } }
534- >
535- < Mark multi = { multi ( ) } picked = { on ( ) } onClick = { toggleCustomMark } />
536- < span data-slot = "question-option-main" >
537- < span data-slot = "option-label" > { customLabel ( ) } </ span >
538617 < textarea
539618 ref = { focusCustom }
540619 data-slot = "question-custom-input"
@@ -562,6 +641,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
562641 </ span >
563642 </ form >
564643 </ Show >
644+ </ div >
565645 </ div >
566646 </ DockPrompt >
567647 )
0 commit comments