summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/components
diff options
context:
space:
mode:
authorDavid Hill <[email protected]>2026-02-17 17:06:21 +0000
committerDavid Hill <[email protected]>2026-02-17 17:43:37 +0000
commit2c17a980ffdc019d46b9e48a22bf719c009075e0 (patch)
tree9bb151f2cd0be28daa582f4779ec7014776cab9c /packages/app/src/components
parentb784c923a8eeab52412eaebb9a44ad05a1411165 (diff)
downloadopencode-2c17a980ffdc019d46b9e48a22bf719c009075e0.tar.gz
opencode-2c17a980ffdc019d46b9e48a22bf719c009075e0.zip
refactor(ui): extract dock prompt shell
Diffstat (limited to 'packages/app/src/components')
-rw-r--r--packages/app/src/components/question-dock.tsx316
1 files changed, 158 insertions, 158 deletions
diff --git a/packages/app/src/components/question-dock.tsx b/packages/app/src/components/question-dock.tsx
index 1a0bbbe97..cd2e495b1 100644
--- a/packages/app/src/components/question-dock.tsx
+++ b/packages/app/src/components/question-dock.tsx
@@ -1,6 +1,7 @@
import { For, Show, createMemo, onCleanup, onMount, type Component } from "solid-js"
import { createStore } from "solid-js/store"
import { Button } from "@opencode-ai/ui/button"
+import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
import { Icon } from "@opencode-ai/ui/icon"
import { showToast } from "@opencode-ai/ui/toast"
import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2"
@@ -232,9 +233,11 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
}
return (
- <div data-component="question-prompt" ref={(el) => (root = el)}>
- <div data-slot="question-body">
- <div data-slot="question-header">
+ <DockPrompt
+ kind="question"
+ ref={(el) => (root = el)}
+ header={
+ <>
<div data-slot="question-header-title">{summary()}</div>
<div data-slot="question-progress">
<For each={questions()}>
@@ -254,172 +257,169 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
)}
</For>
</div>
- </div>
-
- <div data-slot="question-content">
- <div data-slot="question-text">{question()?.question}</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>
- </Show>
- <div data-slot="question-options">
- <For each={options()}>
- {(opt, i) => {
- const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
- return (
- <button
- data-slot="question-option"
- data-picked={picked()}
- role={multi() ? "checkbox" : "radio"}
- aria-checked={picked()}
- disabled={store.sending}
- onClick={() => selectOption(i())}
- >
- <span data-slot="question-option-check" aria-hidden="true">
- <span
- data-slot="question-option-box"
- data-type={multi() ? "checkbox" : "radio"}
- data-picked={picked()}
- >
- <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="option-label">{opt.label}</span>
- <Show when={opt.description}>
- <span data-slot="option-description">{opt.description}</span>
- </Show>
- </span>
- </button>
- )
- }}
- </For>
-
- <Show
- when={store.editing}
- fallback={
- <button
- data-slot="question-option"
- data-custom="true"
- data-picked={on()}
- role={multi() ? "checkbox" : "radio"}
- aria-checked={on()}
- disabled={store.sending}
- onClick={customOpen}
- >
+ </>
+ }
+ footer={
+ <>
+ <Button variant="ghost" size="large" disabled={store.sending} onClick={reject}>
+ {language.t("ui.common.dismiss")}
+ </Button>
+ <div data-slot="question-footer-actions">
+ <Show when={store.tab > 0}>
+ <Button variant="secondary" size="large" disabled={store.sending} onClick={back}>
+ {language.t("ui.common.back")}
+ </Button>
+ </Show>
+ <Button variant={last() ? "primary" : "secondary"} size="large" disabled={store.sending} onClick={next}>
+ {last() ? language.t("ui.common.submit") : language.t("ui.common.next")}
+ </Button>
+ </div>
+ </>
+ }
+ >
+ <div data-slot="question-text">{question()?.question}</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>
+ </Show>
+ <div data-slot="question-options">
+ <For each={options()}>
+ {(opt, i) => {
+ const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
+ return (
+ <button
+ data-slot="question-option"
+ data-picked={picked()}
+ role={multi() ? "checkbox" : "radio"}
+ aria-checked={picked()}
+ disabled={store.sending}
+ onClick={() => selectOption(i())}
+ >
+ <span data-slot="question-option-check" aria-hidden="true">
<span
- data-slot="question-option-check"
- aria-hidden="true"
- onClick={(e) => {
- e.preventDefault()
- e.stopPropagation()
- customToggle()
- }}
+ data-slot="question-option-box"
+ data-type={multi() ? "checkbox" : "radio"}
+ data-picked={picked()}
>
- <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="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
- <span data-slot="option-description">
- {input() || language.t("ui.question.custom.placeholder")}
- </span>
+ <Show when={multi()} fallback={<span data-slot="question-option-radio-dot" />}>
+ <Icon name="check-small" size="small" />
+ </Show>
</span>
- </button>
+ </span>
+ <span data-slot="question-option-main">
+ <span data-slot="option-label">{opt.label}</span>
+ <Show when={opt.description}>
+ <span data-slot="option-description">{opt.description}</span>
+ </Show>
+ </span>
+ </button>
+ )
+ }}
+ </For>
+
+ <Show
+ when={store.editing}
+ fallback={
+ <button
+ data-slot="question-option"
+ data-custom="true"
+ data-picked={on()}
+ role={multi() ? "checkbox" : "radio"}
+ aria-checked={on()}
+ disabled={store.sending}
+ onClick={customOpen}
+ >
+ <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="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
+ <span data-slot="option-description">{input() || language.t("ui.question.custom.placeholder")}</span>
+ </span>
+ </button>
+ }
+ >
+ <form
+ data-slot="question-option"
+ data-custom="true"
+ data-picked={on()}
+ role={multi() ? "checkbox" : "radio"}
+ aria-checked={on()}
+ onMouseDown={(e) => {
+ if (store.sending) {
+ e.preventDefault()
+ return
}
+ if (e.target instanceof HTMLTextAreaElement) return
+ const input = e.currentTarget.querySelector('[data-slot="question-custom-input"]')
+ if (input instanceof HTMLTextAreaElement) input.focus()
+ }}
+ onSubmit={(e) => {
+ e.preventDefault()
+ commitCustom()
+ }}
+ >
+ <span
+ data-slot="question-option-check"
+ aria-hidden="true"
+ onClick={(e) => {
+ e.preventDefault()
+ e.stopPropagation()
+ customToggle()
+ }}
>
- <form
- data-slot="question-option"
- data-custom="true"
- data-picked={on()}
- role={multi() ? "checkbox" : "radio"}
- aria-checked={on()}
- onMouseDown={(e) => {
- if (store.sending) {
+ <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="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
+ <textarea
+ ref={(el) =>
+ setTimeout(() => {
+ el.focus()
+ el.style.height = "0px"
+ el.style.height = `${el.scrollHeight}px`
+ }, 0)
+ }
+ data-slot="question-custom-input"
+ placeholder={language.t("ui.question.custom.placeholder")}
+ value={input()}
+ rows={1}
+ disabled={store.sending}
+ onKeyDown={(e) => {
+ if (e.key === "Escape") {
e.preventDefault()
+ setStore("editing", false)
return
}
- if (e.target instanceof HTMLTextAreaElement) return
- const input = e.currentTarget.querySelector('[data-slot="question-custom-input"]')
- if (input instanceof HTMLTextAreaElement) input.focus()
- }}
- onSubmit={(e) => {
+ if (e.key !== "Enter" || e.shiftKey) return
e.preventDefault()
commitCustom()
}}
- >
- <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="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
- <textarea
- ref={(el) =>
- setTimeout(() => {
- el.focus()
- el.style.height = "0px"
- el.style.height = `${el.scrollHeight}px`
- }, 0)
- }
- data-slot="question-custom-input"
- placeholder={language.t("ui.question.custom.placeholder")}
- value={input()}
- rows={1}
- disabled={store.sending}
- onKeyDown={(e) => {
- if (e.key === "Escape") {
- e.preventDefault()
- setStore("editing", false)
- return
- }
- if (e.key !== "Enter" || e.shiftKey) return
- e.preventDefault()
- commitCustom()
- }}
- onInput={(e) => {
- customUpdate(e.currentTarget.value)
- e.currentTarget.style.height = "0px"
- e.currentTarget.style.height = `${e.currentTarget.scrollHeight}px`
- }}
- />
- </span>
- </form>
- </Show>
- </div>
- </div>
- </div>
-
- <div data-slot="question-footer">
- <Button variant="ghost" size="large" disabled={store.sending} onClick={reject}>
- {language.t("ui.common.dismiss")}
- </Button>
- <div data-slot="question-footer-actions">
- <Show when={store.tab > 0}>
- <Button variant="secondary" size="large" disabled={store.sending} onClick={back}>
- {language.t("ui.common.back")}
- </Button>
- </Show>
- <Button variant={last() ? "primary" : "secondary"} size="large" disabled={store.sending} onClick={next}>
- {last() ? language.t("ui.common.submit") : language.t("ui.common.next")}
- </Button>
- </div>
+ onInput={(e) => {
+ customUpdate(e.currentTarget.value)
+ e.currentTarget.style.height = "0px"
+ e.currentTarget.style.height = `${e.currentTarget.scrollHeight}px`
+ }}
+ />
+ </span>
+ </form>
+ </Show>
</div>
- </div>
+ </DockPrompt>
)
}