feat(desktop): refresh question composer (#54822)

Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
This commit is contained in:
usrnk1
2026-10-14 00:02:38 +02:00
committed by GitHub
Unverified
parent 2a593c0d1b
commit 3ec856b6c2
14 changed files with 1517 additions and 1104 deletions
@@ -1,19 +1,187 @@
import { expect, story } from "../../storybook/playwright/story"
story("shows question navigation shortcuts", async ({ mount, page }) => {
const component = await mount("app-current-session-surface--question-request")
const shortcut = await page.evaluate(() => (/Mac|iPhone|iPad|iPod/.test(navigator.platform) ? "⌘⏎" : "Ctrl+⏎"))
const next = component.getByRole("button", { name: "Next", exact: true })
await expect(next.locator('[data-slot="question-submit-shortcut"]')).toHaveText(shortcut)
story("resizes the dock to fit different answer counts", async ({ mount }) => {
const component = await mount("app-current-session-surface--question-answer-counts")
const question = component.locator('[data-component="dock-prompt"][data-kind="question"]')
const next = question.getByRole("button", { name: "Next", exact: true })
await expect(question.getByRole("radio", { name: /Focused/ })).toBeFocused()
await expect(next).toBeEnabled()
const height = () => question.evaluate((element) => element.getBoundingClientRect().height)
const initial = await height()
await next.click()
await expect(component.getByRole("button", { name: "Submit", exact: true })).toContainText(shortcut)
await expect.poll(height).toBeGreaterThan(initial)
await expect(question.getByRole("checkbox", { name: "Diagnostics", exact: true })).toBeVisible()
const expanded = await height()
const back = question.getByRole("button", { name: "Back", exact: true })
await expect(back).toBeEnabled()
await back.click()
await expect.poll(height).toBeLessThan(expanded)
await expect(question.getByRole("radio", { name: /Focused/ })).toBeVisible()
})
story("selects single answers and toggles multiple answers with number keys", async ({ mount }) => {
const component = await mount("app-current-session-surface--question-request")
const question = component.locator('[data-component="dock-prompt"][data-kind="question"]')
const focused = question.getByRole("radio", { name: /Focused/ })
const wide = question.getByRole("radio", { name: /Wide/ })
const customRadio = question.getByRole("radio", { name: "Type your own answer…", exact: true })
const input = question.getByRole("textbox", { name: "Type your own answer…", exact: true })
const next = question.getByRole("button", { name: "Next", exact: true })
await expect(focused).toBeFocused()
await focused.press("2")
await expect(wide).toBeChecked()
await expect(focused).not.toBeChecked()
await expect(wide).toBeFocused()
await next.press("1")
await expect(focused).toBeChecked()
await expect(wide).not.toBeChecked()
await focused.press("3")
await expect(customRadio).toBeChecked()
await expect(input).toBeFocused()
await expect(focused).not.toBeChecked()
await expect(wide).not.toBeChecked()
await input.press("Enter")
await expect(customRadio).toBeFocused()
await customRadio.press("2")
await expect(wide).toBeChecked()
await expect(customRadio).not.toBeChecked()
await next.click()
const files = question.getByRole("checkbox", { name: "Changed files", exact: true })
const terminal = question.getByRole("checkbox", { name: "Terminal", exact: true })
const customCheckbox = question.getByRole("checkbox", { name: "Type your own answer…", exact: true })
await expect(files).toBeFocused()
await files.press("1")
await expect(files).toBeChecked()
await files.press("2")
await expect(terminal).toBeChecked()
await expect(files).toBeChecked()
await terminal.press("2")
await expect(terminal).not.toBeChecked()
await expect(files).toBeChecked()
await terminal.press("4")
await expect(customCheckbox).toBeChecked()
await expect(input).toBeFocused()
await expect(files).toBeChecked()
await expect(terminal).not.toBeChecked()
await input.press("Enter")
await expect(customCheckbox).toBeFocused()
await customCheckbox.press("4")
await expect(customCheckbox).not.toBeChecked()
await expect(files).toBeChecked()
await expect(terminal).not.toBeChecked()
})
story("ignores modified, out-of-range, and repeated number shortcuts", async ({ mount, page }) => {
const component = await mount("app-current-session-surface--question-request")
const question = component.locator('[data-component="dock-prompt"][data-kind="question"]')
const focused = question.getByRole("radio", { name: /Focused/ })
const wide = question.getByRole("radio", { name: /Wide/ })
await expect(focused).toBeFocused()
await focused.press("1")
await focused.press("9")
await focused.press("Alt+2")
await expect(focused).toBeChecked()
await expect(wide).not.toBeChecked()
await question.getByRole("button", { name: "Next", exact: true }).click()
const terminal = question.getByRole("checkbox", { name: "Terminal", exact: true })
await expect(question.getByRole("checkbox", { name: "Changed files", exact: true })).toBeFocused()
await page.keyboard.down("2")
await expect(terminal).toBeChecked()
await page.keyboard.down("2")
await expect(terminal).toBeChecked()
await page.keyboard.up("2")
})
story("types digits into the custom answer without selecting numbered options", async ({ mount }) => {
const component = await mount("app-current-session-surface--question-request")
const question = component.locator('[data-component="dock-prompt"][data-kind="question"]')
const focused = question.getByRole("radio", { name: /Focused/ })
const custom = question.getByRole("radio", { name: "Type your own answer…", exact: true })
const input = question.getByRole("textbox", { name: "Type your own answer…", exact: true })
await expect(focused).toBeFocused()
await focused.press("3")
await expect(input).toBeFocused()
await input.press("1")
await input.press("2")
await input.press("3")
await expect(input).toHaveValue("123")
await expect(custom).toBeChecked()
await expect(focused).not.toBeChecked()
await expect(question.getByRole("radio", { name: /Wide/ })).not.toBeChecked()
})
story("shows question navigation shortcuts in buttons and tooltips", async ({ mount, page }) => {
const component = await mount("app-current-session-surface--question-request")
const mac = await page.evaluate(() => /Mac|iPhone|iPad|iPod/.test(navigator.platform))
const shortcut = mac ? "⌘⏎" : "Ctrl+⏎"
const next = component.getByRole("button", { name: "Next", exact: true })
await expect(next.locator('[data-slot="question-submit-shortcut"]')).toBeVisible()
await expect(next.locator('[data-slot="question-submit-shortcut"]')).toHaveText(shortcut)
await expect(next).toHaveAttribute("aria-keyshortcuts", "Meta+Enter Control+Enter")
await next.hover()
await expect(page.getByRole("tooltip")).toContainText("Next")
await expect(page.getByRole("tooltip")).toContainText(shortcut)
await next.press(mac ? "Meta+Enter" : "Control+Enter")
const submit = component.getByRole("button", { name: "Submit", exact: true })
await expect(submit.locator('[data-slot="question-submit-shortcut"]')).toBeVisible()
await expect(submit.locator('[data-slot="question-submit-shortcut"]')).toHaveText(shortcut)
await expect(submit).toHaveAttribute("aria-keyshortcuts", "Meta+Enter Control+Enter")
await submit.hover()
await expect(page.getByRole("tooltip")).toContainText("Submit")
await expect(page.getByRole("tooltip")).toContainText(shortcut)
const back = component.getByRole("button", { name: "Back", exact: true })
const backShortcut = await page.evaluate(() => (/Mac|iPhone|iPad|iPod/.test(navigator.platform) ? "⌘[" : "Alt+←"))
await expect(back).toHaveText("Back")
await expect(back).toHaveAttribute("aria-keyshortcuts", mac ? "Meta+[" : "Alt+ArrowLeft")
await back.hover()
await expect(page.getByRole("tooltip")).toContainText(backShortcut)
await page.keyboard.press(
await page.evaluate(() => (/Mac|iPhone|iPad|iPod/.test(navigator.platform) ? "Meta+[" : "Alt+ArrowLeft")),
)
await expect(page.getByRole("tooltip")).toContainText(mac ? "⌘[" : "Alt+←")
await back.press(mac ? "Meta+[" : "Alt+ArrowLeft")
await expect(next).toBeVisible()
})
story("keeps the custom answer input and dock geometry stable while editing", async ({ mount }) => {
const component = await mount("app-current-session-surface--question-request")
const question = component.locator('[data-component="dock-prompt"][data-kind="question"]')
const row = question.locator('[data-slot="question-option"][data-custom="true"]')
const input = question.getByRole("textbox", { name: "Type your own answer…", exact: true })
const toggle = question.getByRole("radio", { name: "Type your own answer…", exact: true })
await expect(question.getByRole("radio", { name: /Focused/ })).toBeFocused()
await expect(input).toBeEditable()
await expect(input).toHaveValue("")
const node = await input.evaluateHandle((element) => element)
const height = () => question.evaluate((element) => element.getBoundingClientRect().height)
const initial = await height()
const draft = "A custom answer long enough to wrap if the editor becomes multiline instead of staying single-line"
const predefined = question.getByRole("radio", { name: /Focused/ })
await predefined.click()
await input.focus()
await expect(input).toBeFocused()
await expect(predefined).toBeChecked()
await expect(toggle).not.toBeChecked()
await row.click({ position: { x: 4, y: 4 } })
await expect(toggle).toBeChecked()
await expect(input).toBeFocused()
await expect(input).toHaveValue("")
await expect(predefined).not.toBeChecked()
await input.fill(draft)
await expect(input).toHaveValue(draft)
await expect(toggle).toBeChecked()
await expect(predefined).not.toBeChecked()
await expect.poll(height).toBe(initial)
await input.press("Enter")
await expect(toggle).toBeFocused()
await expect(input).toHaveValue(draft)
await row.click({ position: { x: 30, y: 20 } })
await expect(input).toBeFocused()
await expect(input).toHaveValue(draft)
await expect
.poll(() => input.evaluate((element, original) => element === original && original.isConnected, node))
.toBe(true)
await node.dispose()
})
@@ -83,6 +83,93 @@ test("shows a pending question dock", async ({ page }) => {
expect((await reply).postDataJSON()).toEqual({ answer: { q0: "minimal" } })
})
test("replies with predefined choices and the edited custom multiselect answer", async ({ page }) => {
await mockServer(page, {
forms: [
{
id: "frm_question_multiselect",
sessionID,
title: "Questions",
metadata: { kind: "question" },
fields: [
{
key: "q0",
type: "multiselect",
title: "Include",
description: "Which supporting surfaces should remain visible?",
options: [
{ value: "files", label: "Changed files" },
{ value: "terminal", label: "Terminal" },
],
custom: true,
},
],
},
],
})
await page.goto(sessionHref(sessionID))
await expectSessionTitle(page, title)
const question = page.locator('[data-component="dock-prompt"][data-kind="question"]')
const files = question.getByRole("checkbox", { name: "Changed files", exact: true })
const terminal = question.getByRole("checkbox", { name: "Terminal", exact: true })
const custom = question.getByRole("checkbox", { name: "Type your own answer…", exact: true })
const input = question.getByRole("textbox", { name: "Type your own answer…", exact: true })
const cancellations: string[] = []
page.on("request", (request) => {
if (
request.method() === "DELETE" &&
new URL(request.url()).pathname === `/api/session/${sessionID}/form/frm_question_multiselect`
)
cancellations.push(request.url())
})
await expect(files).not.toBeChecked()
await files.click()
await input.fill("Original custom answer")
await expect(custom).toBeChecked()
await expect(files).toBeChecked()
await terminal.click()
await expect(terminal).toBeChecked()
await expect(custom).toBeChecked()
await expect(files).toBeChecked()
await custom.click()
await expect(custom).not.toBeChecked()
await expect(input).toHaveValue("Original custom answer")
await expect(files).toBeChecked()
await expect(terminal).toBeChecked()
await input.focus()
await expect(custom).not.toBeChecked()
await expect(files).toBeChecked()
await expect(terminal).toBeChecked()
await custom.click()
await expect(custom).toBeChecked()
await expect(input).toHaveValue("Original custom answer")
await expect(files).toBeChecked()
await expect(terminal).toBeChecked()
await input.fill("Updated custom answer")
await input.press("Escape")
await expect(input).not.toBeFocused()
await expect(question.getByRole("button", { name: "Submit", exact: true })).toBeEnabled()
await expect(input).toHaveValue("Updated custom answer")
await expect(custom).toBeChecked()
await expect(files).toBeChecked()
await expect(terminal).toBeChecked()
expect(cancellations).toEqual([])
const reply = page.waitForRequest(
(request) =>
request.method() === "POST" &&
new URL(request.url()).pathname === `/api/session/${sessionID}/form/frm_question_multiselect/reply`,
)
await question.getByRole("button", { name: "Submit", exact: true }).press("Control+Enter")
expect((await reply).postDataJSON()).toEqual({ answer: { q0: ["files", "terminal", "Updated custom answer"] } })
expect(cancellations).toEqual([])
})
test("shows a pending permission dock", async ({ page }) => {
const replies: { sessionID: string; permissionID: string; body: unknown }[] = []
await mockServer(page, {
+1
View File
@@ -553,6 +553,7 @@ export const dict = {
"session.messages.jumpToLatest": "Jump to latest",
"session.question.progress": "{{current}} of {{total}} questions",
"session.question.title": "Question",
"session.websearch.title": "Third-party web search",
"session.websearch.description": "Select the search provider agents use to search the web",
"session.websearch.provider": "Search provider",
@@ -6,6 +6,7 @@ import { IconButton } from "@opencode/ui/icon-button"
import { Tooltip } from "@opencode/ui/tooltip"
import { DockPrompt } from "@opencode/session-ui/dock-prompt"
import { Icon } from "@opencode/ui/icon"
import { TextInput } from "@opencode/ui/text-input"
import { useSpring } from "@opencode/ui/motion-spring"
import { showToast } from "@/shell/notifications/toast"
import type { FormAnswer, FormInfo, FormMultiselectField, FormStringField } from "@opencode/client/promise"
@@ -18,7 +19,7 @@ import { useCommand } from "@/shell/commands/command"
const cache = new Map<string, { tab: number; answers: string[][]; custom: string[]; customOn: boolean[] }>()
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
const IS_MAC = /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
type QuestionField = FormStringField | FormMultiselectField
@@ -26,12 +27,12 @@ function questionField(field: FormInfo["fields"][number]): field is QuestionFiel
return field.type === "string" || field.type === "multiselect"
}
function Mark(props: { multi: boolean; picked: boolean; onClick?: (event: MouseEvent) => void }) {
function Mark(props: { multi: boolean; picked: boolean }) {
return (
<span data-slot="question-option-check" aria-hidden="true" onClick={props.onClick}>
<span data-slot="question-option-check" aria-hidden="true">
<span data-slot="question-option-box" data-type={props.multi ? "checkbox" : "radio"} data-picked={props.picked}>
<Show when={props.multi} fallback={<span data-slot="question-option-radio-dot" />}>
<Icon name="check-small" size="small" />
<Icon name="check" size="normal" />
</Show>
</span>
</span>
@@ -43,6 +44,7 @@ function Option(props: {
picked: boolean
label: string
description?: string
shortcut?: string
disabled: boolean
ref?: (el: HTMLButtonElement) => void
onFocus?: VoidFunction
@@ -56,15 +58,20 @@ function Option(props: {
data-picked={props.picked}
role={props.multi ? "checkbox" : "radio"}
aria-checked={props.picked}
aria-keyshortcuts={props.shortcut}
disabled={props.disabled}
onFocus={props.onFocus}
onClick={props.onClick}
>
<Mark multi={props.multi} picked={props.picked} />
<span data-slot="question-option-main">
<span data-slot="option-label">{props.label}</span>
<span data-slot="option-label">
<bdi>{props.label}</bdi>
</span>
<Show when={props.description}>
<span data-slot="option-description">{props.description}</span>
<span data-slot="option-description">
<bdi>{props.description}</bdi>
</span>
</Show>
</span>
</button>
@@ -93,9 +100,9 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
const [store, setStore] = createStore({
tab: cached?.tab ?? 0,
answers: cached?.answers ?? ([] as string[][]),
custom: cached?.custom ?? ([] as string[]),
customOn: cached?.customOn ?? ([] as boolean[]),
answers: cached?.answers ?? [],
custom: cached?.custom ?? [],
customOn: cached?.customOn ?? [],
editing: false,
focus: 0,
minimized: false,
@@ -105,6 +112,7 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
let root: HTMLDivElement | undefined
let optionsRef: HTMLDivElement | undefined
let customRef: HTMLButtonElement | undefined
let customInput: HTMLInputElement | undefined
let optsRef: HTMLButtonElement[] = []
let replied = false
let focusFrame: number | undefined
@@ -117,13 +125,13 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
const count = createMemo(() => options().length + 1)
const summary = createMemo(() => {
if (total() === 1) return language.t("session.question.title")
const n = Math.min(store.tab + 1, total())
return language.t("session.question.progress", { current: n, total: total() })
})
const customLabel = () => language.t("ui.messagePart.option.typeOwnAnswer")
const customPlaceholder = () => language.t("ui.question.custom.placeholder")
const customLabel = () => language.t("ui.question.custom.answerPlaceholder")
const last = createMemo(() => store.tab >= total() - 1)
const collapse = useSpring(() => (store.minimized ? 1 : 0), { visualDuration: 0.3, bounce: 0 })
@@ -255,10 +263,7 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
})
})
const fail = (err: unknown) => {
const message = err instanceof Error ? err.message : String(err)
showToast({ title: language.t("common.requestFailed"), description: message })
}
const fail = (err: Error) => showToast({ title: language.t("common.requestFailed"), description: err.message })
const replyMutation = useMutation(() => ({
mutationFn: (answer: FormAnswer) =>
@@ -274,7 +279,8 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
}))
const rejectMutation = useMutation(() => ({
mutationFn: () => serverSDK.api.session.form.cancel({ sessionID: props.request.sessionID, formID: props.request.id }),
mutationFn: () =>
serverSDK.api.session.form.cancel({ sessionID: props.request.sessionID, formID: props.request.id }),
onMutate: () => {
props.onSubmit()
},
@@ -320,12 +326,9 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
const picked = (answer: string) => store.answers[store.tab]?.includes(answer) ?? false
const pick = (answer: string, custom: boolean = false) => {
const pick = (answer: string) => {
setStore("answers", store.tab, [answer])
if (custom) setStore("custom", store.tab, answer)
if (!custom) setStore("customOn", store.tab, false)
setStore("customOn", store.tab, false)
setStore("editing", false)
}
@@ -345,6 +348,7 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
setStore("customOn", store.tab, true)
setStore("editing", true)
customUpdate(input(), true)
customInput?.focus()
return
}
@@ -355,6 +359,7 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
if (next) {
setStore("editing", true)
customUpdate(input(), true)
customInput?.focus()
return
}
@@ -366,15 +371,6 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
focus(options().length)
}
const customOpen = () => {
if (sending()) return
setStore("focus", options().length)
if (!on()) setStore("customOn", store.tab, true)
setStore("editing", true)
customUpdate(input(), true)
}
const move = (step: number) => {
if (store.editing || sending()) return
focus(store.focus + step)
@@ -417,10 +413,29 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
if (store.editing) return
if (!(target instanceof HTMLElement)) return
if (event.altKey || event.ctrlKey || event.metaKey) return
if (/^[1-9]$/.test(event.key)) {
if (event.repeat || event.isComposing || store.minimized || sending()) return
const index = Number(event.key) - 1
if (index >= count()) return
event.preventDefault()
if (index === options().length) {
customToggle()
return
}
selectOption(index)
focus(index)
return
}
if (!(target instanceof HTMLElement)) return
if (event.key === "ArrowDown" || event.key === "ArrowRight") {
event.preventDefault()
move(1)
@@ -450,12 +465,6 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
const selectOption = (optIndex: number) => {
if (sending()) return
if (optIndex === options().length) {
customOpen()
return
}
const opt = options()[optIndex]
if (!opt) return
@@ -476,24 +485,6 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
focus(options().length)
}
const resizeInput = (el: HTMLTextAreaElement) => {
el.style.height = "0px"
el.style.height = `${el.scrollHeight}px`
}
const focusCustom = (el: HTMLTextAreaElement) => {
setTimeout(() => {
el.focus()
resizeInput(el)
}, 0)
}
const toggleCustomMark = (event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
customToggle()
}
const next = () => {
if (sending()) return
@@ -505,29 +496,23 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
return
}
const tab = store.tab + 1
setStore("tab", tab)
setStore("editing", false)
if (!store.minimized) focus(pickFocus(tab))
jump(store.tab + 1)
}
const back = () => {
if (sending()) return
if (store.tab <= 0) return
const tab = store.tab - 1
setStore("tab", tab)
setStore("editing", false)
if (!store.minimized) focus(pickFocus(tab))
jump(store.tab - 1)
}
const jump = (tab: number) => {
if (sending()) return
if (sending() || tab === store.tab) return
setStore("tab", tab)
setStore("editing", false)
if (optionsRef) setStore("optionsHeight", optionsRef.scrollHeight)
if (!store.minimized) focus(pickFocus(tab))
}
@@ -564,33 +549,57 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
onKeyDown={nav}
header={
<>
<div data-slot="question-header-title">{summary()}</div>
<div data-slot="question-header-actions">
<Show when={total() > 1}>
<div data-slot="question-progress">
<For each={questions()}>
{(_, i) => (
<button
type="button"
data-slot="question-progress-segment"
data-active={i() === store.tab}
data-answered={answered(i())}
disabled={sending()}
onClick={() => jump(i())}
aria-label={language.t("ui.tool.questions.numbered", { number: i() + 1 })}
/>
)}
</For>
<div data-slot="question-header-top">
<div data-slot="question-header-title">
<bdi>{summary()}</bdi>
</div>
<div data-slot="question-header-actions">
<Show when={total() > 1}>
<div data-slot="question-progress">
<For each={questions()}>
{(_, i) => (
<button
type="button"
data-slot="question-progress-segment"
data-active={i() === store.tab}
data-answered={answered(i())}
disabled={sending()}
onClick={() => jump(i())}
aria-label={language.t("ui.tool.questions.numbered", { number: i() + 1 })}
aria-current={i() === store.tab ? "step" : undefined}
/>
)}
</For>
</div>
</Show>
<IconButton
icon={<Icon name="outline-chevron-down" size="normal" />}
variant="ghost"
size="large"
disabled={sending()}
style={{ transform: `rotate(${hidden() * 180}deg)` }}
onClick={store.minimized ? restore : minimize}
aria-label={language.t(store.minimized ? "session.question.restore" : "session.question.minimize")}
/>
</div>
</div>
<div data-slot="question-details">
<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,
}}
>
<bdi>{question()?.question}</bdi>
</div>
<Show when={!store.minimized}>
<div data-slot="question-hint">
{language.t(multi() ? "ui.question.multiHint" : "ui.question.singleHint")}
</div>
</Show>
<IconButton
icon={<Icon name="chevron-down" size="small" />}
variant="ghost"
disabled={sending()}
style={{ transform: `rotate(${hidden() * 180}deg)` }}
onClick={store.minimized ? restore : minimize}
aria-label={language.t(store.minimized ? "session.question.restore" : "session.question.minimize")}
/>
</div>
</>
}
@@ -606,7 +615,7 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
value={
<>
{language.t("ui.common.back")}
<span class="opacity-60">{backShortcut()}</span>
<span style={{ opacity: 0.6 }}>{backShortcut()}</span>
</>
}
>
@@ -621,41 +630,38 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
</Button>
</Tooltip>
</Show>
<Button
variant={last() ? "submit" : "neutral"}
size="large"
disabled={sending()}
onClick={next}
aria-keyshortcuts="Meta+Enter Control+Enter"
<Tooltip
placement="top"
value={
<>
{language.t(last() ? "ui.common.submit" : "ui.common.next")}
<span style={{ opacity: 0.6 }}>{submitShortcut()}</span>
</>
}
>
{last() ? language.t("ui.common.submit") : language.t("ui.common.next")}
<span data-slot="question-submit-shortcut" aria-hidden="true" class="text-11-medium opacity-60">
{submitShortcut()}
</span>
</Button>
<Button
variant={last() ? "submit" : "neutral"}
size="large"
disabled={sending()}
onClick={next}
aria-keyshortcuts="Meta+Enter Control+Enter"
>
{language.t(last() ? "ui.common.submit" : "ui.common.next")}
<span data-slot="question-submit-shortcut" aria-hidden="true">
{submitShortcut()}
</span>
</Button>
</Tooltip>
</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"
role={multi() ? "group" : "radiogroup"}
aria-label={question()?.question}
inert={store.minimized}
aria-hidden={store.minimized || optionsOff() ? "true" : undefined}
classList={{ "pointer-events-none": hidden() > 0.1 }}
style={{
@@ -671,6 +677,7 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
picked={picked(opt.value)}
label={opt.label}
description={opt.description}
shortcut={i() < 9 ? String(i() + 1) : undefined}
disabled={sending()}
ref={(el) => (optsRef[i()] = el)}
onFocus={() => setStore("focus", i())}
@@ -679,89 +686,75 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
)}
</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" dir="auto">
{input() || customPlaceholder()}
</span>
</span>
</button>
}
<div
data-slot="question-option"
data-custom="true"
data-picked={on()}
data-editing={store.editing}
data-disabled={sending()}
onClick={(event) => {
if (event.target !== event.currentTarget || sending()) return
setStore("focus", options().length)
setStore("customOn", store.tab, true)
setStore("editing", true)
customUpdate(input(), true)
customInput?.focus()
}}
>
<form
data-slot="question-option"
data-custom="true"
data-picked={on()}
<button
type="button"
ref={customRef}
data-slot="question-custom-toggle"
role={multi() ? "checkbox" : "radio"}
aria-checked={on()}
onMouseDown={(e) => {
if (sending()) {
aria-label={customLabel()}
aria-keyshortcuts={count() <= 9 ? String(count()) : undefined}
disabled={sending()}
onFocus={() => setStore("focus", options().length)}
onClick={customToggle}
>
<Mark multi={multi()} picked={on()} />
</button>
<TextInput
ref={(el) => (customInput = el)}
class="question-custom-field"
dir="auto"
aria-label={customLabel()}
placeholder={customLabel()}
value={input()}
disabled={sending()}
onFocus={() => {
if (focusFrame !== undefined) cancelAnimationFrame(focusFrame)
focusFrame = undefined
setStore("focus", options().length)
setStore("editing", true)
}}
onClick={() => {
setStore("customOn", store.tab, true)
customUpdate(input(), true)
}}
onBlur={() => setStore("editing", false)}
onKeyDown={(e) => {
if (e.key === "Escape") {
e.preventDefault()
setStore("editing", false)
focus(options().length)
return
}
if (e.target instanceof HTMLTextAreaElement) return
const input = e.currentTarget.querySelector('[data-slot="question-custom-input"]')
if ((e.metaKey || e.ctrlKey) && !e.altKey) return
if (input instanceof HTMLTextAreaElement) input.focus()
}}
onSubmit={(e) => {
if (e.key !== "Enter") return
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"
dir="auto"
placeholder={customPlaceholder()}
value={input()}
rows={1}
disabled={sending()}
style={{ "unicode-bidi": "plaintext", "text-align": "start" }}
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>
onInput={(e) => {
setStore("customOn", store.tab, true)
customUpdate(e.currentTarget.value, true)
}}
/>
</div>
</div>
</DockPrompt>
</div>
@@ -142,6 +142,29 @@ export const QuestionRequest = {
),
}
export const QuestionAnswerCounts = {
render: () => (
<SessionPreview
title="Choose supporting surfaces"
description={description}
document={questionPendingDocument}
request={{
type: "question",
value: {
...activeQuestionRequest,
fields: [
activeQuestionRequest.fields[0],
{
...activeQuestionRequest.fields[1],
options: [...activeQuestionRequest.fields[1].options, { value: "diagnostics", label: "Diagnostics" }],
},
],
},
}}
/>
),
}
export const WebSearchRequest = {
render: () => (
<SessionPreview
@@ -19,19 +19,29 @@ export const Question = {
render: () => (
<DockPrompt
kind="question"
header={<div class="text-13-medium text-text-strong">Session layout</div>}
header={
<>
<div data-slot="question-header-top">
<div data-slot="question-header-title">Question</div>
</div>
<div data-slot="question-details">
<div data-slot="question-text">Which Session layout should I implement?</div>
<div data-slot="question-hint">Select one answer</div>
</div>
</>
}
footer={
<>
<Button size="normal" variant="ghost">
<Button size="large" variant="ghost">
Dismiss
</Button>
<Button size="normal" variant="contrast">
<Button size="large" variant="submit">
Submit
</Button>
</>
}
>
<div class="text-13-regular text-text-base">Which Session layout should I implement?</div>
<div data-slot="question-options" />
</DockPrompt>
),
}
@@ -933,351 +933,6 @@
}
}
[data-component="dock-prompt"][data-kind="question"] {
position: relative;
display: flex;
flex-direction: column;
gap: 0;
min-height: 0;
max-height: var(--question-prompt-max-height, 100dvh);
[data-slot="question-body"] {
display: flex;
flex-direction: column;
gap: 16px;
flex: 1;
min-height: 0;
padding: 8px 8px 0;
}
[data-slot="question-header"] {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 10px;
}
[data-slot="question-header-title"] {
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);
min-width: 0;
}
[data-slot="question-header-actions"] {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
[data-slot="question-progress"] {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
[data-slot="question-progress-segment"] {
width: 16px;
height: 16px;
padding: 0;
border: 0;
background: transparent;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
touch-action: manipulation;
&::after {
content: "";
width: 16px;
height: 2px;
border-radius: 999px;
background-color: var(--v2-icon-icon-muted);
transition: background-color 0.2s ease;
}
&[data-active="true"]::after {
background-color: var(--icon-strong-base);
}
&[data-answered="true"]::after {
background-color: var(--icon-interactive-base);
}
&:disabled {
cursor: not-allowed;
opacity: 0.6;
}
}
[data-slot="question-content"] {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
min-height: 0;
}
[data-slot="question-text"] {
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);
padding: 0 10px;
flex-shrink: 0;
-webkit-user-select: text;
user-select: text;
}
[data-slot="question-hint"] {
font-family: var(--font-family-sans);
font-size: 13px;
font-weight: var(--font-weight-regular);
line-height: var(--line-height-large);
color: var(--v2-text-text-muted);
padding: 0 10px;
flex-shrink: 0;
}
[data-slot="question-options"] {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 12px;
padding: 1px 1px 16px;
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
[data-slot="question-option"] {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 8px 8px 8px 10px;
background-color: var(--v2-background-bg-layer-01);
border: 0.5px solid var(--v2-border-border-base);
border-radius: 6px;
box-shadow: none;
text-align: left;
width: 100%;
cursor: pointer;
transition:
background-color 0.15s ease,
border-color 0.15s ease,
box-shadow 0.15s ease;
&:hover:not([data-picked="true"]) {
background-color: var(--v2-background-bg-base);
}
&[data-picked="true"] {
background-color: var(--v2-state-bg-info);
border-color: transparent;
box-shadow: var(--shadow-xs-border-hover);
}
&:disabled {
cursor: not-allowed;
opacity: 0.6;
}
}
[data-slot="question-option-check"] {
display: inline-flex;
transform: translateY(2px);
}
[data-slot="question-option-box"] {
width: 16px;
height: 16px;
padding: 2px;
border-radius: var(--radius-sm);
border: 0.5px solid var(--v2-border-border-base);
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
background-color: transparent;
transition:
background-color 0.15s ease,
border-color 0.15s ease;
[data-slot="icon-svg"] {
opacity: 0;
color: var(--icon-base);
}
&[data-type="radio"] {
border-radius: 999px;
}
[data-slot="question-option-radio-dot"] {
width: 6px;
height: 6px;
border-radius: 999px;
background-color: var(--v2-background-bg-layer-01);
opacity: 0;
}
&[data-picked="true"] {
border-color: var(--icon-interactive-base);
[data-slot="icon-svg"] {
opacity: 1;
color: var(--icon-invert-base);
}
&[data-type="checkbox"] {
background-color: var(--icon-interactive-base);
}
&[data-type="radio"] {
background-color: var(--icon-interactive-base);
[data-slot="question-option-radio-dot"] {
opacity: 1;
}
}
}
}
[data-slot="question-option-main"] {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
flex: 1;
}
[data-slot="option-label"] {
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-slot="option-description"] {
font-family: var(--font-family-sans);
font-size: 14px;
font-weight: var(--font-weight-regular);
line-height: var(--line-height-large);
color: var(--v2-text-text-muted);
min-width: 0;
overflow-wrap: anywhere;
white-space: normal;
}
[data-slot="question-custom"] {
display: flex;
flex-direction: column;
gap: 8px;
}
[data-slot="question-custom-input-wrap"] {
padding-left: 36px;
}
[data-slot="question-custom-input"] {
width: 100%;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
outline: none;
font-family: var(--font-family-sans);
font-size: 14px;
font-weight: var(--font-weight-regular);
line-height: var(--line-height-large);
color: var(--v2-text-text-muted);
min-width: 0;
cursor: text;
resize: none;
overflow: hidden;
overflow-wrap: anywhere;
&::placeholder {
color: var(--v2-text-text-muted);
}
&:focus-visible {
outline: 0.5px solid var(--v2-border-border-focus);
outline-offset: 2px;
border-radius: var(--radius-xs);
}
&:disabled {
opacity: 0.6;
}
}
[data-slot="question-footer"] {
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
padding: 32px 8px 8px;
margin-top: -24px;
}
[data-slot="question-footer-actions"] {
display: flex;
align-items: center;
gap: 8px;
}
}
[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-slot="icon-svg"] {
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"] {
display: flex;
flex-direction: column;
@@ -0,0 +1,439 @@
[data-component="dock-prompt"][data-kind="question"] {
--v2-question-answer-bg-selected: light-dark(#f5f8ff, #7698fd26);
position: relative;
display: flex;
flex-direction: column;
gap: 0;
min-block-size: 0;
max-block-size: var(--question-prompt-max-height, 100dvh);
font-family: var(--v2-font-family-sans);
[data-slot="question-body"] {
display: flex;
flex-direction: column;
gap: 16px;
flex: 1;
min-block-size: 0;
padding: 8px;
background-color: var(--v2-background-bg-base);
border-radius: 10px;
box-shadow: var(--v2-elevation-raised);
}
[data-slot="question-header"] {
display: flex;
flex-direction: column;
gap: 4px;
flex-shrink: 0;
min-inline-size: 0;
padding-inline: 4px;
}
[data-slot="question-header-top"] {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-block-size: 28px;
min-inline-size: 0;
}
[data-slot="question-header-title"] {
min-inline-size: 0;
font-size: 13px;
font-weight: 440;
line-height: var(--line-height-base);
color: var(--v2-text-text-muted);
overflow-wrap: anywhere;
}
[data-slot="question-header-actions"] {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
[data-component="icon-button-v2"] {
flex-shrink: 0;
color: var(--v2-icon-icon-muted);
}
[data-slot="icon-svg"] {
flex-shrink: 0;
}
}
[data-slot="question-details"] {
display: flex;
flex-direction: column;
gap: 4px;
min-inline-size: 0;
}
[data-slot="question-text"] {
font-size: 13px;
font-weight: 530;
line-height: var(--line-height-base);
color: var(--v2-text-text-base);
overflow-wrap: anywhere;
-webkit-user-select: text;
user-select: text;
}
[data-slot="question-hint"] {
font-size: 11px;
font-weight: 440;
line-height: var(--line-height-tight);
color: var(--v2-text-text-faint);
overflow-wrap: anywhere;
}
[data-slot="question-progress"] {
display: flex;
align-items: center;
gap: 2px;
flex-shrink: 0;
}
[data-slot="question-progress-segment"] {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
inline-size: 20px;
block-size: 20px;
padding: 0;
border: 0;
border-radius: 4px;
background: transparent;
cursor: pointer;
touch-action: manipulation;
&::after {
content: "";
inline-size: 14px;
block-size: 2px;
flex-shrink: 0;
background-color: var(--v2-background-bg-layer-04);
}
&[data-answered="true"]::after {
background-color: var(--v2-background-bg-accent);
}
&[data-active="true"]::after {
background-color: var(--v2-icon-icon-muted);
}
&:focus-visible {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: 1px;
}
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
}
[data-slot="question-content"] {
display: flex;
flex-direction: column;
flex: 1;
min-block-size: 0;
min-inline-size: 0;
}
[data-slot="question-options"] {
display: flex;
flex-direction: column;
gap: 6px;
flex: 1 1 auto;
min-block-size: 0;
min-inline-size: 0;
margin: 0;
padding: 0;
overflow-y: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
[data-slot="question-option"] {
display: flex;
align-items: flex-start;
gap: 8px;
flex-shrink: 0;
box-sizing: border-box;
inline-size: 100%;
padding: 10px;
border: 0;
border-radius: 6px;
background-color: var(--v2-background-bg-layer-01);
box-shadow: inset 0 0 0 0.5px var(--v2-border-border-base);
color: var(--v2-text-text-base);
text-align: start;
cursor: pointer;
touch-action: manipulation;
transition: background-color 150ms ease;
&[data-picked="true"] {
background-image: linear-gradient(var(--v2-question-answer-bg-selected), var(--v2-question-answer-bg-selected));
box-shadow: inset 0 0 0 0.5px var(--v2-border-border-focus);
}
&:focus-visible {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: -2px;
}
&:disabled,
&[data-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
[data-slot="question-option-check"] {
display: inline-flex;
align-items: center;
justify-content: center;
inline-size: 16px;
block-size: 20px;
flex: 0 0 16px;
}
[data-slot="question-option-box"] {
display: inline-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
inline-size: 16px;
block-size: 16px;
flex: 0 0 16px;
padding: 0;
border: 0;
border-radius: 4px;
background: linear-gradient(180deg, var(--v2-alpha-light-6), var(--v2-alpha-light-0)), var(--v2-background-bg-base);
box-shadow: inset 0 0 0 0.5px var(--v2-border-border-strong);
[data-slot="icon-svg"] {
inline-size: 16px;
block-size: 16px;
flex: 0 0 16px;
opacity: 0;
color: var(--v2-icon-icon-contrast);
}
&[data-type="radio"] {
border-radius: 50%;
}
[data-slot="question-option-radio-dot"] {
inline-size: 6px;
block-size: 6px;
flex: 0 0 6px;
border-radius: 50%;
background-color: var(--v2-grey-300);
box-shadow: var(--v2-elevation-elements);
opacity: 0;
}
&[data-picked="true"] {
background:
linear-gradient(180deg, var(--v2-alpha-light-20), var(--v2-alpha-light-0)), var(--v2-background-bg-accent);
[data-slot="icon-svg"],
[data-slot="question-option-radio-dot"] {
opacity: 1;
}
&[data-type="radio"] {
box-shadow: none;
}
}
}
[data-slot="question-option-main"] {
display: flex;
flex-direction: column;
gap: 4px;
min-inline-size: 0;
flex: 1;
}
[data-slot="option-label"] {
font-family: var(--v2-font-family-sans);
font-size: 13px;
font-weight: 530;
line-height: var(--line-height-base);
color: var(--v2-text-text-base);
overflow-wrap: anywhere;
}
[data-slot="option-description"] {
min-inline-size: 0;
font-family: var(--v2-font-family-sans);
font-size: 13px;
font-weight: 440;
line-height: var(--line-height-base);
color: var(--v2-text-text-muted);
overflow-wrap: anywhere;
white-space: normal;
}
[data-slot="question-option"][data-custom="true"] {
align-items: center;
block-size: 40px;
min-block-size: 40px;
max-block-size: 40px;
padding-block: 0;
padding-inline: 10px;
&:not([data-editing="true"]) [data-slot="text-input-v2-input"] {
font-weight: 530;
}
&[data-editing="true"] .question-custom-field[data-component="text-input-v2"] {
background:
linear-gradient(180deg, var(--v2-alpha-light-2), var(--v2-alpha-light-0)), var(--v2-background-bg-base);
box-shadow: inset 0 0 0 1px var(--v2-border-border-focus);
[data-slot="text-input-v2-input"] {
padding-inline: 8px;
&::placeholder {
color: var(--v2-text-text-faint);
}
}
}
}
[data-slot="question-custom-toggle"] {
display: inline-flex;
align-items: center;
justify-content: center;
inline-size: 16px;
block-size: 20px;
flex: 0 0 16px;
margin: 0;
padding: 0;
border: 0;
border-radius: 4px;
background: transparent;
cursor: pointer;
touch-action: manipulation;
&:focus-visible {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: 2px;
}
&:disabled {
cursor: not-allowed;
}
}
[data-slot="question-option"] .question-custom-field[data-component="text-input-v2"] {
flex: 1;
inline-size: 100%;
min-inline-size: 0;
block-size: 28px;
align-self: center;
padding: 0;
gap: 0;
border: 0;
border-radius: 6px;
background: transparent;
box-shadow: none;
outline: none;
opacity: 1;
transition: none;
[data-slot="text-input-v2-value"] {
min-inline-size: 0;
min-block-size: 0;
gap: 0;
}
[data-slot="text-input-v2-input"] {
box-sizing: border-box;
inline-size: 100%;
min-inline-size: 0;
block-size: 28px;
margin: 0;
padding: 0;
border: 0;
background: transparent;
outline: none;
font-family: var(--v2-font-family-sans);
font-size: 13px;
font-weight: 440;
line-height: var(--line-height-compact);
color: var(--v2-text-text-base);
text-align: start;
cursor: text;
&::placeholder {
color: var(--v2-text-text-base);
opacity: 1;
}
&:disabled {
cursor: not-allowed;
}
}
}
[data-slot="question-footer"] {
display: flex;
box-sizing: border-box;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
padding-block: 16px 8px;
padding-inline: 12px;
margin-block-start: -9px;
border: 0.5px solid var(--v2-border-border-base);
border-start-start-radius: 0;
border-start-end-radius: 0;
border-end-start-radius: 10px;
border-end-end-radius: 10px;
background-color: var(--v2-background-bg-layer-01);
> [data-component="button-v2"][data-variant="ghost"] {
color: var(--v2-text-text-muted);
}
}
[data-slot="question-footer-actions"] {
display: flex;
align-items: center;
gap: 8px;
}
[data-slot="question-submit-shortcut"] {
flex-shrink: 0;
opacity: 0.6;
}
@media (hover: hover) and (pointer: fine) {
[data-slot="question-progress-segment"]:hover:not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-slot="question-option"]:hover:not(:disabled):not([data-disabled="true"]):not([data-picked="true"]) {
background-color: var(--v2-background-bg-layer-02);
}
}
@media (prefers-reduced-motion: reduce) {
[data-slot="question-option"],
.question-custom-field[data-component="text-input-v2"] {
transition: none;
}
}
}
+1
View File
@@ -5,6 +5,7 @@
@import "../components/markdown.css" layer(components);
@import "../components/message-part.css" layer(components);
@import "../components/message-nav.css" layer(components);
@import "../components/question-prompt.css" layer(components);
@import "../components/session-review.css" layer(components);
@import "../components/session-turn.css" layer(components);
@import "../components/shell-submessage.css" layer(components);
+263 -246
View File
@@ -1,296 +1,313 @@
/* Vite can link this before the app entry CSS, so name every layer here or `components` ranks below Tailwind's base reset. */
@layer properties, theme, base, components, utilities;
@import "../submit.css";
[data-component="button-v2"] {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
border-radius: 6px;
font-style: normal;
font-weight: 530;
font-size: 13px;
line-height: 20px;
color: var(--v2-text-text-base);
text-shadow: none;
font-variant-numeric: tabular-nums;
letter-spacing: -0.04px;
user-select: none;
cursor: pointer;
outline: none;
}
@layer components {
[data-component="button-v2"] {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
border-radius: 6px;
font-style: normal;
font-weight: 530;
font-size: 13px;
line-height: 20px;
color: var(--v2-text-text-base);
text-shadow: none;
font-variant-numeric: tabular-nums;
letter-spacing: -0.04px;
user-select: none;
cursor: pointer;
outline: none;
}
[data-component="button-v2"] > * {
pointer-events: none;
}
[data-component="button-v2"] > * {
pointer-events: none;
}
[data-component="button-v2"]:focus {
outline: none;
}
[data-component="button-v2"]:focus {
outline: none;
}
[data-component="button-v2"]:is(:focus-visible, [data-state="focus"]):not(:disabled):not([data-variant="loading"]) {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: 2.5px;
}
[data-component="button-v2"]:is(:focus-visible, [data-state="focus"]):not(:disabled):not([data-variant="loading"]) {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: 2.5px;
}
[data-component="button-v2"][data-size="normal"] {
height: 28px;
padding: 0 11px;
}
[data-component="button-v2"][data-size="normal"] {
height: 28px;
padding: 0 11px;
}
[data-component="button-v2"][data-size="small"] {
height: 24px;
padding: 0 9px;
border-radius: 4px;
}
[data-component="button-v2"][data-size="small"] {
height: 24px;
padding: 0 9px;
border-radius: 4px;
}
[data-component="button-v2"][data-size="large"] {
height: 32px;
padding: 0 15px;
}
[data-component="button-v2"][data-size="large"] {
height: 32px;
padding: 0 15px;
}
[data-component="button-v2"][data-icon][data-size="small"] {
padding-left: 9px;
}
[data-component="button-v2"][data-icon][data-size="small"] {
padding-left: 9px;
}
[data-component="button-v2"][data-icon][data-size="normal"] {
padding-left: 11px;
}
[data-component="button-v2"][data-icon][data-size="normal"] {
padding-left: 11px;
}
[data-component="button-v2"][data-icon][data-size="large"] {
padding-left: 15px;
}
[data-component="button-v2"][data-icon][data-size="large"] {
padding-left: 15px;
}
[data-component="button-v2"] [data-slot="icon-svg"] {
color: currentColor;
}
[data-component="button-v2"] [data-slot="icon-svg"] {
color: currentColor;
}
/* Neutral */
[data-component="button-v2"][data-variant="neutral"] {
background-color: var(--v2-background-bg-button-neutral);
color: var(--v2-text-text-base);
box-shadow: var(--v2-elevation-button-neutral);
}
/* Neutral */
[data-component="button-v2"][data-variant="neutral"] {
background-color: var(--v2-background-bg-button-neutral);
color: var(--v2-text-text-base);
box-shadow: var(--v2-elevation-button-neutral);
}
[data-component="button-v2"][data-variant="neutral"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="neutral"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="neutral"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-pressed) 0%, var(--v2-overlay-simple-overlay-pressed) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="neutral"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-pressed) 0%,
var(--v2-overlay-simple-overlay-pressed) 100%
),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="neutral"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
[data-component="button-v2"][data-variant="neutral"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
/* Danger */
[data-component="button-v2"][data-variant="danger"] {
background-color: var(--v2-background-bg-button-neutral);
color: var(--v2-state-fg-danger);
box-shadow: var(--v2-elevation-button-neutral);
}
/* Danger */
[data-component="button-v2"][data-variant="danger"] {
background-color: var(--v2-background-bg-button-neutral);
color: var(--v2-state-fg-danger);
box-shadow: var(--v2-elevation-button-neutral);
}
[data-component="button-v2"][data-variant="danger"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="danger"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="danger"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-pressed) 0%, var(--v2-overlay-simple-overlay-pressed) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="danger"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-pressed) 0%,
var(--v2-overlay-simple-overlay-pressed) 100%
),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="danger"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
[data-component="button-v2"][data-variant="danger"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
[data-component="button-v2"][data-variant="warning"] {
background-color: var(--v2-background-bg-button-neutral);
color: var(--v2-state-fg-warning);
box-shadow: var(--v2-elevation-button-neutral);
}
[data-component="button-v2"][data-variant="warning"] {
background-color: var(--v2-background-bg-button-neutral);
color: var(--v2-state-fg-warning);
box-shadow: var(--v2-elevation-button-neutral);
}
[data-component="button-v2"][data-variant="warning"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="warning"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="warning"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-pressed) 0%, var(--v2-overlay-simple-overlay-pressed) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="warning"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-pressed) 0%,
var(--v2-overlay-simple-overlay-pressed) 100%
),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="button-v2"][data-variant="warning"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
[data-component="button-v2"][data-variant="warning"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
/* Outline */
[data-component="button-v2"][data-variant="outline"] {
background-color: transparent;
color: var(--v2-text-text-base);
box-shadow: inset 0 0 0 1px var(--v2-border-border-muted);
}
/* Outline */
[data-component="button-v2"][data-variant="outline"] {
background-color: transparent;
color: var(--v2-text-text-base);
box-shadow: inset 0 0 0 1px var(--v2-border-border-muted);
}
[data-component="button-v2"][data-variant="outline"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-component="button-v2"][data-variant="outline"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-component="button-v2"][data-variant="outline"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="outline"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="outline"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
[data-component="button-v2"][data-variant="outline"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
/* Contrast */
[data-component="button-v2"][data-variant="contrast"] {
background-image:
linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%);
color: var(--v2-text-text-contrast);
text-shadow: 0 0.5px 0.5px rgba(0, 0, 0, 0.3);
box-shadow: var(--v2-elevation-button-contrast);
}
/* Contrast */
[data-component="button-v2"][data-variant="contrast"] {
background-image:
linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%);
color: var(--v2-text-text-contrast);
text-shadow: 0 0.5px 0.5px rgba(0, 0, 0, 0.3);
box-shadow: var(--v2-elevation-button-contrast);
}
[data-component="button-v2"][data-variant="contrast"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-contrast-hover) 0%,
var(--v2-overlay-simple-overlay-contrast-hover) 100%
),
linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%);
}
[data-component="button-v2"][data-variant="contrast"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-contrast-hover) 0%,
var(--v2-overlay-simple-overlay-contrast-hover) 100%
),
linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%);
}
[data-component="button-v2"][data-variant="contrast"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-contrast-pressed) 0%,
var(--v2-overlay-simple-overlay-contrast-pressed) 100%
),
linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%);
}
[data-component="button-v2"][data-variant="contrast"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-contrast-pressed) 0%,
var(--v2-overlay-simple-overlay-contrast-pressed) 100%
),
linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%);
}
[data-component="button-v2"][data-variant="contrast"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.4;
cursor: not-allowed;
}
[data-component="button-v2"][data-variant="contrast"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.4;
cursor: not-allowed;
}
/* Loading */
[data-component="button-v2"][data-variant="loading"] {
background: var(--v2-background-bg-layer-02);
color: var(--v2-text-text-base);
box-shadow: inset 0 0 0 0.5px var(--v2-border-border-muted);
cursor: default;
pointer-events: none;
}
/* Loading */
[data-component="button-v2"][data-variant="loading"] {
background: var(--v2-background-bg-layer-02);
color: var(--v2-text-text-base);
box-shadow: inset 0 0 0 0.5px var(--v2-border-border-muted);
cursor: default;
pointer-events: none;
}
/* Ghost */
[data-component="button-v2"][data-variant="ghost"] {
background-color: transparent;
color: var(--v2-text-text-base);
}
/* Ghost */
[data-component="button-v2"][data-variant="ghost"] {
background-color: transparent;
color: var(--v2-text-text-base);
}
[data-component="button-v2"][data-variant="ghost"]:is(:hover, [data-state="hover"]):not(:disabled):not(
[data-expanded]
) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-component="button-v2"][data-variant="ghost"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="ghost"]:where([data-expanded]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="ghost"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
/* Ghost muted */
[data-component="button-v2"][data-variant="ghost-muted"] {
background-color: transparent;
color: var(--v2-text-text-muted);
font-weight: 440;
}
[data-component="button-v2"][data-variant="ghost-muted"] [data-slot="icon-svg"] {
color: var(--v2-icon-icon-muted);
}
@media (hover: hover) {
[data-component="button-v2"][data-variant="ghost-muted"]:is(:hover, [data-state="hover"]):not(:disabled):not(
[data-component="button-v2"][data-variant="ghost"]:is(:hover, [data-state="hover"]):not(:disabled):not(
[data-expanded]
) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
}
[data-component="button-v2"][data-variant="ghost-muted"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="ghost"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="ghost-muted"]:where([data-expanded]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
color: var(--v2-text-text-base);
}
[data-component="button-v2"][data-variant="ghost"]:where([data-expanded]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="ghost-muted"]:where([data-expanded]):not(:disabled) [data-slot="icon-svg"] {
color: var(--v2-icon-icon-base);
}
[data-component="button-v2"][data-variant="ghost"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
[data-component="button-v2"][data-variant="ghost-muted"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
/* Ghost muted */
[data-component="button-v2"][data-variant="ghost-muted"] {
background-color: transparent;
color: var(--v2-text-text-muted);
font-weight: 440;
}
/* Ghost faint */
[data-component="button-v2"][data-variant="ghost-faint"] {
background-color: transparent;
color: var(--v2-text-text-faint);
}
[data-component="button-v2"][data-variant="ghost-muted"] [data-slot="icon-svg"] {
color: var(--v2-icon-icon-muted);
}
[data-component="button-v2"][data-variant="ghost-faint"] [data-slot="icon-svg"] {
color: var(--v2-icon-icon-faint);
}
@media (hover: hover) {
[data-component="button-v2"][data-variant="ghost-muted"]:is(:hover, [data-state="hover"]):not(:disabled):not(
[data-expanded]
) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
}
[data-component="button-v2"][data-variant="ghost-faint"]:is(:hover, [data-state="hover"]):not(:disabled):not(
[data-expanded]
) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-component="button-v2"][data-variant="ghost-muted"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="ghost-faint"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="ghost-muted"]:where([data-expanded]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
color: var(--v2-text-text-base);
}
[data-component="button-v2"][data-variant="ghost-faint"]:where([data-expanded]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="ghost-muted"]:where([data-expanded]):not(:disabled)
[data-slot="icon-svg"] {
color: var(--v2-icon-icon-base);
}
[data-component="button-v2"][data-variant="ghost-faint"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
[data-component="button-v2"][data-variant="ghost-muted"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
/* Ghost faint */
[data-component="button-v2"][data-variant="ghost-faint"] {
background-color: transparent;
color: var(--v2-text-text-faint);
}
[data-component="button-v2"][data-variant="ghost-faint"] [data-slot="icon-svg"] {
color: var(--v2-icon-icon-faint);
}
[data-component="button-v2"][data-variant="ghost-faint"]:is(:hover, [data-state="hover"]):not(:disabled):not(
[data-expanded]
) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-component="button-v2"][data-variant="ghost-faint"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="ghost-faint"]:where([data-expanded]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="button-v2"][data-variant="ghost-faint"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
}
@@ -1,120 +1,128 @@
/* Vite can link this before the app entry CSS, so name every layer here or `components` ranks below Tailwind's base reset. */
@layer properties, theme, base, components, utilities;
@import "../submit.css";
[data-component="icon-button-v2"] {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 6px;
color: var(--v2-text-text-base);
user-select: none;
outline: none;
padding: 0;
cursor: default;
}
@layer components {
[data-component="icon-button-v2"] {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 6px;
color: var(--v2-text-text-base);
user-select: none;
outline: none;
padding: 0;
cursor: default;
}
[data-component="icon-button-v2"]:focus {
outline: none;
}
[data-component="icon-button-v2"]:focus {
outline: none;
}
[data-component="icon-button-v2"]:is(:focus-visible, [data-state="focus"]):not(:disabled) {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: 2.5px;
}
[data-component="icon-button-v2"]:is(:focus-visible, [data-state="focus"]):not(:disabled) {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: 2.5px;
}
[data-component="icon-button-v2"][data-size="small"] {
width: 20px;
height: 20px;
border-radius: 4px;
}
[data-component="icon-button-v2"][data-size="small"] {
width: 20px;
height: 20px;
border-radius: 4px;
}
[data-component="icon-button-v2"][data-size="normal"] {
width: 24px;
height: 24px;
}
[data-component="icon-button-v2"][data-size="normal"] {
width: 24px;
height: 24px;
}
[data-component="icon-button-v2"][data-size="large"] {
width: 28px;
height: 28px;
}
[data-component="icon-button-v2"][data-size="large"] {
width: 28px;
height: 28px;
}
[data-component="icon-button-v2"] [data-slot="icon-svg"] {
color: currentColor;
}
[data-component="icon-button-v2"] [data-slot="icon-svg"] {
color: currentColor;
}
/* Neutral */
[data-component="icon-button-v2"][data-variant="neutral"] {
background-color: var(--v2-background-bg-button-neutral);
color: var(--v2-text-text-base);
box-shadow: var(--v2-elevation-button-neutral);
}
/* Neutral */
[data-component="icon-button-v2"][data-variant="neutral"] {
background-color: var(--v2-background-bg-button-neutral);
color: var(--v2-text-text-base);
box-shadow: var(--v2-elevation-button-neutral);
}
[data-component="icon-button-v2"][data-variant="neutral"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="icon-button-v2"][data-variant="neutral"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="icon-button-v2"][data-variant="neutral"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--v2-overlay-simple-overlay-pressed) 0%, var(--v2-overlay-simple-overlay-pressed) 100%),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="icon-button-v2"][data-variant="neutral"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-pressed) 0%,
var(--v2-overlay-simple-overlay-pressed) 100%
),
linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
}
[data-component="icon-button-v2"][data-variant="neutral"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
[data-component="icon-button-v2"][data-variant="neutral"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
/* Ghost */
[data-component="icon-button-v2"][data-variant="ghost"] {
background-color: transparent;
color: var(--v2-text-text-base);
}
/* Ghost */
[data-component="icon-button-v2"][data-variant="ghost"] {
background-color: transparent;
color: var(--v2-text-text-base);
}
[data-component="icon-button-v2"][data-variant="ghost"]:is(:hover, [data-state="hover"]):not(:disabled):not(
[data-expanded]
) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-component="icon-button-v2"][data-variant="ghost"]:is(:hover, [data-state="hover"]):not(:disabled):not(
[data-expanded]
) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-component="icon-button-v2"][data-variant="ghost"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="icon-button-v2"][data-variant="ghost"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="icon-button-v2"][data-variant="ghost"]:where([data-expanded]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="icon-button-v2"][data-variant="ghost"]:where([data-expanded]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="icon-button-v2"][data-variant="ghost"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
[data-component="icon-button-v2"][data-variant="ghost"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
/* Ghost */
[data-component="icon-button-v2"][data-variant="ghost-muted"] {
background-color: transparent;
color: var(--v2-icon-icon-muted);
}
/* Ghost */
[data-component="icon-button-v2"][data-variant="ghost-muted"] {
background-color: transparent;
color: var(--v2-icon-icon-muted);
}
[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:hover, [data-state="hover"]):not(:disabled):not(
[data-expanded]
) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:hover, [data-state="hover"]):not(:disabled):not(
[data-expanded]
) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
color: var(--v2-icon-icon-base);
}
[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
color: var(--v2-icon-icon-base);
}
[data-component="icon-button-v2"][data-variant="ghost-muted"]:where([data-expanded]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
color: var(--v2-icon-icon-base);
}
[data-component="icon-button-v2"][data-variant="ghost-muted"]:where([data-expanded]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
color: var(--v2-icon-icon-base);
}
[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.5;
cursor: not-allowed;
}
}
+58 -53
View File
@@ -1,45 +1,68 @@
/* Shared by Composer submit actions and contrast icon buttons. */
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
) {
--submit-highlight: var(--v2-alpha-light-20);
--submit-hover: var(--v2-overlay-simple-overlay-contrast-hover);
/* Vite can link this before the app entry CSS, so name every layer here or `components` ranks below Tailwind's base reset. */
@layer properties, theme, base, components, utilities;
background-image:
linear-gradient(180deg, var(--submit-highlight) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(
90deg,
var(--v2-background-bg-icon-button-contrast) 0%,
var(--v2-background-bg-icon-button-contrast) 100%
);
color: var(--v2-text-text-contrast);
text-shadow: 0 0.5px 0.5px rgba(0, 0, 0, 0.3);
box-shadow: var(--v2-elevation-button-contrast);
}
[data-color-scheme="dark"]
@layer components {
/* Shared by Composer submit actions and contrast icon buttons. */
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
) {
--submit-highlight: var(--v2-alpha-light-60);
--submit-hover: var(--v2-alpha-dark-8);
--submit-highlight: var(--v2-alpha-light-20);
--submit-hover: var(--v2-overlay-simple-overlay-contrast-hover);
color: var(--v2-text-text-inverse);
}
background-image:
linear-gradient(180deg, var(--submit-highlight) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(
90deg,
var(--v2-background-bg-icon-button-contrast) 0%,
var(--v2-background-bg-icon-button-contrast) 100%
);
color: var(--v2-text-text-contrast);
text-shadow: 0 0.5px 0.5px rgba(0, 0, 0, 0.3);
box-shadow: var(--v2-elevation-button-contrast);
}
[data-color-scheme="dark"] [data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"]) {
color: var(--v2-icon-icon-inverse);
}
[data-color-scheme="dark"]
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
) {
--submit-highlight: var(--v2-alpha-light-60);
--submit-hover: var(--v2-alpha-dark-8);
color: var(--v2-text-text-inverse);
}
[data-color-scheme="dark"] [data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"]) {
color: var(--v2-icon-icon-inverse);
}
@media (hover: hover) {
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
):is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--submit-hover) 0%, var(--submit-hover) 100%),
linear-gradient(180deg, var(--submit-highlight) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(
90deg,
var(--v2-background-bg-icon-button-contrast) 0%,
var(--v2-background-bg-icon-button-contrast) 100%
);
}
}
@media (hover: hover) {
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
):is(:hover, [data-state="hover"]):not(:disabled) {
):is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--submit-hover) 0%, var(--submit-hover) 100%),
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-contrast-pressed) 0%,
var(--v2-overlay-simple-overlay-contrast-pressed) 100%
),
linear-gradient(180deg, var(--submit-highlight) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(
90deg,
@@ -47,30 +70,12 @@
var(--v2-background-bg-icon-button-contrast) 100%
);
}
}
:is(
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
):is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-contrast-pressed) 0%,
var(--v2-overlay-simple-overlay-contrast-pressed) 100%
),
linear-gradient(180deg, var(--submit-highlight) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(
90deg,
var(--v2-background-bg-icon-button-contrast) 0%,
var(--v2-background-bg-icon-button-contrast) 100%
);
}
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
):is(:disabled, [data-state="disabled"]) {
opacity: 0.4;
cursor: not-allowed;
):is(:disabled, [data-state="disabled"]) {
opacity: 0.4;
cursor: not-allowed;
}
}
+162 -157
View File
@@ -1,181 +1,186 @@
[data-component="text-input-v2"] {
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: stretch;
padding-block: 0;
padding-inline: 0 8px;
gap: 8px;
width: 280px;
height: 28px;
border: 0;
border-radius: 6px;
outline: 1px solid transparent;
outline-offset: 0;
background:
linear-gradient(180deg, var(--v2-alpha-light-6) 0%, var(--v2-alpha-light-2) 100%), var(--v2-background-bg-base);
box-shadow: var(--v2-elevation-button-neutral);
flex: none;
align-self: stretch;
transition:
background 85ms ease-out,
outline-color 85ms ease-out,
box-shadow 85ms ease-out;
}
/* Vite can link this before the app entry CSS, so name every layer here or `components` ranks below Tailwind's base reset. */
@layer properties, theme, base, components, utilities;
[data-component="text-input-v2"][data-appearance="large"] {
height: 32px;
}
@layer components {
[data-component="text-input-v2"] {
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: stretch;
padding-block: 0;
padding-inline: 0 8px;
gap: 8px;
width: 280px;
height: 28px;
border: 0;
border-radius: 6px;
outline: 1px solid transparent;
outline-offset: 0;
background:
linear-gradient(180deg, var(--v2-alpha-light-6) 0%, var(--v2-alpha-light-2) 100%), var(--v2-background-bg-base);
box-shadow: var(--v2-elevation-button-neutral);
flex: none;
align-self: stretch;
transition:
background 85ms ease-out,
outline-color 85ms ease-out,
box-shadow 85ms ease-out;
}
[data-component="text-input-v2"]:where(:hover):not([data-disabled], [data-invalid]):not(:focus-within) {
background:
linear-gradient(0deg, var(--v2-overlay-simple-overlay-hover), var(--v2-overlay-simple-overlay-hover)),
linear-gradient(180deg, var(--v2-alpha-light-6) 0%, var(--v2-alpha-light-2) 100%), var(--v2-background-bg-base);
}
[data-component="text-input-v2"][data-appearance="large"] {
height: 32px;
}
[data-component="text-input-v2"]:where(:focus-within):not([data-disabled], [data-invalid]) {
outline-color: var(--v2-border-border-focus);
box-shadow: none;
}
[data-component="text-input-v2"]:where(:hover):not([data-disabled], [data-invalid]):not(:focus-within) {
background:
linear-gradient(0deg, var(--v2-overlay-simple-overlay-hover), var(--v2-overlay-simple-overlay-hover)),
linear-gradient(180deg, var(--v2-alpha-light-6) 0%, var(--v2-alpha-light-2) 100%), var(--v2-background-bg-base);
}
[data-component="text-input-v2"]:where([data-invalid]):not([data-disabled]) {
outline-color: var(--v2-state-fg-danger);
box-shadow: none;
}
[data-component="text-input-v2"]:where(:focus-within):not([data-disabled], [data-invalid]) {
outline-color: var(--v2-border-border-focus);
box-shadow: none;
}
[data-component="text-input-v2"]:where([data-disabled]) {
opacity: 0.5;
cursor: not-allowed;
}
[data-component="text-input-v2"]:where([data-invalid]):not([data-disabled]) {
outline-color: var(--v2-state-fg-danger);
box-shadow: none;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-value"] {
display: flex;
flex-direction: row;
align-items: center;
align-self: stretch;
padding: 0;
gap: 6px;
min-width: 0;
flex: 1 1 auto;
min-height: 0;
}
[data-component="text-input-v2"]:where([data-disabled]) {
opacity: 0.5;
cursor: not-allowed;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-leading-icon"] {
display: flex;
flex: none;
align-items: center;
justify-content: center;
padding-inline-start: 8px;
color: var(--v2-icon-icon-muted);
}
[data-component="text-input-v2"] [data-slot="text-input-v2-value"] {
display: flex;
flex-direction: row;
align-items: center;
align-self: stretch;
padding: 0;
gap: 6px;
min-width: 0;
flex: 1 1 auto;
min-height: 0;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-leading-icon"] :is(svg, [data-slot="icon-svg"]) {
display: block;
flex: none;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-leading-icon"] {
display: flex;
flex: none;
align-items: center;
justify-content: center;
padding-inline-start: 8px;
color: var(--v2-icon-icon-muted);
}
[data-component="text-input-v2"][data-leading-icon] [data-slot="text-input-v2-input"] {
padding-inline-start: 0;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-leading-icon"] :is(svg, [data-slot="icon-svg"]) {
display: block;
flex: none;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-input"] {
display: block;
width: 100%;
min-width: 0;
height: 100%;
padding-block: 0;
padding-inline: 8px 0;
margin: 0;
border: 0;
background: transparent;
outline: none;
font-style: normal;
font-weight: 440;
font-size: 13px;
line-height: var(--line-height-compact);
letter-spacing: -0.04px;
color: var(--v2-text-text-base);
font-variation-settings: "slnt" 0;
}
[data-component="text-input-v2"][data-leading-icon] [data-slot="text-input-v2-input"] {
padding-inline-start: 0;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-input"]::placeholder {
color: var(--v2-text-text-faint);
user-select: none;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-input"] {
display: block;
width: 100%;
min-width: 0;
height: 100%;
padding-block: 0;
padding-inline: 8px 0;
margin: 0;
border: 0;
background: transparent;
outline: none;
font-style: normal;
font-weight: 440;
font-size: 13px;
line-height: var(--line-height-compact);
letter-spacing: -0.04px;
color: var(--v2-text-text-base);
font-variation-settings: "slnt" 0;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-input"][type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none;
appearance: none;
display: none;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-input"]::placeholder {
color: var(--v2-text-text-faint);
user-select: none;
}
[data-component="text-input-v2"][data-numeric] [data-slot="text-input-v2-input"] {
font-variant-numeric: tabular-nums;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-input"][type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none;
appearance: none;
display: none;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-icon-button"] {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
align-self: center;
flex: none;
width: 20px;
height: 20px;
padding: 2px;
gap: 3px;
border: 0;
border-radius: 4px;
background: transparent;
color: var(--v2-icon-icon-muted);
cursor: pointer;
outline: none;
}
[data-component="text-input-v2"][data-numeric] [data-slot="text-input-v2-input"] {
font-variant-numeric: tabular-nums;
}
[data-component="text-input-v2"]
[data-slot="text-input-v2-icon-button"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-component="text-input-v2"] [data-slot="text-input-v2-icon-button"] {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
align-self: center;
flex: none;
width: 20px;
height: 20px;
padding: 2px;
gap: 3px;
border: 0;
border-radius: 4px;
background: transparent;
color: var(--v2-icon-icon-muted);
cursor: pointer;
outline: none;
}
[data-component="text-input-v2"]
[data-slot="text-input-v2-icon-button"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="text-input-v2"]
[data-slot="text-input-v2-icon-button"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-hover);
}
[data-component="text-input-v2"] [data-slot="text-input-v2-icon-button"]:focus {
outline: none;
}
[data-component="text-input-v2"]
[data-slot="text-input-v2-icon-button"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-color: var(--v2-overlay-simple-overlay-pressed);
}
[data-component="text-input-v2"] [data-slot="text-input-v2-icon-button"]:focus-visible {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: 1px;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-icon-button"]:focus {
outline: none;
}
[data-component="text-input-v2"]:where([data-disabled]) [data-slot="text-input-v2-icon-button"] {
cursor: not-allowed;
pointer-events: none;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-icon-button"]:focus-visible {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: 1px;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-icon-button"] [data-slot="icon-svg"] {
display: block;
flex: none;
color: currentColor;
}
[data-component="text-input-v2"]:where([data-disabled]) [data-slot="text-input-v2-icon-button"] {
cursor: not-allowed;
pointer-events: none;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-icon-button"][data-variant="clear"] {
width: 28px;
height: 28px;
padding: 0;
border-radius: 6px;
margin-inline-end: -8px;
}
[data-component="text-input-v2"] [data-slot="text-input-v2-icon-button"] [data-slot="icon-svg"] {
display: block;
flex: none;
color: currentColor;
}
[data-component="text-input-v2"][data-invalid]:not([data-disabled]) [data-slot="text-input-v2-input"] {
color: var(--v2-state-fg-danger);
caret-color: var(--v2-state-fg-danger);
}
[data-component="text-input-v2"] [data-slot="text-input-v2-icon-button"][data-variant="clear"] {
width: 28px;
height: 28px;
padding: 0;
border-radius: 6px;
margin-inline-end: -8px;
}
[data-component="text-input-v2"][data-invalid]:not([data-disabled]) [data-slot="text-input-v2-input"]::placeholder {
color: var(--v2-state-fg-danger);
opacity: 1;
[data-component="text-input-v2"][data-invalid]:not([data-disabled]) [data-slot="text-input-v2-input"] {
color: var(--v2-state-fg-danger);
caret-color: var(--v2-state-fg-danger);
}
[data-component="text-input-v2"][data-invalid]:not([data-disabled]) [data-slot="text-input-v2-input"]::placeholder {
color: var(--v2-state-fg-danger);
opacity: 1;
}
}
+1
View File
@@ -279,6 +279,7 @@ const source = {
"ui.question.multiHint": "Select all answers that apply",
"ui.question.singleHint": "Select one answer",
"ui.question.custom.placeholder": "Type your answer…",
"ui.question.custom.answerPlaceholder": "Type your own answer…",
} satisfies Record<string, string>
export type Key = keyof typeof source