{
  "schemaVersion": 1,
  "name": "questionnaire",
  "useFor": "Use when an agent or a setup flow needs a few answers in a row (how to route calls, what to include in a summary). Pass the questions as items to Questionnaire, then render a QuestionnaireItem per question with a QuestionnaireTitle, a QuestionnaireDescription, QuestionnaireChoices of QuestionnaireChoice (add QuestionnaireInput for another answer) and a QuestionnaireError; end with QuestionnaireActions holding Previous, Skip, Next and Submit. Mark required questions; set multiple to allow several answers. onSubmit receives the native form event: read the answers with FormData.",
  "constraints": [
    "More than one question on screen",
    "Animating between questions",
    "A required question without its error message",
    "A purple card fill for a chosen answer"
  ],
  "documentation": "",
  "source": "Extrapolated from the Inkbox console (Figma 427:2715): the radio and checks in Create Agent Identity and the Where does this agent run? picker",
  "exports": [
    "Questionnaire",
    "QuestionnaireActions",
    "QuestionnaireChoice",
    "QuestionnaireChoiceDescription",
    "QuestionnaireChoices",
    "QuestionnaireDescription",
    "QuestionnaireError",
    "QuestionnaireInput",
    "QuestionnaireItem",
    "QuestionnaireNext",
    "QuestionnairePrevious",
    "QuestionnaireProgress",
    "QuestionnaireSkip",
    "QuestionnaireSubmit",
    "QuestionnaireTitle"
  ],
  "client": true,
  "docsUrl": "https://inkbox-ds.vercel.app/components/questionnaire",
  "markdownUrl": "https://inkbox-ds.vercel.app/r/questionnaire.md",
  "examples": [
    {
      "path": "examples/questionnaire.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  Questionnaire,\n  QuestionnaireActions,\n  QuestionnaireChoice,\n  QuestionnaireChoiceDescription,\n  QuestionnaireChoices,\n  QuestionnaireDescription,\n  QuestionnaireError,\n  QuestionnaireInput,\n  QuestionnaireItem,\n  QuestionnaireNext,\n  QuestionnairePrevious,\n  QuestionnaireProgress,\n  QuestionnaireSkip,\n  QuestionnaireSubmit,\n  QuestionnaireTitle,\n} from \"@/components/ui/questionnaire\";\n\nconst ITEMS = [\n  { name: \"channel\", required: true, choices: [{ value: \"email\" }, { value: \"phone\" }, { value: \"imessage\" }] },\n  { name: \"include\", multiple: true, choices: [{ value: \"sender\" }, { value: \"summary\" }, { value: \"attachments\" }, { value: \"next-step\" }] },\n  { name: \"when\", required: true, choices: [{ value: \"now\" }, { value: \"hourly\" }, { value: \"daily\" }] },\n] as const;\n\nfunction Saved({ answers }: { answers: string }) {\n  return (\n    <p role=\"status\" className=\"rounded-control bg-success-bg px-3.5 py-2.5 text-body text-fg\">\n      Saved. {answers}\n    </p>\n  );\n}\n\nexport function Preview() {\n  const [saved, setSaved] = React.useState<string>();\n  return (\n    <div className=\"mx-auto grid w-full max-w-110 gap-4\">\n      <Questionnaire\n        items={ITEMS}\n        onSubmit={(event) => {\n          event.preventDefault();\n          const data = new FormData(event.currentTarget);\n          setSaved(`Channel: ${data.get(\"channel\")}. Include: ${data.getAll(\"include\").join(\", \") || \"nothing\"}. When: ${data.get(\"when\")}.`);\n        }}\n      >\n        <QuestionnaireProgress />\n        <QuestionnaireItem name=\"channel\" required>\n          <QuestionnaireTitle>Where should @acme tell you about new mail?</QuestionnaireTitle>\n          <QuestionnaireDescription>Choose a channel, or type another one.</QuestionnaireDescription>\n          <QuestionnaireChoices>\n            <QuestionnaireChoice value=\"email\">\n              <span className=\"font-medium\">Email</span>\n              <QuestionnaireChoiceDescription>A note to robert@acme.com</QuestionnaireChoiceDescription>\n            </QuestionnaireChoice>\n            <QuestionnaireChoice value=\"phone\">\n              <span className=\"font-medium\">Phone</span>\n              <QuestionnaireChoiceDescription>A text from the agent&apos;s number</QuestionnaireChoiceDescription>\n            </QuestionnaireChoice>\n            <QuestionnaireChoice value=\"imessage\">\n              <span className=\"font-medium\">iMessage</span>\n              <QuestionnaireChoiceDescription>Through the shared iMessage line</QuestionnaireChoiceDescription>\n            </QuestionnaireChoice>\n            <QuestionnaireInput aria-label=\"Another channel\" placeholder=\"Another channel\" />\n          </QuestionnaireChoices>\n          <QuestionnaireError />\n        </QuestionnaireItem>\n        <QuestionnaireItem name=\"include\" multiple>\n          <QuestionnaireTitle>What should each note include?</QuestionnaireTitle>\n          <QuestionnaireDescription>Choose any, or skip this question.</QuestionnaireDescription>\n          <QuestionnaireChoices>\n            <QuestionnaireChoice value=\"sender\">The sender</QuestionnaireChoice>\n            <QuestionnaireChoice value=\"summary\">A one-line summary</QuestionnaireChoice>\n            <QuestionnaireChoice value=\"attachments\">Attachments</QuestionnaireChoice>\n            <QuestionnaireChoice value=\"next-step\">The suggested next step</QuestionnaireChoice>\n          </QuestionnaireChoices>\n          <QuestionnaireError />\n        </QuestionnaireItem>\n        <QuestionnaireItem name=\"when\" required>\n          <QuestionnaireTitle>How often?</QuestionnaireTitle>\n          <QuestionnaireDescription>Batched notes arrive once per period.</QuestionnaireDescription>\n          <QuestionnaireChoices>\n            <QuestionnaireChoice value=\"now\">As each email arrives</QuestionnaireChoice>\n            <QuestionnaireChoice value=\"hourly\">Once an hour</QuestionnaireChoice>\n            <QuestionnaireChoice value=\"daily\">Once a day</QuestionnaireChoice>\n          </QuestionnaireChoices>\n          <QuestionnaireError />\n        </QuestionnaireItem>\n        <QuestionnaireActions>\n          <QuestionnairePrevious />\n          <QuestionnaireSkip />\n          <QuestionnaireNext />\n          <QuestionnaireSubmit>Save rule</QuestionnaireSubmit>\n        </QuestionnaireActions>\n      </Questionnaire>\n      {saved && <Saved answers={saved} />}\n    </div>\n  );\n}\n\nconst PLAN = [{ name: \"line\", required: true, choices: [{ value: \"shared\" }, { value: \"dedicated\" }, { value: \"port\" }] }] as const;\n\nexport function Disabled() {\n  return (\n    <Questionnaire items={PLAN} shortcuts=\"letters\" className=\"mx-auto max-w-110\" onSubmit={(event) => event.preventDefault()}>\n      <QuestionnaireItem name=\"line\" required>\n        <QuestionnaireTitle>Which iMessage line should the agent use?</QuestionnaireTitle>\n        <QuestionnaireDescription>No dedicated lines are available to assign right now.</QuestionnaireDescription>\n        <QuestionnaireChoices>\n          <QuestionnaireChoice value=\"shared\">\n            <span className=\"font-medium\">Shared</span>\n            <QuestionnaireChoiceDescription>Assigned when someone connects</QuestionnaireChoiceDescription>\n          </QuestionnaireChoice>\n          <QuestionnaireChoice value=\"dedicated\" disabled>\n            <span className=\"font-medium\">Dedicated</span>\n            <QuestionnaireChoiceDescription>None available</QuestionnaireChoiceDescription>\n          </QuestionnaireChoice>\n          <QuestionnaireChoice value=\"port\">\n            <span className=\"font-medium\">Port a number</span>\n            <QuestionnaireChoiceDescription>Bring an existing number</QuestionnaireChoiceDescription>\n          </QuestionnaireChoice>\n        </QuestionnaireChoices>\n        <QuestionnaireError />\n      </QuestionnaireItem>\n      <QuestionnaireActions>\n        <QuestionnaireSubmit>Assign line</QuestionnaireSubmit>\n      </QuestionnaireActions>\n    </Questionnaire>\n  );\n}\n",
      "exports": [
        "Preview",
        "Disabled"
      ],
      "requires": [
        "questionnaire"
      ],
      "packages": [],
      "previews": [
        {
          "name": "Preview",
          "url": "https://inkbox-ds.vercel.app/preview/questionnaire/Preview"
        },
        {
          "name": "Disabled",
          "url": "https://inkbox-ds.vercel.app/preview/questionnaire/Disabled"
        }
      ]
    }
  ],
  "type": "ui",
  "title": "Questionnaire",
  "description": "Questions asked one at a time: white choice cards with the brand's radio or checkbox and a shortcut key, a typed answer, a progress count and Previous, Skip and Next pills.",
  "surface": "site",
  "kinds": [],
  "requires": [
    "theme",
    "button",
    "input",
    "kbd",
    "progress",
    "utils"
  ],
  "packages": [
    "@phosphor-icons/react"
  ],
  "files": [
    {
      "path": "components/ui/questionnaire.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { CheckIcon } from \"@phosphor-icons/react/ssr\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Kbd } from \"@/components/ui/kbd\";\nimport { Progress } from \"@/components/ui/progress\";\nimport { cn } from \"@/lib/utils\";\n\n// A short set of questions asked one at a time (extrapolated from the console, Figma 427:2715: Create Agent Identity's\n// choices and the Where does this agent run? picker). A choice is a 44px white card with the field edge and 8px\n// corners, the brand's radio (a thick pink ring when chosen) or checkbox (purple when checked) 14px in, a 14px ink label\n// and its number or letter key at the right; a chosen card takes the purple edge. The question is 16px medium ink with\n// a 14px secondary line under it; progress is a 12px muted count over a thin purple bar. Changing questions is instant:\n// it is driven by the keyboard as often as by the pointer. The answers submit as one native form (FormData), every\n// question's inputs included, so the page owns saving and what happens next.\n\ntype Choice = { readonly value: string; readonly disabled?: boolean };\ntype ItemConfig = { readonly name: string; readonly required?: boolean; readonly multiple?: boolean; readonly choices?: readonly Choice[] };\ntype Shortcuts = \"numbers\" | \"letters\" | \"none\";\n\ntype QuestionnaireContextValue = {\n  order: string[];\n  active: string | undefined;\n  goTo: (name: string) => void;\n  shortcuts: Shortcuts;\n  answers: Record<string, string[]>;\n  other: Record<string, string>;\n  errors: Record<string, string | undefined>;\n  configs: React.RefObject<Map<string, ItemConfig>>;\n  register: (config: ItemConfig) => () => void;\n  setAnswer: (name: string, value: string, checked: boolean) => void;\n  setOther: (name: string, text: string) => void;\n  next: () => void;\n  previous: () => void;\n  skip: () => void;\n};\n\nconst QuestionnaireContext = React.createContext<QuestionnaireContextValue | null>(null);\nconst ItemContext = React.createContext<{ name: string; required: boolean; multiple: boolean; id: string } | null>(null);\n\nfunction useQuestionnaire() {\n  const context = React.useContext(QuestionnaireContext);\n  if (!context) throw new Error(\"Questionnaire parts must be inside a Questionnaire.\");\n  return context;\n}\n\nfunction useItem() {\n  const context = React.useContext(ItemContext);\n  if (!context) throw new Error(\"This part must be inside a QuestionnaireItem.\");\n  return context;\n}\n\nconst shortcutKey = (index: number, shortcuts: Shortcuts) => (shortcuts === \"none\" || index > 8 ? undefined : shortcuts === \"letters\" ? String.fromCharCode(65 + index) : String(index + 1));\n\n/**\n * The questions and their answers. Pass `items` (name, required, multiple, choices) so the order, progress and shortcuts\n * are known on the server; without it the order is the QuestionnaireItems' order. `defaultItem` or a controlled\n * `item` with `onItemChange` picks the question shown. `shortcuts` puts 1 to 9 (or A to I) on the choices.\n */\nfunction Questionnaire({\n  items,\n  defaultItem,\n  item,\n  onItemChange,\n  shortcuts = \"numbers\",\n  className,\n  onSubmit,\n  onKeyDown,\n  children,\n  ...props\n}: Omit<React.ComponentProps<\"form\">, \"onSubmit\"> & {\n  items?: readonly ItemConfig[];\n  defaultItem?: string;\n  item?: string;\n  onItemChange?: (name: string) => void;\n  shortcuts?: Shortcuts;\n  onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;\n}) {\n  const form = React.useRef<HTMLFormElement>(null);\n  const configs = React.useRef(new Map<string, ItemConfig>(items?.map((i) => [i.name, i])));\n  const [registered, setRegistered] = React.useState<string[]>([]);\n  const order = items ? items.map((i) => i.name) : registered;\n  const [internal, setInternal] = React.useState(defaultItem ?? items?.[0]?.name);\n  const active = item ?? internal ?? order[0];\n  const [answers, setAnswers] = React.useState<Record<string, string[]>>({});\n  const [other, setOtherState] = React.useState<Record<string, string>>({});\n  const [errors, setErrors] = React.useState<Record<string, string | undefined>>({});\n\n  const focusItem = React.useCallback((name: string) => {\n    requestAnimationFrame(() => {\n      const fieldset = form.current?.querySelector<HTMLElement>(`[data-slot=questionnaire-item][data-name=\"${CSS.escape(name)}\"]`);\n      fieldset?.querySelector<HTMLElement>(\"input:not(:disabled)\")?.focus({ preventScroll: true });\n    });\n  }, []);\n\n  const goTo = React.useCallback(\n    (name: string) => {\n      if (item === undefined) setInternal(name);\n      onItemChange?.(name);\n      focusItem(name);\n    },\n    [item, onItemChange, focusItem],\n  );\n\n  const register = React.useCallback((config: ItemConfig) => {\n    configs.current.set(config.name, { ...configs.current.get(config.name), ...config });\n    setRegistered((list) => (list.includes(config.name) ? list : [...list, config.name]));\n    return () => setRegistered((list) => list.filter((n) => n !== config.name));\n  }, []);\n\n  const answered = React.useCallback((name: string) => (answers[name]?.length ?? 0) > 0 || (other[name] ?? \"\").trim() !== \"\", [answers, other]);\n\n  const validate = React.useCallback(\n    (name: string) => {\n      const config = configs.current.get(name);\n      const message = config?.required && !answered(name) ? (config.multiple ? \"Choose at least one answer to continue.\" : \"Choose an answer to continue.\") : undefined;\n      setErrors((e) => ({ ...e, [name]: message }));\n      return !message;\n    },\n    [answered],\n  );\n\n  const setAnswer = React.useCallback((name: string, value: string, checked: boolean) => {\n    const multiple = configs.current.get(name)?.multiple;\n    setAnswers((a) => ({ ...a, [name]: multiple ? (checked ? [...(a[name] ?? []).filter((v) => v !== value), value] : (a[name] ?? []).filter((v) => v !== value)) : checked ? [value] : [] }));\n    if (!multiple && checked) setOtherState((o) => ({ ...o, [name]: \"\" }));\n    setErrors((e) => ({ ...e, [name]: undefined }));\n  }, []);\n\n  const setOther = React.useCallback((name: string, text: string) => {\n    setOtherState((o) => ({ ...o, [name]: text }));\n    if (text && !configs.current.get(name)?.multiple) setAnswers((a) => ({ ...a, [name]: [] }));\n    setErrors((e) => ({ ...e, [name]: undefined }));\n  }, []);\n\n  const index = active ? order.indexOf(active) : -1;\n  const next = React.useCallback(() => {\n    if (!active || !validate(active)) return focusItem(active ?? \"\");\n    const following = order[index + 1];\n    if (following) goTo(following);\n  }, [active, validate, focusItem, order, index, goTo]);\n  const previous = React.useCallback(() => {\n    const before = order[index - 1];\n    if (before) goTo(before);\n  }, [order, index, goTo]);\n  const skip = React.useCallback(() => {\n    if (!active) return;\n    setAnswers((a) => ({ ...a, [active]: [] }));\n    setOtherState((o) => ({ ...o, [active]: \"\" }));\n    setErrors((e) => ({ ...e, [active]: undefined }));\n    const following = order[index + 1];\n    if (following) goTo(following);\n  }, [active, order, index, goTo]);\n\n  const value = React.useMemo<QuestionnaireContextValue>(\n    () => ({ order, active, goTo, shortcuts, answers, other, errors, configs, register, setAnswer, setOther, next, previous, skip }),\n    [order, active, goTo, shortcuts, answers, other, errors, register, setAnswer, setOther, next, previous, skip],\n  );\n\n  return (\n    <QuestionnaireContext.Provider value={value}>\n      <form\n        ref={form}\n        data-slot=\"questionnaire\"\n        noValidate\n        className={cn(\"flex w-full min-w-0 flex-col gap-4\", className)}\n        onSubmit={(event) => {\n          // Every required question must be answered; the first one that is not is shown with its message.\n          const missing = order.find((name) => !validate(name));\n          if (missing) {\n            event.preventDefault();\n            goTo(missing);\n            return;\n          }\n          onSubmit?.(event);\n        }}\n        onKeyDown={(event) => {\n          onKeyDown?.(event);\n          if (event.defaultPrevented || !active || event.metaKey || event.ctrlKey || event.altKey) return;\n          const target = event.target as HTMLElement;\n          const typing = target instanceof HTMLInputElement && target.type === \"text\";\n          if (event.key === \"Enter\" && !(target instanceof HTMLButtonElement) && !(target instanceof HTMLTextAreaElement)) {\n            event.preventDefault();\n            if (index < order.length - 1) next();\n            else form.current?.requestSubmit();\n            return;\n          }\n          if (typing || shortcuts === \"none\") return;\n          const key = event.key.toUpperCase();\n          const at = shortcuts === \"letters\" ? key.charCodeAt(0) - 65 : Number(key) - 1;\n          if (key.length !== 1 || Number.isNaN(at) || at < 0 || at > 8) return;\n          const choice = form.current?.querySelectorAll<HTMLInputElement>(`[data-slot=questionnaire-item][data-name=\"${CSS.escape(active)}\"] [data-slot=questionnaire-choice-input]`)[at];\n          if (!choice || choice.disabled) return;\n          event.preventDefault();\n          choice.focus();\n          setAnswer(active, choice.value, choice.type === \"checkbox\" ? !choice.checked : true);\n        }}\n        {...props}\n      >\n        {children}\n      </form>\n    </QuestionnaireContext.Provider>\n  );\n}\n\n/** Where the person is: \"Question 2 of 3\" in 12px muted medium over a 4px purple bar. */\nfunction QuestionnaireProgress({ className, children, ...props }: React.ComponentProps<\"div\">) {\n  const { order, active } = useQuestionnaire();\n  const at = active ? order.indexOf(active) + 1 : 0;\n  const total = order.length;\n  return (\n    <div data-slot=\"questionnaire-progress\" className={cn(\"grid w-full gap-1.5\", className)} {...props}>\n      <span className=\"font-sans text-caption leading-tight font-medium text-fg-muted tabular-nums\" aria-live=\"polite\">\n        {children ?? (total ? `Question ${at} of ${total}` : \" \")}\n      </span>\n      <Progress value={total ? (at / total) * 100 : 0} aria-label=\"Progress\" className=\"h-1\" />\n    </div>\n  );\n}\n\n/** One question. Only the current one shows; the others stay in the form so their answers submit with it. */\nfunction QuestionnaireItem({ name, required = false, multiple = false, className, children, ...props }: Omit<React.ComponentProps<\"fieldset\">, \"name\"> & { name: string; required?: boolean; multiple?: boolean }) {\n  const { active, register, errors } = useQuestionnaire();\n  const id = React.useId();\n  React.useLayoutEffect(() => register({ name, required, multiple }), [register, name, required, multiple]);\n  const context = React.useMemo(() => ({ name, required, multiple, id }), [name, required, multiple, id]);\n  const isActive = active === name;\n  return (\n    <ItemContext.Provider value={context}>\n      <fieldset\n        data-slot=\"questionnaire-item\"\n        data-name={name}\n        data-active={isActive || undefined}\n        hidden={!isActive}\n        aria-labelledby={`${id}-title`}\n        aria-describedby={`${id}-description${errors[name] ? ` ${id}-error` : \"\"}`}\n        aria-invalid={errors[name] ? true : undefined}\n        className={cn(\"m-0 flex min-w-0 flex-col gap-4 border-0 p-0 outline-none\", className)}\n        {...props}\n      >\n        {children}\n      </fieldset>\n    </ItemContext.Provider>\n  );\n}\n\nfunction QuestionnaireTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { id } = useItem();\n  return <div id={`${id}-title`} data-slot=\"questionnaire-title\" className={cn(\"font-sans text-body-lg leading-snug font-medium text-pretty text-fg\", className)} {...props} />;\n}\n\nfunction QuestionnaireDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  const { id } = useItem();\n  return <p id={`${id}-description`} data-slot=\"questionnaire-description\" className={cn(\"-mt-3 font-sans text-body leading-body text-pretty text-fg-secondary\", className)} {...props} />;\n}\n\n/** The choices of a question, 8px apart; each direct QuestionnaireChoice gets its shortcut in order. */\nfunction QuestionnaireChoices({ className, children, ...props }: React.ComponentProps<\"div\">) {\n  const { multiple, id } = useItem();\n  let index = 0;\n  return (\n    <div data-slot=\"questionnaire-choices\" role={multiple ? \"group\" : \"radiogroup\"} aria-labelledby={`${id}-title`} className={cn(\"group/questionnaire-choices grid min-w-0 gap-2\", className)} {...props}>\n      {React.Children.map(children, (child) => (React.isValidElement<{ index?: number }>(child) && child.type === QuestionnaireChoice ? React.cloneElement(child, { index: index++ }) : child))}\n    </div>\n  );\n}\n\nfunction QuestionnaireChoice({ value, disabled = false, index = 0, className, children, ...props }: Omit<React.ComponentProps<\"label\">, \"children\"> & { value: string; disabled?: boolean; index?: number; children?: React.ReactNode }) {\n  const { answers, setAnswer, shortcuts } = useQuestionnaire();\n  const { name, multiple } = useItem();\n  const checked = answers[name]?.includes(value) ?? false;\n  const key = shortcutKey(index, shortcuts);\n  return (\n    <label\n      data-slot=\"questionnaire-choice\"\n      data-type={multiple ? \"checkbox\" : \"radio\"}\n      data-checked={checked || undefined}\n      data-disabled={disabled || undefined}\n      className={cn(\n        \"group/questionnaire-choice relative flex min-h-11 cursor-pointer items-start gap-2.5 rounded-control border border-line-field bg-surface px-3.5 py-2.5 text-start font-sans text-body leading-snug text-fg select-none transition-[background-color,border-color] duration-150 ease-out hover:bg-subtle has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-focus-ring data-checked:border-line-brand data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50 motion-reduce:transition-none\",\n        className,\n      )}\n      {...props}\n    >\n      <input\n        data-slot=\"questionnaire-choice-input\"\n        type={multiple ? \"checkbox\" : \"radio\"}\n        name={name}\n        value={value}\n        checked={checked}\n        disabled={disabled}\n        onChange={(event) => setAnswer(name, value, event.target.checked)}\n        className=\"peer sr-only\"\n      />\n      <span\n        aria-hidden\n        data-slot=\"questionnaire-choice-indicator\"\n        className={cn(\n          \"pointer-events-none mt-px flex size-4.5 shrink-0 items-center justify-center border-[1.5px] border-line-strong bg-surface text-on-primary transition-[background-color,border-color,border-width] duration-150 ease-out motion-reduce:transition-none\",\n          multiple ? \"rounded-xs group-data-checked/questionnaire-choice:border-primary group-data-checked/questionnaire-choice:bg-primary\" : \"rounded-pill group-data-checked/questionnaire-choice:border-4 group-data-checked/questionnaire-choice:border-pink\",\n        )}\n      >\n        {multiple && <CheckIcon weight=\"bold\" className=\"hidden size-3.5 group-data-checked/questionnaire-choice:block\" />}\n      </span>\n      <span data-slot=\"questionnaire-choice-label\" className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n        {children}\n      </span>\n      {key && (\n        <Kbd data-slot=\"questionnaire-choice-shortcut\" aria-hidden className=\"ml-auto hidden shrink-0 sm:inline-flex\">\n          {key}\n        </Kbd>\n      )}\n    </label>\n  );\n}\n\n/** A choice's second line: 14px muted. */\nfunction QuestionnaireChoiceDescription({ className, ...props }: React.ComponentProps<\"span\">) {\n  return <span data-slot=\"questionnaire-choice-description\" className={cn(\"text-fg-muted\", className)} {...props} />;\n}\n\n/** A free answer typed instead of a choice (another answer); typing clears a single choice, choosing clears the text. */\nfunction QuestionnaireInput({ className, ...props }: Omit<React.ComponentProps<typeof Input>, \"name\" | \"value\" | \"onChange\">) {\n  const { other, setOther } = useQuestionnaire();\n  const { name } = useItem();\n  return (\n    <div data-slot=\"questionnaire-input-wrapper\" className=\"group/questionnaire-input relative w-full min-w-0\">\n      <Input data-slot=\"questionnaire-input\" type=\"text\" name={name} value={other[name] ?? \"\"} onChange={(event) => setOther(name, event.target.value)} className={cn(\"h-11\", className)} {...props} />\n    </div>\n  );\n}\n\n/** Why the question cannot be passed yet: 12px red, read out when it appears. */\nfunction QuestionnaireError({ className, children, ...props }: React.ComponentProps<\"p\">) {\n  const { errors } = useQuestionnaire();\n  const { name, id } = useItem();\n  const message = children ?? errors[name];\n  if (!message) return null;\n  return (\n    <p id={`${id}-error`} role=\"alert\" data-slot=\"questionnaire-error\" className={cn(\"-mt-2 font-sans text-caption leading-tight text-danger\", className)} {...props}>\n      {message}\n    </p>\n  );\n}\n\n/** The buttons under the question: Previous at the left, then Skip and Next (or Submit on the last question). */\nfunction QuestionnaireActions({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"questionnaire-actions\" className={cn(\"grid min-h-11 w-full grid-cols-[minmax(0,1fr)_auto_auto] items-center gap-2\", className)} {...props} />;\n}\n\ntype ActionProps = Omit<React.ComponentProps<typeof Button>, \"onClick\" | \"type\"> & { onClick?: React.MouseEventHandler<HTMLButtonElement> };\n\nfunction QuestionnairePrevious({ children, className, variant = \"secondary\", onClick, ...props }: ActionProps) {\n  const { order, active, previous } = useQuestionnaire();\n  const first = !active || order.indexOf(active) <= 0;\n  return (\n    <Button\n      data-slot=\"questionnaire-previous\"\n      variant={variant}\n      disabled={first}\n      className={cn(\"col-start-1 row-start-1 justify-self-start\", first && \"invisible\", className)}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) previous();\n      }}\n      {...props}\n    >\n      {children ?? \"Previous\"}\n    </Button>\n  );\n}\n\nfunction QuestionnaireSkip({ children, className, variant = \"ghost\", onClick, ...props }: ActionProps) {\n  const { active, configs, order, skip } = useQuestionnaire();\n  const required = active ? configs.current.get(active)?.required : true;\n  const last = !active || order.indexOf(active) === order.length - 1;\n  if (required || last) return null;\n  return (\n    <Button\n      data-slot=\"questionnaire-skip\"\n      variant={variant}\n      className={cn(\"col-start-2 row-start-1 justify-self-end px-4 text-fg-secondary\", className)}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) skip();\n      }}\n      {...props}\n    >\n      {children ?? \"Skip\"}\n    </Button>\n  );\n}\n\nfunction QuestionnaireNext({ children, className, variant = \"default\", onClick, ...props }: ActionProps) {\n  const { order, active, next } = useQuestionnaire();\n  if (!active || order.indexOf(active) >= order.length - 1) return null;\n  return (\n    <Button\n      data-slot=\"questionnaire-next\"\n      variant={variant}\n      className={cn(\"col-start-3 row-start-1 justify-self-end\", className)}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) next();\n      }}\n      {...props}\n    >\n      {children ?? \"Next\"}\n    </Button>\n  );\n}\n\nfunction QuestionnaireSubmit({ children, className, variant = \"default\", ...props }: Omit<React.ComponentProps<typeof Button>, \"type\">) {\n  const { order, active } = useQuestionnaire();\n  if (!active || order.indexOf(active) !== order.length - 1) return null;\n  return (\n    <Button data-slot=\"questionnaire-submit\" type=\"submit\" variant={variant} className={cn(\"col-start-3 row-start-1 justify-self-end\", className)} {...props}>\n      {children ?? \"Submit\"}\n    </Button>\n  );\n}\n\nexport {\n  Questionnaire,\n  QuestionnaireActions,\n  QuestionnaireChoice,\n  QuestionnaireChoiceDescription,\n  QuestionnaireChoices,\n  QuestionnaireDescription,\n  QuestionnaireError,\n  QuestionnaireInput,\n  QuestionnaireItem,\n  QuestionnaireNext,\n  QuestionnairePrevious,\n  QuestionnaireProgress,\n  QuestionnaireSkip,\n  QuestionnaireSubmit,\n  QuestionnaireTitle,\n};\n"
    }
  ]
}
