(null);
const { ref, width } = useElementSize();
const customEnabled = activeQuestion?.allowCustom ?? false;
+ const asBoxes = activeQuestion?.control === 'checkbox';
const showNav = resolvedTotal > 1 && (!!onPreviousQuestion || !!onNextQuestion);
const canGoPrev = clampedIndex > 1;
const canGoNext = clampedIndex < resolvedTotal;
@@ -247,21 +253,50 @@ export function QuestionPrompt({
{options.map((option, idx) => {
const checked = selectedIds.includes(option.id);
+ const pick = () => {
+ if (activeQuestion.kind === 'single') {
+ handleSingleSelect(option.id);
+ if (customEnabled) {
+ setCustomText('');
+ }
+ } else {
+ toggleMulti(option.id);
+ }
+ };
+ const hover = option.preview ? () => setActiveOptionId(option.id) : undefined;
+ if (asBoxes) {
+ const Control = activeQuestion.kind === 'single' ? Radio : Checkbox;
+ return (
+
+
+ {option.label}
+ {option.description && (
+ {option.description}
+ )}
+
+ }
+ />
+ {!isWide &&
+ previewOption?.id === option.id &&
+ (checked || activeOptionId === option.id) &&
+ renderPreview(classes.inlinePreview)}
+
+ );
+ }
return (
setActiveOptionId(option.id) : undefined}
- onFocus={option.preview ? () => setActiveOptionId(option.id) : undefined}
- onClick={() => {
- if (activeQuestion.kind === 'single') {
- handleSingleSelect(option.id);
- if (customEnabled) {
- setCustomText('');
- }
- } else {
- toggleMulti(option.id);
- }
- }}
+ onMouseEnter={hover}
+ onFocus={hover}
+ onClick={pick}
className={classes.option}
data-checked={checked || undefined}
aria-pressed={checked}
diff --git a/scripts/check-size.ts b/scripts/check-size.ts
index a9e9ffb..a04cd3c 100644
--- a/scripts/check-size.ts
+++ b/scripts/check-size.ts
@@ -58,7 +58,7 @@ const entries: Entry[] = [
name: 'import *',
source: "import * as kit from '@sinups/ai-kit'; console.log(kit);",
stylesheets: ['styles.css'],
- gzipBudget: 242_200,
+ gzipBudget: 243_500,
},
];
diff --git a/site/app/data/bundle-size.json b/site/app/data/bundle-size.json
index 3d82e5a..bf2d83e 100644
--- a/site/app/data/bundle-size.json
+++ b/site/app/data/bundle-size.json
@@ -5,9 +5,9 @@
"styles/base.css",
"styles/AgentChat.css"
],
- "jsGzip": 85062,
- "cssGzip": 12498,
- "totalGzip": 97560
+ "jsGzip": 85285,
+ "cssGzip": 12676,
+ "totalGzip": 97961
},
{
"name": "MessageList",
@@ -15,9 +15,9 @@
"styles/base.css",
"styles/MessageList.css"
],
- "jsGzip": 70186,
- "cssGzip": 9742,
- "totalGzip": 79928
+ "jsGzip": 70431,
+ "cssGzip": 9867,
+ "totalGzip": 80298
},
{
"name": "InputBar",
@@ -25,9 +25,9 @@
"styles/base.css",
"styles/InputBar.css"
],
- "jsGzip": 30396,
- "cssGzip": 6985,
- "totalGzip": 37381
+ "jsGzip": 30507,
+ "cssGzip": 7087,
+ "totalGzip": 37594
},
{
"name": "Markdown",
@@ -285,9 +285,9 @@
"stylesheets": [
"styles.css"
],
- "jsGzip": 218497,
- "cssGzip": 23145,
- "totalGzip": 241642
+ "jsGzip": 218879,
+ "cssGzip": 23369,
+ "totalGzip": 242248
},
{
"name": "embed widget.js",
diff --git a/site/app/data/component-docs-chat.ts b/site/app/data/component-docs-chat.ts
index c37ff9c..abaee6b 100644
--- a/site/app/data/component-docs-chat.ts
+++ b/site/app/data/component-docs-chat.ts
@@ -581,7 +581,7 @@ export function Example({ answer }: { answer: (value: unknown) => void }) {
type: 'usage',
title: 'Usage',
content:
- 'The form for one question the agent asks the user. `kind` is `single` (lettered options, one choice), `multi` (several choices, bounded by `minSelections` and `maxSelections`) or `text` (a free-text area). `allowCustom` adds a typed answer as the last option and `allowNotes` adds an optional notes field. An option can carry a `preview` (Markdown or code): from 640px of prompt width it is shown beside the options, below that under the chosen or hovered option. The primary button reads Next while more questions follow and Send on the last one; it stays disabled until the answer is valid. Skip calls `onSkip`, or `onSubmit({ kind: "skip" })` when `onSkip` is not set. `initialAnswer` is read only on mount, so remount with `key` when the question changes. QuestionTool and InputBar `questionBar` use this component and handle multi-step navigation for you; render it directly only for a custom flow. Related helpers: `getInitialQuestionDraft`, `canSubmitQuestion`, `buildQuestionAnswer`, `formatQuestionAnswer`.',
+ 'The form for one question the agent asks the user. `kind` is `single` (lettered options, one choice), `multi` (several choices, bounded by `minSelections` and `maxSelections`) or `text` (a free-text area). `control: "checkbox"` swaps the lettered rows for the boxes people already know — squares for `multi`, circles for `single` — with the same answer and the same bounds; the letters stay by default. `allowCustom` adds a typed answer as the last option and `allowNotes` adds an optional notes field. An option can carry a `preview` (Markdown or code): from 640px of prompt width it is shown beside the options, below that under the chosen or hovered option. The primary button reads Next while more questions follow and Send on the last one; it stays disabled until the answer is valid. Skip calls `onSkip`, or `onSubmit({ kind: "skip" })` when `onSkip` is not set. `initialAnswer` is read only on mount, so remount with `key` when the question changes. QuestionTool and InputBar `questionBar` use this component and handle multi-step navigation for you; render it directly only for a custom flow. Related helpers: `getInitialQuestionDraft`, `canSubmitQuestion`, `buildQuestionAnswer`, `formatQuestionAnswer`.',
},
{
type: 'example',
diff --git a/site/app/data/component-docs.ts b/site/app/data/component-docs.ts
index 460dd70..34adbee 100644
--- a/site/app/data/component-docs.ts
+++ b/site/app/data/component-docs.ts
@@ -2251,7 +2251,7 @@ export function Example() {
type: 'usage',
title: 'Usage',
content:
- 'Answer MCP elicitation requests. Pass the server\'s requestedSchema and the form builds Mantine fields for strings (email, uri, date, date-time), numbers, booleans, single and multi-select enums, validates required fields and bounds, and calls onAccept with only the filled values. Use mode="url" when the server asks the user to open a link. Short fields go into two columns once the form itself is wider than 520px, so the same component fits a narrow widget and a full-page chat.',
+ 'Answer MCP elicitation requests. Pass the server\'s requestedSchema and the form builds Mantine fields for strings (email, uri, date, date-time), numbers, booleans, single and multi-select enums, validates required fields and bounds, and calls onAccept with only the filled values. A string that expects more than a line takes `multiline: true`, and then the field is a text area that grows from three lines to eight (`rows` sets where it starts); it never shares a row with another field, and its label, limit and required mark work as they do on a one-line field. Use mode="url" when the server asks the user to open a link. Short fields go into two columns once the form itself is wider than 520px, so the same component fits a narrow widget and a full-page chat.',
},
{
type: 'example',