Apply PR #15697: tweak(ui): make questions popup collapsible

beta
opencode-agent[bot] 2026-04-08 07:44:01 +00:00
commit 4133f55458
2 changed files with 187 additions and 89 deletions

View File

@ -4,6 +4,7 @@ import { useMutation } from "@tanstack/solid-query"
import { Button } from "@opencode-ai/ui/button" import { Button } from "@opencode-ai/ui/button"
import { DockPrompt } from "@opencode-ai/ui/dock-prompt" import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
import { Icon } from "@opencode-ai/ui/icon" import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { showToast } from "@opencode-ai/ui/toast" import { showToast } from "@opencode-ai/ui/toast"
import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2" import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
@ -73,6 +74,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
customOn: cached?.customOn ?? ([] as boolean[]), customOn: cached?.customOn ?? ([] as boolean[]),
editing: false, editing: false,
focus: 0, focus: 0,
collapsed: false,
}) })
let root: HTMLDivElement | undefined let root: HTMLDivElement | undefined
@ -87,6 +89,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
const on = createMemo(() => store.customOn[store.tab] === true) const on = createMemo(() => store.customOn[store.tab] === true)
const multi = createMemo(() => question()?.multiple === true) const multi = createMemo(() => question()?.multiple === true)
const count = createMemo(() => options().length + 1) const count = createMemo(() => options().length + 1)
const num = createMemo(() => store.answers[store.tab]?.length ?? 0)
const summary = createMemo(() => { const summary = createMemo(() => {
const n = Math.min(store.tab + 1, total()) const n = Math.min(store.tab + 1, total())
@ -98,6 +101,8 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
const last = createMemo(() => store.tab >= total() - 1) const last = createMemo(() => store.tab >= total() - 1)
const fold = () => setStore("collapsed", (value) => !value)
const customUpdate = (value: string, selected: boolean = on()) => { const customUpdate = (value: string, selected: boolean = on()) => {
const prev = input().trim() const prev = input().trim()
const next = value.trim() const next = value.trim()
@ -426,9 +431,21 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
ref={(el) => (root = el)} ref={(el) => (root = el)}
onKeyDown={nav} onKeyDown={nav}
header={ header={
<> <div
data-action="session-question-toggle"
class="flex flex-1 min-w-0 items-center gap-2 cursor-default select-none"
role="button"
tabIndex={0}
style={{ margin: "0 -10px", padding: "0 0 0 10px" }}
onClick={fold}
onKeyDown={(event) => {
if (event.key !== "Enter" && event.key !== " ") return
event.preventDefault()
fold()
}}
>
<div data-slot="question-header-title">{summary()}</div> <div data-slot="question-header-title">{summary()}</div>
<div data-slot="question-progress"> <div data-slot="question-progress" class="ml-auto mr-1">
<For each={questions()}> <For each={questions()}>
{(_, i) => ( {(_, i) => (
<button <button
@ -437,13 +454,38 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
data-active={i() === store.tab} data-active={i() === store.tab}
data-answered={answered(i())} data-answered={answered(i())}
disabled={sending()} disabled={sending()}
onClick={() => jump(i())} onMouseDown={(event) => {
event.preventDefault()
event.stopPropagation()
}}
onClick={(event) => {
event.stopPropagation()
jump(i())
}}
aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`} aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`}
/> />
)} )}
</For> </For>
</div> </div>
</> <div>
<IconButton
data-action="session-question-toggle-button"
icon="chevron-down"
size="normal"
variant="ghost"
classList={{ "rotate-180": store.collapsed }}
onMouseDown={(event) => {
event.preventDefault()
event.stopPropagation()
}}
onClick={(event) => {
event.stopPropagation()
fold()
}}
aria-label={store.collapsed ? language.t("session.todo.expand") : language.t("session.todo.collapse")}
/>
</div>
</div>
} }
footer={ footer={
<> <>
@ -469,7 +511,30 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
</> </>
} }
> >
<div data-slot="question-text">{question()?.question}</div> <div
data-slot="question-text"
class="cursor-default"
classList={{
"mb-6": store.collapsed && num() === 0,
}}
role={store.collapsed ? "button" : undefined}
tabIndex={store.collapsed ? 0 : undefined}
onClick={fold}
onKeyDown={(event) => {
if (!store.collapsed) return
if (event.key !== "Enter" && event.key !== " ") return
event.preventDefault()
fold()
}}
>
{question()?.question}
</div>
<Show when={store.collapsed && num() > 0}>
<div data-slot="question-hint" class="cursor-default mb-6">
{num()} answer{num() === 1 ? "" : "s"} selected
</div>
</Show>
<div data-slot="question-answers" hidden={store.collapsed} aria-hidden={store.collapsed}>
<Show when={multi()} fallback={<div data-slot="question-hint">{language.t("ui.question.singleHint")}</div>}> <Show when={multi()} fallback={<div data-slot="question-hint">{language.t("ui.question.singleHint")}</div>}>
<div data-slot="question-hint">{language.t("ui.question.multiHint")}</div> <div data-slot="question-hint">{language.t("ui.question.multiHint")}</div>
</Show> </Show>
@ -504,7 +569,21 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
onFocus={() => setStore("focus", options().length)} onFocus={() => setStore("focus", options().length)}
onClick={customOpen} onClick={customOpen}
> >
<Mark multi={multi()} picked={on()} onClick={toggleCustomMark} /> <span
data-slot="question-option-check"
aria-hidden="true"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
customToggle()
}}
>
<span data-slot="question-option-box" data-type={multi() ? "checkbox" : "radio"} data-picked={on()}>
<Show when={multi()} fallback={<span data-slot="question-option-radio-dot" />}>
<Icon name="check-small" size="small" />
</Show>
</span>
</span>
<span data-slot="question-option-main"> <span data-slot="question-option-main">
<span data-slot="option-label">{customLabel()}</span> <span data-slot="option-label">{customLabel()}</span>
<span data-slot="option-description">{input() || customPlaceholder()}</span> <span data-slot="option-description">{input() || customPlaceholder()}</span>
@ -563,6 +642,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
</form> </form>
</Show> </Show>
</div> </div>
</div>
</DockPrompt> </DockPrompt>
) )
} }

View File

@ -832,7 +832,7 @@
[data-slot="question-body"] { [data-slot="question-body"] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 0;
flex: 1; flex: 1;
min-height: 0; min-height: 0;
padding: 8px 8px 0; padding: 8px 8px 0;
@ -912,7 +912,7 @@
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
line-height: var(--line-height-large); line-height: var(--line-height-large);
color: var(--text-strong); color: var(--text-strong);
padding: 0 10px; padding: 16px 10px 0;
} }
[data-slot="question-hint"] { [data-slot="question-hint"] {
@ -1055,8 +1055,26 @@
line-height: var(--line-height-large); line-height: var(--line-height-large);
color: var(--text-base); color: var(--text-base);
min-width: 0; min-width: 0;
overflow-wrap: anywhere; overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-slot="question-option"][data-custom="true"] {
[data-slot="option-description"] {
overflow: visible;
text-overflow: clip;
white-space: normal; white-space: normal;
overflow-wrap: anywhere;
}
&[data-picked="true"] {
[data-slot="question-custom-input"]:focus-visible {
outline: none;
outline-offset: 0;
border-radius: 0;
}
}
} }
[data-slot="question-custom"] { [data-slot="question-custom"] {