fix(app): question UI fixes and UX improvements (#34116)
Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
This commit is contained in:
parent
34ee94232f
commit
cd00422298
4 changed files with 279 additions and 144 deletions
|
|
@ -38,6 +38,29 @@ test("shows a pending question dock", async ({ page }) => {
|
||||||
await expect(question.getByRole("radio", { name: /Extended/ })).toBeVisible()
|
await expect(question.getByRole("radio", { name: /Extended/ })).toBeVisible()
|
||||||
await expect(page.locator('[data-component="session-composer"]')).toHaveCount(0)
|
await expect(page.locator('[data-component="session-composer"]')).toHaveCount(0)
|
||||||
|
|
||||||
|
const rejectRequests: string[] = []
|
||||||
|
page.on("request", (request) => {
|
||||||
|
if (request.method() !== "POST") return
|
||||||
|
if (new URL(request.url()).pathname === "/question/question-request/reject") rejectRequests.push(request.url())
|
||||||
|
})
|
||||||
|
|
||||||
|
await question.locator('[data-component="icon-button"][data-icon="chevron-down"]').click()
|
||||||
|
await expect(question).toBeVisible()
|
||||||
|
await expect(question.getByText("Which implementation should be used?")).toBeVisible()
|
||||||
|
await expect(question.getByText("Select one answer")).toBeHidden()
|
||||||
|
await expect(question.getByRole("radio", { name: /Minimal/ })).toBeHidden()
|
||||||
|
await expect(question.getByRole("radio", { name: /Extended/ })).toBeHidden()
|
||||||
|
await expect(question.getByRole("button", { name: "Dismiss" })).toBeVisible()
|
||||||
|
await expect(question.getByRole("button", { name: "Submit" })).toBeVisible()
|
||||||
|
await expect(page.locator('[data-component="question-minimized-dock"]')).toHaveCount(0)
|
||||||
|
expect(rejectRequests).toEqual([])
|
||||||
|
|
||||||
|
await question.locator('[data-component="icon-button"][data-icon="chevron-down"]').click()
|
||||||
|
await expect(question).toBeVisible()
|
||||||
|
await expect(question.getByText("Which implementation should be used?")).toBeVisible()
|
||||||
|
await expect(question.getByRole("radio", { name: /Minimal/ })).toBeVisible()
|
||||||
|
expect(rejectRequests).toEqual([])
|
||||||
|
|
||||||
await question.getByRole("radio", { name: /Minimal/ }).click()
|
await question.getByRole("radio", { name: /Minimal/ }).click()
|
||||||
const reply = page.waitForRequest(
|
const reply = page.waitForRequest(
|
||||||
(request) => request.method() === "POST" && new URL(request.url()).pathname === "/question/question-request/reply",
|
(request) => request.method() === "POST" && new URL(request.url()).pathname === "/question/question-request/reply",
|
||||||
|
|
|
||||||
|
|
@ -657,6 +657,10 @@ export const dict = {
|
||||||
"session.todo.expand": "Expand",
|
"session.todo.expand": "Expand",
|
||||||
"session.todo.progress": "{{done}} of {{total}} todos completed",
|
"session.todo.progress": "{{done}} of {{total}} todos completed",
|
||||||
"session.question.progress": "{{current}} of {{total}} questions",
|
"session.question.progress": "{{current}} of {{total}} questions",
|
||||||
|
"session.question.minimize": "Minimize question",
|
||||||
|
"session.question.restore": "Restore question",
|
||||||
|
"session.question.pending.one": "{{count}} pending question",
|
||||||
|
"session.question.pending.other": "{{count}} pending questions",
|
||||||
"session.followupDock.summary.one": "{{count}} queued message",
|
"session.followupDock.summary.one": "{{count}} queued message",
|
||||||
"session.followupDock.summary.other": "{{count}} queued messages",
|
"session.followupDock.summary.other": "{{count}} queued messages",
|
||||||
"session.followupDock.sendNow": "Send now",
|
"session.followupDock.sendNow": "Send now",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { For, Show, createMemo, onCleanup, onMount, type Component } from "solid-js"
|
import { For, Show, createEffect, createMemo, onCleanup, onMount, type Component } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useMutation } from "@tanstack/solid-query"
|
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/session-ui/dock-prompt"
|
import { DockPrompt } from "@opencode-ai/session-ui/dock-prompt"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
|
import { useSpring } from "@opencode-ai/ui/motion-spring"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/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"
|
||||||
|
|
@ -77,9 +78,12 @@ 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,
|
||||||
|
minimized: false,
|
||||||
|
optionsHeight: 180,
|
||||||
})
|
})
|
||||||
|
|
||||||
let root: HTMLDivElement | undefined
|
let root: HTMLDivElement | undefined
|
||||||
|
let optionsRef: HTMLDivElement | undefined
|
||||||
let customRef: HTMLButtonElement | undefined
|
let customRef: HTMLButtonElement | undefined
|
||||||
let optsRef: HTMLButtonElement[] = []
|
let optsRef: HTMLButtonElement[] = []
|
||||||
let replied = false
|
let replied = false
|
||||||
|
|
@ -96,11 +100,13 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||||
const n = Math.min(store.tab + 1, total())
|
const n = Math.min(store.tab + 1, total())
|
||||||
return language.t("session.question.progress", { current: n, total: total() })
|
return language.t("session.question.progress", { current: n, total: total() })
|
||||||
})
|
})
|
||||||
|
|
||||||
const customLabel = () => language.t("ui.messagePart.option.typeOwnAnswer")
|
const customLabel = () => language.t("ui.messagePart.option.typeOwnAnswer")
|
||||||
const customPlaceholder = () => language.t("ui.question.custom.placeholder")
|
const customPlaceholder = () => language.t("ui.question.custom.placeholder")
|
||||||
|
|
||||||
const last = createMemo(() => store.tab >= total() - 1)
|
const last = createMemo(() => store.tab >= total() - 1)
|
||||||
|
const collapse = useSpring(() => (store.minimized ? 1 : 0), { visualDuration: 0.3, bounce: 0 })
|
||||||
|
const hidden = createMemo(() => Math.max(0, Math.min(1, collapse())))
|
||||||
|
const optionsOff = createMemo(() => hidden() > 0.98)
|
||||||
|
|
||||||
const customUpdate = (value: string, selected: boolean = on()) => {
|
const customUpdate = (value: string, selected: boolean = on()) => {
|
||||||
const prev = input().trim()
|
const prev = input().trim()
|
||||||
|
|
@ -192,6 +198,14 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||||
focus(pickFocus())
|
focus(pickFocus())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const el = optionsRef
|
||||||
|
if (!el) return
|
||||||
|
const update = () => setStore("optionsHeight", (height) => Math.max(height, el.scrollHeight))
|
||||||
|
update()
|
||||||
|
createResizeObserver(el, update)
|
||||||
|
})
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
if (focusFrame !== undefined) cancelAnimationFrame(focusFrame)
|
if (focusFrame !== undefined) cancelAnimationFrame(focusFrame)
|
||||||
if (replied) return
|
if (replied) return
|
||||||
|
|
@ -405,7 +419,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||||
const tab = store.tab + 1
|
const tab = store.tab + 1
|
||||||
setStore("tab", tab)
|
setStore("tab", tab)
|
||||||
setStore("editing", false)
|
setStore("editing", false)
|
||||||
focus(pickFocus(tab))
|
if (!store.minimized) focus(pickFocus(tab))
|
||||||
}
|
}
|
||||||
|
|
||||||
const back = () => {
|
const back = () => {
|
||||||
|
|
@ -414,163 +428,212 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||||
const tab = store.tab - 1
|
const tab = store.tab - 1
|
||||||
setStore("tab", tab)
|
setStore("tab", tab)
|
||||||
setStore("editing", false)
|
setStore("editing", false)
|
||||||
focus(pickFocus(tab))
|
if (!store.minimized) focus(pickFocus(tab))
|
||||||
}
|
}
|
||||||
|
|
||||||
const jump = (tab: number) => {
|
const jump = (tab: number) => {
|
||||||
if (sending()) return
|
if (sending()) return
|
||||||
setStore("tab", tab)
|
setStore("tab", tab)
|
||||||
setStore("editing", false)
|
setStore("editing", false)
|
||||||
focus(pickFocus(tab))
|
if (!store.minimized) focus(pickFocus(tab))
|
||||||
|
}
|
||||||
|
|
||||||
|
const minimize = () => {
|
||||||
|
if (sending()) return
|
||||||
|
setStore("editing", false)
|
||||||
|
setStore("minimized", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const restore = () => {
|
||||||
|
if (sending()) return
|
||||||
|
setStore("minimized", false)
|
||||||
|
focus(pickFocus())
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DockPrompt
|
<div data-component="session-question-dock">
|
||||||
kind="question"
|
<DockPrompt
|
||||||
ref={(el) => (root = el)}
|
kind="question"
|
||||||
onKeyDown={nav}
|
ref={(el) => (root = el)}
|
||||||
header={
|
onKeyDown={nav}
|
||||||
<>
|
header={
|
||||||
<div data-slot="question-header-title">{summary()}</div>
|
<>
|
||||||
<Show when={total() > 1}>
|
<div data-slot="question-header-title">{summary()}</div>
|
||||||
<div data-slot="question-progress">
|
<div data-slot="question-header-actions">
|
||||||
<For each={questions()}>
|
<Show when={total() > 1}>
|
||||||
{(_, i) => (
|
<div data-slot="question-progress">
|
||||||
<button
|
<For each={questions()}>
|
||||||
type="button"
|
{(_, i) => (
|
||||||
data-slot="question-progress-segment"
|
<button
|
||||||
data-active={i() === store.tab}
|
type="button"
|
||||||
data-answered={answered(i())}
|
data-slot="question-progress-segment"
|
||||||
disabled={sending()}
|
data-active={i() === store.tab}
|
||||||
onClick={() => jump(i())}
|
data-answered={answered(i())}
|
||||||
aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`}
|
disabled={sending()}
|
||||||
/>
|
onClick={() => jump(i())}
|
||||||
)}
|
aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`}
|
||||||
</For>
|
/>
|
||||||
</div>
|
)}
|
||||||
</Show>
|
</For>
|
||||||
</>
|
</div>
|
||||||
}
|
</Show>
|
||||||
footer={
|
<button
|
||||||
<>
|
type="button"
|
||||||
<Button variant="ghost" size="large" disabled={sending()} onClick={reject} aria-keyshortcuts="Escape">
|
data-component="icon-button"
|
||||||
{language.t("ui.common.dismiss")}
|
data-icon="chevron-down"
|
||||||
</Button>
|
data-size="normal"
|
||||||
<div data-slot="question-footer-actions">
|
data-variant="ghost"
|
||||||
<Show when={store.tab > 0}>
|
|
||||||
<Button variant="secondary" size="large" disabled={sending()} onClick={back}>
|
|
||||||
{language.t("ui.common.back")}
|
|
||||||
</Button>
|
|
||||||
</Show>
|
|
||||||
<Button
|
|
||||||
variant={last() ? "primary" : "secondary"}
|
|
||||||
size="large"
|
|
||||||
disabled={sending()}
|
|
||||||
onClick={next}
|
|
||||||
aria-keyshortcuts="Meta+Enter Control+Enter"
|
|
||||||
>
|
|
||||||
{last() ? language.t("ui.common.submit") : language.t("ui.common.next")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div data-slot="question-text" class="overflow-auto">
|
|
||||||
{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) => (
|
|
||||||
<Option
|
|
||||||
multi={multi()}
|
|
||||||
picked={picked(opt.label)}
|
|
||||||
label={opt.label}
|
|
||||||
description={opt.description}
|
|
||||||
disabled={sending()}
|
|
||||||
ref={(el) => (optsRef[i()] = el)}
|
|
||||||
onFocus={() => setStore("focus", i())}
|
|
||||||
onClick={() => selectOption(i())}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
|
|
||||||
<Show
|
|
||||||
when={store.editing}
|
|
||||||
fallback={
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
ref={customRef}
|
|
||||||
data-slot="question-option"
|
|
||||||
data-custom="true"
|
|
||||||
data-picked={on()}
|
|
||||||
role={multi() ? "checkbox" : "radio"}
|
|
||||||
aria-checked={on()}
|
|
||||||
disabled={sending()}
|
|
||||||
onFocus={() => setStore("focus", options().length)}
|
|
||||||
onClick={customOpen}
|
|
||||||
>
|
|
||||||
<Mark multi={multi()} picked={on()} onClick={toggleCustomMark} />
|
|
||||||
<span data-slot="question-option-main">
|
|
||||||
<span data-slot="option-label">{customLabel()}</span>
|
|
||||||
<span data-slot="option-description">{input() || customPlaceholder()}</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<form
|
|
||||||
data-slot="question-option"
|
|
||||||
data-custom="true"
|
|
||||||
data-picked={on()}
|
|
||||||
role={multi() ? "checkbox" : "radio"}
|
|
||||||
aria-checked={on()}
|
|
||||||
onMouseDown={(e) => {
|
|
||||||
if (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()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Mark multi={multi()} picked={on()} onClick={toggleCustomMark} />
|
|
||||||
<span data-slot="question-option-main">
|
|
||||||
<span data-slot="option-label">{customLabel()}</span>
|
|
||||||
<textarea
|
|
||||||
ref={focusCustom}
|
|
||||||
data-slot="question-custom-input"
|
|
||||||
placeholder={customPlaceholder()}
|
|
||||||
value={input()}
|
|
||||||
rows={1}
|
|
||||||
disabled={sending()}
|
disabled={sending()}
|
||||||
onKeyDown={(e) => {
|
style={{ transform: `rotate(${hidden() * 180}deg)` }}
|
||||||
if (e.key === "Escape") {
|
onClick={store.minimized ? restore : minimize}
|
||||||
|
aria-label={language.t(store.minimized ? "session.question.restore" : "session.question.minimize")}
|
||||||
|
>
|
||||||
|
<Icon name="chevron-down" size="small" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
footer={
|
||||||
|
<>
|
||||||
|
<Button variant="ghost" size="large" disabled={sending()} onClick={reject} aria-keyshortcuts="Escape">
|
||||||
|
{language.t("ui.common.dismiss")}
|
||||||
|
</Button>
|
||||||
|
<div data-slot="question-footer-actions">
|
||||||
|
<Show when={store.tab > 0}>
|
||||||
|
<Button variant="secondary" size="large" disabled={sending()} onClick={back}>
|
||||||
|
{language.t("ui.common.back")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
<Button
|
||||||
|
variant={last() ? "primary" : "secondary"}
|
||||||
|
size="large"
|
||||||
|
disabled={sending()}
|
||||||
|
onClick={next}
|
||||||
|
aria-keyshortcuts="Meta+Enter Control+Enter"
|
||||||
|
>
|
||||||
|
{last() ? language.t("ui.common.submit") : language.t("ui.common.next")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-slot="question-text"
|
||||||
|
style={{
|
||||||
|
display: store.minimized ? "-webkit-box" : undefined,
|
||||||
|
"-webkit-line-clamp": store.minimized ? "3" : undefined,
|
||||||
|
"-webkit-box-orient": store.minimized ? "vertical" : undefined,
|
||||||
|
overflow: store.minimized ? "hidden" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{question()?.question}
|
||||||
|
</div>
|
||||||
|
<Show when={!store.minimized}>
|
||||||
|
<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>
|
||||||
|
</Show>
|
||||||
|
<div
|
||||||
|
ref={(el) => (optionsRef = el)}
|
||||||
|
data-slot="question-options"
|
||||||
|
aria-hidden={store.minimized || optionsOff() ? "true" : undefined}
|
||||||
|
classList={{ "pointer-events-none": hidden() > 0.1 }}
|
||||||
|
style={{
|
||||||
|
"max-height": `${Math.max(0, store.optionsHeight * (1 - hidden()))}px`,
|
||||||
|
opacity: `${Math.max(0, Math.min(1, 1 - hidden()))}`,
|
||||||
|
visibility: optionsOff() ? "hidden" : "visible",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<For each={options()}>
|
||||||
|
{(opt, i) => (
|
||||||
|
<Option
|
||||||
|
multi={multi()}
|
||||||
|
picked={picked(opt.label)}
|
||||||
|
label={opt.label}
|
||||||
|
description={opt.description}
|
||||||
|
disabled={sending()}
|
||||||
|
ref={(el) => (optsRef[i()] = el)}
|
||||||
|
onFocus={() => setStore("focus", i())}
|
||||||
|
onClick={() => selectOption(i())}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
|
||||||
|
<Show
|
||||||
|
when={store.editing}
|
||||||
|
fallback={
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
ref={customRef}
|
||||||
|
data-slot="question-option"
|
||||||
|
data-custom="true"
|
||||||
|
data-picked={on()}
|
||||||
|
role={multi() ? "checkbox" : "radio"}
|
||||||
|
aria-checked={on()}
|
||||||
|
disabled={sending()}
|
||||||
|
onFocus={() => setStore("focus", options().length)}
|
||||||
|
onClick={customOpen}
|
||||||
|
>
|
||||||
|
<Mark multi={multi()} picked={on()} onClick={toggleCustomMark} />
|
||||||
|
<span data-slot="question-option-main">
|
||||||
|
<span data-slot="option-label">{customLabel()}</span>
|
||||||
|
<span data-slot="option-description">{input() || customPlaceholder()}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
data-slot="question-option"
|
||||||
|
data-custom="true"
|
||||||
|
data-picked={on()}
|
||||||
|
role={multi() ? "checkbox" : "radio"}
|
||||||
|
aria-checked={on()}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
if (sending()) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setStore("editing", false)
|
|
||||||
focus(options().length)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ((e.metaKey || e.ctrlKey) && !e.altKey) return
|
if (e.target instanceof HTMLTextAreaElement) return
|
||||||
if (e.key !== "Enter" || e.shiftKey) return
|
const input = e.currentTarget.querySelector('[data-slot="question-custom-input"]')
|
||||||
|
if (input instanceof HTMLTextAreaElement) input.focus()
|
||||||
|
}}
|
||||||
|
onSubmit={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
commitCustom()
|
commitCustom()
|
||||||
}}
|
}}
|
||||||
onInput={(e) => {
|
>
|
||||||
customUpdate(e.currentTarget.value)
|
<Mark multi={multi()} picked={on()} onClick={toggleCustomMark} />
|
||||||
resizeInput(e.currentTarget)
|
<span data-slot="question-option-main">
|
||||||
}}
|
<span data-slot="option-label">{customLabel()}</span>
|
||||||
/>
|
<textarea
|
||||||
</span>
|
ref={focusCustom}
|
||||||
</form>
|
data-slot="question-custom-input"
|
||||||
</Show>
|
placeholder={customPlaceholder()}
|
||||||
</div>
|
value={input()}
|
||||||
</DockPrompt>
|
rows={1}
|
||||||
|
disabled={sending()}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
e.preventDefault()
|
||||||
|
setStore("editing", false)
|
||||||
|
focus(options().length)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ((e.metaKey || e.ctrlKey) && !e.altKey) return
|
||||||
|
if (e.key !== "Enter" || e.shiftKey) return
|
||||||
|
e.preventDefault()
|
||||||
|
commitCustom()
|
||||||
|
}}
|
||||||
|
onInput={(e) => {
|
||||||
|
customUpdate(e.currentTarget.value)
|
||||||
|
resizeInput(e.currentTarget)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</form>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</DockPrompt>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -860,6 +860,13 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-slot="question-header-actions"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="question-progress"] {
|
[data-slot="question-progress"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -918,6 +925,7 @@
|
||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
color: var(--v2-text-text-base);
|
color: var(--v2-text-text-base);
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
@ -929,6 +937,7 @@
|
||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
color: var(--v2-text-text-muted);
|
color: var(--v2-text-text-muted);
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="question-options"] {
|
[data-slot="question-options"] {
|
||||||
|
|
@ -936,8 +945,8 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
padding: 1px 1px 8px;
|
padding: 1px 1px 16px;
|
||||||
flex-shrink: 0;
|
flex: 1 1 auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|
@ -1126,6 +1135,42 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-component="question-minimized-dock"] {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
[data-slot="question-minimized-trigger"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--v2-text-text-base);
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
touch-action: manipulation;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="icon"] {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-minimized-summary"] {
|
||||||
|
flex: 1;
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
color: var(--v2-text-text-base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[data-component="question-answers"] {
|
[data-component="question-answers"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue