(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',